Bug 1709347 - Add CanvasRenderingContext2D.reset(). r=lsalzman,webidl,smaug
[gecko.git] / layout / base / PresShell.cpp
blob4b16430f9d713428c9df5607d3b25b9c2d777647
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 /* a presentation of a document, part 2 */
9 #include "mozilla/PresShell.h"
11 #include "Units.h"
12 #include "mozilla/dom/FontFaceSet.h"
13 #include "mozilla/dom/ElementBinding.h"
14 #include "mozilla/ArrayUtils.h"
15 #include "mozilla/Attributes.h"
16 #include "mozilla/AutoRestore.h"
17 #include "mozilla/ContentIterator.h"
18 #include "mozilla/DisplayPortUtils.h"
19 #include "mozilla/EventDispatcher.h"
20 #include "mozilla/EventStateManager.h"
21 #include "mozilla/GeckoMVMContext.h"
22 #include "mozilla/IMEStateManager.h"
23 #include "mozilla/IntegerRange.h"
24 #include "mozilla/MemoryReporting.h"
25 #include "mozilla/dom/BrowserChild.h"
26 #include "mozilla/Likely.h"
27 #include "mozilla/Logging.h"
28 #include "mozilla/MouseEvents.h"
29 #include "mozilla/PerfStats.h"
30 #include "mozilla/PointerLockManager.h"
31 #include "mozilla/PresShellInlines.h"
32 #include "mozilla/RangeUtils.h"
33 #include "mozilla/ScopeExit.h"
34 #include "mozilla/Sprintf.h"
35 #include "mozilla/StaticAnalysisFunctions.h"
36 #include "mozilla/StaticPrefs_apz.h"
37 #include "mozilla/StaticPrefs_dom.h"
38 #include "mozilla/StaticPrefs_font.h"
39 #include "mozilla/StaticPrefs_image.h"
40 #include "mozilla/StaticPrefs_layout.h"
41 #include "mozilla/StaticPrefs_toolkit.h"
42 #include "mozilla/TextEvents.h"
43 #include "mozilla/TimeStamp.h"
44 #include "mozilla/TouchEvents.h"
45 #include "mozilla/UniquePtr.h"
46 #include "mozilla/Unused.h"
47 #include "mozilla/ViewportUtils.h"
48 #include "mozilla/gfx/Types.h"
49 #include "nsBoxLayoutState.h"
50 #include <algorithm>
52 #ifdef XP_WIN
53 # include "winuser.h"
54 #endif
56 #include "gfxContext.h"
57 #include "gfxUserFontSet.h"
58 #include "nsContentList.h"
59 #include "nsPresContext.h"
60 #include "nsIContent.h"
61 #include "mozilla/dom/BrowserBridgeChild.h"
62 #include "mozilla/dom/BrowsingContext.h"
63 #include "mozilla/dom/CanonicalBrowsingContext.h"
64 #include "mozilla/dom/ContentChild.h"
65 #include "mozilla/dom/ContentParent.h"
66 #include "mozilla/dom/Element.h"
67 #include "mozilla/dom/PointerEventHandler.h"
68 #include "mozilla/dom/PopupBlocker.h"
69 #include "mozilla/dom/Document.h"
70 #include "mozilla/dom/DocumentInlines.h"
71 #include "mozilla/dom/UserActivation.h"
72 #include "nsAnimationManager.h"
73 #include "nsNameSpaceManager.h" // for Pref-related rule management (bugs 22963,20760,31816)
74 #include "nsFlexContainerFrame.h"
75 #include "nsIFrame.h"
76 #include "nsViewManager.h"
77 #include "nsView.h"
78 #include "nsCRTGlue.h"
79 #include "prinrval.h"
80 #include "nsTArray.h"
81 #include "nsCOMArray.h"
82 #include "nsContainerFrame.h"
83 #include "mozilla/dom/Selection.h"
84 #include "nsGkAtoms.h"
85 #include "nsRange.h"
86 #include "nsWindowSizes.h"
87 #include "nsCOMPtr.h"
88 #include "nsReadableUtils.h"
89 #include "nsPageSequenceFrame.h"
90 #include "nsCaret.h"
91 #include "mozilla/AccessibleCaretEventHub.h"
92 #include "nsFrameManager.h"
93 #include "nsXPCOM.h"
94 #include "nsILayoutHistoryState.h"
95 #include "nsILineIterator.h" // for ScrollContentIntoView
96 #include "PLDHashTable.h"
97 #include "mozilla/dom/Touch.h"
98 #include "mozilla/dom/TouchEvent.h"
99 #include "mozilla/dom/PointerEventBinding.h"
100 #include "mozilla/dom/ShadowIncludingTreeIterator.h"
101 #include "nsIObserverService.h"
102 #include "nsDocShell.h" // for reflow observation
103 #include "nsIBaseWindow.h"
104 #include "nsError.h"
105 #include "nsLayoutUtils.h"
106 #include "nsViewportInfo.h"
107 #include "nsCSSRendering.h"
108 // for |#ifdef DEBUG| code
109 #include "prenv.h"
110 #include "nsDisplayList.h"
111 #include "nsRegion.h"
112 #include "nsAutoLayoutPhase.h"
113 #include "AutoProfilerStyleMarker.h"
114 #ifdef MOZ_REFLOW_PERF
115 # include "nsFontMetrics.h"
116 #endif
117 #include "MobileViewportManager.h"
118 #include "OverflowChangedTracker.h"
119 #include "PositionedEventTargeting.h"
121 #include "nsIReflowCallback.h"
123 #include "nsPIDOMWindow.h"
124 #include "nsFocusManager.h"
125 #include "nsNetUtil.h"
126 #include "nsThreadUtils.h"
127 #include "nsStyleSheetService.h"
128 #include "gfxUtils.h"
129 #include "mozilla/SMILAnimationController.h"
130 #include "mozilla/dom/SVGAnimationElement.h"
131 #include "mozilla/SVGObserverUtils.h"
132 #include "mozilla/SVGFragmentIdentifier.h"
133 #include "nsFrameSelection.h"
135 #include "mozilla/dom/Performance.h"
136 #include "nsRefreshDriver.h"
137 #include "nsDOMNavigationTiming.h"
139 // Drag & Drop, Clipboard
140 #include "nsIDocShellTreeItem.h"
141 #include "nsIURI.h"
142 #include "nsIScrollableFrame.h"
143 #include "nsITimer.h"
144 #ifdef ACCESSIBILITY
145 # include "mozilla/a11y/DocAccessible.h"
146 # ifdef DEBUG
147 # include "mozilla/a11y/Logging.h"
148 # endif
149 #endif
151 // For style data reconstruction
152 #include "nsStyleChangeList.h"
153 #include "nsCSSFrameConstructor.h"
154 #include "nsTreeBodyFrame.h"
155 #include "XULTreeElement.h"
156 #include "nsMenuPopupFrame.h"
157 #include "nsTreeColumns.h"
158 #include "nsIDOMXULMultSelectCntrlEl.h"
159 #include "nsIDOMXULSelectCntrlItemEl.h"
160 #include "nsIDOMXULMenuListElement.h"
161 #include "nsXULElement.h"
163 #include "mozilla/layers/CompositorBridgeChild.h"
164 #include "gfxPlatform.h"
165 #include "mozilla/css/ImageLoader.h"
166 #include "mozilla/dom/DocumentTimeline.h"
167 #include "mozilla/dom/ScriptSettings.h"
168 #include "mozilla/ErrorResult.h"
169 #include "mozilla/Preferences.h"
170 #include "mozilla/Telemetry.h"
171 #include "nsCanvasFrame.h"
172 #include "nsImageFrame.h"
173 #include "nsIScreen.h"
174 #include "nsIScreenManager.h"
175 #include "nsPlaceholderFrame.h"
176 #include "nsTransitionManager.h"
177 #include "ChildIterator.h"
178 #include "mozilla/RestyleManager.h"
179 #include "nsIDragSession.h"
180 #include "nsIFrameInlines.h"
181 #include "mozilla/gfx/2D.h"
182 #include "nsNetUtil.h"
183 #include "nsSubDocumentFrame.h"
184 #include "nsQueryObject.h"
185 #include "mozilla/GlobalStyleSheetCache.h"
186 #include "mozilla/layers/InputAPZContext.h"
187 #include "mozilla/layers/FocusTarget.h"
188 #include "mozilla/layers/ScrollingInteractionContext.h"
189 #include "mozilla/layers/WebRenderLayerManager.h"
190 #include "mozilla/layers/WebRenderUserData.h"
191 #include "mozilla/layout/ScrollAnchorContainer.h"
192 #include "mozilla/layers/APZPublicUtils.h"
193 #include "mozilla/ProfilerLabels.h"
194 #include "mozilla/ProfilerMarkers.h"
195 #include "mozilla/ScrollTimelineAnimationTracker.h"
196 #include "mozilla/ScrollTypes.h"
197 #include "mozilla/ServoBindings.h"
198 #include "mozilla/ServoStyleSet.h"
199 #include "mozilla/StyleSheet.h"
200 #include "mozilla/StyleSheetInlines.h"
201 #include "mozilla/InputTaskManager.h"
202 #include "mozilla/dom/ImageTracker.h"
203 #include "nsIDocShellTreeOwner.h"
204 #include "nsClassHashtable.h"
205 #include "nsHashKeys.h"
206 #include "ScrollSnap.h"
207 #include "VisualViewport.h"
208 #include "ZoomConstraintsClient.h"
210 // define the scalfactor of drag and drop images
211 // relative to the max screen height/width
212 #define RELATIVE_SCALEFACTOR 0.0925f
214 using namespace mozilla;
215 using namespace mozilla::css;
216 using namespace mozilla::dom;
217 using namespace mozilla::gfx;
218 using namespace mozilla::layers;
219 using namespace mozilla::gfx;
220 using namespace mozilla::layout;
221 using PaintFrameFlags = nsLayoutUtils::PaintFrameFlags;
222 typedef ScrollableLayerGuid::ViewID ViewID;
224 PresShell::CapturingContentInfo PresShell::sCapturingContentInfo;
226 // RangePaintInfo is used to paint ranges to offscreen buffers
227 struct RangePaintInfo {
228 RefPtr<nsRange> mRange;
229 nsDisplayListBuilder mBuilder;
230 nsDisplayList mList;
232 // offset of builder's reference frame to the root frame
233 nsPoint mRootOffset;
235 // Resolution at which the items are normally painted. So if we're painting
236 // these items in a range separately from the "full display list", we may want
237 // to paint them at this resolution.
238 float mResolution = 1.0;
240 RangePaintInfo(nsRange* aRange, nsIFrame* aFrame)
241 : mRange(aRange),
242 mBuilder(aFrame, nsDisplayListBuilderMode::Painting, false),
243 mList(&mBuilder) {
244 MOZ_COUNT_CTOR(RangePaintInfo);
245 mBuilder.BeginFrame();
248 ~RangePaintInfo() {
249 mList.DeleteAll(&mBuilder);
250 mBuilder.EndFrame();
251 MOZ_COUNT_DTOR(RangePaintInfo);
255 #undef NOISY
257 // ----------------------------------------------------------------------
259 #ifdef DEBUG
260 // Set the environment variable GECKO_VERIFY_REFLOW_FLAGS to one or
261 // more of the following flags (comma separated) for handy debug
262 // output.
263 static VerifyReflowFlags gVerifyReflowFlags;
265 struct VerifyReflowFlagData {
266 const char* name;
267 VerifyReflowFlags bit;
270 static const VerifyReflowFlagData gFlags[] = {
271 // clang-format off
272 { "verify", VerifyReflowFlags::On },
273 { "reflow", VerifyReflowFlags::Noisy },
274 { "all", VerifyReflowFlags::All },
275 { "list-commands", VerifyReflowFlags::DumpCommands },
276 { "noisy-commands", VerifyReflowFlags::NoisyCommands },
277 { "really-noisy-commands", VerifyReflowFlags::ReallyNoisyCommands },
278 { "resize", VerifyReflowFlags::DuringResizeReflow },
279 // clang-format on
282 # define NUM_VERIFY_REFLOW_FLAGS (sizeof(gFlags) / sizeof(gFlags[0]))
284 static void ShowVerifyReflowFlags() {
285 printf("Here are the available GECKO_VERIFY_REFLOW_FLAGS:\n");
286 const VerifyReflowFlagData* flag = gFlags;
287 const VerifyReflowFlagData* limit = gFlags + NUM_VERIFY_REFLOW_FLAGS;
288 while (flag < limit) {
289 printf(" %s\n", flag->name);
290 ++flag;
292 printf("Note: GECKO_VERIFY_REFLOW_FLAGS is a comma separated list of flag\n");
293 printf("names (no whitespace)\n");
295 #endif
297 //========================================================================
298 //========================================================================
299 //========================================================================
300 #ifdef MOZ_REFLOW_PERF
301 class ReflowCountMgr;
303 static const char kGrandTotalsStr[] = "Grand Totals";
305 // Counting Class
306 class ReflowCounter {
307 public:
308 explicit ReflowCounter(ReflowCountMgr* aMgr = nullptr);
309 ~ReflowCounter();
311 void ClearTotals();
312 void DisplayTotals(const char* aStr);
313 void DisplayDiffTotals(const char* aStr);
314 void DisplayHTMLTotals(const char* aStr);
316 void Add() { mTotal++; }
317 void Add(uint32_t aTotal) { mTotal += aTotal; }
319 void CalcDiffInTotals();
320 void SetTotalsCache();
322 void SetMgr(ReflowCountMgr* aMgr) { mMgr = aMgr; }
324 uint32_t GetTotal() { return mTotal; }
326 protected:
327 void DisplayTotals(uint32_t aTotal, const char* aTitle);
328 void DisplayHTMLTotals(uint32_t aTotal, const char* aTitle);
330 uint32_t mTotal;
331 uint32_t mCacheTotal;
333 ReflowCountMgr* mMgr; // weak reference (don't delete)
336 // Counting Class
337 class IndiReflowCounter {
338 public:
339 explicit IndiReflowCounter(ReflowCountMgr* aMgr = nullptr)
340 : mFrame(nullptr),
341 mCount(0),
342 mMgr(aMgr),
343 mCounter(aMgr),
344 mHasBeenOutput(false) {}
345 virtual ~IndiReflowCounter() = default;
347 nsAutoString mName;
348 nsIFrame* mFrame; // weak reference (don't delete)
349 int32_t mCount;
351 ReflowCountMgr* mMgr; // weak reference (don't delete)
353 ReflowCounter mCounter;
354 bool mHasBeenOutput;
357 //--------------------
358 // Manager Class
359 //--------------------
360 class ReflowCountMgr {
361 public:
362 ReflowCountMgr();
363 virtual ~ReflowCountMgr();
365 void ClearTotals();
366 void ClearGrandTotals();
367 void DisplayTotals(const char* aStr);
368 void DisplayHTMLTotals(const char* aStr);
369 void DisplayDiffsInTotals();
371 void Add(const char* aName, nsIFrame* aFrame);
372 ReflowCounter* LookUp(const char* aName);
374 void PaintCount(const char* aName, gfxContext* aRenderingContext,
375 nsPresContext* aPresContext, nsIFrame* aFrame,
376 const nsPoint& aOffset, uint32_t aColor);
378 FILE* GetOutFile() { return mFD; }
380 void SetPresContext(nsPresContext* aPresContext) {
381 mPresContext = aPresContext; // weak reference
383 void SetPresShell(PresShell* aPresShell) {
384 mPresShell = aPresShell; // weak reference
387 void SetDumpFrameCounts(bool aVal) { mDumpFrameCounts = aVal; }
388 void SetDumpFrameByFrameCounts(bool aVal) { mDumpFrameByFrameCounts = aVal; }
389 void SetPaintFrameCounts(bool aVal) { mPaintFrameByFrameCounts = aVal; }
391 bool IsPaintingFrameCounts() { return mPaintFrameByFrameCounts; }
393 protected:
394 void DisplayTotals(uint32_t aTotal, uint32_t* aDupArray, char* aTitle);
395 void DisplayHTMLTotals(uint32_t aTotal, uint32_t* aDupArray, char* aTitle);
397 void DoGrandTotals();
398 void DoIndiTotalsTree();
400 // HTML Output Methods
401 void DoGrandHTMLTotals();
403 nsClassHashtable<nsCharPtrHashKey, ReflowCounter> mCounts;
404 nsClassHashtable<nsCharPtrHashKey, IndiReflowCounter> mIndiFrameCounts;
405 FILE* mFD;
407 bool mDumpFrameCounts;
408 bool mDumpFrameByFrameCounts;
409 bool mPaintFrameByFrameCounts;
411 bool mCycledOnce;
413 // Root Frame for Individual Tracking
414 nsPresContext* mPresContext;
415 PresShell* mPresShell;
417 // ReflowCountMgr gReflowCountMgr;
419 #endif
420 //========================================================================
422 // comment out to hide caret
423 #define SHOW_CARET
425 // The upper bound on the amount of time to spend reflowing, in
426 // microseconds. When this bound is exceeded and reflow commands are
427 // still queued up, a reflow event is posted. The idea is for reflow
428 // to not hog the processor beyond the time specifed in
429 // gMaxRCProcessingTime. This data member is initialized from the
430 // layout.reflow.timeslice pref.
431 #define NS_MAX_REFLOW_TIME 1000000
432 static int32_t gMaxRCProcessingTime = -1;
434 struct nsCallbackEventRequest {
435 nsIReflowCallback* callback;
436 nsCallbackEventRequest* next;
439 // ----------------------------------------------------------------------------
441 // NOTE(emilio): It'd be nice for this to assert that our document isn't in the
442 // bfcache, but font pref changes don't care about that, and maybe / probably
443 // shouldn't.
444 #ifdef DEBUG
445 # define ASSERT_REFLOW_SCHEDULED_STATE() \
447 if (ObservingLayoutFlushes()) { \
448 MOZ_ASSERT( \
449 mDocument->GetBFCacheEntry() || \
450 mPresContext->RefreshDriver()->IsLayoutFlushObserver(this), \
451 "Unexpected state"); \
452 } else { \
453 MOZ_ASSERT( \
454 !mPresContext->RefreshDriver()->IsLayoutFlushObserver(this), \
455 "Unexpected state"); \
458 #else
459 # define ASSERT_REFLOW_SCHEDULED_STATE() /* nothing */
460 #endif
462 class nsAutoCauseReflowNotifier {
463 public:
464 MOZ_CAN_RUN_SCRIPT explicit nsAutoCauseReflowNotifier(PresShell* aPresShell)
465 : mPresShell(aPresShell) {
466 mPresShell->WillCauseReflow();
468 MOZ_CAN_RUN_SCRIPT ~nsAutoCauseReflowNotifier() {
469 // This check should not be needed. Currently the only place that seem
470 // to need it is the code that deals with bug 337586.
471 if (!mPresShell->mHaveShutDown) {
472 RefPtr<PresShell> presShell(mPresShell);
473 presShell->DidCauseReflow();
474 } else {
475 nsContentUtils::RemoveScriptBlocker();
479 PresShell* mPresShell;
482 class MOZ_STACK_CLASS nsPresShellEventCB : public EventDispatchingCallback {
483 public:
484 explicit nsPresShellEventCB(PresShell* aPresShell) : mPresShell(aPresShell) {}
486 MOZ_CAN_RUN_SCRIPT
487 virtual void HandleEvent(EventChainPostVisitor& aVisitor) override {
488 if (aVisitor.mPresContext && aVisitor.mEvent->mClass != eBasicEventClass) {
489 if (aVisitor.mEvent->mMessage == eMouseDown ||
490 aVisitor.mEvent->mMessage == eMouseUp) {
491 // Mouse-up and mouse-down events call nsIFrame::HandlePress/Release
492 // which call GetContentOffsetsFromPoint which requires up-to-date
493 // layout. Bring layout up-to-date now so that GetCurrentEventFrame()
494 // below will return a real frame and we don't have to worry about
495 // destroying it by flushing later.
496 MOZ_KnownLive(mPresShell)->FlushPendingNotifications(FlushType::Layout);
497 } else if (aVisitor.mEvent->mMessage == eWheel &&
498 aVisitor.mEventStatus != nsEventStatus_eConsumeNoDefault) {
499 nsIFrame* frame = mPresShell->GetCurrentEventFrame();
500 if (frame) {
501 // chrome (including addons) should be able to know if content
502 // handles both D3E "wheel" event and legacy mouse scroll events.
503 // We should dispatch legacy mouse events before dispatching the
504 // "wheel" event into system group.
505 RefPtr<EventStateManager> esm =
506 aVisitor.mPresContext->EventStateManager();
507 esm->DispatchLegacyMouseScrollEvents(
508 frame, aVisitor.mEvent->AsWheelEvent(), &aVisitor.mEventStatus);
511 nsIFrame* frame = mPresShell->GetCurrentEventFrame();
512 if (!frame && (aVisitor.mEvent->mMessage == eMouseUp ||
513 aVisitor.mEvent->mMessage == eTouchEnd)) {
514 // Redirect BUTTON_UP and TOUCH_END events to the root frame to ensure
515 // that capturing is released.
516 frame = mPresShell->GetRootFrame();
518 if (frame) {
519 frame->HandleEvent(aVisitor.mPresContext, aVisitor.mEvent->AsGUIEvent(),
520 &aVisitor.mEventStatus);
525 RefPtr<PresShell> mPresShell;
528 class nsBeforeFirstPaintDispatcher : public Runnable {
529 public:
530 explicit nsBeforeFirstPaintDispatcher(Document* aDocument)
531 : mozilla::Runnable("nsBeforeFirstPaintDispatcher"),
532 mDocument(aDocument) {}
534 // Fires the "before-first-paint" event so that interested parties (right now,
535 // the mobile browser) are aware of it.
536 NS_IMETHOD Run() override {
537 nsCOMPtr<nsIObserverService> observerService =
538 mozilla::services::GetObserverService();
539 if (observerService) {
540 observerService->NotifyObservers(ToSupports(mDocument),
541 "before-first-paint", nullptr);
543 return NS_OK;
546 private:
547 RefPtr<Document> mDocument;
550 // This is a helper class to track whether the targeted frame is destroyed after
551 // dispatching pointer events. In that case, we need the original targeted
552 // content so that we can dispatch the mouse events to it.
553 class MOZ_STACK_CLASS AutoPointerEventTargetUpdater final {
554 public:
555 AutoPointerEventTargetUpdater(PresShell* aShell, WidgetEvent* aEvent,
556 nsIFrame* aFrame, nsIContent** aTargetContent) {
557 MOZ_ASSERT(aEvent);
558 if (!aTargetContent || aEvent->mClass != ePointerEventClass) {
559 // Make the destructor happy.
560 mTargetContent = nullptr;
561 return;
563 MOZ_ASSERT(aShell);
564 MOZ_ASSERT(aFrame);
565 MOZ_ASSERT(!aFrame->GetContent() ||
566 aShell->GetDocument() == aFrame->GetContent()->OwnerDoc());
568 mShell = aShell;
569 mWeakFrame = aFrame;
570 mTargetContent = aTargetContent;
571 aShell->mPointerEventTarget = aFrame->GetContent();
574 ~AutoPointerEventTargetUpdater() {
575 if (!mTargetContent || !mShell || mWeakFrame.IsAlive()) {
576 return;
578 mShell->mPointerEventTarget.swap(*mTargetContent);
581 private:
582 RefPtr<PresShell> mShell;
583 AutoWeakFrame mWeakFrame;
584 nsIContent** mTargetContent;
587 bool PresShell::sDisableNonTestMouseEvents = false;
589 LazyLogModule PresShell::gLog("PresShell");
591 TimeStamp PresShell::EventHandler::sLastInputCreated;
592 TimeStamp PresShell::EventHandler::sLastInputProcessed;
593 StaticRefPtr<Element> PresShell::EventHandler::sLastKeyDownEventTargetElement;
595 bool PresShell::sProcessInteractable = false;
597 static bool gVerifyReflowEnabled;
599 bool PresShell::GetVerifyReflowEnable() {
600 #ifdef DEBUG
601 static bool firstTime = true;
602 if (firstTime) {
603 firstTime = false;
604 char* flags = PR_GetEnv("GECKO_VERIFY_REFLOW_FLAGS");
605 if (flags) {
606 bool error = false;
608 for (;;) {
609 char* comma = strchr(flags, ',');
610 if (comma) *comma = '\0';
612 bool found = false;
613 const VerifyReflowFlagData* flag = gFlags;
614 const VerifyReflowFlagData* limit = gFlags + NUM_VERIFY_REFLOW_FLAGS;
615 while (flag < limit) {
616 if (nsCRT::strcasecmp(flag->name, flags) == 0) {
617 gVerifyReflowFlags |= flag->bit;
618 found = true;
619 break;
621 ++flag;
624 if (!found) error = true;
626 if (!comma) break;
628 *comma = ',';
629 flags = comma + 1;
632 if (error) ShowVerifyReflowFlags();
635 if (VerifyReflowFlags::On & gVerifyReflowFlags) {
636 gVerifyReflowEnabled = true;
638 printf("Note: verifyreflow is enabled");
639 if (VerifyReflowFlags::Noisy & gVerifyReflowFlags) {
640 printf(" (noisy)");
642 if (VerifyReflowFlags::All & gVerifyReflowFlags) {
643 printf(" (all)");
645 if (VerifyReflowFlags::DumpCommands & gVerifyReflowFlags) {
646 printf(" (show reflow commands)");
648 if (VerifyReflowFlags::NoisyCommands & gVerifyReflowFlags) {
649 printf(" (noisy reflow commands)");
650 if (VerifyReflowFlags::ReallyNoisyCommands & gVerifyReflowFlags) {
651 printf(" (REALLY noisy reflow commands)");
654 printf("\n");
657 #endif
658 return gVerifyReflowEnabled;
661 void PresShell::SetVerifyReflowEnable(bool aEnabled) {
662 gVerifyReflowEnabled = aEnabled;
665 void PresShell::AddAutoWeakFrame(AutoWeakFrame* aWeakFrame) {
666 if (aWeakFrame->GetFrame()) {
667 aWeakFrame->GetFrame()->AddStateBits(NS_FRAME_EXTERNAL_REFERENCE);
669 aWeakFrame->SetPreviousWeakFrame(mAutoWeakFrames);
670 mAutoWeakFrames = aWeakFrame;
673 void PresShell::AddWeakFrame(WeakFrame* aWeakFrame) {
674 if (aWeakFrame->GetFrame()) {
675 aWeakFrame->GetFrame()->AddStateBits(NS_FRAME_EXTERNAL_REFERENCE);
677 MOZ_ASSERT(!mWeakFrames.Contains(aWeakFrame));
678 mWeakFrames.Insert(aWeakFrame);
681 void PresShell::RemoveAutoWeakFrame(AutoWeakFrame* aWeakFrame) {
682 if (mAutoWeakFrames == aWeakFrame) {
683 mAutoWeakFrames = aWeakFrame->GetPreviousWeakFrame();
684 return;
686 AutoWeakFrame* nextWeak = mAutoWeakFrames;
687 while (nextWeak && nextWeak->GetPreviousWeakFrame() != aWeakFrame) {
688 nextWeak = nextWeak->GetPreviousWeakFrame();
690 if (nextWeak) {
691 nextWeak->SetPreviousWeakFrame(aWeakFrame->GetPreviousWeakFrame());
695 void PresShell::RemoveWeakFrame(WeakFrame* aWeakFrame) {
696 MOZ_ASSERT(mWeakFrames.Contains(aWeakFrame));
697 mWeakFrames.Remove(aWeakFrame);
700 already_AddRefed<nsFrameSelection> PresShell::FrameSelection() {
701 RefPtr<nsFrameSelection> ret = mSelection;
702 return ret.forget();
705 //----------------------------------------------------------------------
707 static uint32_t sNextPresShellId = 0;
709 /* static */
710 bool PresShell::AccessibleCaretEnabled(nsIDocShell* aDocShell) {
711 // If the pref forces it on, then enable it.
712 if (StaticPrefs::layout_accessiblecaret_enabled()) {
713 return true;
715 // If the touch pref is on, and touch events are enabled (this depends
716 // on the specific device running), then enable it.
717 if (StaticPrefs::layout_accessiblecaret_enabled_on_touch() &&
718 dom::TouchEvent::PrefEnabled(aDocShell)) {
719 return true;
721 // Otherwise, disabled.
722 return false;
725 PresShell::PresShell(Document* aDocument)
726 : mDocument(aDocument),
727 mViewManager(nullptr),
728 mFrameManager(nullptr),
729 mAutoWeakFrames(nullptr),
730 #ifdef ACCESSIBILITY
731 mDocAccessible(nullptr),
732 #endif // ACCESSIBILITY
733 mCurrentEventFrame(nullptr),
734 mMouseLocation(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE),
735 mLastResolutionChangeOrigin(ResolutionChangeOrigin::Apz),
736 mPaintCount(0),
737 mAPZFocusSequenceNumber(0),
738 mCanvasBackgroundColor(NS_RGBA(0, 0, 0, 0)),
739 mActiveSuppressDisplayport(0),
740 mPresShellId(++sNextPresShellId),
741 mFontSizeInflationEmPerLine(0),
742 mFontSizeInflationMinTwips(0),
743 mFontSizeInflationLineThreshold(0),
744 mSelectionFlags(nsISelectionDisplay::DISPLAY_TEXT |
745 nsISelectionDisplay::DISPLAY_IMAGES),
746 mChangeNestCount(0),
747 mRenderingStateFlags(RenderingStateFlags::None),
748 mInFlush(false),
749 mCaretEnabled(false),
750 mNeedLayoutFlush(true),
751 mNeedStyleFlush(true),
752 mNeedThrottledAnimationFlush(true),
753 mVisualViewportSizeSet(false),
754 mDidInitialize(false),
755 mIsDestroying(false),
756 mIsReflowing(false),
757 mIsObservingDocument(false),
758 mForbiddenToFlush(false),
759 mIsDocumentGone(false),
760 mHaveShutDown(false),
761 mPaintingSuppressed(false),
762 mLastRootReflowHadUnconstrainedBSize(false),
763 mShouldUnsuppressPainting(false),
764 mIgnoreFrameDestruction(false),
765 mIsActive(true),
766 mIsInActiveTab(true),
767 mFrozen(false),
768 mIsFirstPaint(true),
769 mObservesMutationsForPrint(false),
770 mWasLastReflowInterrupted(false),
771 mObservingStyleFlushes(false),
772 mObservingLayoutFlushes(false),
773 mResizeEventPending(false),
774 mFontSizeInflationForceEnabled(false),
775 mFontSizeInflationDisabledInMasterProcess(false),
776 mFontSizeInflationEnabled(false),
777 mIsNeverPainting(false),
778 mResolutionUpdated(false),
779 mResolutionUpdatedByApz(false),
780 mUnderHiddenEmbedderElement(false),
781 mDocumentLoading(false),
782 mNoDelayedMouseEvents(false),
783 mNoDelayedKeyEvents(false),
784 mApproximateFrameVisibilityVisited(false),
785 mHasCSSBackgroundColor(true),
786 mIsLastChromeOnlyEscapeKeyConsumed(false),
787 mHasReceivedPaintMessage(false),
788 mIsLastKeyDownCanceled(false),
789 mHasHandledUserInput(false),
790 mForceDispatchKeyPressEventsForNonPrintableKeys(false),
791 mForceUseLegacyKeyCodeAndCharCodeValues(false),
792 mInitializedWithKeyPressEventDispatchingBlacklist(false),
793 mForceUseLegacyNonPrimaryDispatch(false),
794 mInitializedWithClickEventDispatchingBlacklist(false),
795 mMouseLocationWasSetBySynthesizedMouseEventForTests(false),
796 mHasTriedFastUnsuppress(false),
797 mProcessingReflowCommands(false),
798 mPendingDidDoReflow(false) {
799 MOZ_LOG(gLog, LogLevel::Debug, ("PresShell::PresShell this=%p", this));
800 MOZ_ASSERT(aDocument);
802 #ifdef MOZ_REFLOW_PERF
803 mReflowCountMgr = MakeUnique<ReflowCountMgr>();
804 mReflowCountMgr->SetPresContext(mPresContext);
805 mReflowCountMgr->SetPresShell(this);
806 #endif
807 mLastOSWake = mLoadBegin = TimeStamp::Now();
810 NS_INTERFACE_TABLE_HEAD(PresShell)
811 NS_INTERFACE_TABLE_BEGIN
812 // In most cases, PresShell should be treated as concrete class, but need to
813 // QI for weak reference. Therefore, the case needed by do_QueryReferent()
814 // should be tested first.
815 NS_INTERFACE_TABLE_ENTRY(PresShell, PresShell)
816 NS_INTERFACE_TABLE_ENTRY(PresShell, nsIDocumentObserver)
817 NS_INTERFACE_TABLE_ENTRY(PresShell, nsISelectionController)
818 NS_INTERFACE_TABLE_ENTRY(PresShell, nsISelectionDisplay)
819 NS_INTERFACE_TABLE_ENTRY(PresShell, nsIObserver)
820 NS_INTERFACE_TABLE_ENTRY(PresShell, nsISupportsWeakReference)
821 NS_INTERFACE_TABLE_ENTRY(PresShell, nsIMutationObserver)
822 NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(PresShell, nsISupports, nsIObserver)
823 NS_INTERFACE_TABLE_END
824 NS_INTERFACE_TABLE_TO_MAP_SEGUE
825 NS_INTERFACE_MAP_END
827 NS_IMPL_ADDREF(PresShell)
828 NS_IMPL_RELEASE(PresShell)
830 PresShell::~PresShell() {
831 MOZ_RELEASE_ASSERT(!mForbiddenToFlush,
832 "Flag should only be set temporarily, while doing things "
833 "that shouldn't cause destruction");
834 MOZ_LOG(gLog, LogLevel::Debug, ("PresShell::~PresShell this=%p", this));
836 if (!mHaveShutDown) {
837 MOZ_ASSERT_UNREACHABLE("Someone did not call PresShell::Destroy()");
838 Destroy();
841 NS_ASSERTION(mCurrentEventContentStack.Count() == 0,
842 "Huh, event content left on the stack in pres shell dtor!");
843 NS_ASSERTION(mFirstCallbackEventRequest == nullptr &&
844 mLastCallbackEventRequest == nullptr,
845 "post-reflow queues not empty. This means we're leaking");
847 MOZ_ASSERT(!mAllocatedPointers || mAllocatedPointers->IsEmpty(),
848 "Some pres arena objects were not freed");
850 mFrameManager = nullptr;
851 mFrameConstructor = nullptr;
853 mCurrentEventContent = nullptr;
857 * Initialize the presentation shell. Create view manager and style
858 * manager.
859 * Note this can't be merged into our constructor because caret initialization
860 * calls AddRef() on us.
862 void PresShell::Init(nsPresContext* aPresContext, nsViewManager* aViewManager) {
863 MOZ_ASSERT(mDocument);
864 MOZ_ASSERT(aPresContext);
865 MOZ_ASSERT(aViewManager);
866 MOZ_ASSERT(!mViewManager, "already initialized");
868 mViewManager = aViewManager;
870 // mDocument is now set. It might have a display document whose "need layout/
871 // style" flush flags are not set, but ours will be set. To keep these
872 // consistent, call the flag setting functions to propagate those flags up
873 // to the display document.
874 SetNeedLayoutFlush();
875 SetNeedStyleFlush();
877 // Create our frame constructor.
878 mFrameConstructor = MakeUnique<nsCSSFrameConstructor>(mDocument, this);
880 mFrameManager = mFrameConstructor.get();
882 // The document viewer owns both view manager and pres shell.
883 mViewManager->SetPresShell(this);
885 // Bind the context to the presentation shell.
886 // FYI: We cannot initialize mPresContext in the constructor because we
887 // cannot call AttachPresShell() in it and once we initialize
888 // mPresContext, other objects may refer refresh driver or restyle
889 // manager via mPresContext and that causes hitting MOZ_ASSERT in some
890 // places. Therefore, we should initialize mPresContext here with
891 // const_cast hack since we want to guarantee that mPresContext lives
892 // as long as the PresShell.
893 const_cast<RefPtr<nsPresContext>&>(mPresContext) = aPresContext;
894 mPresContext->AttachPresShell(this);
896 mPresContext->InitFontCache();
898 // FIXME(emilio, bug 1544185): Some Android code somehow depends on the shell
899 // being eagerly registered as a style flush observer. This shouldn't be
900 // needed otherwise.
901 EnsureStyleFlush();
903 // Add the preference style sheet.
904 UpdatePreferenceStyles();
906 bool accessibleCaretEnabled =
907 AccessibleCaretEnabled(mDocument->GetDocShell());
908 if (accessibleCaretEnabled) {
909 // Need to happen before nsFrameSelection has been set up.
910 mAccessibleCaretEventHub = new AccessibleCaretEventHub(this);
913 mSelection = new nsFrameSelection(this, nullptr, accessibleCaretEnabled);
915 // Important: this has to happen after the selection has been set up
916 #ifdef SHOW_CARET
917 // make the caret
918 mCaret = new nsCaret();
919 mCaret->Init(this);
920 mOriginalCaret = mCaret;
922 // SetCaretEnabled(true); // make it show in browser windows
923 #endif
924 // set up selection to be displayed in document
925 // Don't enable selection for print media
926 nsPresContext::nsPresContextType type = mPresContext->Type();
927 if (type != nsPresContext::eContext_PrintPreview &&
928 type != nsPresContext::eContext_Print) {
929 SetDisplaySelection(nsISelectionController::SELECTION_DISABLED);
932 if (gMaxRCProcessingTime == -1) {
933 gMaxRCProcessingTime =
934 Preferences::GetInt("layout.reflow.timeslice", NS_MAX_REFLOW_TIME);
937 if (nsStyleSheetService* ss = nsStyleSheetService::GetInstance()) {
938 ss->RegisterPresShell(this);
942 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
943 if (os) {
944 os->AddObserver(this, "memory-pressure", false);
945 os->AddObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC, false);
946 if (XRE_IsParentProcess() && !sProcessInteractable) {
947 os->AddObserver(this, "sessionstore-one-or-no-tab-restored", false);
949 os->AddObserver(this, "font-info-updated", false);
950 os->AddObserver(this, "internal-look-and-feel-changed", false);
954 #ifdef MOZ_REFLOW_PERF
955 if (mReflowCountMgr) {
956 bool paintFrameCounts =
957 Preferences::GetBool("layout.reflow.showframecounts");
959 bool dumpFrameCounts =
960 Preferences::GetBool("layout.reflow.dumpframecounts");
962 bool dumpFrameByFrameCounts =
963 Preferences::GetBool("layout.reflow.dumpframebyframecounts");
965 mReflowCountMgr->SetDumpFrameCounts(dumpFrameCounts);
966 mReflowCountMgr->SetDumpFrameByFrameCounts(dumpFrameByFrameCounts);
967 mReflowCountMgr->SetPaintFrameCounts(paintFrameCounts);
969 #endif
971 if (mDocument->HasAnimationController()) {
972 SMILAnimationController* animCtrl = mDocument->GetAnimationController();
973 animCtrl->NotifyRefreshDriverCreated(GetPresContext()->RefreshDriver());
976 for (DocumentTimeline* timeline : mDocument->Timelines()) {
977 timeline->NotifyRefreshDriverCreated(GetPresContext()->RefreshDriver());
980 // Get our activeness from the docShell.
981 ActivenessMaybeChanged();
983 // Setup our font inflation preferences.
984 mFontSizeInflationEmPerLine = StaticPrefs::font_size_inflation_emPerLine();
985 mFontSizeInflationMinTwips = StaticPrefs::font_size_inflation_minTwips();
986 mFontSizeInflationLineThreshold =
987 StaticPrefs::font_size_inflation_lineThreshold();
988 mFontSizeInflationForceEnabled =
989 StaticPrefs::font_size_inflation_forceEnabled();
990 mFontSizeInflationDisabledInMasterProcess =
991 StaticPrefs::font_size_inflation_disabledInMasterProcess();
992 // We'll compute the font size inflation state in Initialize(), when we know
993 // the document type.
995 mTouchManager.Init(this, mDocument);
997 if (mPresContext->IsRootContentDocumentCrossProcess()) {
998 mZoomConstraintsClient = new ZoomConstraintsClient();
999 mZoomConstraintsClient->Init(this, mDocument);
1001 // We call this to create mMobileViewportManager, if it is needed.
1002 MaybeRecreateMobileViewportManager(false);
1005 if (nsCOMPtr<nsIDocShell> docShell = mPresContext->GetDocShell()) {
1006 BrowsingContext* bc = docShell->GetBrowsingContext();
1007 bool embedderFrameIsHidden = true;
1008 if (Element* embedderElement = bc->GetEmbedderElement()) {
1009 if (auto embedderFrame = embedderElement->GetPrimaryFrame()) {
1010 embedderFrameIsHidden = !embedderFrame->StyleVisibility()->IsVisible();
1014 if (BrowsingContext* parent = bc->GetParent()) {
1015 if (nsCOMPtr<nsIDocShell> parentDocShell = parent->GetDocShell()) {
1016 if (PresShell* parentPresShell = parentDocShell->GetPresShell()) {
1017 mUnderHiddenEmbedderElement =
1018 parentPresShell->IsUnderHiddenEmbedderElement() ||
1019 embedderFrameIsHidden;
1026 enum TextPerfLogType { eLog_reflow, eLog_loaddone, eLog_totals };
1028 static void LogTextPerfStats(gfxTextPerfMetrics* aTextPerf,
1029 PresShell* aPresShell,
1030 const gfxTextPerfMetrics::TextCounts& aCounts,
1031 float aTime, TextPerfLogType aLogType,
1032 const char* aURL) {
1033 LogModule* tpLog = gfxPlatform::GetLog(eGfxLog_textperf);
1035 // ignore XUL contexts unless at debug level
1036 mozilla::LogLevel logLevel = LogLevel::Warning;
1037 if (aCounts.numContentTextRuns == 0) {
1038 logLevel = LogLevel::Debug;
1041 if (!MOZ_LOG_TEST(tpLog, logLevel)) {
1042 return;
1045 char prefix[256];
1047 switch (aLogType) {
1048 case eLog_reflow:
1049 SprintfLiteral(prefix, "(textperf-reflow) %p time-ms: %7.0f", aPresShell,
1050 aTime);
1051 break;
1052 case eLog_loaddone:
1053 SprintfLiteral(prefix, "(textperf-loaddone) %p time-ms: %7.0f",
1054 aPresShell, aTime);
1055 break;
1056 default:
1057 MOZ_ASSERT(aLogType == eLog_totals, "unknown textperf log type");
1058 SprintfLiteral(prefix, "(textperf-totals) %p", aPresShell);
1061 double hitRatio = 0.0;
1062 uint32_t lookups = aCounts.wordCacheHit + aCounts.wordCacheMiss;
1063 if (lookups) {
1064 hitRatio = double(aCounts.wordCacheHit) / double(lookups);
1067 if (aLogType == eLog_loaddone) {
1068 MOZ_LOG(
1069 tpLog, logLevel,
1070 ("%s reflow: %d chars: %d "
1071 "[%s] "
1072 "content-textruns: %d chrome-textruns: %d "
1073 "max-textrun-len: %d "
1074 "word-cache-lookups: %d word-cache-hit-ratio: %4.3f "
1075 "word-cache-space: %d word-cache-long: %d "
1076 "pref-fallbacks: %d system-fallbacks: %d "
1077 "textruns-const: %d textruns-destr: %d "
1078 "generic-lookups: %d "
1079 "cumulative-textruns-destr: %d\n",
1080 prefix, aTextPerf->reflowCount, aCounts.numChars, (aURL ? aURL : ""),
1081 aCounts.numContentTextRuns, aCounts.numChromeTextRuns,
1082 aCounts.maxTextRunLen, lookups, hitRatio, aCounts.wordCacheSpaceRules,
1083 aCounts.wordCacheLong, aCounts.fallbackPrefs, aCounts.fallbackSystem,
1084 aCounts.textrunConst, aCounts.textrunDestr, aCounts.genericLookups,
1085 aTextPerf->cumulative.textrunDestr));
1086 } else {
1087 MOZ_LOG(
1088 tpLog, logLevel,
1089 ("%s reflow: %d chars: %d "
1090 "content-textruns: %d chrome-textruns: %d "
1091 "max-textrun-len: %d "
1092 "word-cache-lookups: %d word-cache-hit-ratio: %4.3f "
1093 "word-cache-space: %d word-cache-long: %d "
1094 "pref-fallbacks: %d system-fallbacks: %d "
1095 "textruns-const: %d textruns-destr: %d "
1096 "generic-lookups: %d "
1097 "cumulative-textruns-destr: %d\n",
1098 prefix, aTextPerf->reflowCount, aCounts.numChars,
1099 aCounts.numContentTextRuns, aCounts.numChromeTextRuns,
1100 aCounts.maxTextRunLen, lookups, hitRatio, aCounts.wordCacheSpaceRules,
1101 aCounts.wordCacheLong, aCounts.fallbackPrefs, aCounts.fallbackSystem,
1102 aCounts.textrunConst, aCounts.textrunDestr, aCounts.genericLookups,
1103 aTextPerf->cumulative.textrunDestr));
1107 bool PresShell::InRDMPane() {
1108 if (Document* doc = GetDocument()) {
1109 if (BrowsingContext* bc = doc->GetBrowsingContext()) {
1110 return bc->InRDMPane();
1113 return false;
1116 #if defined(MOZ_WIDGET_ANDROID)
1117 void PresShell::MaybeNotifyShowDynamicToolbar() {
1118 const DynamicToolbarState dynToolbarState = GetDynamicToolbarState();
1119 if ((dynToolbarState == DynamicToolbarState::Collapsed ||
1120 dynToolbarState == DynamicToolbarState::InTransition)) {
1121 MOZ_ASSERT(mPresContext &&
1122 mPresContext->IsRootContentDocumentCrossProcess());
1123 if (BrowserChild* browserChild = BrowserChild::GetFrom(this)) {
1124 browserChild->SendShowDynamicToolbar();
1128 #endif // defined(MOZ_WIDGET_ANDROID)
1130 void PresShell::Destroy() {
1131 // Do not add code before this line please!
1132 if (mHaveShutDown) {
1133 return;
1136 NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
1137 "destroy called on presshell while scripts not blocked");
1139 [[maybe_unused]] nsIURI* uri = mDocument->GetDocumentURI();
1140 AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING_RELEVANT_FOR_JS(
1141 "Layout tree destruction", LAYOUT_Destroy,
1142 uri ? uri->GetSpecOrDefault() : "N/A"_ns);
1144 // Try to determine if the page is the user had a meaningful opportunity to
1145 // zoom this page. This is not 100% accurate but should be "good enough" for
1146 // telemetry purposes.
1147 auto isUserZoomablePage = [&]() -> bool {
1148 if (mIsFirstPaint) {
1149 // Page was never painted, so it wasn't zoomable by the user. We get a
1150 // handful of these "transient" presShells.
1151 return false;
1153 if (!mPresContext->IsRootContentDocumentCrossProcess()) {
1154 // Not a root content document, so APZ doesn't support zooming it.
1155 return false;
1157 if (InRDMPane()) {
1158 // Responsive design mode is a special case that we want to ignore here.
1159 return false;
1161 if (mDocument && mDocument->IsInitialDocument()) {
1162 // Ignore initial about:blank page loads
1163 return false;
1165 if (XRE_IsContentProcess() &&
1166 IsExtensionRemoteType(ContentChild::GetSingleton()->GetRemoteType())) {
1167 // Also omit presShells from the extension process because they sometimes
1168 // can't be zoomed by the user.
1169 return false;
1171 // Otherwise assume the page is user-zoomable.
1172 return true;
1174 if (isUserZoomablePage()) {
1175 Telemetry::Accumulate(Telemetry::APZ_ZOOM_ACTIVITY,
1176 IsResolutionUpdatedByApz());
1179 // dump out cumulative text perf metrics
1180 gfxTextPerfMetrics* tp;
1181 if (mPresContext && (tp = mPresContext->GetTextPerfMetrics())) {
1182 tp->Accumulate();
1183 if (tp->cumulative.numChars > 0) {
1184 LogTextPerfStats(tp, this, tp->cumulative, 0.0, eLog_totals, nullptr);
1187 if (mPresContext) {
1188 if (gfxUserFontSet* fs = mPresContext->GetUserFontSet()) {
1189 uint32_t fontCount;
1190 uint64_t fontSize;
1191 fs->GetLoadStatistics(fontCount, fontSize);
1192 Telemetry::Accumulate(Telemetry::WEBFONT_PER_PAGE, fontCount);
1193 Telemetry::Accumulate(Telemetry::WEBFONT_SIZE_PER_PAGE,
1194 uint32_t(fontSize / 1024));
1195 } else {
1196 Telemetry::Accumulate(Telemetry::WEBFONT_PER_PAGE, 0);
1197 Telemetry::Accumulate(Telemetry::WEBFONT_SIZE_PER_PAGE, 0);
1201 #ifdef MOZ_REFLOW_PERF
1202 DumpReflows();
1203 mReflowCountMgr = nullptr;
1204 #endif
1206 if (mZoomConstraintsClient) {
1207 mZoomConstraintsClient->Destroy();
1208 mZoomConstraintsClient = nullptr;
1210 if (mMobileViewportManager) {
1211 mMobileViewportManager->Destroy();
1212 mMobileViewportManager = nullptr;
1213 mMVMContext = nullptr;
1216 #ifdef ACCESSIBILITY
1217 if (mDocAccessible) {
1218 # ifdef DEBUG
1219 if (a11y::logging::IsEnabled(a11y::logging::eDocDestroy))
1220 a11y::logging::DocDestroy("presshell destroyed", mDocument);
1221 # endif
1223 mDocAccessible->Shutdown();
1224 mDocAccessible = nullptr;
1226 #endif // ACCESSIBILITY
1228 MaybeReleaseCapturingContent();
1230 EventHandler::OnPresShellDestroy(mDocument);
1232 if (mContentToScrollTo) {
1233 mContentToScrollTo->RemoveProperty(nsGkAtoms::scrolling);
1234 mContentToScrollTo = nullptr;
1237 if (mPresContext) {
1238 // We need to notify the destroying the nsPresContext to ESM for
1239 // suppressing to use from ESM.
1240 mPresContext->EventStateManager()->NotifyDestroyPresContext(mPresContext);
1243 if (nsStyleSheetService* ss = nsStyleSheetService::GetInstance()) {
1244 ss->UnregisterPresShell(this);
1248 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
1249 if (os) {
1250 os->RemoveObserver(this, "memory-pressure");
1251 os->RemoveObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC);
1252 if (XRE_IsParentProcess()) {
1253 os->RemoveObserver(this, "sessionstore-one-or-no-tab-restored");
1255 os->RemoveObserver(this, "font-info-updated");
1256 os->RemoveObserver(this, "internal-look-and-feel-changed");
1260 // If our paint suppression timer is still active, kill it.
1261 CancelPaintSuppressionTimer();
1263 // Same for our reflow continuation timer
1264 if (mReflowContinueTimer) {
1265 mReflowContinueTimer->Cancel();
1266 mReflowContinueTimer = nullptr;
1269 mSynthMouseMoveEvent.Revoke();
1271 mUpdateApproximateFrameVisibilityEvent.Revoke();
1273 ClearApproximatelyVisibleFramesList(Some(OnNonvisible::DiscardImages));
1275 if (mCaret) {
1276 mCaret->Terminate();
1277 mCaret = nullptr;
1280 mFocusedFrameSelection = nullptr;
1282 if (mSelection) {
1283 RefPtr<nsFrameSelection> frameSelection = mSelection;
1284 frameSelection->DisconnectFromPresShell();
1287 // release our pref style sheet, if we have one still
1289 // TODO(emilio): Should we move the preference sheet tracking to the Document?
1290 RemovePreferenceStyles();
1292 mIsDestroying = true;
1294 // We can't release all the event content in
1295 // mCurrentEventContentStack here since there might be code on the
1296 // stack that will release the event content too. Double release
1297 // bad!
1299 // The frames will be torn down, so remove them from the current
1300 // event frame stack (since they'd be dangling references if we'd
1301 // leave them in) and null out the mCurrentEventFrame pointer as
1302 // well.
1304 mCurrentEventFrame = nullptr;
1306 int32_t i, count = mCurrentEventFrameStack.Length();
1307 for (i = 0; i < count; i++) {
1308 mCurrentEventFrameStack[i] = nullptr;
1311 mFramesToDirty.Clear();
1312 mPendingScrollAnchorSelection.Clear();
1313 mPendingScrollAnchorAdjustment.Clear();
1314 mPendingScrollResnap.Clear();
1316 if (mViewManager) {
1317 // Clear the view manager's weak pointer back to |this| in case it
1318 // was leaked.
1319 mViewManager->SetPresShell(nullptr);
1320 mViewManager = nullptr;
1323 nsRefreshDriver* rd = GetPresContext()->RefreshDriver();
1325 // This shell must be removed from the document before the frame
1326 // hierarchy is torn down to avoid finding deleted frames through
1327 // this presshell while the frames are being torn down
1328 if (mDocument) {
1329 NS_ASSERTION(mDocument->GetPresShell() == this, "Wrong shell?");
1330 mDocument->ClearServoRestyleRoot();
1331 mDocument->DeletePresShell();
1333 if (mDocument->HasAnimationController()) {
1334 mDocument->GetAnimationController()->NotifyRefreshDriverDestroying(rd);
1336 for (DocumentTimeline* timeline : mDocument->Timelines()) {
1337 timeline->NotifyRefreshDriverDestroying(rd);
1341 if (mPresContext) {
1342 rd->CancelPendingAnimationEvents(mPresContext->AnimationEventDispatcher());
1345 // Revoke any pending events. We need to do this and cancel pending reflows
1346 // before we destroy the frame manager, since apparently frame destruction
1347 // sometimes spins the event queue when plug-ins are involved(!).
1348 // XXXmats is this still needed now that plugins are gone?
1349 StopObservingRefreshDriver();
1351 if (rd->GetPresContext() == GetPresContext()) {
1352 rd->RevokeViewManagerFlush();
1353 rd->ClearHasScheduleFlush();
1356 CancelAllPendingReflows();
1357 CancelPostedReflowCallbacks();
1359 // Destroy the frame manager. This will destroy the frame hierarchy
1360 mFrameConstructor->WillDestroyFrameTree();
1362 NS_WARNING_ASSERTION(!mAutoWeakFrames && mWeakFrames.IsEmpty(),
1363 "Weak frames alive after destroying FrameManager");
1364 while (mAutoWeakFrames) {
1365 mAutoWeakFrames->Clear(this);
1367 const nsTArray<WeakFrame*> weakFrames = ToArray(mWeakFrames);
1368 for (WeakFrame* weakFrame : weakFrames) {
1369 weakFrame->Clear(this);
1372 // Terminate AccessibleCaretEventHub after tearing down the frame tree so that
1373 // we don't need to remove caret element's frame in
1374 // AccessibleCaret::RemoveCaretElement().
1375 if (mAccessibleCaretEventHub) {
1376 mAccessibleCaretEventHub->Terminate();
1377 mAccessibleCaretEventHub = nullptr;
1380 if (mPresContext) {
1381 // We hold a reference to the pres context, and it holds a weak link back
1382 // to us. To avoid the pres context having a dangling reference, set its
1383 // pres shell to nullptr
1384 mPresContext->DetachPresShell();
1387 mHaveShutDown = true;
1389 mTouchManager.Destroy();
1392 void PresShell::StopObservingRefreshDriver() {
1393 nsRefreshDriver* rd = mPresContext->RefreshDriver();
1394 if (mResizeEventPending) {
1395 rd->RemoveResizeEventFlushObserver(this);
1397 if (mObservingLayoutFlushes) {
1398 rd->RemoveLayoutFlushObserver(this);
1400 if (mObservingStyleFlushes) {
1401 rd->RemoveStyleFlushObserver(this);
1405 void PresShell::StartObservingRefreshDriver() {
1406 nsRefreshDriver* rd = mPresContext->RefreshDriver();
1407 if (mResizeEventPending) {
1408 rd->AddResizeEventFlushObserver(this);
1410 if (mObservingLayoutFlushes) {
1411 rd->AddLayoutFlushObserver(this);
1413 if (mObservingStyleFlushes) {
1414 rd->AddStyleFlushObserver(this);
1418 nsRefreshDriver* PresShell::GetRefreshDriver() const {
1419 return mPresContext ? mPresContext->RefreshDriver() : nullptr;
1422 void PresShell::SetAuthorStyleDisabled(bool aStyleDisabled) {
1423 if (aStyleDisabled != StyleSet()->GetAuthorStyleDisabled()) {
1424 StyleSet()->SetAuthorStyleDisabled(aStyleDisabled);
1425 mDocument->ApplicableStylesChanged();
1427 nsCOMPtr<nsIObserverService> observerService =
1428 mozilla::services::GetObserverService();
1429 if (observerService) {
1430 observerService->NotifyObservers(
1431 ToSupports(mDocument), "author-style-disabled-changed", nullptr);
1436 bool PresShell::GetAuthorStyleDisabled() const {
1437 return StyleSet()->GetAuthorStyleDisabled();
1440 void PresShell::UpdatePreferenceStyles() {
1441 if (!mDocument) {
1442 return;
1445 // If the document doesn't have a window there's no need to notify
1446 // its presshell about changes to preferences since the document is
1447 // in a state where it doesn't matter any more (see
1448 // nsDocumentViewer::Close()).
1449 if (!mDocument->GetWindow()) {
1450 return;
1453 // Documents in chrome shells do not have any preference style rules applied.
1454 if (mDocument->IsInChromeDocShell()) {
1455 return;
1458 PreferenceSheet::EnsureInitialized();
1459 auto* cache = GlobalStyleSheetCache::Singleton();
1461 RefPtr<StyleSheet> newPrefSheet =
1462 PreferenceSheet::ShouldUseChromePrefs(*mDocument)
1463 ? cache->ChromePreferenceSheet()
1464 : cache->ContentPreferenceSheet();
1466 if (mPrefStyleSheet == newPrefSheet) {
1467 return;
1470 RemovePreferenceStyles();
1472 // NOTE(emilio): This sheet is added as an agent sheet, because we don't want
1473 // it to be modifiable from devtools and similar, see bugs 1239336 and
1474 // 1436782. I think it conceptually should be a user sheet, and could be
1475 // without too much trouble I'd think.
1476 StyleSet()->AppendStyleSheet(*newPrefSheet);
1477 mPrefStyleSheet = newPrefSheet;
1480 void PresShell::RemovePreferenceStyles() {
1481 if (mPrefStyleSheet) {
1482 StyleSet()->RemoveStyleSheet(*mPrefStyleSheet);
1483 mPrefStyleSheet = nullptr;
1487 void PresShell::AddUserSheet(StyleSheet* aSheet) {
1488 // Make sure this does what nsDocumentViewer::CreateStyleSet does wrt
1489 // ordering. We want this new sheet to come after all the existing stylesheet
1490 // service sheets (which are at the start), but before other user sheets; see
1491 // nsIStyleSheetService.idl for the ordering.
1493 nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance();
1494 nsTArray<RefPtr<StyleSheet>>& userSheets = *sheetService->UserStyleSheets();
1496 // Search for the place to insert the new user sheet. Since all of the
1497 // stylesheet service provided user sheets should be at the start of the style
1498 // set's list, and aSheet should be at the end of userSheets. Given that, we
1499 // can find the right place to insert the new sheet based on the length of
1500 // userSheets.
1501 MOZ_ASSERT(aSheet);
1502 MOZ_ASSERT(userSheets.LastElement() == aSheet);
1504 size_t index = userSheets.Length() - 1;
1506 // Assert that all of userSheets (except for the last, new element) matches up
1507 // with what's in the style set.
1508 for (size_t i = 0; i < index; ++i) {
1509 MOZ_ASSERT(StyleSet()->SheetAt(StyleOrigin::User, i) == userSheets[i]);
1512 if (index == static_cast<size_t>(StyleSet()->SheetCount(StyleOrigin::User))) {
1513 StyleSet()->AppendStyleSheet(*aSheet);
1514 } else {
1515 StyleSheet* ref = StyleSet()->SheetAt(StyleOrigin::User, index);
1516 StyleSet()->InsertStyleSheetBefore(*aSheet, *ref);
1519 mDocument->ApplicableStylesChanged();
1522 void PresShell::AddAgentSheet(StyleSheet* aSheet) {
1523 // Make sure this does what nsDocumentViewer::CreateStyleSet does
1524 // wrt ordering.
1525 StyleSet()->AppendStyleSheet(*aSheet);
1526 mDocument->ApplicableStylesChanged();
1529 void PresShell::AddAuthorSheet(StyleSheet* aSheet) {
1530 // Document specific "additional" Author sheets should be stronger than the
1531 // ones added with the StyleSheetService.
1532 StyleSheet* firstAuthorSheet = mDocument->GetFirstAdditionalAuthorSheet();
1533 if (firstAuthorSheet) {
1534 StyleSet()->InsertStyleSheetBefore(*aSheet, *firstAuthorSheet);
1535 } else {
1536 StyleSet()->AppendStyleSheet(*aSheet);
1539 mDocument->ApplicableStylesChanged();
1542 bool PresShell::FixUpFocus() {
1543 if (!StaticPrefs::dom_focus_fixup()) {
1544 return false;
1546 if (NS_WARN_IF(!mDocument)) {
1547 return false;
1550 nsIContent* currentFocus = mDocument->GetUnretargetedFocusedContent(
1551 Document::IncludeChromeOnly::Yes);
1552 if (!currentFocus) {
1553 return false;
1556 nsIFrame* f = currentFocus->GetPrimaryFrame();
1557 if (f && f->IsFocusable()) {
1558 return false;
1561 if (currentFocus == mDocument->GetBody() ||
1562 currentFocus == mDocument->GetRootElement()) {
1563 return false;
1566 RefPtr fm = nsFocusManager::GetFocusManager();
1567 nsCOMPtr<nsPIDOMWindowOuter> window = mDocument->GetWindow();
1568 if (NS_WARN_IF(!window)) {
1569 return false;
1571 fm->ClearFocus(window);
1572 return true;
1575 void PresShell::SelectionWillTakeFocus() {
1576 if (mSelection) {
1577 FrameSelectionWillTakeFocus(*mSelection);
1581 void PresShell::SelectionWillLoseFocus() {
1582 // Do nothing, the main selection is the default focused selection.
1585 // Selection repainting code relies on selection offsets being properly
1586 // adjusted (see bug 1626291), so we need to wait until the DOM is finished
1587 // notifying.
1588 static void RepaintNormalSelectionWhenSafe(nsFrameSelection& aFrameSelection) {
1589 if (nsContentUtils::IsSafeToRunScript()) {
1590 aFrameSelection.RepaintSelection(SelectionType::eNormal);
1591 return;
1594 // Note that importantly we don't defer changing the DisplaySelection. That'd
1595 // be potentially racy with other code that may change it.
1596 nsContentUtils::AddScriptRunner(NS_NewRunnableFunction(
1597 "RepaintNormalSelectionWhenSafe",
1598 [sel = RefPtr<nsFrameSelection>(&aFrameSelection)] {
1599 sel->RepaintSelection(SelectionType::eNormal);
1600 }));
1603 void PresShell::FrameSelectionWillLoseFocus(nsFrameSelection& aFrameSelection) {
1604 if (mFocusedFrameSelection != &aFrameSelection) {
1605 return;
1608 // Do nothing, the main selection is the default focused selection.
1609 if (&aFrameSelection == mSelection) {
1610 return;
1613 RefPtr<nsFrameSelection> old = std::move(mFocusedFrameSelection);
1614 MOZ_ASSERT(!mFocusedFrameSelection);
1616 if (old->GetDisplaySelection() != nsISelectionController::SELECTION_HIDDEN) {
1617 old->SetDisplaySelection(nsISelectionController::SELECTION_HIDDEN);
1618 RepaintNormalSelectionWhenSafe(*old);
1621 if (mSelection) {
1622 FrameSelectionWillTakeFocus(*mSelection);
1626 void PresShell::FrameSelectionWillTakeFocus(nsFrameSelection& aFrameSelection) {
1627 if (mFocusedFrameSelection == &aFrameSelection) {
1628 #ifdef XP_MACOSX
1629 // FIXME: Mac needs to update the global selection cache, even if the
1630 // document's focused selection doesn't change, and this is currently done
1631 // from RepaintSelection. Maybe we should move part of the global selection
1632 // handling here, or something of that sort, unclear.
1633 RepaintNormalSelectionWhenSafe(aFrameSelection);
1634 #endif
1635 return;
1638 RefPtr<nsFrameSelection> old = std::move(mFocusedFrameSelection);
1639 mFocusedFrameSelection = &aFrameSelection;
1641 if (old &&
1642 old->GetDisplaySelection() != nsISelectionController::SELECTION_HIDDEN) {
1643 old->SetDisplaySelection(nsISelectionController::SELECTION_HIDDEN);
1644 RepaintNormalSelectionWhenSafe(*old);
1647 if (aFrameSelection.GetDisplaySelection() !=
1648 nsISelectionController::SELECTION_ON) {
1649 aFrameSelection.SetDisplaySelection(nsISelectionController::SELECTION_ON);
1650 RepaintNormalSelectionWhenSafe(aFrameSelection);
1654 NS_IMETHODIMP
1655 PresShell::SetDisplaySelection(int16_t aToggle) {
1656 RefPtr<nsFrameSelection> frameSelection = mSelection;
1657 frameSelection->SetDisplaySelection(aToggle);
1658 return NS_OK;
1661 NS_IMETHODIMP
1662 PresShell::GetDisplaySelection(int16_t* aToggle) {
1663 RefPtr<nsFrameSelection> frameSelection = mSelection;
1664 *aToggle = frameSelection->GetDisplaySelection();
1665 return NS_OK;
1668 NS_IMETHODIMP
1669 PresShell::GetSelectionFromScript(RawSelectionType aRawSelectionType,
1670 Selection** aSelection) {
1671 if (!aSelection || !mSelection) return NS_ERROR_NULL_POINTER;
1673 RefPtr<nsFrameSelection> frameSelection = mSelection;
1674 RefPtr<Selection> selection =
1675 frameSelection->GetSelection(ToSelectionType(aRawSelectionType));
1677 if (!selection) {
1678 return NS_ERROR_INVALID_ARG;
1681 selection.forget(aSelection);
1682 return NS_OK;
1685 Selection* PresShell::GetSelection(RawSelectionType aRawSelectionType) {
1686 if (!mSelection) {
1687 return nullptr;
1690 RefPtr<nsFrameSelection> frameSelection = mSelection;
1691 return frameSelection->GetSelection(ToSelectionType(aRawSelectionType));
1694 Selection* PresShell::GetCurrentSelection(SelectionType aSelectionType) {
1695 if (!mSelection) {
1696 return nullptr;
1699 RefPtr<nsFrameSelection> frameSelection = mSelection;
1700 return frameSelection->GetSelection(aSelectionType);
1703 nsFrameSelection* PresShell::GetLastFocusedFrameSelection() {
1704 return mFocusedFrameSelection ? mFocusedFrameSelection : mSelection;
1707 NS_IMETHODIMP
1708 PresShell::ScrollSelectionIntoView(RawSelectionType aRawSelectionType,
1709 SelectionRegion aRegion, int16_t aFlags) {
1710 if (!mSelection) return NS_ERROR_NULL_POINTER;
1712 RefPtr<nsFrameSelection> frameSelection = mSelection;
1713 return frameSelection->ScrollSelectionIntoView(
1714 ToSelectionType(aRawSelectionType), aRegion, aFlags);
1717 NS_IMETHODIMP
1718 PresShell::RepaintSelection(RawSelectionType aRawSelectionType) {
1719 if (!mSelection) {
1720 return NS_ERROR_NULL_POINTER;
1723 if (MOZ_UNLIKELY(mIsDestroying)) {
1724 return NS_OK;
1727 RefPtr<nsFrameSelection> frameSelection = mSelection;
1728 return frameSelection->RepaintSelection(ToSelectionType(aRawSelectionType));
1731 // Make shell be a document observer
1732 void PresShell::BeginObservingDocument() {
1733 if (mDocument && !mIsDestroying) {
1734 mIsObservingDocument = true;
1735 if (mIsDocumentGone) {
1736 NS_WARNING(
1737 "Adding a presshell that was disconnected from the document "
1738 "as a document observer? Sounds wrong...");
1739 mIsDocumentGone = false;
1744 // Make shell stop being a document observer
1745 void PresShell::EndObservingDocument() {
1746 // XXXbz do we need to tell the frame constructor that the document
1747 // is gone, perhaps? Except for printing it's NOT gone, sometimes.
1748 mIsDocumentGone = true;
1749 mIsObservingDocument = false;
1752 #ifdef DEBUG_kipp
1753 char* nsPresShell_ReflowStackPointerTop;
1754 #endif
1756 void PresShell::InitPaintSuppressionTimer() {
1757 // Default to PAINTLOCK_EVENT_DELAY if we can't get the pref value.
1758 Document* doc = mDocument->GetDisplayDocument()
1759 ? mDocument->GetDisplayDocument()
1760 : mDocument.get();
1761 const bool inProcess = !doc->GetBrowsingContext() ||
1762 doc->GetBrowsingContext()->Top()->IsInProcess();
1763 int32_t delay = inProcess
1764 ? StaticPrefs::nglayout_initialpaint_delay()
1765 : StaticPrefs::nglayout_initialpaint_delay_in_oopif();
1766 mPaintSuppressionTimer->InitWithNamedFuncCallback(
1767 [](nsITimer* aTimer, void* aPresShell) {
1768 RefPtr<PresShell> self = static_cast<PresShell*>(aPresShell);
1769 self->UnsuppressPainting();
1771 this, delay, nsITimer::TYPE_ONE_SHOT,
1772 "PresShell::sPaintSuppressionCallback");
1775 nsresult PresShell::Initialize() {
1776 if (mIsDestroying) {
1777 return NS_OK;
1780 if (!mDocument) {
1781 // Nothing to do
1782 return NS_OK;
1785 MOZ_LOG(gLog, LogLevel::Debug, ("PresShell::Initialize this=%p", this));
1787 NS_ASSERTION(!mDidInitialize, "Why are we being called?");
1789 RefPtr<PresShell> kungFuDeathGrip(this);
1791 RecomputeFontSizeInflationEnabled();
1792 MOZ_DIAGNOSTIC_ASSERT(!mIsDestroying);
1794 // Ensure the pres context doesn't think it has changed, since we haven't even
1795 // started layout. This avoids spurious restyles / reflows afterwards.
1797 // Note that this is very intentionally before setting mDidInitialize so it
1798 // doesn't notify the document, or run media query change events.
1799 mPresContext->FlushPendingMediaFeatureValuesChanged();
1800 MOZ_DIAGNOSTIC_ASSERT(!mIsDestroying);
1802 mDidInitialize = true;
1804 #ifdef DEBUG
1805 if (VerifyReflowFlags::NoisyCommands & gVerifyReflowFlags) {
1806 if (mDocument) {
1807 nsIURI* uri = mDocument->GetDocumentURI();
1808 if (uri) {
1809 printf("*** PresShell::Initialize (this=%p, url='%s')\n", (void*)this,
1810 uri->GetSpecOrDefault().get());
1814 #endif
1816 // Get the root frame from the frame manager
1817 // XXXbz it would be nice to move this somewhere else... like frame manager
1818 // Init(), say. But we need to make sure our views are all set up by the
1819 // time we do this!
1820 nsIFrame* rootFrame = mFrameConstructor->GetRootFrame();
1821 NS_ASSERTION(!rootFrame, "How did that happen, exactly?");
1823 if (!rootFrame) {
1824 nsAutoScriptBlocker scriptBlocker;
1825 rootFrame = mFrameConstructor->ConstructRootFrame();
1826 mFrameConstructor->SetRootFrame(rootFrame);
1829 NS_ENSURE_STATE(!mHaveShutDown);
1831 if (!rootFrame) {
1832 return NS_ERROR_OUT_OF_MEMORY;
1835 if (Element* root = mDocument->GetRootElement()) {
1837 nsAutoCauseReflowNotifier reflowNotifier(this);
1838 // Have the style sheet processor construct frame for the root
1839 // content object down
1840 mFrameConstructor->ContentInserted(
1841 root, nsCSSFrameConstructor::InsertionKind::Sync);
1843 // Something in mFrameConstructor->ContentInserted may have caused
1844 // Destroy() to get called, bug 337586. Or, nsAutoCauseReflowNotifier
1845 // (which sets up a script blocker) going out of scope may have killed us
1846 // too
1847 NS_ENSURE_STATE(!mHaveShutDown);
1850 if (mDocument->HasAutoFocusCandidates()) {
1851 mDocument->ScheduleFlushAutoFocusCandidates();
1854 NS_ASSERTION(rootFrame, "How did that happen?");
1856 // Note: when the frame was created above it had the NS_FRAME_IS_DIRTY bit
1857 // set, but XBL processing could have caused a reflow which clears it.
1858 if (MOZ_LIKELY(rootFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY))) {
1859 // Unset the DIRTY bits so that FrameNeedsReflow() will work right.
1860 rootFrame->RemoveStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN);
1861 NS_ASSERTION(!mDirtyRoots.Contains(rootFrame),
1862 "Why is the root in mDirtyRoots already?");
1863 FrameNeedsReflow(rootFrame, IntrinsicDirty::None, NS_FRAME_IS_DIRTY);
1864 NS_ASSERTION(mDirtyRoots.Contains(rootFrame),
1865 "Should be in mDirtyRoots now");
1866 NS_ASSERTION(mObservingLayoutFlushes, "Why no reflow scheduled?");
1869 // Restore our root scroll position now if we're getting here after EndLoad
1870 // got called, since this is our one chance to do it. Note that we need not
1871 // have reflowed for this to work; when the scrollframe is finally reflowed
1872 // it'll pick up the position we store in it here.
1873 if (!mDocumentLoading) {
1874 RestoreRootScrollPosition();
1877 // For printing, we just immediately unsuppress.
1878 if (!mPresContext->IsPaginated()) {
1879 // Kick off a one-shot timer based off our pref value. When this timer
1880 // fires, if painting is still locked down, then we will go ahead and
1881 // trigger a full invalidate and allow painting to proceed normally.
1882 mPaintingSuppressed = true;
1883 // Don't suppress painting if the document isn't loading.
1884 Document::ReadyState readyState = mDocument->GetReadyStateEnum();
1885 if (readyState != Document::READYSTATE_COMPLETE) {
1886 mPaintSuppressionTimer = NS_NewTimer();
1888 if (!mPaintSuppressionTimer) {
1889 mPaintingSuppressed = false;
1890 } else {
1891 // Initialize the timer.
1892 mPaintSuppressionTimer->SetTarget(
1893 mDocument->EventTargetFor(TaskCategory::Other));
1894 InitPaintSuppressionTimer();
1895 if (mHasTriedFastUnsuppress) {
1896 // Someone tried to unsuppress painting before Initialize was called so
1897 // unsuppress painting rather soon.
1898 mHasTriedFastUnsuppress = false;
1899 TryUnsuppressPaintingSoon();
1900 MOZ_ASSERT(mHasTriedFastUnsuppress);
1905 // If we get here and painting is not suppressed, we still want to run the
1906 // unsuppression logic, so set mShouldUnsuppressPainting to true.
1907 if (!mPaintingSuppressed) {
1908 mShouldUnsuppressPainting = true;
1911 return NS_OK; // XXX this needs to be real. MMP
1914 void PresShell::TryUnsuppressPaintingSoon() {
1915 if (mHasTriedFastUnsuppress) {
1916 return;
1918 mHasTriedFastUnsuppress = true;
1920 if (!mDidInitialize || !IsPaintingSuppressed() || !XRE_IsContentProcess()) {
1921 return;
1924 if (!mDocument->IsInitialDocument() &&
1925 mDocument->DidHitCompleteSheetCache() &&
1926 mPresContext->IsRootContentDocumentCrossProcess()) {
1927 // Try to unsuppress faster on a top level page if it uses stylesheet
1928 // cache, since that hints that many resources can be painted sooner than
1929 // in a cold page load case.
1930 NS_DispatchToCurrentThreadQueue(
1931 NS_NewRunnableFunction("PresShell::TryUnsuppressPaintingSoon",
1932 [self = RefPtr{this}]() -> void {
1933 if (self->IsPaintingSuppressed()) {
1934 PROFILER_MARKER_UNTYPED(
1935 "Fast paint unsuppression", GRAPHICS);
1936 self->UnsuppressPainting();
1939 EventQueuePriority::Control);
1943 void PresShell::RefreshZoomConstraintsForScreenSizeChange() {
1944 if (mZoomConstraintsClient) {
1945 mZoomConstraintsClient->ScreenSizeChanged();
1949 void PresShell::ForceResizeReflowWithCurrentDimensions() {
1950 nscoord currentWidth = 0;
1951 nscoord currentHeight = 0;
1952 mViewManager->GetWindowDimensions(&currentWidth, &currentHeight);
1953 ResizeReflow(currentWidth, currentHeight);
1956 void PresShell::ResizeReflow(nscoord aWidth, nscoord aHeight,
1957 ResizeReflowOptions aOptions) {
1958 if (mZoomConstraintsClient) {
1959 // If we have a ZoomConstraintsClient and the available screen area
1960 // changed, then we might need to disable double-tap-to-zoom, so notify
1961 // the ZCC to update itself.
1962 mZoomConstraintsClient->ScreenSizeChanged();
1964 if (UsesMobileViewportSizing()) {
1965 // If we are using mobile viewport sizing, request a reflow from the MVM.
1966 // It can recompute the final CSS viewport and trigger a call to
1967 // ResizeReflowIgnoreOverride if it changed. We don't force adjusting
1968 // of resolution, because that is only necessary when we are destroying
1969 // the MVM.
1970 MOZ_ASSERT(mMobileViewportManager);
1971 mMobileViewportManager->RequestReflow(false);
1972 return;
1974 ResizeReflowIgnoreOverride(aWidth, aHeight, aOptions);
1977 bool PresShell::SimpleResizeReflow(nscoord aWidth, nscoord aHeight) {
1978 MOZ_ASSERT(aWidth != NS_UNCONSTRAINEDSIZE);
1979 MOZ_ASSERT(aHeight != NS_UNCONSTRAINEDSIZE);
1980 nsSize oldSize = mPresContext->GetVisibleArea().Size();
1981 mPresContext->SetVisibleArea(nsRect(0, 0, aWidth, aHeight));
1982 nsIFrame* rootFrame = GetRootFrame();
1983 if (!rootFrame) {
1984 return false;
1986 WritingMode wm = rootFrame->GetWritingMode();
1987 bool isBSizeChanging =
1988 wm.IsVertical() ? oldSize.width != aWidth : oldSize.height != aHeight;
1989 if (isBSizeChanging) {
1990 nsLayoutUtils::MarkIntrinsicISizesDirtyIfDependentOnBSize(rootFrame);
1992 FrameNeedsReflow(rootFrame, IntrinsicDirty::None,
1993 NS_FRAME_HAS_DIRTY_CHILDREN);
1995 if (mMobileViewportManager) {
1996 mMobileViewportManager->UpdateSizesBeforeReflow();
1998 return true;
2001 void PresShell::AddResizeEventFlushObserverIfNeeded() {
2002 if (!mIsDestroying && !mResizeEventPending &&
2003 MOZ_LIKELY(!mDocument->GetBFCacheEntry())) {
2004 mResizeEventPending = true;
2005 mPresContext->RefreshDriver()->AddResizeEventFlushObserver(this);
2009 bool PresShell::ResizeReflowIgnoreOverride(nscoord aWidth, nscoord aHeight,
2010 ResizeReflowOptions aOptions) {
2011 MOZ_ASSERT(!mIsReflowing, "Shouldn't be in reflow here!");
2013 // Historically we never fired resize events if there was no root frame by the
2014 // time this function got called.
2015 const bool initialized = mDidInitialize;
2016 RefPtr<PresShell> kungFuDeathGrip(this);
2018 auto postResizeEventIfNeeded = [this, initialized]() {
2019 if (initialized) {
2020 AddResizeEventFlushObserverIfNeeded();
2024 if (!(aOptions & ResizeReflowOptions::BSizeLimit)) {
2025 nsSize oldSize = mPresContext->GetVisibleArea().Size();
2026 if (oldSize == nsSize(aWidth, aHeight)) {
2027 return false;
2030 bool changed = SimpleResizeReflow(aWidth, aHeight);
2031 postResizeEventIfNeeded();
2032 return changed;
2035 // Make sure that style is flushed before setting the pres context
2036 // VisibleArea.
2038 // Otherwise we may end up with bogus viewport units resolved against the
2039 // unconstrained bsize, or restyling the whole document resolving viewport
2040 // units against targetWidth, which may end up doing wasteful work.
2041 mDocument->FlushPendingNotifications(FlushType::Frames);
2043 nsIFrame* rootFrame = GetRootFrame();
2044 if (mIsDestroying || !rootFrame) {
2045 // If we don't have a root frame yet, that means we haven't had our initial
2046 // reflow... If that's the case, and aWidth or aHeight is unconstrained,
2047 // ignore them altogether.
2048 if (aHeight == NS_UNCONSTRAINEDSIZE || aWidth == NS_UNCONSTRAINEDSIZE) {
2049 // We can't do the work needed for SizeToContent without a root
2050 // frame, and we want to return before setting the visible area.
2051 return false;
2054 mPresContext->SetVisibleArea(nsRect(0, 0, aWidth, aHeight));
2055 // There isn't anything useful we can do if the initial reflow hasn't
2056 // happened.
2057 return true;
2060 WritingMode wm = rootFrame->GetWritingMode();
2061 MOZ_ASSERT((wm.IsVertical() ? aHeight : aWidth) != NS_UNCONSTRAINEDSIZE,
2062 "unconstrained isize not allowed");
2064 nscoord targetWidth = aWidth;
2065 nscoord targetHeight = aHeight;
2066 if (wm.IsVertical()) {
2067 targetWidth = NS_UNCONSTRAINEDSIZE;
2068 } else {
2069 targetHeight = NS_UNCONSTRAINEDSIZE;
2072 mPresContext->SetVisibleArea(nsRect(0, 0, targetWidth, targetHeight));
2073 // XXX Do a full invalidate at the beginning so that invalidates along
2074 // the way don't have region accumulation issues?
2076 // For height:auto BSizes (i.e. layout-controlled), descendant
2077 // intrinsic sizes can't depend on them. So the only other case is
2078 // viewport-controlled BSizes which we handle here.
2079 nsLayoutUtils::MarkIntrinsicISizesDirtyIfDependentOnBSize(rootFrame);
2082 nsAutoCauseReflowNotifier crNotifier(this);
2083 WillDoReflow();
2085 // Kick off a top-down reflow
2086 AUTO_LAYOUT_PHASE_ENTRY_POINT(GetPresContext(), Reflow);
2087 nsViewManager::AutoDisableRefresh refreshBlocker(mViewManager);
2089 mDirtyRoots.Remove(rootFrame);
2090 DoReflow(rootFrame, true, nullptr);
2092 const bool reflowAgain =
2093 wm.IsVertical() ? mPresContext->GetVisibleArea().width > aWidth
2094 : mPresContext->GetVisibleArea().height > aHeight;
2096 if (reflowAgain) {
2097 mPresContext->SetVisibleArea(nsRect(0, 0, aWidth, aHeight));
2098 DoReflow(rootFrame, true, nullptr);
2102 // Now, we may have been destroyed by the destructor of
2103 // `nsAutoCauseReflowNotifier`.
2105 mPendingDidDoReflow = true;
2106 DidDoReflow(true);
2108 // the reflow above should've set our bsize if it was NS_UNCONSTRAINEDSIZE,
2109 // and the isize shouldn't be NS_UNCONSTRAINEDSIZE anyway.
2110 MOZ_DIAGNOSTIC_ASSERT(
2111 mPresContext->GetVisibleArea().width != NS_UNCONSTRAINEDSIZE,
2112 "width should not be NS_UNCONSTRAINEDSIZE after reflow");
2113 MOZ_DIAGNOSTIC_ASSERT(
2114 mPresContext->GetVisibleArea().height != NS_UNCONSTRAINEDSIZE,
2115 "height should not be NS_UNCONSTRAINEDSIZE after reflow");
2117 postResizeEventIfNeeded();
2118 return true;
2121 void PresShell::FireResizeEvent() {
2122 if (mIsDocumentGone) {
2123 return;
2126 // If event handling is suppressed, repost the resize event to the refresh
2127 // driver. The event is marked as delayed so that the refresh driver does not
2128 // continue ticking.
2129 if (mDocument->EventHandlingSuppressed()) {
2130 if (MOZ_LIKELY(!mDocument->GetBFCacheEntry())) {
2131 mDocument->SetHasDelayedRefreshEvent();
2132 mPresContext->RefreshDriver()->AddResizeEventFlushObserver(
2133 this, /* aDelayed = */ true);
2135 return;
2138 mResizeEventPending = false;
2139 FireResizeEventSync();
2142 void PresShell::FireResizeEventSync() {
2143 if (mIsDocumentGone) {
2144 return;
2147 // Send resize event from here.
2148 WidgetEvent event(true, mozilla::eResize);
2149 nsEventStatus status = nsEventStatus_eIgnore;
2151 if (RefPtr<nsPIDOMWindowOuter> window = mDocument->GetWindow()) {
2152 EventDispatcher::Dispatch(window, mPresContext, &event, nullptr, &status);
2156 static nsIContent* GetNativeAnonymousSubtreeRoot(nsIContent* aContent) {
2157 if (!aContent) {
2158 return nullptr;
2160 return aContent->GetClosestNativeAnonymousSubtreeRoot();
2163 void PresShell::NativeAnonymousContentRemoved(nsIContent* aAnonContent) {
2164 MOZ_ASSERT(aAnonContent->IsRootOfNativeAnonymousSubtree());
2165 if (nsIContent* root = GetNativeAnonymousSubtreeRoot(mCurrentEventContent)) {
2166 if (aAnonContent == root) {
2167 mCurrentEventContent = aAnonContent->GetFlattenedTreeParent();
2168 mCurrentEventFrame = nullptr;
2172 for (unsigned int i = 0; i < mCurrentEventContentStack.Length(); i++) {
2173 nsIContent* anon =
2174 GetNativeAnonymousSubtreeRoot(mCurrentEventContentStack.ElementAt(i));
2175 if (aAnonContent == anon) {
2176 mCurrentEventContentStack.ReplaceObjectAt(
2177 aAnonContent->GetFlattenedTreeParent(), i);
2178 mCurrentEventFrameStack[i] = nullptr;
2183 void PresShell::SetIgnoreFrameDestruction(bool aIgnore) {
2184 if (mDocument) {
2185 // We need to tell the ImageLoader to drop all its references to frames
2186 // because they're about to go away and it won't get notifications of that.
2187 mDocument->StyleImageLoader()->ClearFrames(mPresContext);
2189 mIgnoreFrameDestruction = aIgnore;
2192 void PresShell::NotifyDestroyingFrame(nsIFrame* aFrame) {
2193 // We must remove these from FrameLayerBuilder::DisplayItemData::mFrameList
2194 // here, otherwise the DisplayItemData destructor will use the destroyed frame
2195 // when it tries to remove it from the (array) value of this property.
2196 aFrame->RemoveDisplayItemDataForDeletion();
2198 if (!mIgnoreFrameDestruction) {
2199 if (aFrame->HasImageRequest()) {
2200 mDocument->StyleImageLoader()->DropRequestsForFrame(aFrame);
2203 mFrameConstructor->NotifyDestroyingFrame(aFrame);
2205 mDirtyRoots.Remove(aFrame);
2207 // Remove frame properties
2208 aFrame->RemoveAllProperties();
2210 if (aFrame == mCurrentEventFrame) {
2211 mCurrentEventContent = aFrame->GetContent();
2212 mCurrentEventFrame = nullptr;
2215 for (unsigned int i = 0; i < mCurrentEventFrameStack.Length(); i++) {
2216 if (aFrame == mCurrentEventFrameStack.ElementAt(i)) {
2217 // One of our stack frames was deleted. Get its content so that when we
2218 // pop it we can still get its new frame from its content
2219 nsIContent* currentEventContent = aFrame->GetContent();
2220 mCurrentEventContentStack.ReplaceObjectAt(currentEventContent, i);
2221 mCurrentEventFrameStack[i] = nullptr;
2225 mFramesToDirty.Remove(aFrame);
2227 nsIScrollableFrame* scrollableFrame = do_QueryFrame(aFrame);
2228 if (scrollableFrame) {
2229 mPendingScrollAnchorSelection.Remove(scrollableFrame);
2230 mPendingScrollAnchorAdjustment.Remove(scrollableFrame);
2231 mPendingScrollResnap.Remove(scrollableFrame);
2234 mContentVisibilityAutoFrames.Remove(aFrame);
2238 already_AddRefed<nsCaret> PresShell::GetCaret() const {
2239 RefPtr<nsCaret> caret = mCaret;
2240 return caret.forget();
2243 already_AddRefed<AccessibleCaretEventHub>
2244 PresShell::GetAccessibleCaretEventHub() const {
2245 RefPtr<AccessibleCaretEventHub> eventHub = mAccessibleCaretEventHub;
2246 return eventHub.forget();
2249 void PresShell::SetCaret(nsCaret* aNewCaret) { mCaret = aNewCaret; }
2251 void PresShell::RestoreCaret() { mCaret = mOriginalCaret; }
2253 NS_IMETHODIMP PresShell::SetCaretEnabled(bool aInEnable) {
2254 bool oldEnabled = mCaretEnabled;
2256 mCaretEnabled = aInEnable;
2258 if (mCaretEnabled != oldEnabled) {
2259 MOZ_ASSERT(mCaret);
2260 if (mCaret) {
2261 mCaret->SetVisible(mCaretEnabled);
2265 return NS_OK;
2268 NS_IMETHODIMP PresShell::SetCaretReadOnly(bool aReadOnly) {
2269 if (mCaret) mCaret->SetCaretReadOnly(aReadOnly);
2270 return NS_OK;
2273 NS_IMETHODIMP PresShell::GetCaretEnabled(bool* aOutEnabled) {
2274 NS_ENSURE_ARG_POINTER(aOutEnabled);
2275 *aOutEnabled = mCaretEnabled;
2276 return NS_OK;
2279 NS_IMETHODIMP PresShell::SetCaretVisibilityDuringSelection(bool aVisibility) {
2280 if (mCaret) mCaret->SetVisibilityDuringSelection(aVisibility);
2281 return NS_OK;
2284 NS_IMETHODIMP PresShell::GetCaretVisible(bool* aOutIsVisible) {
2285 *aOutIsVisible = false;
2286 if (mCaret) {
2287 *aOutIsVisible = mCaret->IsVisible();
2289 return NS_OK;
2292 NS_IMETHODIMP PresShell::SetSelectionFlags(int16_t aFlags) {
2293 mSelectionFlags = aFlags;
2294 return NS_OK;
2297 NS_IMETHODIMP PresShell::GetSelectionFlags(int16_t* aFlags) {
2298 if (!aFlags) {
2299 return NS_ERROR_INVALID_ARG;
2302 *aFlags = mSelectionFlags;
2303 return NS_OK;
2306 // implementation of nsISelectionController
2308 NS_IMETHODIMP
2309 PresShell::PhysicalMove(int16_t aDirection, int16_t aAmount, bool aExtend) {
2310 RefPtr<nsFrameSelection> frameSelection = mSelection;
2311 return frameSelection->PhysicalMove(aDirection, aAmount, aExtend);
2314 NS_IMETHODIMP
2315 PresShell::CharacterMove(bool aForward, bool aExtend) {
2316 RefPtr<nsFrameSelection> frameSelection = mSelection;
2317 return frameSelection->CharacterMove(aForward, aExtend);
2320 NS_IMETHODIMP
2321 PresShell::WordMove(bool aForward, bool aExtend) {
2322 RefPtr<nsFrameSelection> frameSelection = mSelection;
2323 nsresult result = frameSelection->WordMove(aForward, aExtend);
2324 // if we can't go down/up any more we must then move caret completely to
2325 // end/beginning respectively.
2326 if (NS_FAILED(result)) result = CompleteMove(aForward, aExtend);
2327 return result;
2330 NS_IMETHODIMP
2331 PresShell::LineMove(bool aForward, bool aExtend) {
2332 RefPtr<nsFrameSelection> frameSelection = mSelection;
2333 nsresult result = frameSelection->LineMove(aForward, aExtend);
2334 // if we can't go down/up any more we must then move caret completely to
2335 // end/beginning respectively.
2336 if (NS_FAILED(result)) result = CompleteMove(aForward, aExtend);
2337 return result;
2340 NS_IMETHODIMP
2341 PresShell::IntraLineMove(bool aForward, bool aExtend) {
2342 RefPtr<nsFrameSelection> frameSelection = mSelection;
2343 return frameSelection->IntraLineMove(aForward, aExtend);
2346 NS_IMETHODIMP
2347 PresShell::PageMove(bool aForward, bool aExtend) {
2348 nsIFrame* frame = nullptr;
2349 if (!aExtend) {
2350 frame = do_QueryFrame(GetScrollableFrameToScroll(VerticalScrollDirection));
2351 // If there is no scrollable frame, get the frame to move caret instead.
2353 if (!frame || frame->PresContext() != mPresContext) {
2354 frame = mSelection->GetFrameToPageSelect();
2355 if (!frame) {
2356 return NS_OK;
2359 // We may scroll parent scrollable element of current selection limiter.
2360 // In such case, we don't want to scroll selection into view unless
2361 // selection is changed.
2362 RefPtr<nsFrameSelection> frameSelection = mSelection;
2363 return frameSelection->PageMove(
2364 aForward, aExtend, frame, nsFrameSelection::SelectionIntoView::IfChanged);
2367 NS_IMETHODIMP
2368 PresShell::ScrollPage(bool aForward) {
2369 nsIScrollableFrame* scrollFrame =
2370 GetScrollableFrameToScroll(VerticalScrollDirection);
2371 ScrollMode scrollMode = apz::GetScrollModeForOrigin(ScrollOrigin::Pages);
2372 if (scrollFrame) {
2373 scrollFrame->ScrollBy(nsIntPoint(0, aForward ? 1 : -1), ScrollUnit::PAGES,
2374 scrollMode, nullptr,
2375 mozilla::ScrollOrigin::NotSpecified,
2376 nsIScrollableFrame::NOT_MOMENTUM,
2377 ScrollSnapFlags::IntendedDirection |
2378 ScrollSnapFlags::IntendedEndPosition);
2380 return NS_OK;
2383 NS_IMETHODIMP
2384 PresShell::ScrollLine(bool aForward) {
2385 nsIScrollableFrame* scrollFrame =
2386 GetScrollableFrameToScroll(VerticalScrollDirection);
2387 ScrollMode scrollMode = apz::GetScrollModeForOrigin(ScrollOrigin::Lines);
2388 if (scrollFrame) {
2389 nsRect scrollPort = scrollFrame->GetScrollPortRect();
2390 nsSize lineSize = scrollFrame->GetLineScrollAmount();
2391 int32_t lineCount = StaticPrefs::toolkit_scrollbox_verticalScrollDistance();
2392 if (lineCount * lineSize.height > scrollPort.Height()) {
2393 return ScrollPage(aForward);
2395 scrollFrame->ScrollBy(
2396 nsIntPoint(0, aForward ? lineCount : -lineCount), ScrollUnit::LINES,
2397 scrollMode, nullptr, mozilla::ScrollOrigin::NotSpecified,
2398 nsIScrollableFrame::NOT_MOMENTUM, ScrollSnapFlags::IntendedDirection);
2400 return NS_OK;
2403 NS_IMETHODIMP
2404 PresShell::ScrollCharacter(bool aRight) {
2405 nsIScrollableFrame* scrollFrame =
2406 GetScrollableFrameToScroll(HorizontalScrollDirection);
2407 ScrollMode scrollMode = apz::GetScrollModeForOrigin(ScrollOrigin::Lines);
2408 if (scrollFrame) {
2409 int32_t h = StaticPrefs::toolkit_scrollbox_horizontalScrollDistance();
2410 scrollFrame->ScrollBy(
2411 nsIntPoint(aRight ? h : -h, 0), ScrollUnit::LINES, scrollMode, nullptr,
2412 mozilla::ScrollOrigin::NotSpecified, nsIScrollableFrame::NOT_MOMENTUM,
2413 ScrollSnapFlags::IntendedDirection);
2415 return NS_OK;
2418 NS_IMETHODIMP
2419 PresShell::CompleteScroll(bool aForward) {
2420 nsIScrollableFrame* scrollFrame =
2421 GetScrollableFrameToScroll(VerticalScrollDirection);
2422 ScrollMode scrollMode = apz::GetScrollModeForOrigin(ScrollOrigin::Other);
2423 if (scrollFrame) {
2424 scrollFrame->ScrollBy(
2425 nsIntPoint(0, aForward ? 1 : -1), ScrollUnit::WHOLE, scrollMode,
2426 nullptr, mozilla::ScrollOrigin::NotSpecified,
2427 nsIScrollableFrame::NOT_MOMENTUM, ScrollSnapFlags::IntendedEndPosition);
2429 return NS_OK;
2432 NS_IMETHODIMP
2433 PresShell::CompleteMove(bool aForward, bool aExtend) {
2434 // Beware! This may flush notifications via synchronous
2435 // ScrollSelectionIntoView.
2436 RefPtr<nsFrameSelection> frameSelection = mSelection;
2437 nsIContent* limiter = frameSelection->GetAncestorLimiter();
2438 nsIFrame* frame = limiter ? limiter->GetPrimaryFrame()
2439 : FrameConstructor()->GetRootElementFrame();
2440 if (!frame) return NS_ERROR_FAILURE;
2441 nsIFrame::CaretPosition pos = frame->GetExtremeCaretPosition(!aForward);
2443 const nsFrameSelection::FocusMode focusMode =
2444 aExtend ? nsFrameSelection::FocusMode::kExtendSelection
2445 : nsFrameSelection::FocusMode::kCollapseToNewPoint;
2446 frameSelection->HandleClick(
2447 MOZ_KnownLive(pos.mResultContent) /* bug 1636889 */, pos.mContentOffset,
2448 pos.mContentOffset, focusMode,
2449 aForward ? CARET_ASSOCIATE_AFTER : CARET_ASSOCIATE_BEFORE);
2450 if (limiter) {
2451 // HandleClick resets ancestorLimiter, so set it again.
2452 frameSelection->SetAncestorLimiter(limiter);
2455 // After ScrollSelectionIntoView(), the pending notifications might be
2456 // flushed and PresShell/PresContext/Frames may be dead. See bug 418470.
2457 return ScrollSelectionIntoView(
2458 nsISelectionController::SELECTION_NORMAL,
2459 nsISelectionController::SELECTION_FOCUS_REGION,
2460 nsISelectionController::SCROLL_SYNCHRONOUS |
2461 nsISelectionController::SCROLL_FOR_CARET_MOVE);
2464 // end implementations nsISelectionController
2466 nsIFrame* PresShell::GetRootScrollFrame() const {
2467 nsIFrame* rootFrame = mFrameConstructor->GetRootFrame();
2468 // Ensure root frame is a viewport frame
2469 if (!rootFrame || !rootFrame->IsViewportFrame()) return nullptr;
2470 nsIFrame* theFrame = rootFrame->PrincipalChildList().FirstChild();
2471 if (!theFrame || !theFrame->IsScrollFrame()) return nullptr;
2472 return theFrame;
2475 nsIScrollableFrame* PresShell::GetRootScrollFrameAsScrollable() const {
2476 nsIFrame* frame = GetRootScrollFrame();
2477 if (!frame) return nullptr;
2478 nsIScrollableFrame* scrollableFrame = do_QueryFrame(frame);
2479 NS_ASSERTION(scrollableFrame,
2480 "All scroll frames must implement nsIScrollableFrame");
2481 return scrollableFrame;
2484 nsPageSequenceFrame* PresShell::GetPageSequenceFrame() const {
2485 return mFrameConstructor->GetPageSequenceFrame();
2488 nsCanvasFrame* PresShell::GetCanvasFrame() const {
2489 nsIFrame* frame = mFrameConstructor->GetDocElementContainingBlock();
2490 return do_QueryFrame(frame);
2493 void PresShell::RestoreRootScrollPosition() {
2494 nsIScrollableFrame* scrollableFrame = GetRootScrollFrameAsScrollable();
2495 if (scrollableFrame) {
2496 scrollableFrame->ScrollToRestoredPosition();
2500 void PresShell::MaybeReleaseCapturingContent() {
2501 RefPtr<nsFrameSelection> frameSelection = FrameSelection();
2502 if (frameSelection) {
2503 frameSelection->SetDragState(false);
2505 if (sCapturingContentInfo.mContent &&
2506 sCapturingContentInfo.mContent->OwnerDoc() == mDocument) {
2507 PresShell::ReleaseCapturingContent();
2511 void PresShell::BeginLoad(Document* aDocument) {
2512 mDocumentLoading = true;
2514 gfxTextPerfMetrics* tp = nullptr;
2515 if (mPresContext) {
2516 tp = mPresContext->GetTextPerfMetrics();
2519 bool shouldLog = MOZ_LOG_TEST(gLog, LogLevel::Debug);
2520 if (shouldLog || tp) {
2521 mLoadBegin = TimeStamp::Now();
2524 if (shouldLog) {
2525 nsIURI* uri = mDocument->GetDocumentURI();
2526 MOZ_LOG(gLog, LogLevel::Debug,
2527 ("(presshell) %p load begin [%s]\n", this,
2528 uri ? uri->GetSpecOrDefault().get() : ""));
2532 void PresShell::EndLoad(Document* aDocument) {
2533 MOZ_ASSERT(aDocument == mDocument, "Wrong document");
2535 RestoreRootScrollPosition();
2537 mDocumentLoading = false;
2540 bool PresShell::IsLayoutFlushObserver() {
2541 return GetPresContext()->RefreshDriver()->IsLayoutFlushObserver(this);
2544 void PresShell::LoadComplete() {
2545 gfxTextPerfMetrics* tp = nullptr;
2546 if (mPresContext) {
2547 tp = mPresContext->GetTextPerfMetrics();
2550 // log load
2551 bool shouldLog = MOZ_LOG_TEST(gLog, LogLevel::Debug);
2552 if (shouldLog || tp) {
2553 TimeDuration loadTime = TimeStamp::Now() - mLoadBegin;
2554 nsIURI* uri = mDocument->GetDocumentURI();
2555 nsAutoCString spec;
2556 if (uri) {
2557 spec = uri->GetSpecOrDefault();
2559 if (shouldLog) {
2560 MOZ_LOG(gLog, LogLevel::Debug,
2561 ("(presshell) %p load done time-ms: %9.2f [%s]\n", this,
2562 loadTime.ToMilliseconds(), spec.get()));
2564 if (tp) {
2565 tp->Accumulate();
2566 if (tp->cumulative.numChars > 0) {
2567 LogTextPerfStats(tp, this, tp->cumulative, loadTime.ToMilliseconds(),
2568 eLog_loaddone, spec.get());
2574 #ifdef DEBUG
2575 void PresShell::VerifyHasDirtyRootAncestor(nsIFrame* aFrame) {
2576 // XXXbz due to bug 372769, can't actually assert anything here...
2577 // XXX Since bug 372769 is now fixed, the assertion is being enabled in bug
2578 // 1758104.
2579 # if 0
2580 // XXXbz shouldn't need this part; remove it once FrameNeedsReflow
2581 // handles the root frame correctly.
2582 if (!aFrame->GetParent()) {
2583 return;
2586 // Make sure that there is a reflow root ancestor of |aFrame| that's
2587 // in mDirtyRoots already.
2588 while (aFrame && aFrame->HasAnyStateBits(NS_FRAME_HAS_DIRTY_CHILDREN)) {
2589 if ((aFrame->HasAnyStateBits(NS_FRAME_REFLOW_ROOT |
2590 NS_FRAME_DYNAMIC_REFLOW_ROOT) ||
2591 !aFrame->GetParent()) &&
2592 mDirtyRoots.Contains(aFrame)) {
2593 return;
2596 aFrame = aFrame->GetParent();
2599 MOZ_ASSERT_UNREACHABLE(
2600 "Frame has dirty bits set but isn't scheduled to be "
2601 "reflowed?");
2602 # endif
2604 #endif
2606 void PresShell::PostPendingScrollAnchorSelection(
2607 mozilla::layout::ScrollAnchorContainer* aContainer) {
2608 mPendingScrollAnchorSelection.Insert(aContainer->ScrollableFrame());
2611 void PresShell::FlushPendingScrollAnchorSelections() {
2612 for (nsIScrollableFrame* scroll : mPendingScrollAnchorSelection) {
2613 scroll->Anchor()->SelectAnchor();
2615 mPendingScrollAnchorSelection.Clear();
2618 void PresShell::PostPendingScrollAnchorAdjustment(
2619 ScrollAnchorContainer* aContainer) {
2620 mPendingScrollAnchorAdjustment.Insert(aContainer->ScrollableFrame());
2623 void PresShell::FlushPendingScrollAnchorAdjustments() {
2624 for (nsIScrollableFrame* scroll : mPendingScrollAnchorAdjustment) {
2625 scroll->Anchor()->ApplyAdjustments();
2627 mPendingScrollAnchorAdjustment.Clear();
2630 void PresShell::PostPendingScrollResnap(nsIScrollableFrame* aScrollableFrame) {
2631 mPendingScrollResnap.Insert(aScrollableFrame);
2634 void PresShell::FlushPendingScrollResnap() {
2635 for (nsIScrollableFrame* scrollableFrame : mPendingScrollResnap) {
2636 scrollableFrame->TryResnap();
2638 mPendingScrollResnap.Clear();
2641 void PresShell::FrameNeedsReflow(nsIFrame* aFrame,
2642 IntrinsicDirty aIntrinsicDirty,
2643 nsFrameState aBitToAdd,
2644 ReflowRootHandling aRootHandling) {
2645 MOZ_ASSERT(aBitToAdd == NS_FRAME_IS_DIRTY ||
2646 aBitToAdd == NS_FRAME_HAS_DIRTY_CHILDREN || !aBitToAdd,
2647 "Unexpected bits being added");
2649 // FIXME bug 478135
2650 NS_ASSERTION(
2651 aIntrinsicDirty != IntrinsicDirty::FrameAncestorsAndDescendants ||
2652 aBitToAdd != NS_FRAME_HAS_DIRTY_CHILDREN,
2653 "bits don't correspond to style change reason");
2655 // FIXME bug 457400
2656 NS_ASSERTION(!mIsReflowing, "can't mark frame dirty during reflow");
2658 // If we've not yet done the initial reflow, then don't bother
2659 // enqueuing a reflow command yet.
2660 if (!mDidInitialize) return;
2662 // If we're already destroying, don't bother with this either.
2663 if (mIsDestroying) return;
2665 #ifdef DEBUG
2666 // printf("gShellCounter: %d\n", gShellCounter++);
2667 if (mInVerifyReflow) return;
2669 if (VerifyReflowFlags::NoisyCommands & gVerifyReflowFlags) {
2670 printf("\nPresShell@%p: frame %p needs reflow\n", (void*)this,
2671 (void*)aFrame);
2672 if (VerifyReflowFlags::ReallyNoisyCommands & gVerifyReflowFlags) {
2673 printf("Current content model:\n");
2674 Element* rootElement = mDocument->GetRootElement();
2675 if (rootElement) {
2676 rootElement->List(stdout, 0);
2680 #endif
2682 AutoTArray<nsIFrame*, 4> subtrees;
2683 subtrees.AppendElement(aFrame);
2685 do {
2686 nsIFrame* subtreeRoot = subtrees.PopLastElement();
2688 // Grab |wasDirty| now so we can go ahead and update the bits on
2689 // subtreeRoot.
2690 bool wasDirty = subtreeRoot->IsSubtreeDirty();
2691 subtreeRoot->AddStateBits(aBitToAdd);
2693 // Determine whether we need to keep looking for the next ancestor
2694 // reflow root if subtreeRoot itself is a reflow root.
2695 bool targetNeedsReflowFromParent;
2696 switch (aRootHandling) {
2697 case ReflowRootHandling::PositionOrSizeChange:
2698 targetNeedsReflowFromParent = true;
2699 break;
2700 case ReflowRootHandling::NoPositionOrSizeChange:
2701 targetNeedsReflowFromParent = false;
2702 break;
2703 case ReflowRootHandling::InferFromBitToAdd:
2704 targetNeedsReflowFromParent = (aBitToAdd == NS_FRAME_IS_DIRTY);
2705 break;
2708 auto FrameIsReflowRoot = [](const nsIFrame* aFrame) {
2709 return aFrame->HasAnyStateBits(NS_FRAME_REFLOW_ROOT |
2710 NS_FRAME_DYNAMIC_REFLOW_ROOT);
2713 auto CanStopClearingAncestorIntrinsics = [&](const nsIFrame* aFrame) {
2714 return FrameIsReflowRoot(aFrame) && aFrame != subtreeRoot;
2717 auto IsReflowBoundary = [&](const nsIFrame* aFrame) {
2718 return FrameIsReflowRoot(aFrame) &&
2719 (aFrame != subtreeRoot || !targetNeedsReflowFromParent);
2722 // Mark the intrinsic widths as dirty on the frame, all of its ancestors,
2723 // and all of its descendants, if needed:
2725 if (aIntrinsicDirty != IntrinsicDirty::None) {
2726 // Mark argument and all ancestors dirty. (Unless we hit a reflow root
2727 // that should contain the reflow.
2728 for (nsIFrame* a = subtreeRoot;
2729 a && !CanStopClearingAncestorIntrinsics(a); a = a->GetParent()) {
2730 a->MarkIntrinsicISizesDirty();
2731 if (a->IsAbsolutelyPositioned()) {
2732 // If we get here, 'a' is abspos, so its subtree's intrinsic sizing
2733 // has no effect on its ancestors' intrinsic sizing. So, don't loop
2734 // upwards any further.
2735 break;
2740 const bool frameAncestorAndDescendantISizesDirty =
2741 (aIntrinsicDirty == IntrinsicDirty::FrameAncestorsAndDescendants);
2742 const bool dirty = (aBitToAdd == NS_FRAME_IS_DIRTY);
2743 if (frameAncestorAndDescendantISizesDirty || dirty) {
2744 // Mark all descendants dirty (using an nsTArray stack rather than
2745 // recursion).
2746 // Note that ReflowInput::InitResizeFlags has some similar
2747 // code; see comments there for how and why it differs.
2748 AutoTArray<nsIFrame*, 32> stack;
2749 stack.AppendElement(subtreeRoot);
2751 do {
2752 nsIFrame* f = stack.PopLastElement();
2754 if (frameAncestorAndDescendantISizesDirty && f->IsPlaceholderFrame()) {
2755 // Call `GetOutOfFlowFrame` directly because we can get here from
2756 // frame destruction and the placeholder might be already torn down.
2757 if (nsIFrame* oof =
2758 static_cast<nsPlaceholderFrame*>(f)->GetOutOfFlowFrame()) {
2759 if (!nsLayoutUtils::IsProperAncestorFrame(subtreeRoot, oof)) {
2760 // We have another distinct subtree we need to mark.
2761 subtrees.AppendElement(oof);
2766 for (const auto& childList : f->ChildLists()) {
2767 for (nsIFrame* kid : childList.mList) {
2768 if (frameAncestorAndDescendantISizesDirty) {
2769 kid->MarkIntrinsicISizesDirty();
2771 if (dirty) {
2772 kid->AddStateBits(NS_FRAME_IS_DIRTY);
2774 stack.AppendElement(kid);
2777 } while (stack.Length() != 0);
2780 // Skip setting dirty bits up the tree if we weren't given a bit to add.
2781 if (!aBitToAdd) {
2782 continue;
2785 // Set NS_FRAME_HAS_DIRTY_CHILDREN bits (via nsIFrame::ChildIsDirty)
2786 // up the tree until we reach either a frame that's already dirty or
2787 // a reflow root.
2788 nsIFrame* f = subtreeRoot;
2789 for (;;) {
2790 if (IsReflowBoundary(f) || !f->GetParent()) {
2791 // we've hit a reflow root or the root frame
2792 if (!wasDirty) {
2793 mDirtyRoots.Add(f);
2794 SetNeedLayoutFlush();
2796 #ifdef DEBUG
2797 else {
2798 VerifyHasDirtyRootAncestor(f);
2800 #endif
2802 break;
2805 nsIFrame* child = f;
2806 f = f->GetParent();
2807 wasDirty = f->IsSubtreeDirty();
2808 f->ChildIsDirty(child);
2809 NS_ASSERTION(f->HasAnyStateBits(NS_FRAME_HAS_DIRTY_CHILDREN),
2810 "ChildIsDirty didn't do its job");
2811 if (wasDirty) {
2812 // This frame was already marked dirty.
2813 #ifdef DEBUG
2814 VerifyHasDirtyRootAncestor(f);
2815 #endif
2816 break;
2819 } while (subtrees.Length() != 0);
2821 MaybeScheduleReflow();
2824 void PresShell::FrameNeedsToContinueReflow(nsIFrame* aFrame) {
2825 NS_ASSERTION(mIsReflowing, "Must be in reflow when marking path dirty.");
2826 MOZ_ASSERT(mCurrentReflowRoot, "Must have a current reflow root here");
2827 NS_ASSERTION(
2828 aFrame == mCurrentReflowRoot ||
2829 nsLayoutUtils::IsProperAncestorFrame(mCurrentReflowRoot, aFrame),
2830 "Frame passed in is not the descendant of mCurrentReflowRoot");
2831 NS_ASSERTION(aFrame->HasAnyStateBits(NS_FRAME_IN_REFLOW),
2832 "Frame passed in not in reflow?");
2834 mFramesToDirty.Insert(aFrame);
2837 already_AddRefed<nsIContent> PresShell::GetContentForScrolling() const {
2838 if (nsCOMPtr<nsIContent> focused = GetFocusedContentInOurWindow()) {
2839 return focused.forget();
2841 return GetSelectedContentForScrolling();
2844 already_AddRefed<nsIContent> PresShell::GetSelectedContentForScrolling() const {
2845 nsCOMPtr<nsIContent> selectedContent;
2846 if (mSelection) {
2847 Selection* domSelection = mSelection->GetSelection(SelectionType::eNormal);
2848 if (domSelection) {
2849 selectedContent =
2850 nsIContent::FromNodeOrNull(domSelection->GetFocusNode());
2853 return selectedContent.forget();
2856 nsIScrollableFrame* PresShell::GetScrollableFrameToScrollForContent(
2857 nsIContent* aContent, ScrollDirections aDirections) {
2858 nsIScrollableFrame* scrollFrame = nullptr;
2859 if (aContent) {
2860 nsIFrame* startFrame = aContent->GetPrimaryFrame();
2861 if (startFrame) {
2862 scrollFrame = startFrame->GetScrollTargetFrame();
2863 if (scrollFrame) {
2864 startFrame = scrollFrame->GetScrolledFrame();
2866 scrollFrame = nsLayoutUtils::GetNearestScrollableFrameForDirection(
2867 startFrame, aDirections);
2870 if (!scrollFrame) {
2871 scrollFrame = GetRootScrollFrameAsScrollable();
2872 if (!scrollFrame || !scrollFrame->GetScrolledFrame()) {
2873 return nullptr;
2875 scrollFrame = nsLayoutUtils::GetNearestScrollableFrameForDirection(
2876 scrollFrame->GetScrolledFrame(), aDirections);
2878 return scrollFrame;
2881 nsIScrollableFrame* PresShell::GetScrollableFrameToScroll(
2882 ScrollDirections aDirections) {
2883 nsCOMPtr<nsIContent> content = GetContentForScrolling();
2884 return GetScrollableFrameToScrollForContent(content.get(), aDirections);
2887 void PresShell::CancelAllPendingReflows() {
2888 mDirtyRoots.Clear();
2890 if (mObservingLayoutFlushes) {
2891 GetPresContext()->RefreshDriver()->RemoveLayoutFlushObserver(this);
2892 mObservingLayoutFlushes = false;
2895 ASSERT_REFLOW_SCHEDULED_STATE();
2898 static bool DestroyFramesAndStyleDataFor(
2899 Element* aElement, nsPresContext& aPresContext,
2900 RestyleManager::IncludeRoot aIncludeRoot) {
2901 bool didReconstruct =
2902 aPresContext.FrameConstructor()->DestroyFramesFor(aElement);
2903 RestyleManager::ClearServoDataFromSubtree(aElement, aIncludeRoot);
2904 return didReconstruct;
2907 void PresShell::SlotAssignmentWillChange(Element& aElement,
2908 HTMLSlotElement* aOldSlot,
2909 HTMLSlotElement* aNewSlot) {
2910 MOZ_ASSERT(aOldSlot != aNewSlot);
2912 if (MOZ_UNLIKELY(!mDidInitialize)) {
2913 return;
2916 // If the old slot is about to become empty and show fallback, let layout know
2917 // that it needs to do work.
2918 if (aOldSlot && aOldSlot->AssignedNodes().Length() == 1 &&
2919 aOldSlot->HasChildren()) {
2920 DestroyFramesForAndRestyle(aOldSlot);
2923 // Ensure the new element starts off clean.
2924 DestroyFramesAndStyleDataFor(&aElement, *mPresContext,
2925 RestyleManager::IncludeRoot::Yes);
2927 if (aNewSlot) {
2928 // If the new slot will stop showing fallback content, we need to reframe it
2929 // altogether.
2930 if (aNewSlot->AssignedNodes().IsEmpty() && aNewSlot->HasChildren()) {
2931 DestroyFramesForAndRestyle(aNewSlot);
2932 // Otherwise we just care about the element, but we need to ensure that
2933 // something takes care of traversing to the relevant slot, if needed.
2934 } else if (aNewSlot->HasServoData() &&
2935 !Servo_Element_IsDisplayNone(aNewSlot)) {
2936 // Set the reframe bits...
2937 aNewSlot->NoteDescendantsNeedFramesForServo();
2938 aElement.SetFlags(NODE_NEEDS_FRAME);
2939 // Now the style dirty bits. Note that we can't just do
2940 // aElement.NoteDirtyForServo(), because the new slot is not setup yet.
2941 aNewSlot->SetHasDirtyDescendantsForServo();
2942 aNewSlot->NoteDirtySubtreeForServo();
2947 #ifdef DEBUG
2948 static void AssertNoFramesOrStyleDataInDescendants(Element& aElement) {
2949 for (nsINode* node : ShadowIncludingTreeIterator(aElement)) {
2950 nsIContent* c = nsIContent::FromNode(node);
2951 if (c == &aElement) {
2952 continue;
2954 // FIXME(emilio): The <area> check is needed because of bug 135040.
2955 MOZ_ASSERT(!c->GetPrimaryFrame() || c->IsHTMLElement(nsGkAtoms::area));
2956 MOZ_ASSERT(!c->IsElement() || !c->AsElement()->HasServoData());
2959 #endif
2961 void PresShell::DestroyFramesForAndRestyle(Element* aElement) {
2962 #ifdef DEBUG
2963 auto postCondition = MakeScopeExit([&]() {
2964 MOZ_ASSERT(!aElement->GetPrimaryFrame());
2965 AssertNoFramesOrStyleDataInDescendants(*aElement);
2967 #endif
2969 MOZ_ASSERT(aElement);
2970 if (!aElement->HasServoData()) {
2971 // Nothing to do here, the element already is out of the flat tree or is not
2972 // styled.
2973 return;
2976 // Mark ourselves as not safe to flush while we're doing frame destruction.
2977 nsAutoScriptBlocker scriptBlocker;
2978 ++mChangeNestCount;
2980 const bool didReconstruct = FrameConstructor()->DestroyFramesFor(aElement);
2981 // Clear the style data from all the flattened tree descendants, but _not_
2982 // from us, since otherwise we wouldn't see the reframe.
2983 RestyleManager::ClearServoDataFromSubtree(aElement,
2984 RestyleManager::IncludeRoot::No);
2985 auto changeHint =
2986 didReconstruct ? nsChangeHint(0) : nsChangeHint_ReconstructFrame;
2987 mPresContext->RestyleManager()->PostRestyleEvent(
2988 aElement, RestyleHint::RestyleSubtree(), changeHint);
2990 --mChangeNestCount;
2993 void PresShell::ShadowRootWillBeAttached(Element& aElement) {
2994 #ifdef DEBUG
2995 auto postCondition = MakeScopeExit(
2996 [&]() { AssertNoFramesOrStyleDataInDescendants(aElement); });
2997 #endif
2999 if (!aElement.HasServoData()) {
3000 // Nothing to do here, the element already is out of the flat tree or is not
3001 // styled.
3002 return;
3005 if (!aElement.HasChildren()) {
3006 // The element has no children, just avoid the work.
3007 return;
3010 // Mark ourselves as not safe to flush while we're doing frame destruction.
3011 nsAutoScriptBlocker scriptBlocker;
3012 ++mChangeNestCount;
3014 // NOTE(emilio): We use FlattenedChildIterator intentionally here (rather than
3015 // StyleChildrenIterator), since we don't want to remove ::before / ::after
3016 // content.
3017 FlattenedChildIterator iter(&aElement);
3018 nsCSSFrameConstructor* fc = FrameConstructor();
3019 for (nsIContent* c = iter.GetNextChild(); c; c = iter.GetNextChild()) {
3020 fc->DestroyFramesFor(c);
3021 if (c->IsElement()) {
3022 RestyleManager::ClearServoDataFromSubtree(c->AsElement());
3026 #ifdef ACCESSIBILITY
3027 if (nsAccessibilityService* accService = GetAccService()) {
3028 accService->ScheduleAccessibilitySubtreeUpdate(this, &aElement);
3030 #endif
3032 --mChangeNestCount;
3035 void PresShell::PostRecreateFramesFor(Element* aElement) {
3036 if (MOZ_UNLIKELY(!mDidInitialize)) {
3037 // Nothing to do here. In fact, if we proceed and aElement is the root, we
3038 // will crash.
3039 return;
3042 mPresContext->RestyleManager()->PostRestyleEvent(
3043 aElement, RestyleHint{0}, nsChangeHint_ReconstructFrame);
3046 void PresShell::RestyleForAnimation(Element* aElement, RestyleHint aHint) {
3047 // Now that we no longer have separate non-animation and animation
3048 // restyles, this method having a distinct identity is less important,
3049 // but it still seems useful to offer as a "more public" API and as a
3050 // checkpoint for these restyles to go through.
3051 mPresContext->RestyleManager()->PostRestyleEvent(aElement, aHint,
3052 nsChangeHint(0));
3055 void PresShell::SetForwardingContainer(const WeakPtr<nsDocShell>& aContainer) {
3056 mForwardingContainer = aContainer;
3059 void PresShell::ClearFrameRefs(nsIFrame* aFrame) {
3060 mPresContext->EventStateManager()->ClearFrameRefs(aFrame);
3062 AutoWeakFrame* weakFrame = mAutoWeakFrames;
3063 while (weakFrame) {
3064 AutoWeakFrame* prev = weakFrame->GetPreviousWeakFrame();
3065 if (weakFrame->GetFrame() == aFrame) {
3066 // This removes weakFrame from mAutoWeakFrames.
3067 weakFrame->Clear(this);
3069 weakFrame = prev;
3072 AutoTArray<WeakFrame*, 4> toRemove;
3073 for (WeakFrame* weakFrame : mWeakFrames) {
3074 if (weakFrame->GetFrame() == aFrame) {
3075 toRemove.AppendElement(weakFrame);
3078 for (WeakFrame* weakFrame : toRemove) {
3079 weakFrame->Clear(this);
3083 UniquePtr<gfxContext> PresShell::CreateReferenceRenderingContext() {
3084 if (mPresContext->IsScreen()) {
3085 return gfxContext::CreateOrNull(
3086 gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget().get());
3089 // We assume the devCtx has positive width and height for this call.
3090 // However, width and height, may be outside of the reasonable range
3091 // so rc may still be null.
3092 nsDeviceContext* devCtx = mPresContext->DeviceContext();
3093 return devCtx->CreateReferenceRenderingContext();
3096 // https://html.spec.whatwg.org/#scroll-to-the-fragment-identifier
3097 nsresult PresShell::GoToAnchor(const nsAString& aAnchorName, bool aScroll,
3098 ScrollFlags aAdditionalScrollFlags) {
3099 if (!mDocument) {
3100 return NS_ERROR_FAILURE;
3103 const Element* root = mDocument->GetRootElement();
3104 if (root && root->IsSVGElement(nsGkAtoms::svg)) {
3105 // We need to execute this even if there is an empty anchor name
3106 // so that any existing SVG fragment identifier effect is removed
3107 if (SVGFragmentIdentifier::ProcessFragmentIdentifier(mDocument,
3108 aAnchorName)) {
3109 return NS_OK;
3113 // Hold a reference to the ESM in case event dispatch tears us down.
3114 RefPtr<EventStateManager> esm = mPresContext->EventStateManager();
3116 // 1. If there is no indicated part of the document, set the Document's target
3117 // element to null.
3119 // FIXME(emilio): Per spec empty fragment string should take the same
3120 // code-path as "top"!
3121 if (aAnchorName.IsEmpty()) {
3122 NS_ASSERTION(!aScroll, "can't scroll to empty anchor name");
3123 esm->SetContentState(nullptr, ElementState::URLTARGET);
3124 return NS_OK;
3127 // 2. If the indicated part of the document is the top of the document,
3128 // then:
3129 // (handled below when `target` is null, and anchor is `top`)
3131 // 3.1. Let target be element that is the indicated part of the document.
3133 // https://html.spec.whatwg.org/#target-element
3134 // https://html.spec.whatwg.org/#find-a-potential-indicated-element
3135 RefPtr<Element> target =
3136 nsContentUtils::GetTargetElement(mDocument, aAnchorName);
3138 // 1. If there is no indicated part of the document, set the Document's
3139 // target element to null.
3140 // 2.1. Set the Document's target element to null.
3141 // 3.2. Set the Document's target element to target.
3142 esm->SetContentState(target, ElementState::URLTARGET);
3144 // TODO: Spec probably needs a section to account for this.
3145 if (nsIScrollableFrame* rootScroll = GetRootScrollFrameAsScrollable()) {
3146 if (rootScroll->DidHistoryRestore()) {
3147 // Scroll position restored from history trumps scrolling to anchor.
3148 aScroll = false;
3149 rootScroll->ClearDidHistoryRestore();
3153 if (target) {
3154 if (aScroll) {
3155 // 3.3. TODO: Run the ancestor details revealing algorithm on target.
3156 // 3.4. Scroll target into view, with behavior set to "auto", block set to
3157 // "start", and inline set to "nearest".
3158 // FIXME(emilio): Not all callers pass ScrollSmoothAuto (but we use auto
3159 // smooth scroll for `top` regardless below, so maybe they should!).
3160 ScrollingInteractionContext scrollToAnchorContext(true);
3161 MOZ_TRY(ScrollContentIntoView(
3162 target, ScrollAxis(WhereToScroll::Start, WhenToScroll::Always),
3163 ScrollAxis(),
3164 ScrollFlags::AnchorScrollFlags | aAdditionalScrollFlags));
3166 if (nsIScrollableFrame* rootScroll = GetRootScrollFrameAsScrollable()) {
3167 mLastAnchorScrolledTo = target;
3168 mLastAnchorScrollPositionY = rootScroll->GetScrollPosition().y;
3173 // 3.6. Move the sequential focus navigation starting point to target.
3175 // Move the caret to the anchor. That way tabbing will start from the new
3176 // location.
3178 // TODO(emilio): Do we want to do this even if aScroll is false?
3180 // NOTE: Intentionally out of order for now with the focus steps, see
3181 // https://github.com/whatwg/html/issues/7759
3182 RefPtr<nsRange> jumpToRange = nsRange::Create(mDocument);
3183 nsCOMPtr<nsIContent> nodeToSelect = target.get();
3184 while (nodeToSelect->GetFirstChild()) {
3185 nodeToSelect = nodeToSelect->GetFirstChild();
3187 jumpToRange->SelectNodeContents(*nodeToSelect, IgnoreErrors());
3188 if (RefPtr sel = mSelection->GetSelection(SelectionType::eNormal)) {
3189 sel->RemoveAllRanges(IgnoreErrors());
3190 sel->AddRangeAndSelectFramesAndNotifyListeners(*jumpToRange,
3191 IgnoreErrors());
3192 if (!StaticPrefs::layout_selectanchor()) {
3193 // Use a caret (collapsed selection) at the start of the anchor.
3194 sel->CollapseToStart(IgnoreErrors());
3199 // 3.5. Run the focusing steps for target, with the Document's viewport as
3200 // the fallback target.
3202 // Note that ScrollContentIntoView flushes, so we don't need to do that
3203 // again here. We also don't need to scroll again either.
3205 // We intentionally focus the target only when aScroll is true, we need to
3206 // sort out if the spec needs to differentiate these cases. When aScroll is
3207 // false we still clear the focus unconditionally, that's legacy behavior,
3208 // maybe we shouldn't do it.
3210 // TODO(emilio): Do we really want to clear the focus even if aScroll is
3211 // false?
3212 const bool shouldFocusTarget = [&] {
3213 if (!aScroll) {
3214 return false;
3216 nsIFrame* targetFrame = target->GetPrimaryFrame();
3217 return targetFrame && targetFrame->IsFocusable();
3218 }();
3220 if (shouldFocusTarget) {
3221 FocusOptions options;
3222 options.mPreventScroll = true;
3223 target->Focus(options, CallerType::NonSystem, IgnoreErrors());
3224 } else if (RefPtr<nsIFocusManager> fm = nsFocusManager::GetFocusManager()) {
3225 if (nsPIDOMWindowOuter* win = mDocument->GetWindow()) {
3226 // Now focus the document itself if focus is on an element within it.
3227 nsCOMPtr<mozIDOMWindowProxy> focusedWindow;
3228 fm->GetFocusedWindow(getter_AddRefs(focusedWindow));
3229 if (SameCOMIdentity(win, focusedWindow)) {
3230 fm->ClearFocus(focusedWindow);
3235 // If the target is an animation element, activate the animation
3236 if (nsCOMPtr<SVGAnimationElement> animationElement =
3237 do_QueryInterface(target)) {
3238 animationElement->ActivateByHyperlink();
3241 #ifdef ACCESSIBILITY
3242 if (nsAccessibilityService* accService = GetAccService()) {
3243 accService->NotifyOfAnchorJumpTo(target);
3245 #endif
3246 } else if (nsContentUtils::EqualsIgnoreASCIICase(aAnchorName, u"top"_ns)) {
3247 // 2.2. Scroll to the beginning of the document for the Document.
3248 nsIScrollableFrame* sf = GetRootScrollFrameAsScrollable();
3249 // Check |aScroll| after setting |rv| so we set |rv| to the same
3250 // thing whether or not |aScroll| is true.
3251 if (aScroll && sf) {
3252 ScrollMode scrollMode =
3253 sf->IsSmoothScroll() ? ScrollMode::SmoothMsd : ScrollMode::Instant;
3254 // Scroll to the top of the page
3255 sf->ScrollTo(nsPoint(0, 0), scrollMode);
3257 } else {
3258 return NS_ERROR_FAILURE;
3261 return NS_OK;
3264 nsresult PresShell::ScrollToAnchor() {
3265 nsCOMPtr<nsIContent> lastAnchor = std::move(mLastAnchorScrolledTo);
3266 if (!lastAnchor) {
3267 return NS_OK;
3270 NS_ASSERTION(mDidInitialize, "should have done initial reflow by now");
3271 nsIScrollableFrame* rootScroll = GetRootScrollFrameAsScrollable();
3272 if (!rootScroll ||
3273 mLastAnchorScrollPositionY != rootScroll->GetScrollPosition().y) {
3274 return NS_OK;
3276 return ScrollContentIntoView(
3277 lastAnchor, ScrollAxis(WhereToScroll::Start, WhenToScroll::Always),
3278 ScrollAxis(), ScrollFlags::AnchorScrollFlags);
3282 * Helper (per-continuation) for ScrollContentIntoView.
3284 * @param aContainerFrame [in] the frame which aRect is relative to
3285 * @param aFrame [in] Frame whose bounds should be unioned
3286 * @param aUseWholeLineHeightForInlines [in] if true, then for inline frames
3287 * we should include the top of the line in the added rectangle
3288 * @param aRect [inout] rect into which its bounds should be unioned
3289 * @param aHaveRect [inout] whether aRect contains data yet
3290 * @param aPrevBlock [inout] the block aLines is a line iterator for
3291 * @param aLines [inout] the line iterator we're using
3292 * @param aCurLine [inout] the line to start looking from in this iterator
3294 static void AccumulateFrameBounds(nsIFrame* aContainerFrame, nsIFrame* aFrame,
3295 bool aUseWholeLineHeightForInlines,
3296 nsRect& aRect, bool& aHaveRect,
3297 nsIFrame*& aPrevBlock,
3298 nsILineIterator*& aLines, int32_t& aCurLine) {
3299 nsIFrame* frame = aFrame;
3300 nsRect frameBounds = nsRect(nsPoint(0, 0), aFrame->GetSize());
3302 // If this is an inline frame and either the bounds height is 0 (quirks
3303 // layout model) or aUseWholeLineHeightForInlines is set, we need to
3304 // change the top of the bounds to include the whole line.
3305 if (frameBounds.height == 0 || aUseWholeLineHeightForInlines) {
3306 nsIFrame* prevFrame = aFrame;
3307 nsIFrame* f = aFrame;
3309 while (f && f->IsFrameOfType(nsIFrame::eLineParticipant) &&
3310 !f->IsTransformed() && !f->IsAbsPosContainingBlock()) {
3311 prevFrame = f;
3312 f = prevFrame->GetParent();
3315 if (f != aFrame && f && f->IsBlockFrame()) {
3316 // find the line containing aFrame and increase the top of |offset|.
3317 if (f != aPrevBlock) {
3318 aLines = f->GetLineIterator();
3319 aPrevBlock = f;
3320 aCurLine = 0;
3322 if (aLines) {
3323 int32_t index = aLines->FindLineContaining(prevFrame, aCurLine);
3324 if (index >= 0) {
3325 auto line = aLines->GetLine(index).unwrap();
3326 frameBounds += frame->GetOffsetTo(f);
3327 frame = f;
3328 if (line.mLineBounds.y < frameBounds.y) {
3329 frameBounds.height = frameBounds.YMost() - line.mLineBounds.y;
3330 frameBounds.y = line.mLineBounds.y;
3337 nsRect transformedBounds = nsLayoutUtils::TransformFrameRectToAncestor(
3338 frame, frameBounds, aContainerFrame);
3340 if (aHaveRect) {
3341 // We can't use nsRect::UnionRect since it drops empty rects on
3342 // the floor, and we need to include them. (Thus we need
3343 // aHaveRect to know when to drop the initial value on the floor.)
3344 aRect = aRect.UnionEdges(transformedBounds);
3345 } else {
3346 aHaveRect = true;
3347 aRect = transformedBounds;
3351 static bool ComputeNeedToScroll(WhenToScroll aWhenToScroll, nscoord aLineSize,
3352 nscoord aRectMin, nscoord aRectMax,
3353 nscoord aViewMin, nscoord aViewMax) {
3354 // See how the rect should be positioned in a given axis.
3355 switch (aWhenToScroll) {
3356 case WhenToScroll::Always:
3357 // The caller wants the frame as visible as possible
3358 return true;
3359 case WhenToScroll::IfNotVisible:
3360 // Scroll only if no part of the frame is visible in this view.
3361 return aRectMax - aLineSize <= aViewMin ||
3362 aRectMin + aLineSize >= aViewMax;
3363 case WhenToScroll::IfNotFullyVisible:
3364 // Scroll only if part of the frame is hidden and more can fit in view
3365 return !(aRectMin >= aViewMin && aRectMax <= aViewMax) &&
3366 std::min(aViewMax, aRectMax) - std::max(aRectMin, aViewMin) <
3367 aViewMax - aViewMin;
3369 return false;
3372 static nscoord ComputeWhereToScroll(WhereToScroll aWhereToScroll,
3373 nscoord aOriginalCoord, nscoord aRectMin,
3374 nscoord aRectMax, nscoord aViewMin,
3375 nscoord aViewMax, nscoord* aRangeMin,
3376 nscoord* aRangeMax) {
3377 nscoord resultCoord = aOriginalCoord;
3378 nscoord scrollPortLength = aViewMax - aViewMin;
3379 if (!aWhereToScroll.mPercentage) {
3380 // Scroll the minimum amount necessary to show as much as possible of the
3381 // frame. If the frame is too large, don't hide any initially visible part
3382 // of it.
3383 nscoord min = std::min(aRectMin, aRectMax - scrollPortLength);
3384 nscoord max = std::max(aRectMin, aRectMax - scrollPortLength);
3385 resultCoord = std::min(std::max(aOriginalCoord, min), max);
3386 } else {
3387 float percent = aWhereToScroll.mPercentage.value() / 100.0f;
3388 nscoord frameAlignCoord =
3389 NSToCoordRound(aRectMin + (aRectMax - aRectMin) * percent);
3390 resultCoord = NSToCoordRound(frameAlignCoord - scrollPortLength * percent);
3392 // Force the scroll range to extend to include resultCoord.
3393 *aRangeMin = std::min(resultCoord, aRectMax - scrollPortLength);
3394 *aRangeMax = std::max(resultCoord, aRectMin);
3395 return resultCoord;
3398 static WhereToScroll GetApplicableWhereToScroll(
3399 const nsIScrollableFrame* aFrameAsScrollable,
3400 const nsIFrame* aScrollableFrame, const nsIFrame* aTarget,
3401 ScrollDirection aScrollDirection, WhereToScroll aOriginal) {
3402 MOZ_ASSERT(do_QueryFrame(aFrameAsScrollable) == aScrollableFrame);
3403 if (aTarget == aScrollableFrame) {
3404 return aOriginal;
3407 StyleScrollSnapAlignKeyword align =
3408 aScrollDirection == ScrollDirection::eHorizontal
3409 ? aFrameAsScrollable->GetScrollSnapAlignFor(aTarget).first
3410 : aFrameAsScrollable->GetScrollSnapAlignFor(aTarget).second;
3412 switch (align) {
3413 case StyleScrollSnapAlignKeyword::None:
3414 return aOriginal;
3415 case StyleScrollSnapAlignKeyword::Start:
3416 return WhereToScroll::Start;
3417 case StyleScrollSnapAlignKeyword::Center:
3418 return WhereToScroll::Center;
3419 case StyleScrollSnapAlignKeyword::End:
3420 return WhereToScroll::End;
3422 return aOriginal;
3426 * This function takes a scrollable frame, a rect in the coordinate system
3427 * of the scrolled frame, and a desired percentage-based scroll
3428 * position and attempts to scroll the rect to that position in the
3429 * visual viewport.
3431 * This needs to work even if aRect has a width or height of zero.
3433 static void ScrollToShowRect(nsIScrollableFrame* aFrameAsScrollable,
3434 const nsIFrame* aScrollableFrame,
3435 const nsIFrame* aTarget, const nsRect& aRect,
3436 const Sides aScrollPaddingSkipSides,
3437 const nsMargin& aMargin, ScrollAxis aVertical,
3438 ScrollAxis aHorizontal, ScrollFlags aScrollFlags) {
3439 nsPoint scrollPt = aFrameAsScrollable->GetVisualViewportOffset();
3440 const nsPoint originalScrollPt = scrollPt;
3441 const nsRect visibleRect(scrollPt,
3442 aFrameAsScrollable->GetVisualViewportSize());
3444 const nsMargin padding = [&] {
3445 nsMargin p = aFrameAsScrollable->GetScrollPadding();
3446 p.ApplySkipSides(aScrollPaddingSkipSides);
3447 return p + aMargin;
3448 }();
3450 const nsRect rectToScrollIntoView = [&] {
3451 nsRect r(aRect);
3452 r.Inflate(padding);
3453 return r.Intersect(aFrameAsScrollable->GetScrolledRect());
3454 }();
3456 nsSize lineSize;
3457 // Don't call GetLineScrollAmount unless we actually need it. Not only
3458 // does this save time, but it's not safe to call GetLineScrollAmount
3459 // during reflow (because it depends on font size inflation and doesn't
3460 // use the in-reflow-safe font-size inflation path). If we did call it,
3461 // it would assert and possible give the wrong result.
3462 if (aVertical.mWhenToScroll == WhenToScroll::IfNotVisible ||
3463 aHorizontal.mWhenToScroll == WhenToScroll::IfNotVisible) {
3464 lineSize = aFrameAsScrollable->GetLineScrollAmount();
3466 ScrollStyles ss = aFrameAsScrollable->GetScrollStyles();
3467 nsRect allowedRange(scrollPt, nsSize(0, 0));
3468 ScrollDirections directions =
3469 aFrameAsScrollable->GetAvailableScrollingDirections();
3471 if (((aScrollFlags & ScrollFlags::ScrollOverflowHidden) ||
3472 ss.mVertical != StyleOverflow::Hidden) &&
3473 (!aVertical.mOnlyIfPerceivedScrollableDirection ||
3474 (directions.contains(ScrollDirection::eVertical)))) {
3475 if (ComputeNeedToScroll(aVertical.mWhenToScroll, lineSize.height, aRect.y,
3476 aRect.YMost(), visibleRect.y + padding.top,
3477 visibleRect.YMost() - padding.bottom)) {
3478 // If the scroll-snap-align on the frame is valid, we need to respect it.
3479 WhereToScroll whereToScroll = GetApplicableWhereToScroll(
3480 aFrameAsScrollable, aScrollableFrame, aTarget,
3481 ScrollDirection::eVertical, aVertical.mWhereToScroll);
3483 nscoord maxHeight;
3484 scrollPt.y = ComputeWhereToScroll(
3485 whereToScroll, scrollPt.y, rectToScrollIntoView.y,
3486 rectToScrollIntoView.YMost(), visibleRect.y, visibleRect.YMost(),
3487 &allowedRange.y, &maxHeight);
3488 allowedRange.height = maxHeight - allowedRange.y;
3492 if (((aScrollFlags & ScrollFlags::ScrollOverflowHidden) ||
3493 ss.mHorizontal != StyleOverflow::Hidden) &&
3494 (!aHorizontal.mOnlyIfPerceivedScrollableDirection ||
3495 (directions.contains(ScrollDirection::eHorizontal)))) {
3496 if (ComputeNeedToScroll(aHorizontal.mWhenToScroll, lineSize.width, aRect.x,
3497 aRect.XMost(), visibleRect.x + padding.left,
3498 visibleRect.XMost() - padding.right)) {
3499 // If the scroll-snap-align on the frame is valid, we need to respect it.
3500 WhereToScroll whereToScroll = GetApplicableWhereToScroll(
3501 aFrameAsScrollable, aScrollableFrame, aTarget,
3502 ScrollDirection::eHorizontal, aHorizontal.mWhereToScroll);
3504 nscoord maxWidth;
3505 scrollPt.x = ComputeWhereToScroll(
3506 whereToScroll, scrollPt.x, rectToScrollIntoView.x,
3507 rectToScrollIntoView.XMost(), visibleRect.x, visibleRect.XMost(),
3508 &allowedRange.x, &maxWidth);
3509 allowedRange.width = maxWidth - allowedRange.x;
3513 // If we don't need to scroll, then don't try since it might cancel
3514 // a current smooth scroll operation.
3515 if (scrollPt == originalScrollPt) {
3516 return;
3519 ScrollMode scrollMode = ScrollMode::Instant;
3520 // Default to an instant scroll, but if the scroll behavior given is "auto"
3521 // or "smooth", use that as the specified behavior. If the user has disabled
3522 // smooth scrolls, a given mode of "auto" or "smooth" should not result in
3523 // a smooth scroll.
3524 ScrollBehavior behavior = ScrollBehavior::Instant;
3525 if (aScrollFlags & ScrollFlags::ScrollSmooth) {
3526 behavior = ScrollBehavior::Smooth;
3527 } else if (aScrollFlags & ScrollFlags::ScrollSmoothAuto) {
3528 behavior = ScrollBehavior::Auto;
3530 bool smoothScroll = aFrameAsScrollable->IsSmoothScroll(behavior);
3531 if (smoothScroll) {
3532 scrollMode = ScrollMode::SmoothMsd;
3534 nsIFrame* frame = do_QueryFrame(aFrameAsScrollable);
3535 AutoWeakFrame weakFrame(frame);
3536 aFrameAsScrollable->ScrollTo(scrollPt, scrollMode, &allowedRange,
3537 ScrollSnapFlags::IntendedEndPosition,
3538 aScrollFlags & ScrollFlags::TriggeredByScript
3539 ? ScrollTriggeredByScript::Yes
3540 : ScrollTriggeredByScript::No);
3541 if (!weakFrame.IsAlive()) {
3542 return;
3545 // If this is the RCD-RSF, also call ScrollToVisual() since we want to
3546 // scroll the rect into view visually, and that may require scrolling
3547 // the visual viewport in scenarios where there is not enough layout
3548 // scroll range.
3549 if (aFrameAsScrollable->IsRootScrollFrameOfDocument() &&
3550 frame->PresContext()->IsRootContentDocumentCrossProcess()) {
3551 frame->PresShell()->ScrollToVisual(scrollPt, FrameMetrics::eMainThread,
3552 scrollMode);
3556 nsresult PresShell::ScrollContentIntoView(nsIContent* aContent,
3557 ScrollAxis aVertical,
3558 ScrollAxis aHorizontal,
3559 ScrollFlags aScrollFlags) {
3560 NS_ENSURE_TRUE(aContent, NS_ERROR_NULL_POINTER);
3561 RefPtr<Document> composedDoc = aContent->GetComposedDoc();
3562 NS_ENSURE_STATE(composedDoc);
3564 NS_ASSERTION(mDidInitialize, "should have done initial reflow by now");
3566 if (mContentToScrollTo) {
3567 mContentToScrollTo->RemoveProperty(nsGkAtoms::scrolling);
3569 mContentToScrollTo = aContent;
3570 ScrollIntoViewData* data = new ScrollIntoViewData();
3571 data->mContentScrollVAxis = aVertical;
3572 data->mContentScrollHAxis = aHorizontal;
3573 data->mContentToScrollToFlags = aScrollFlags;
3574 if (NS_FAILED(mContentToScrollTo->SetProperty(
3575 nsGkAtoms::scrolling, data,
3576 nsINode::DeleteProperty<PresShell::ScrollIntoViewData>))) {
3577 mContentToScrollTo = nullptr;
3580 // If the target frame is an ancestor of a `content-visibility: auto`
3581 // element ensure that it is laid out, so that the boundary rectangle is
3582 // correct.
3583 if (mContentToScrollTo) {
3584 if (nsIFrame* frame = mContentToScrollTo->GetPrimaryFrame()) {
3585 if (frame->IsHiddenByContentVisibilityOnAnyAncestor(
3586 nsIFrame::IncludeContentVisibility::Auto)) {
3587 frame->PresShell()->EnsureReflowIfFrameHasHiddenContent(frame);
3592 // Flush layout and attempt to scroll in the process.
3593 if (PresShell* presShell = composedDoc->GetPresShell()) {
3594 presShell->SetNeedLayoutFlush();
3596 composedDoc->FlushPendingNotifications(FlushType::InterruptibleLayout);
3598 // If mContentToScrollTo is non-null, that means we interrupted the reflow
3599 // (or suppressed it altogether because we're suppressing interruptible
3600 // flushes right now) and won't necessarily get the position correct, but do
3601 // a best-effort scroll here. The other option would be to do this inside
3602 // FlushPendingNotifications, but I'm not sure the repeated scrolling that
3603 // could trigger if reflows keep getting interrupted would be more desirable
3604 // than a single best-effort scroll followed by one final scroll on the first
3605 // completed reflow.
3606 if (mContentToScrollTo) {
3607 DoScrollContentIntoView();
3609 return NS_OK;
3612 static nsMargin GetScrollMargin(const nsIFrame* aFrame) {
3613 MOZ_ASSERT(aFrame);
3614 // If we're focusing something that can't be targeted by content, allow
3615 // content to customize the margin.
3617 // TODO: This is also a bit of an issue for delegated focus, see
3618 // https://github.com/whatwg/html/issues/7033.
3619 if (aFrame->GetContent() && aFrame->GetContent()->ChromeOnlyAccess()) {
3620 if (const nsIContent* userContent =
3621 aFrame->GetContent()->GetChromeOnlyAccessSubtreeRootParent()) {
3622 if (const nsIFrame* frame = userContent->GetPrimaryFrame()) {
3623 return frame->StyleMargin()->GetScrollMargin();
3627 return aFrame->StyleMargin()->GetScrollMargin();
3630 void PresShell::DoScrollContentIntoView() {
3631 NS_ASSERTION(mDidInitialize, "should have done initial reflow by now");
3633 nsIFrame* frame = mContentToScrollTo->GetPrimaryFrame();
3635 if (!frame || frame->IsHiddenByContentVisibilityOnAnyAncestor(
3636 nsIFrame::IncludeContentVisibility::Hidden)) {
3637 mContentToScrollTo->RemoveProperty(nsGkAtoms::scrolling);
3638 mContentToScrollTo = nullptr;
3639 return;
3642 if (frame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
3643 // The reflow flush before this scroll got interrupted, and this frame's
3644 // coords and size are all zero, and it has no content showing anyway.
3645 // Don't bother scrolling to it. We'll try again when we finish up layout.
3646 return;
3649 auto* data = static_cast<ScrollIntoViewData*>(
3650 mContentToScrollTo->GetProperty(nsGkAtoms::scrolling));
3651 if (MOZ_UNLIKELY(!data)) {
3652 mContentToScrollTo = nullptr;
3653 return;
3656 ScrollFrameIntoView(frame, Nothing(), data->mContentScrollVAxis,
3657 data->mContentScrollHAxis, data->mContentToScrollToFlags);
3660 bool PresShell::ScrollFrameIntoView(
3661 nsIFrame* aTargetFrame, const Maybe<nsRect>& aKnownRectRelativeToTarget,
3662 ScrollAxis aVertical, ScrollAxis aHorizontal, ScrollFlags aScrollFlags) {
3663 // The scroll margin only applies to the whole bounds of the element, so don't
3664 // apply it if we get an arbitrary rect / point to scroll to.
3665 const nsMargin scrollMargin =
3666 aKnownRectRelativeToTarget ? nsMargin() : GetScrollMargin(aTargetFrame);
3668 Sides skipPaddingSides;
3669 const auto MaybeSkipPaddingSides = [&](nsIFrame* aFrame) {
3670 if (!aFrame->IsStickyPositioned()) {
3671 return;
3673 const nsPoint pos = aFrame->GetPosition();
3674 const nsPoint normalPos = aFrame->GetNormalPosition();
3675 if (pos == normalPos) {
3676 return; // Frame is not stuck.
3678 // If we're targetting a sticky element, make sure not to apply
3679 // scroll-padding on the direction we're stuck.
3680 const auto& offsets = aFrame->StylePosition()->mOffset;
3681 for (auto side : AllPhysicalSides()) {
3682 if (offsets.Get(side).IsAuto()) {
3683 continue;
3685 // See if this axis is stuck.
3686 const bool yAxis = side == eSideTop || side == eSideBottom;
3687 const bool stuck = yAxis ? pos.y != normalPos.y : pos.x != normalPos.x;
3688 if (!stuck) {
3689 continue;
3691 skipPaddingSides |= SideToSideBit(side);
3695 nsIFrame* container = aTargetFrame;
3697 // This function needs to work even if rect has a width or height of 0.
3698 nsRect rect = [&] {
3699 if (aKnownRectRelativeToTarget) {
3700 return *aKnownRectRelativeToTarget;
3702 MaybeSkipPaddingSides(aTargetFrame);
3703 while (nsIFrame* parent = container->GetParent()) {
3704 container = parent;
3705 if (static_cast<nsIScrollableFrame*>(do_QueryFrame(container))) {
3706 // We really just need a non-fragmented frame so that we can accumulate
3707 // the bounds of all our continuations relative to it. We shouldn't jump
3708 // out of our nearest scrollable frame, and that's an ok reference
3709 // frame, so try to use that, or the root frame if there's nothing to
3710 // scroll in this document.
3711 break;
3713 MaybeSkipPaddingSides(container);
3715 MOZ_DIAGNOSTIC_ASSERT(container);
3717 nsRect targetFrameBounds;
3719 bool haveRect = false;
3720 const bool useWholeLineHeightForInlines =
3721 aVertical.mWhenToScroll != WhenToScroll::IfNotFullyVisible;
3722 AutoAssertNoDomMutations
3723 guard; // Ensure use of nsILineIterators is safe.
3724 nsIFrame* prevBlock = nullptr;
3725 // Reuse the same line iterator across calls to AccumulateFrameBounds.
3726 // We set it every time we detect a new block (stored in prevBlock).
3727 nsILineIterator* lines = nullptr;
3728 // The last line we found a continuation on in |lines|. We assume that
3729 // later continuations cannot come on earlier lines.
3730 int32_t curLine = 0;
3731 nsIFrame* frame = aTargetFrame;
3732 do {
3733 AccumulateFrameBounds(container, frame, useWholeLineHeightForInlines,
3734 targetFrameBounds, haveRect, prevBlock, lines,
3735 curLine);
3736 } while ((frame = frame->GetNextContinuation()));
3739 return targetFrameBounds;
3740 }();
3742 bool didScroll = false;
3743 const nsIFrame* target = aTargetFrame;
3744 // Walk up the frame hierarchy scrolling the rect into view and
3745 // keeping rect relative to container
3746 do {
3747 if (nsIScrollableFrame* sf = do_QueryFrame(container)) {
3748 nsPoint oldPosition = sf->GetScrollPosition();
3749 nsRect targetRect = rect;
3750 // Inflate the scrolled rect by the container's padding in each dimension,
3751 // unless we have 'overflow-clip-box-*: content-box' in that dimension.
3752 auto* disp = container->StyleDisplay();
3753 if (disp->mOverflowClipBoxBlock == StyleOverflowClipBox::ContentBox ||
3754 disp->mOverflowClipBoxInline == StyleOverflowClipBox::ContentBox) {
3755 WritingMode wm = container->GetWritingMode();
3756 bool cbH = (wm.IsVertical() ? disp->mOverflowClipBoxBlock
3757 : disp->mOverflowClipBoxInline) ==
3758 StyleOverflowClipBox::ContentBox;
3759 bool cbV = (wm.IsVertical() ? disp->mOverflowClipBoxInline
3760 : disp->mOverflowClipBoxBlock) ==
3761 StyleOverflowClipBox::ContentBox;
3762 nsMargin padding = container->GetUsedPadding();
3763 if (!cbH) {
3764 padding.left = padding.right = nscoord(0);
3766 if (!cbV) {
3767 padding.top = padding.bottom = nscoord(0);
3769 targetRect.Inflate(padding);
3772 targetRect -= sf->GetScrolledFrame()->GetPosition();
3775 AutoWeakFrame wf(container);
3776 ScrollToShowRect(sf, container, target, targetRect, skipPaddingSides,
3777 scrollMargin, aVertical, aHorizontal, aScrollFlags);
3778 if (!wf.IsAlive()) {
3779 return didScroll;
3783 nsPoint newPosition = sf->LastScrollDestination();
3784 // If the scroll position increased, that means our content moved up,
3785 // so our rect's offset should decrease
3786 rect += oldPosition - newPosition;
3788 if (oldPosition != newPosition) {
3789 didScroll = true;
3792 // only scroll one container when this flag is set
3793 if (aScrollFlags & ScrollFlags::ScrollFirstAncestorOnly) {
3794 break;
3797 // This scroll container will be the next target element in the nearest
3798 // ancestor scroll container.
3799 target = container;
3800 // We found a sticky scroll container, we shouldn't skip that side
3801 // anymore.
3802 skipPaddingSides = {};
3805 MaybeSkipPaddingSides(container);
3807 nsIFrame* parent;
3808 if (container->IsTransformed()) {
3809 container->GetTransformMatrix(ViewportType::Layout, RelativeTo{nullptr},
3810 &parent);
3811 rect =
3812 nsLayoutUtils::TransformFrameRectToAncestor(container, rect, parent);
3813 } else {
3814 rect += container->GetPosition();
3815 parent = container->GetParent();
3817 if (!parent && !(aScrollFlags & ScrollFlags::ScrollNoParentFrames)) {
3818 nsPoint extraOffset(0, 0);
3819 int32_t APD = container->PresContext()->AppUnitsPerDevPixel();
3820 parent = nsLayoutUtils::GetCrossDocParentFrameInProcess(container,
3821 &extraOffset);
3822 if (parent) {
3823 int32_t parentAPD = parent->PresContext()->AppUnitsPerDevPixel();
3824 rect = rect.ScaleToOtherAppUnitsRoundOut(APD, parentAPD);
3825 rect += extraOffset;
3826 } else {
3827 nsCOMPtr<nsIDocShell> docShell =
3828 container->PresContext()->GetDocShell();
3829 if (BrowserChild* browserChild = BrowserChild::GetFrom(docShell)) {
3830 // Defer to the parent document if this is an out-of-process iframe.
3831 Unused << browserChild->SendScrollRectIntoView(
3832 rect, aVertical, aHorizontal, aScrollFlags, APD);
3836 container = parent;
3837 } while (container);
3839 return didScroll;
3842 void PresShell::ScheduleViewManagerFlush() {
3843 if (MOZ_UNLIKELY(mIsDestroying)) {
3844 return;
3847 nsPresContext* presContext = GetPresContext();
3848 if (presContext) {
3849 presContext->RefreshDriver()->ScheduleViewManagerFlush();
3851 SetNeedLayoutFlush();
3854 void PresShell::DispatchSynthMouseMove(WidgetGUIEvent* aEvent) {
3855 AUTO_PROFILER_TRACING_MARKER_DOCSHELL("Paint", "DispatchSynthMouseMove",
3856 GRAPHICS, mPresContext->GetDocShell());
3857 nsEventStatus status = nsEventStatus_eIgnore;
3858 nsView* targetView = nsView::GetViewFor(aEvent->mWidget);
3859 if (!targetView) return;
3860 RefPtr<nsViewManager> viewManager = targetView->GetViewManager();
3861 viewManager->DispatchEvent(aEvent, targetView, &status);
3864 void PresShell::ClearMouseCaptureOnView(nsView* aView) {
3865 if (nsIContent* capturingContent = GetCapturingContent()) {
3866 if (aView) {
3867 // if a view was specified, ensure that the captured content is within
3868 // this view.
3869 nsIFrame* frame = capturingContent->GetPrimaryFrame();
3870 if (frame) {
3871 nsView* view = frame->GetClosestView();
3872 // if there is no view, capturing won't be handled any more, so
3873 // just release the capture.
3874 if (view) {
3875 do {
3876 if (view == aView) {
3877 ReleaseCapturingContent();
3878 // the view containing the captured content likely disappeared so
3879 // disable capture for now.
3880 AllowMouseCapture(false);
3881 break;
3884 view = view->GetParent();
3885 } while (view);
3886 // return if the view wasn't found
3887 return;
3892 ReleaseCapturingContent();
3895 // disable mouse capture until the next mousedown as a dialog has opened
3896 // or a drag has started. Otherwise, someone could start capture during
3897 // the modal dialog or drag.
3898 AllowMouseCapture(false);
3901 void PresShell::ClearMouseCapture() {
3902 ReleaseCapturingContent();
3903 AllowMouseCapture(false);
3906 void PresShell::ClearMouseCapture(nsIFrame* aFrame) {
3907 MOZ_ASSERT(aFrame);
3909 nsIContent* capturingContent = GetCapturingContent();
3910 if (!capturingContent) {
3911 return;
3914 nsIFrame* capturingFrame = capturingContent->GetPrimaryFrame();
3915 const bool shouldClear =
3916 !capturingFrame ||
3917 nsLayoutUtils::IsAncestorFrameCrossDocInProcess(aFrame, capturingFrame);
3918 if (shouldClear) {
3919 ClearMouseCapture();
3923 nsresult PresShell::CaptureHistoryState(nsILayoutHistoryState** aState) {
3924 MOZ_ASSERT(nullptr != aState, "null state pointer");
3926 // We actually have to mess with the docshell here, since we want to
3927 // store the state back in it.
3928 // XXXbz this isn't really right, since this is being called in the
3929 // content viewer's Hide() method... by that point the docshell's
3930 // state could be wrong. We should sort out a better ownership
3931 // model for the layout history state.
3932 nsCOMPtr<nsIDocShell> docShell(mPresContext->GetDocShell());
3933 if (!docShell) return NS_ERROR_FAILURE;
3935 nsCOMPtr<nsILayoutHistoryState> historyState;
3936 docShell->GetLayoutHistoryState(getter_AddRefs(historyState));
3937 if (!historyState) {
3938 // Create the document state object
3939 historyState = NS_NewLayoutHistoryState();
3940 docShell->SetLayoutHistoryState(historyState);
3943 *aState = historyState;
3944 NS_IF_ADDREF(*aState);
3946 // Capture frame state for the entire frame hierarchy
3947 nsIFrame* rootFrame = mFrameConstructor->GetRootFrame();
3948 if (!rootFrame) return NS_OK;
3950 mFrameConstructor->CaptureFrameState(rootFrame, historyState);
3952 return NS_OK;
3955 void PresShell::ScheduleBeforeFirstPaint() {
3956 if (!mDocument->IsResourceDoc()) {
3957 // Notify observers that a new page is about to be drawn. Execute this
3958 // as soon as it is safe to run JS, which is guaranteed to be before we
3959 // go back to the event loop and actually draw the page.
3960 MOZ_LOG(gLog, LogLevel::Debug,
3961 ("PresShell::ScheduleBeforeFirstPaint this=%p", this));
3963 nsContentUtils::AddScriptRunner(
3964 new nsBeforeFirstPaintDispatcher(mDocument));
3968 void PresShell::UnsuppressAndInvalidate() {
3969 // Note: We ignore the EnsureVisible check for resource documents, because
3970 // they won't have a docshell, so they'll always fail EnsureVisible.
3971 if ((!mDocument->IsResourceDoc() && !mPresContext->EnsureVisible()) ||
3972 mHaveShutDown) {
3973 // No point; we're about to be torn down anyway.
3974 return;
3977 ScheduleBeforeFirstPaint();
3979 PROFILER_MARKER_UNTYPED("UnsuppressAndInvalidate", GRAPHICS);
3981 mPaintingSuppressed = false;
3982 if (nsIFrame* rootFrame = mFrameConstructor->GetRootFrame()) {
3983 // let's assume that outline on a root frame is not supported
3984 rootFrame->InvalidateFrame();
3987 if (mPresContext->IsRootContentDocumentCrossProcess()) {
3988 if (auto* bc = BrowserChild::GetFrom(mDocument->GetDocShell())) {
3989 if (mDocument->IsInitialDocument()) {
3990 bc->SendDidUnsuppressPaintingNormalPriority();
3991 } else {
3992 bc->SendDidUnsuppressPainting();
3997 // now that painting is unsuppressed, focus may be set on the document
3998 if (nsPIDOMWindowOuter* win = mDocument->GetWindow()) {
3999 win->SetReadyForFocus();
4002 if (!mHaveShutDown) {
4003 SynthesizeMouseMove(false);
4004 ScheduleApproximateFrameVisibilityUpdateNow();
4008 void PresShell::CancelPaintSuppressionTimer() {
4009 if (mPaintSuppressionTimer) {
4010 mPaintSuppressionTimer->Cancel();
4011 mPaintSuppressionTimer = nullptr;
4015 void PresShell::UnsuppressPainting() {
4016 CancelPaintSuppressionTimer();
4018 if (mIsDocumentGone || !mPaintingSuppressed) {
4019 return;
4022 // If we have reflows pending, just wait until we process
4023 // the reflows and get all the frames where we want them
4024 // before actually unlocking the painting. Otherwise
4025 // go ahead and unlock now.
4026 if (!mDirtyRoots.IsEmpty())
4027 mShouldUnsuppressPainting = true;
4028 else
4029 UnsuppressAndInvalidate();
4032 // Post a request to handle an arbitrary callback after reflow has finished.
4033 nsresult PresShell::PostReflowCallback(nsIReflowCallback* aCallback) {
4034 void* result = AllocateByObjectID(eArenaObjectID_nsCallbackEventRequest,
4035 sizeof(nsCallbackEventRequest));
4036 nsCallbackEventRequest* request = (nsCallbackEventRequest*)result;
4038 request->callback = aCallback;
4039 request->next = nullptr;
4041 if (mLastCallbackEventRequest) {
4042 mLastCallbackEventRequest = mLastCallbackEventRequest->next = request;
4043 } else {
4044 mFirstCallbackEventRequest = request;
4045 mLastCallbackEventRequest = request;
4048 return NS_OK;
4051 void PresShell::CancelReflowCallback(nsIReflowCallback* aCallback) {
4052 nsCallbackEventRequest* before = nullptr;
4053 nsCallbackEventRequest* node = mFirstCallbackEventRequest;
4054 while (node) {
4055 nsIReflowCallback* callback = node->callback;
4057 if (callback == aCallback) {
4058 nsCallbackEventRequest* toFree = node;
4059 if (node == mFirstCallbackEventRequest) {
4060 node = node->next;
4061 mFirstCallbackEventRequest = node;
4062 NS_ASSERTION(before == nullptr, "impossible");
4063 } else {
4064 node = node->next;
4065 before->next = node;
4068 if (toFree == mLastCallbackEventRequest) {
4069 mLastCallbackEventRequest = before;
4072 FreeByObjectID(eArenaObjectID_nsCallbackEventRequest, toFree);
4073 } else {
4074 before = node;
4075 node = node->next;
4080 void PresShell::CancelPostedReflowCallbacks() {
4081 while (mFirstCallbackEventRequest) {
4082 nsCallbackEventRequest* node = mFirstCallbackEventRequest;
4083 mFirstCallbackEventRequest = node->next;
4084 if (!mFirstCallbackEventRequest) {
4085 mLastCallbackEventRequest = nullptr;
4087 nsIReflowCallback* callback = node->callback;
4088 FreeByObjectID(eArenaObjectID_nsCallbackEventRequest, node);
4089 if (callback) {
4090 callback->ReflowCallbackCanceled();
4095 void PresShell::HandlePostedReflowCallbacks(bool aInterruptible) {
4096 while (true) {
4097 // Call all our callbacks, tell us if we need to flush again.
4098 bool shouldFlush = false;
4099 while (mFirstCallbackEventRequest) {
4100 nsCallbackEventRequest* node = mFirstCallbackEventRequest;
4101 mFirstCallbackEventRequest = node->next;
4102 if (!mFirstCallbackEventRequest) {
4103 mLastCallbackEventRequest = nullptr;
4105 nsIReflowCallback* callback = node->callback;
4106 FreeByObjectID(eArenaObjectID_nsCallbackEventRequest, node);
4107 if (callback && callback->ReflowFinished()) {
4108 shouldFlush = true;
4112 if (!shouldFlush || mIsDestroying) {
4113 return;
4116 // The flush might cause us to have more callbacks.
4117 const auto flushType =
4118 aInterruptible ? FlushType::InterruptibleLayout : FlushType::Layout;
4119 FlushPendingNotifications(flushType);
4123 bool PresShell::IsSafeToFlush() const {
4124 // Not safe if we are getting torn down, reflowing, or in the middle of frame
4125 // construction.
4126 if (mIsReflowing || mChangeNestCount || mIsDestroying) {
4127 return false;
4130 // Not safe if we are painting
4131 if (nsViewManager* viewManager = GetViewManager()) {
4132 bool isPainting = false;
4133 viewManager->IsPainting(isPainting);
4134 if (isPainting) {
4135 return false;
4139 return true;
4142 void PresShell::NotifyFontFaceSetOnRefresh() {
4143 if (FontFaceSet* set = mDocument->GetFonts()) {
4144 set->DidRefresh();
4148 void PresShell::DoFlushPendingNotifications(FlushType aType) {
4149 // by default, flush animations if aType >= FlushType::Style
4150 mozilla::ChangesToFlush flush(aType, aType >= FlushType::Style);
4151 FlushPendingNotifications(flush);
4154 #ifdef DEBUG
4155 static void AssertFrameSubtreeIsSane(const nsIFrame& aRoot) {
4156 if (const nsIContent* content = aRoot.GetContent()) {
4157 MOZ_ASSERT(content->GetFlattenedTreeParentNodeForStyle(),
4158 "Node not in the flattened tree still has a frame?");
4161 for (const auto& childList : aRoot.ChildLists()) {
4162 for (const nsIFrame* child : childList.mList) {
4163 AssertFrameSubtreeIsSane(*child);
4167 #endif
4169 static inline void AssertFrameTreeIsSane(const PresShell& aPresShell) {
4170 #ifdef DEBUG
4171 if (const nsIFrame* root = aPresShell.GetRootFrame()) {
4172 AssertFrameSubtreeIsSane(*root);
4174 #endif
4177 static void TriggerPendingScrollTimelineAnimations(Document* aDocument) {
4178 auto* tracker = aDocument->GetScrollTimelineAnimationTracker();
4179 if (!tracker || !tracker->HasPendingAnimations()) {
4180 return;
4182 tracker->TriggerPendingAnimations();
4185 void PresShell::DoFlushPendingNotifications(mozilla::ChangesToFlush aFlush) {
4186 // FIXME(emilio, bug 1530177): Turn into a release assert when bug 1530188 and
4187 // bug 1530190 are fixed.
4188 MOZ_DIAGNOSTIC_ASSERT(!mForbiddenToFlush, "This is bad!");
4190 // Per our API contract, hold a strong ref to ourselves until we return.
4191 RefPtr<PresShell> kungFuDeathGrip = this;
4194 * VERY IMPORTANT: If you add some sort of new flushing to this
4195 * method, make sure to add the relevant SetNeedLayoutFlush or
4196 * SetNeedStyleFlush calls on the shell.
4198 FlushType flushType = aFlush.mFlushType;
4200 // If this is a layout flush, first update the relevancy of any content
4201 // of elements with `content-visibility: auto` so that the values
4202 // returned from script queries are up-to-date.
4203 if (flushType >= mozilla::FlushType::Layout) {
4204 UpdateRelevancyOfContentVisibilityAutoFrames();
4207 MOZ_ASSERT(NeedFlush(flushType), "Why did we get called?");
4209 AUTO_PROFILER_MARKER_TEXT(
4210 "DoFlushPendingNotifications", LAYOUT,
4211 MarkerOptions(MarkerStack::Capture(), MarkerInnerWindowIdFromDocShell(
4212 mPresContext->GetDocShell())),
4213 nsDependentCString(kFlushTypeNames[flushType]));
4214 AUTO_PROFILER_LABEL_DYNAMIC_CSTR_NONSENSITIVE(
4215 "PresShell::DoFlushPendingNotifications", LAYOUT,
4216 kFlushTypeNames[flushType]);
4218 #ifdef ACCESSIBILITY
4219 # ifdef DEBUG
4220 if (nsAccessibilityService* accService = GetAccService()) {
4221 NS_ASSERTION(!accService->IsProcessingRefreshDriverNotification(),
4222 "Flush during accessible tree update!");
4224 # endif
4225 #endif
4227 NS_ASSERTION(flushType >= FlushType::Style, "Why did we get called?");
4229 mNeedStyleFlush = false;
4230 mNeedThrottledAnimationFlush =
4231 mNeedThrottledAnimationFlush && !aFlush.mFlushAnimations;
4232 mNeedLayoutFlush =
4233 mNeedLayoutFlush && (flushType < FlushType::InterruptibleLayout);
4235 bool isSafeToFlush = IsSafeToFlush();
4237 // If layout could possibly trigger scripts, then it's only safe to flush if
4238 // it's safe to run script.
4239 bool hasHadScriptObject;
4240 if (mDocument->GetScriptHandlingObject(hasHadScriptObject) ||
4241 hasHadScriptObject) {
4242 isSafeToFlush = isSafeToFlush && nsContentUtils::IsSafeToRunScript();
4245 // Don't flush if the doc is already in the bfcache.
4246 if (MOZ_UNLIKELY(mDocument->GetPresShell() != this)) {
4247 MOZ_DIAGNOSTIC_ASSERT(!mDocument->GetPresShell(),
4248 "Where did this shell come from?");
4249 isSafeToFlush = false;
4252 MOZ_DIAGNOSTIC_ASSERT(!mIsDestroying || !isSafeToFlush);
4253 MOZ_DIAGNOSTIC_ASSERT(mIsDestroying || mViewManager);
4254 MOZ_DIAGNOSTIC_ASSERT(mIsDestroying || mDocument->HasShellOrBFCacheEntry());
4256 // Make sure the view manager stays alive.
4257 RefPtr<nsViewManager> viewManager = mViewManager;
4258 bool didStyleFlush = false;
4259 bool didLayoutFlush = false;
4260 if (isSafeToFlush) {
4261 // Record that we are in a flush, so that our optimization in
4262 // Document::FlushPendingNotifications doesn't skip any re-entrant
4263 // calls to us. Otherwise, we might miss some needed flushes, since
4264 // we clear mNeedStyleFlush / mNeedLayoutFlush here at the top of
4265 // the function but we might not have done the work yet.
4266 AutoRestore<bool> guard(mInFlush);
4267 mInFlush = true;
4269 // We need to make sure external resource documents are flushed too (for
4270 // example, svg filters that reference a filter in an external document
4271 // need the frames in the external document to be constructed for the
4272 // filter to work). We only need external resources to be flushed when the
4273 // main document is flushing >= FlushType::Frames, so we flush external
4274 // resources here instead of Document::FlushPendingNotifications.
4275 mDocument->FlushExternalResources(flushType);
4277 // Force flushing of any pending content notifications that might have
4278 // queued up while our event was pending. That will ensure that we don't
4279 // construct frames for content right now that's still waiting to be
4280 // notified on,
4281 mDocument->FlushPendingNotifications(FlushType::ContentAndNotify);
4283 mDocument->UpdateSVGUseElementShadowTrees();
4285 // Ensure our preference sheets are up-to-date.
4286 UpdatePreferenceStyles();
4288 // Process pending restyles, since any flush of the presshell wants
4289 // up-to-date style data.
4290 if (MOZ_LIKELY(!mIsDestroying)) {
4291 viewManager->FlushDelayedResize();
4292 mPresContext->FlushPendingMediaFeatureValuesChanged();
4295 if (MOZ_LIKELY(!mIsDestroying)) {
4296 // Now that we have flushed media queries, update the rules before looking
4297 // up @font-face / @counter-style / @font-feature-values rules.
4298 StyleSet()->UpdateStylistIfNeeded();
4300 // Flush any pending update of the user font set, since that could
4301 // cause style changes (for updating ex/ch units, and to cause a
4302 // reflow).
4303 mDocument->FlushUserFontSet();
4305 mPresContext->FlushCounterStyles();
4307 mPresContext->FlushFontFeatureValues();
4309 mPresContext->FlushFontPaletteValues();
4311 // Flush any requested SMIL samples.
4312 if (mDocument->HasAnimationController()) {
4313 mDocument->GetAnimationController()->FlushResampleRequests();
4317 // The FlushResampleRequests() above flushed style changes.
4318 if (MOZ_LIKELY(!mIsDestroying) && aFlush.mFlushAnimations &&
4319 mPresContext->EffectCompositor()) {
4320 mPresContext->EffectCompositor()->PostRestyleForThrottledAnimations();
4323 // The FlushResampleRequests() above flushed style changes.
4324 if (MOZ_LIKELY(!mIsDestroying)) {
4325 nsAutoScriptBlocker scriptBlocker;
4326 Maybe<uint64_t> innerWindowID;
4327 if (auto* window = mDocument->GetInnerWindow()) {
4328 innerWindowID = Some(window->WindowID());
4330 AutoProfilerStyleMarker tracingStyleFlush(std::move(mStyleCause),
4331 innerWindowID);
4332 PerfStats::AutoMetricRecording<PerfStats::Metric::Styling> autoRecording;
4333 LAYOUT_TELEMETRY_RECORD_BASE(Restyle);
4335 mPresContext->RestyleManager()->ProcessPendingRestyles();
4336 mNeedStyleFlush = false;
4339 AssertFrameTreeIsSane(*this);
4341 didStyleFlush = true;
4343 // There might be more pending constructors now, but we're not going to
4344 // worry about them. They can't be triggered during reflow, so we should
4345 // be good.
4347 if (flushType >= (SuppressInterruptibleReflows()
4348 ? FlushType::Layout
4349 : FlushType::InterruptibleLayout) &&
4350 !mIsDestroying) {
4351 didLayoutFlush = true;
4352 if (DoFlushLayout(/* aInterruptible = */ flushType < FlushType::Layout)) {
4353 if (mContentToScrollTo) {
4354 DoScrollContentIntoView();
4355 if (mContentToScrollTo) {
4356 mContentToScrollTo->RemoveProperty(nsGkAtoms::scrolling);
4357 mContentToScrollTo = nullptr;
4363 FlushPendingScrollResnap();
4365 if (MOZ_LIKELY(!mIsDestroying)) {
4366 // Try to trigger pending scroll-driven animations after we flush
4367 // style and layout (if any). If we try to trigger them after flushing
4368 // style but the frame tree is not ready, we will check them again after
4369 // we flush layout because the requirement to trigger scroll-driven
4370 // animations is that the associated scroll containers are ready (i.e. the
4371 // scroll-timeline is active), and this depends on the readiness of the
4372 // scrollable frame and the primary frame of the scroll container.
4373 TriggerPendingScrollTimelineAnimations(mDocument);
4376 if (flushType >= FlushType::Layout) {
4377 if (!mIsDestroying) {
4378 viewManager->UpdateWidgetGeometry();
4383 if (!didStyleFlush && flushType >= FlushType::Style && !mIsDestroying) {
4384 SetNeedStyleFlush();
4385 if (aFlush.mFlushAnimations) {
4386 SetNeedThrottledAnimationFlush();
4390 if (!didLayoutFlush && flushType >= FlushType::InterruptibleLayout &&
4391 !mIsDestroying) {
4392 // We suppressed this flush either due to it not being safe to flush,
4393 // or due to SuppressInterruptibleReflows(). Either way, the
4394 // mNeedLayoutFlush flag needs to be re-set.
4395 SetNeedLayoutFlush();
4398 // Update flush counters
4399 if (didStyleFlush) {
4400 mLayoutTelemetry.IncReqsPerFlush(FlushType::Style);
4403 if (didLayoutFlush) {
4404 mLayoutTelemetry.IncReqsPerFlush(FlushType::Layout);
4407 // Record telemetry for the number of requests per each flush type.
4409 // Flushes happen as style or style+layout. This depends upon the `flushType`
4410 // where flushType >= InterruptibleLayout means flush layout and flushType >=
4411 // Style means flush style. We only report if didLayoutFlush or didStyleFlush
4412 // is true because we care if a flush really did take place. (Flush is guarded
4413 // by `isSafeToFlush == true`.)
4414 if (flushType >= FlushType::InterruptibleLayout && didLayoutFlush) {
4415 MOZ_ASSERT(didLayoutFlush == didStyleFlush);
4416 mLayoutTelemetry.PingReqsPerFlushTelemetry(FlushType::Layout);
4417 } else if (flushType >= FlushType::Style && didStyleFlush) {
4418 MOZ_ASSERT(!didLayoutFlush);
4419 mLayoutTelemetry.PingReqsPerFlushTelemetry(FlushType::Style);
4423 MOZ_CAN_RUN_SCRIPT_BOUNDARY void PresShell::CharacterDataChanged(
4424 nsIContent* aContent, const CharacterDataChangeInfo& aInfo) {
4425 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript());
4426 MOZ_ASSERT(!mIsDocumentGone, "Unexpected CharacterDataChanged");
4427 MOZ_ASSERT(aContent->OwnerDoc() == mDocument, "Unexpected document");
4429 nsAutoCauseReflowNotifier crNotifier(this);
4431 mPresContext->RestyleManager()->CharacterDataChanged(aContent, aInfo);
4432 mFrameConstructor->CharacterDataChanged(aContent, aInfo);
4435 MOZ_CAN_RUN_SCRIPT_BOUNDARY void PresShell::ElementStateChanged(
4436 Document* aDocument, Element* aElement, ElementState aStateMask) {
4437 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript());
4438 MOZ_ASSERT(!mIsDocumentGone, "Unexpected ContentStateChanged");
4439 MOZ_ASSERT(aDocument == mDocument, "Unexpected aDocument");
4441 if (mDidInitialize) {
4442 nsAutoCauseReflowNotifier crNotifier(this);
4443 mPresContext->RestyleManager()->ElementStateChanged(aElement, aStateMask);
4447 void PresShell::DocumentStatesChanged(DocumentState aStateMask) {
4448 MOZ_ASSERT(!mIsDocumentGone, "Unexpected DocumentStatesChanged");
4449 MOZ_ASSERT(mDocument);
4450 MOZ_ASSERT(!aStateMask.IsEmpty());
4452 if (mDidInitialize) {
4453 StyleSet()->InvalidateStyleForDocumentStateChanges(aStateMask);
4456 if (aStateMask.HasState(DocumentState::WINDOW_INACTIVE)) {
4457 if (nsIFrame* root = mFrameConstructor->GetRootFrame()) {
4458 root->SchedulePaint();
4463 MOZ_CAN_RUN_SCRIPT_BOUNDARY void PresShell::AttributeWillChange(
4464 Element* aElement, int32_t aNameSpaceID, nsAtom* aAttribute,
4465 int32_t aModType) {
4466 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript());
4467 MOZ_ASSERT(!mIsDocumentGone, "Unexpected AttributeWillChange");
4468 MOZ_ASSERT(aElement->OwnerDoc() == mDocument, "Unexpected document");
4470 // XXXwaterson it might be more elegant to wait until after the
4471 // initial reflow to begin observing the document. That would
4472 // squelch any other inappropriate notifications as well.
4473 if (mDidInitialize) {
4474 nsAutoCauseReflowNotifier crNotifier(this);
4475 mPresContext->RestyleManager()->AttributeWillChange(aElement, aNameSpaceID,
4476 aAttribute, aModType);
4480 MOZ_CAN_RUN_SCRIPT_BOUNDARY void PresShell::AttributeChanged(
4481 Element* aElement, int32_t aNameSpaceID, nsAtom* aAttribute,
4482 int32_t aModType, const nsAttrValue* aOldValue) {
4483 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript());
4484 MOZ_ASSERT(!mIsDocumentGone, "Unexpected AttributeChanged");
4485 MOZ_ASSERT(aElement->OwnerDoc() == mDocument, "Unexpected document");
4487 // XXXwaterson it might be more elegant to wait until after the
4488 // initial reflow to begin observing the document. That would
4489 // squelch any other inappropriate notifications as well.
4490 if (mDidInitialize) {
4491 nsAutoCauseReflowNotifier crNotifier(this);
4492 mPresContext->RestyleManager()->AttributeChanged(
4493 aElement, aNameSpaceID, aAttribute, aModType, aOldValue);
4497 MOZ_CAN_RUN_SCRIPT_BOUNDARY void PresShell::ContentAppended(
4498 nsIContent* aFirstNewContent) {
4499 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript());
4500 MOZ_ASSERT(!mIsDocumentGone, "Unexpected ContentAppended");
4501 MOZ_ASSERT(aFirstNewContent->OwnerDoc() == mDocument, "Unexpected document");
4503 // We never call ContentAppended with a document as the container, so we can
4504 // assert that we have an nsIContent parent.
4505 MOZ_ASSERT(aFirstNewContent->GetParent());
4506 MOZ_ASSERT(aFirstNewContent->GetParent()->IsElement() ||
4507 aFirstNewContent->GetParent()->IsShadowRoot());
4509 if (!mDidInitialize) {
4510 return;
4513 nsAutoCauseReflowNotifier crNotifier(this);
4515 // Call this here so it only happens for real content mutations and
4516 // not cases when the frame constructor calls its own methods to force
4517 // frame reconstruction.
4518 mPresContext->RestyleManager()->ContentAppended(aFirstNewContent);
4520 mFrameConstructor->ContentAppended(
4521 aFirstNewContent, nsCSSFrameConstructor::InsertionKind::Async);
4524 MOZ_CAN_RUN_SCRIPT_BOUNDARY void PresShell::ContentInserted(
4525 nsIContent* aChild) {
4526 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript());
4527 MOZ_ASSERT(!mIsDocumentGone, "Unexpected ContentInserted");
4528 MOZ_ASSERT(aChild->OwnerDoc() == mDocument, "Unexpected document");
4530 if (!mDidInitialize) {
4531 return;
4534 nsAutoCauseReflowNotifier crNotifier(this);
4536 // Call this here so it only happens for real content mutations and
4537 // not cases when the frame constructor calls its own methods to force
4538 // frame reconstruction.
4539 mPresContext->RestyleManager()->ContentInserted(aChild);
4541 mFrameConstructor->ContentInserted(
4542 aChild, nsCSSFrameConstructor::InsertionKind::Async);
4545 MOZ_CAN_RUN_SCRIPT_BOUNDARY void PresShell::ContentRemoved(
4546 nsIContent* aChild, nsIContent* aPreviousSibling) {
4547 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript());
4548 MOZ_ASSERT(!mIsDocumentGone, "Unexpected ContentRemoved");
4549 MOZ_ASSERT(aChild->OwnerDoc() == mDocument, "Unexpected document");
4550 nsINode* container = aChild->GetParentNode();
4552 // Notify the ESM that the content has been removed, so that
4553 // it can clean up any state related to the content.
4555 mPresContext->EventStateManager()->ContentRemoved(mDocument, aChild);
4557 nsAutoCauseReflowNotifier crNotifier(this);
4559 // Call this here so it only happens for real content mutations and
4560 // not cases when the frame constructor calls its own methods to force
4561 // frame reconstruction.
4562 nsIContent* oldNextSibling = nullptr;
4564 // Editor calls into here with NAC via HTMLEditor::DeleteRefToAnonymousNode.
4565 // This could be asserted if that caller is fixed.
4566 if (MOZ_LIKELY(!aChild->IsRootOfNativeAnonymousSubtree())) {
4567 oldNextSibling = aPreviousSibling ? aPreviousSibling->GetNextSibling()
4568 : container->GetFirstChild();
4571 // After removing aChild from tree we should save information about live
4572 // ancestor
4573 if (mPointerEventTarget &&
4574 mPointerEventTarget->IsInclusiveDescendantOf(aChild)) {
4575 mPointerEventTarget = aChild->GetParent();
4578 mFrameConstructor->ContentRemoved(aChild, oldNextSibling,
4579 nsCSSFrameConstructor::REMOVE_CONTENT);
4581 // NOTE(emilio): It's important that this goes after the frame constructor
4582 // stuff, otherwise the frame constructor can't see elements which are
4583 // display: contents / display: none, because we'd have cleared all the style
4584 // data from there.
4585 mPresContext->RestyleManager()->ContentRemoved(aChild, oldNextSibling);
4588 void PresShell::NotifyCounterStylesAreDirty() {
4589 // TODO: Looks like that nsFrameConstructor::NotifyCounterStylesAreDirty()
4590 // does not run script. If so, we don't need to block script with
4591 // nsAutoCauseReflowNotifier here. Instead, there should be methods
4592 // and stack only class which manages only mChangeNestCount for
4593 // avoiding unnecessary `MOZ_CAN_RUN_SCRIPT` marking.
4594 nsAutoCauseReflowNotifier reflowNotifier(this);
4595 mFrameConstructor->NotifyCounterStylesAreDirty();
4598 bool PresShell::FrameIsAncestorOfDirtyRoot(nsIFrame* aFrame) const {
4599 return mDirtyRoots.FrameIsAncestorOfAnyElement(aFrame);
4602 void PresShell::ReconstructFrames() {
4603 MOZ_ASSERT(!mFrameConstructor->GetRootFrame() || mDidInitialize,
4604 "Must not have root frame before initial reflow");
4605 if (!mDidInitialize || mIsDestroying) {
4606 // Nothing to do here
4607 return;
4610 if (Element* root = mDocument->GetRootElement()) {
4611 PostRecreateFramesFor(root);
4614 mDocument->FlushPendingNotifications(FlushType::Frames);
4617 nsresult PresShell::RenderDocument(const nsRect& aRect,
4618 RenderDocumentFlags aFlags,
4619 nscolor aBackgroundColor,
4620 gfxContext* aThebesContext) {
4621 NS_ENSURE_TRUE(!(aFlags & RenderDocumentFlags::IsUntrusted),
4622 NS_ERROR_NOT_IMPLEMENTED);
4624 nsRootPresContext* rootPresContext = mPresContext->GetRootPresContext();
4625 if (rootPresContext) {
4626 rootPresContext->FlushWillPaintObservers();
4627 if (mIsDestroying) return NS_OK;
4630 nsAutoScriptBlocker blockScripts;
4632 // Set up the rectangle as the path in aThebesContext
4633 gfxRect r(0, 0, nsPresContext::AppUnitsToFloatCSSPixels(aRect.width),
4634 nsPresContext::AppUnitsToFloatCSSPixels(aRect.height));
4635 aThebesContext->NewPath();
4636 #ifdef MOZ_GFX_OPTIMIZE_MOBILE
4637 aThebesContext->SnappedRectangle(r);
4638 #else
4639 aThebesContext->Rectangle(r);
4640 #endif
4642 nsIFrame* rootFrame = mFrameConstructor->GetRootFrame();
4643 if (!rootFrame) {
4644 // Nothing to paint, just fill the rect
4645 aThebesContext->SetColor(sRGBColor::FromABGR(aBackgroundColor));
4646 aThebesContext->Fill();
4647 return NS_OK;
4650 gfxContextAutoSaveRestore save(aThebesContext);
4652 MOZ_ASSERT(aThebesContext->CurrentOp() == CompositionOp::OP_OVER);
4654 aThebesContext->Clip();
4656 nsDeviceContext* devCtx = mPresContext->DeviceContext();
4658 gfxPoint offset(-nsPresContext::AppUnitsToFloatCSSPixels(aRect.x),
4659 -nsPresContext::AppUnitsToFloatCSSPixels(aRect.y));
4660 gfxFloat scale =
4661 gfxFloat(devCtx->AppUnitsPerDevPixel()) / AppUnitsPerCSSPixel();
4663 // Since canvas APIs use floats to set up their matrices, we may have some
4664 // slight rounding errors here. We use NudgeToIntegers() here to adjust
4665 // matrix components that are integers up to the accuracy of floats to be
4666 // those integers.
4667 gfxMatrix newTM = aThebesContext->CurrentMatrixDouble()
4668 .PreTranslate(offset)
4669 .PreScale(scale, scale)
4670 .NudgeToIntegers();
4671 aThebesContext->SetMatrixDouble(newTM);
4673 AutoSaveRestoreRenderingState _(this);
4675 bool wouldFlushRetainedLayers = false;
4676 PaintFrameFlags flags = PaintFrameFlags::IgnoreSuppression;
4677 if (aThebesContext->CurrentMatrix().HasNonIntegerTranslation()) {
4678 flags |= PaintFrameFlags::InTransform;
4680 if (!(aFlags & RenderDocumentFlags::AsyncDecodeImages)) {
4681 flags |= PaintFrameFlags::SyncDecodeImages;
4683 if (aFlags & RenderDocumentFlags::UseHighQualityScaling) {
4684 flags |= PaintFrameFlags::UseHighQualityScaling;
4686 if (aFlags & RenderDocumentFlags::UseWidgetLayers) {
4687 // We only support using widget layers on display root's with widgets.
4688 nsView* view = rootFrame->GetView();
4689 if (view && view->GetWidget() &&
4690 nsLayoutUtils::GetDisplayRootFrame(rootFrame) == rootFrame) {
4691 WindowRenderer* renderer = view->GetWidget()->GetWindowRenderer();
4692 // WebRenderLayerManagers in content processes
4693 // don't support taking snapshots.
4694 if (renderer &&
4695 (!renderer->AsKnowsCompositor() || XRE_IsParentProcess())) {
4696 flags |= PaintFrameFlags::WidgetLayers;
4700 if (!(aFlags & RenderDocumentFlags::DrawCaret)) {
4701 wouldFlushRetainedLayers = true;
4702 flags |= PaintFrameFlags::HideCaret;
4704 if (aFlags & RenderDocumentFlags::IgnoreViewportScrolling) {
4705 wouldFlushRetainedLayers = !IgnoringViewportScrolling();
4706 mRenderingStateFlags |= RenderingStateFlags::IgnoringViewportScrolling;
4708 if (aFlags & RenderDocumentFlags::ResetViewportScrolling) {
4709 wouldFlushRetainedLayers = true;
4710 flags |= PaintFrameFlags::ResetViewportScrolling;
4712 if (aFlags & RenderDocumentFlags::DrawWindowNotFlushing) {
4713 mRenderingStateFlags |= RenderingStateFlags::DrawWindowNotFlushing;
4715 if (aFlags & RenderDocumentFlags::DocumentRelative) {
4716 // XXX be smarter about this ... drawWindow might want a rect
4717 // that's "pretty close" to what our retained layer tree covers.
4718 // In that case, it wouldn't disturb normal rendering too much,
4719 // and we should allow it.
4720 wouldFlushRetainedLayers = true;
4721 flags |= PaintFrameFlags::DocumentRelative;
4724 // Don't let drawWindow blow away our retained layer tree
4725 if ((flags & PaintFrameFlags::WidgetLayers) && wouldFlushRetainedLayers) {
4726 flags &= ~PaintFrameFlags::WidgetLayers;
4729 nsLayoutUtils::PaintFrame(aThebesContext, rootFrame, nsRegion(aRect),
4730 aBackgroundColor,
4731 nsDisplayListBuilderMode::Painting, flags);
4733 return NS_OK;
4737 * Clip the display list aList to a range. Returns the clipped
4738 * rectangle surrounding the range.
4740 nsRect PresShell::ClipListToRange(nsDisplayListBuilder* aBuilder,
4741 nsDisplayList* aList, nsRange* aRange) {
4742 // iterate though the display items and add up the bounding boxes of each.
4743 // This will allow the total area of the frames within the range to be
4744 // determined. To do this, remove an item from the bottom of the list, check
4745 // whether it should be part of the range, and if so, append it to the top
4746 // of the temporary list tmpList. If the item is a text frame at the end of
4747 // the selection range, clip it to the portion of the text frame that is
4748 // part of the selection. Then, append the wrapper to the top of the list.
4749 // Otherwise, just delete the item and don't append it.
4750 nsRect surfaceRect;
4752 for (nsDisplayItem* i : aList->TakeItems()) {
4753 if (i->GetType() == DisplayItemType::TYPE_CONTAINER) {
4754 aList->AppendToTop(i);
4755 surfaceRect.UnionRect(
4756 surfaceRect, ClipListToRange(aBuilder, i->GetChildren(), aRange));
4757 continue;
4760 // itemToInsert indiciates the item that should be inserted into the
4761 // temporary list. If null, no item should be inserted.
4762 nsDisplayItem* itemToInsert = nullptr;
4763 nsIFrame* frame = i->Frame();
4764 nsIContent* content = frame->GetContent();
4765 if (content) {
4766 bool atStart = (content == aRange->GetStartContainer());
4767 bool atEnd = (content == aRange->GetEndContainer());
4768 if ((atStart || atEnd) && frame->IsTextFrame()) {
4769 auto [frameStartOffset, frameEndOffset] = frame->GetOffsets();
4771 int32_t hilightStart =
4772 atStart ? std::max(static_cast<int32_t>(aRange->StartOffset()),
4773 frameStartOffset)
4774 : frameStartOffset;
4775 int32_t hilightEnd =
4776 atEnd ? std::min(static_cast<int32_t>(aRange->EndOffset()),
4777 frameEndOffset)
4778 : frameEndOffset;
4779 if (hilightStart < hilightEnd) {
4780 // determine the location of the start and end edges of the range.
4781 nsPoint startPoint, endPoint;
4782 frame->GetPointFromOffset(hilightStart, &startPoint);
4783 frame->GetPointFromOffset(hilightEnd, &endPoint);
4785 // The clip rectangle is determined by taking the the start and
4786 // end points of the range, offset from the reference frame.
4787 // Because of rtl, the end point may be to the left of (or above,
4788 // in vertical mode) the start point, so x (or y) is set to the
4789 // lower of the values.
4790 nsRect textRect(aBuilder->ToReferenceFrame(frame), frame->GetSize());
4791 if (frame->GetWritingMode().IsVertical()) {
4792 nscoord y = std::min(startPoint.y, endPoint.y);
4793 textRect.y += y;
4794 textRect.height = std::max(startPoint.y, endPoint.y) - y;
4795 } else {
4796 nscoord x = std::min(startPoint.x, endPoint.x);
4797 textRect.x += x;
4798 textRect.width = std::max(startPoint.x, endPoint.x) - x;
4800 surfaceRect.UnionRect(surfaceRect, textRect);
4802 const ActiveScrolledRoot* asr = i->GetActiveScrolledRoot();
4804 DisplayItemClip newClip;
4805 newClip.SetTo(textRect);
4807 const DisplayItemClipChain* newClipChain =
4808 aBuilder->AllocateDisplayItemClipChain(newClip, asr, nullptr);
4810 i->IntersectClip(aBuilder, newClipChain, true);
4811 itemToInsert = i;
4814 // Don't try to descend into subdocuments.
4815 // If this ever changes we'd need to add handling for subdocuments with
4816 // different zoom levels.
4817 else if (content->GetUncomposedDoc() ==
4818 aRange->GetStartContainer()->GetUncomposedDoc()) {
4819 // if the node is within the range, append it to the temporary list
4820 bool before, after;
4821 nsresult rv =
4822 RangeUtils::CompareNodeToRange(content, aRange, &before, &after);
4823 if (NS_SUCCEEDED(rv) && !before && !after) {
4824 itemToInsert = i;
4825 bool snap;
4826 surfaceRect.UnionRect(surfaceRect, i->GetBounds(aBuilder, &snap));
4831 // insert the item into the list if necessary. If the item has a child
4832 // list, insert that as well
4833 nsDisplayList* sublist = i->GetSameCoordinateSystemChildren();
4834 if (itemToInsert || sublist) {
4835 aList->AppendToTop(itemToInsert ? itemToInsert : i);
4836 // if the item is a list, iterate over it as well
4837 if (sublist)
4838 surfaceRect.UnionRect(surfaceRect,
4839 ClipListToRange(aBuilder, sublist, aRange));
4840 } else {
4841 // otherwise, just delete the item and don't readd it to the list
4842 i->Destroy(aBuilder);
4846 return surfaceRect;
4849 #ifdef DEBUG
4850 # include <stdio.h>
4852 static bool gDumpRangePaintList = false;
4853 #endif
4855 UniquePtr<RangePaintInfo> PresShell::CreateRangePaintInfo(
4856 nsRange* aRange, nsRect& aSurfaceRect, bool aForPrimarySelection) {
4857 nsIFrame* ancestorFrame = nullptr;
4858 nsIFrame* rootFrame = GetRootFrame();
4860 // If the start or end of the range is the document, just use the root
4861 // frame, otherwise get the common ancestor of the two endpoints of the
4862 // range.
4863 nsINode* startContainer = aRange->GetStartContainer();
4864 nsINode* endContainer = aRange->GetEndContainer();
4865 Document* doc = startContainer->GetComposedDoc();
4866 if (startContainer == doc || endContainer == doc) {
4867 ancestorFrame = rootFrame;
4868 } else {
4869 nsINode* ancestor = nsContentUtils::GetClosestCommonInclusiveAncestor(
4870 startContainer, endContainer);
4871 NS_ASSERTION(!ancestor || ancestor->IsContent(),
4872 "common ancestor is not content");
4874 while (ancestor && ancestor->IsContent()) {
4875 ancestorFrame = ancestor->AsContent()->GetPrimaryFrame();
4876 if (ancestorFrame) {
4877 break;
4880 ancestor = ancestor->GetParentOrShadowHostNode();
4883 // use the nearest ancestor frame that includes all continuations as the
4884 // root for building the display list
4885 while (ancestorFrame &&
4886 nsLayoutUtils::GetNextContinuationOrIBSplitSibling(ancestorFrame))
4887 ancestorFrame = ancestorFrame->GetParent();
4890 if (!ancestorFrame) {
4891 return nullptr;
4894 // get a display list containing the range
4895 auto info = MakeUnique<RangePaintInfo>(aRange, ancestorFrame);
4896 info->mBuilder.SetIncludeAllOutOfFlows();
4897 if (aForPrimarySelection) {
4898 info->mBuilder.SetSelectedFramesOnly();
4900 info->mBuilder.EnterPresShell(ancestorFrame);
4902 ContentSubtreeIterator subtreeIter;
4903 nsresult rv = subtreeIter.Init(aRange);
4904 if (NS_FAILED(rv)) {
4905 return nullptr;
4908 auto BuildDisplayListForNode = [&](nsINode* aNode) {
4909 if (MOZ_UNLIKELY(!aNode->IsContent())) {
4910 return;
4912 nsIFrame* frame = aNode->AsContent()->GetPrimaryFrame();
4913 // XXX deal with frame being null due to display:contents
4914 for (; frame;
4915 frame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(frame)) {
4916 info->mBuilder.SetVisibleRect(frame->InkOverflowRect());
4917 info->mBuilder.SetDirtyRect(frame->InkOverflowRect());
4918 frame->BuildDisplayListForStackingContext(&info->mBuilder, &info->mList);
4921 if (startContainer->NodeType() == nsINode::TEXT_NODE) {
4922 BuildDisplayListForNode(startContainer);
4924 for (; !subtreeIter.IsDone(); subtreeIter.Next()) {
4925 nsCOMPtr<nsINode> node = subtreeIter.GetCurrentNode();
4926 BuildDisplayListForNode(node);
4928 if (endContainer != startContainer &&
4929 endContainer->NodeType() == nsINode::TEXT_NODE) {
4930 BuildDisplayListForNode(endContainer);
4933 // If one of the ancestor presShells (including this one) has a resolution
4934 // set, we may have some APZ zoom applied. That means we may want to rasterize
4935 // the nodes at that zoom level. Populate `info` with the relevant information
4936 // so that the caller can decide what to do. Also wrap the display list in
4937 // appropriate nsDisplayAsyncZoom display items. This code handles the general
4938 // case with nested async zooms (even though that never actually happens),
4939 // because it fell out of the implementation for free.
4941 // TODO: Do we need to do the same for ancestor transforms?
4942 for (nsPresContext* ctx = GetPresContext(); ctx;
4943 ctx = ctx->GetParentPresContext()) {
4944 PresShell* shell = ctx->PresShell();
4945 float resolution = shell->GetResolution();
4947 // If we are at the root document in the process, try to see if documents
4948 // in enclosing processes have a resolution and include that as well.
4949 if (!ctx->GetParentPresContext()) {
4950 // xScale is an arbitrary choice. Outside of edge cases involving CSS
4951 // transforms, xScale == yScale so it doesn't matter.
4952 resolution *= ViewportUtils::TryInferEnclosingResolution(shell).xScale;
4955 if (resolution == 1.0) {
4956 continue;
4959 info->mResolution *= resolution;
4960 nsIFrame* rootScrollFrame = shell->GetRootScrollFrame();
4961 ViewID zoomedId =
4962 nsLayoutUtils::FindOrCreateIDFor(rootScrollFrame->GetContent());
4964 nsDisplayList wrapped(&info->mBuilder);
4965 wrapped.AppendNewToTop<nsDisplayAsyncZoom>(&info->mBuilder, rootScrollFrame,
4966 &info->mList, nullptr, zoomedId);
4967 info->mList.AppendToTop(&wrapped);
4970 #ifdef DEBUG
4971 if (gDumpRangePaintList) {
4972 fprintf(stderr, "CreateRangePaintInfo --- before ClipListToRange:\n");
4973 nsIFrame::PrintDisplayList(&(info->mBuilder), info->mList);
4975 #endif
4977 nsRect rangeRect = ClipListToRange(&info->mBuilder, &info->mList, aRange);
4979 info->mBuilder.LeavePresShell(ancestorFrame, &info->mList);
4981 #ifdef DEBUG
4982 if (gDumpRangePaintList) {
4983 fprintf(stderr, "CreateRangePaintInfo --- after ClipListToRange:\n");
4984 nsIFrame::PrintDisplayList(&(info->mBuilder), info->mList);
4986 #endif
4988 // determine the offset of the reference frame for the display list
4989 // to the root frame. This will allow the coordinates used when painting
4990 // to all be offset from the same point
4991 info->mRootOffset = ancestorFrame->GetBoundingClientRect().TopLeft();
4992 rangeRect.MoveBy(info->mRootOffset);
4993 aSurfaceRect.UnionRect(aSurfaceRect, rangeRect);
4995 return info;
4998 already_AddRefed<SourceSurface> PresShell::PaintRangePaintInfo(
4999 const nsTArray<UniquePtr<RangePaintInfo>>& aItems, Selection* aSelection,
5000 const Maybe<CSSIntRegion>& aRegion, nsRect aArea,
5001 const LayoutDeviceIntPoint aPoint, LayoutDeviceIntRect* aScreenRect,
5002 RenderImageFlags aFlags) {
5003 nsPresContext* pc = GetPresContext();
5004 if (!pc || aArea.width == 0 || aArea.height == 0) return nullptr;
5006 // use the rectangle to create the surface
5007 LayoutDeviceIntRect pixelArea = LayoutDeviceIntRect::FromAppUnitsToOutside(
5008 aArea, pc->AppUnitsPerDevPixel());
5010 // if the image should not be resized, scale must be 1
5011 float scale = 1.0;
5013 nsRect maxSize;
5014 pc->DeviceContext()->GetClientRect(maxSize);
5016 // check if the image should be resized
5017 bool resize = !!(aFlags & RenderImageFlags::AutoScale);
5019 if (resize) {
5020 // check if image-resizing-algorithm should be used
5021 if (aFlags & RenderImageFlags::IsImage) {
5022 // get max screensize
5023 int32_t maxWidth = pc->AppUnitsToDevPixels(maxSize.width);
5024 int32_t maxHeight = pc->AppUnitsToDevPixels(maxSize.height);
5025 // resize image relative to the screensize
5026 // get best height/width relative to screensize
5027 float bestHeight = float(maxHeight) * RELATIVE_SCALEFACTOR;
5028 float bestWidth = float(maxWidth) * RELATIVE_SCALEFACTOR;
5029 // calculate scale for bestWidth
5030 float adjustedScale = bestWidth / float(pixelArea.width);
5031 // get the worst height (height when width is perfect)
5032 float worstHeight = float(pixelArea.height) * adjustedScale;
5033 // get the difference of best and worst height
5034 float difference = bestHeight - worstHeight;
5035 // halve the difference and add it to worstHeight to get
5036 // the best compromise between bestHeight and bestWidth,
5037 // then calculate the corresponding scale factor
5038 adjustedScale = (worstHeight + difference / 2) / float(pixelArea.height);
5039 // prevent upscaling
5040 scale = std::min(scale, adjustedScale);
5041 } else {
5042 // get half of max screensize
5043 int32_t maxWidth = pc->AppUnitsToDevPixels(maxSize.width >> 1);
5044 int32_t maxHeight = pc->AppUnitsToDevPixels(maxSize.height >> 1);
5045 if (pixelArea.width > maxWidth || pixelArea.height > maxHeight) {
5046 // divide the maximum size by the image size in both directions.
5047 // Whichever direction produces the smallest result determines how much
5048 // should be scaled.
5049 if (pixelArea.width > maxWidth)
5050 scale = std::min(scale, float(maxWidth) / pixelArea.width);
5051 if (pixelArea.height > maxHeight)
5052 scale = std::min(scale, float(maxHeight) / pixelArea.height);
5056 // Pick a resolution scale factor that is the highest we need for any of
5057 // the items. This means some items may get rendered at a higher-than-needed
5058 // resolution but at least nothing will be avoidably blurry.
5059 float resolutionScale = 1.0;
5060 for (const UniquePtr<RangePaintInfo>& rangeInfo : aItems) {
5061 resolutionScale = std::max(resolutionScale, rangeInfo->mResolution);
5063 float unclampedResolution = resolutionScale;
5064 // Clamp the resolution scale so that `pixelArea` when scaled by `scale` and
5065 // `resolutionScale` isn't bigger than `maxSize`. This prevents creating
5066 // giant/unbounded images.
5067 resolutionScale =
5068 std::min(resolutionScale, maxSize.width / (scale * pixelArea.width));
5069 resolutionScale =
5070 std::min(resolutionScale, maxSize.height / (scale * pixelArea.height));
5071 // The following assert should only get hit if pixelArea scaled by `scale`
5072 // alone would already have been bigger than `maxSize`, which should never
5073 // be the case. For release builds we handle gracefully by reverting
5074 // resolutionScale to 1.0 to avoid unexpected consequences.
5075 MOZ_ASSERT(resolutionScale >= 1.0);
5076 resolutionScale = std::max(1.0f, resolutionScale);
5078 scale *= resolutionScale;
5080 // Now we need adjust the output screen position of the surface based on the
5081 // scaling factor and any APZ zoom that may be in effect. The goal is here
5082 // to set `aScreenRect`'s top-left corner (in screen-relative LD pixels)
5083 // such that the scaling effect on the surface appears anchored at `aPoint`
5084 // ("anchor" here is like "transform-origin"). When this code is e.g. used
5085 // to generate a drag image for dragging operations, `aPoint` refers to the
5086 // position of the mouse cursor (also in screen-relative LD pixels), and the
5087 // user-visible effect of doing this is that the point at which the user
5088 // clicked to start the drag remains under the mouse during the drag.
5090 // In order to do this we first compute the top-left corner of the
5091 // pixelArea is screen-relative LD pixels.
5092 LayoutDevicePoint visualPoint = ViewportUtils::ToScreenRelativeVisual(
5093 LayoutDevicePoint(pixelArea.TopLeft()), pc);
5094 // And then adjust the output screen position based on that, which we can do
5095 // since everything here is screen-relative LD pixels. Note that the scale
5096 // factor we use here is the effective "transform" scale applied to the
5097 // content we're painting, relative to the scale at which it would normally
5098 // get painted at as part of page rendering (`unclampedResolution`).
5099 float scaleRelativeToNormalContent = scale / unclampedResolution;
5100 aScreenRect->x =
5101 NSToIntFloor(aPoint.x - float(aPoint.x.value - visualPoint.x.value) *
5102 scaleRelativeToNormalContent);
5103 aScreenRect->y =
5104 NSToIntFloor(aPoint.y - float(aPoint.y.value - visualPoint.y.value) *
5105 scaleRelativeToNormalContent);
5107 pixelArea.width = NSToIntFloor(float(pixelArea.width) * scale);
5108 pixelArea.height = NSToIntFloor(float(pixelArea.height) * scale);
5109 if (!pixelArea.width || !pixelArea.height) {
5110 return nullptr;
5112 } else {
5113 // move aScreenRect to the position of the surface in screen coordinates
5114 LayoutDevicePoint visualPoint = ViewportUtils::ToScreenRelativeVisual(
5115 LayoutDevicePoint(pixelArea.TopLeft()), pc);
5116 aScreenRect->MoveTo(RoundedToInt(visualPoint));
5118 aScreenRect->width = pixelArea.width;
5119 aScreenRect->height = pixelArea.height;
5121 RefPtr<DrawTarget> dt =
5122 gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
5123 IntSize(pixelArea.width, pixelArea.height), SurfaceFormat::B8G8R8A8);
5124 if (!dt || !dt->IsValid()) {
5125 return nullptr;
5128 gfxContext ctx(dt);
5130 if (aRegion) {
5131 RefPtr<PathBuilder> builder = dt->CreatePathBuilder(FillRule::FILL_WINDING);
5133 // Convert aRegion from CSS pixels to dev pixels
5134 nsIntRegion region = aRegion->ToAppUnits(AppUnitsPerCSSPixel())
5135 .ToOutsidePixels(pc->AppUnitsPerDevPixel());
5136 for (auto iter = region.RectIter(); !iter.Done(); iter.Next()) {
5137 const IntRect& rect = iter.Get();
5139 builder->MoveTo(rect.TopLeft());
5140 builder->LineTo(rect.TopRight());
5141 builder->LineTo(rect.BottomRight());
5142 builder->LineTo(rect.BottomLeft());
5143 builder->LineTo(rect.TopLeft());
5146 RefPtr<Path> path = builder->Finish();
5147 ctx.Clip(path);
5150 gfxMatrix initialTM = ctx.CurrentMatrixDouble();
5152 if (resize) {
5153 initialTM.PreScale(scale, scale);
5156 // translate so that points are relative to the surface area
5157 gfxPoint surfaceOffset = nsLayoutUtils::PointToGfxPoint(
5158 -aArea.TopLeft(), pc->AppUnitsPerDevPixel());
5159 initialTM.PreTranslate(surfaceOffset);
5161 // temporarily hide the selection so that text is drawn normally. If a
5162 // selection is being rendered, use that, otherwise use the presshell's
5163 // selection.
5164 RefPtr<nsFrameSelection> frameSelection;
5165 if (aSelection) {
5166 frameSelection = aSelection->GetFrameSelection();
5167 } else {
5168 frameSelection = FrameSelection();
5170 int16_t oldDisplaySelection = frameSelection->GetDisplaySelection();
5171 frameSelection->SetDisplaySelection(nsISelectionController::SELECTION_HIDDEN);
5173 // next, paint each range in the selection
5174 for (const UniquePtr<RangePaintInfo>& rangeInfo : aItems) {
5175 // the display lists paint relative to the offset from the reference
5176 // frame, so account for that translation too:
5177 gfxPoint rootOffset = nsLayoutUtils::PointToGfxPoint(
5178 rangeInfo->mRootOffset, pc->AppUnitsPerDevPixel());
5179 ctx.SetMatrixDouble(initialTM.PreTranslate(rootOffset));
5180 aArea.MoveBy(-rangeInfo->mRootOffset.x, -rangeInfo->mRootOffset.y);
5181 nsRegion visible(aArea);
5182 rangeInfo->mList.PaintRoot(&rangeInfo->mBuilder, &ctx,
5183 nsDisplayList::PAINT_DEFAULT, Nothing());
5184 aArea.MoveBy(rangeInfo->mRootOffset.x, rangeInfo->mRootOffset.y);
5187 // restore the old selection display state
5188 frameSelection->SetDisplaySelection(oldDisplaySelection);
5190 return dt->Snapshot();
5193 already_AddRefed<SourceSurface> PresShell::RenderNode(
5194 nsINode* aNode, const Maybe<CSSIntRegion>& aRegion,
5195 const LayoutDeviceIntPoint aPoint, LayoutDeviceIntRect* aScreenRect,
5196 RenderImageFlags aFlags) {
5197 // area will hold the size of the surface needed to draw the node, measured
5198 // from the root frame.
5199 nsRect area;
5200 nsTArray<UniquePtr<RangePaintInfo>> rangeItems;
5202 // nothing to draw if the node isn't in a document
5203 if (!aNode->IsInComposedDoc()) {
5204 return nullptr;
5207 RefPtr<nsRange> range = nsRange::Create(aNode);
5208 IgnoredErrorResult rv;
5209 range->SelectNode(*aNode, rv);
5210 if (rv.Failed()) {
5211 return nullptr;
5214 UniquePtr<RangePaintInfo> info = CreateRangePaintInfo(range, area, false);
5215 if (info) {
5216 // XXX(Bug 1631371) Check if this should use a fallible operation as it
5217 // pretended earlier, or change the return type to void.
5218 rangeItems.AppendElement(std::move(info));
5221 Maybe<CSSIntRegion> region = aRegion;
5222 if (region) {
5223 // combine the area with the supplied region
5224 CSSIntRect rrectPixels = region->GetBounds();
5226 nsRect rrect = ToAppUnits(rrectPixels, AppUnitsPerCSSPixel());
5227 area.IntersectRect(area, rrect);
5229 nsPresContext* pc = GetPresContext();
5230 if (!pc) return nullptr;
5232 // move the region so that it is offset from the topleft corner of the
5233 // surface
5234 region->MoveBy(-nsPresContext::AppUnitsToIntCSSPixels(area.x),
5235 -nsPresContext::AppUnitsToIntCSSPixels(area.y));
5238 return PaintRangePaintInfo(rangeItems, nullptr, region, area, aPoint,
5239 aScreenRect, aFlags);
5242 already_AddRefed<SourceSurface> PresShell::RenderSelection(
5243 Selection* aSelection, const LayoutDeviceIntPoint aPoint,
5244 LayoutDeviceIntRect* aScreenRect, RenderImageFlags aFlags) {
5245 // area will hold the size of the surface needed to draw the selection,
5246 // measured from the root frame.
5247 nsRect area;
5248 nsTArray<UniquePtr<RangePaintInfo>> rangeItems;
5250 // iterate over each range and collect them into the rangeItems array.
5251 // This is done so that the size of selection can be determined so as
5252 // to allocate a surface area
5253 const uint32_t rangeCount = aSelection->RangeCount();
5254 NS_ASSERTION(rangeCount > 0, "RenderSelection called with no selection");
5255 for (const uint32_t r : IntegerRange(rangeCount)) {
5256 MOZ_ASSERT(aSelection->RangeCount() == rangeCount);
5257 RefPtr<nsRange> range = aSelection->GetRangeAt(r);
5259 UniquePtr<RangePaintInfo> info = CreateRangePaintInfo(range, area, true);
5260 if (info) {
5261 // XXX(Bug 1631371) Check if this should use a fallible operation as it
5262 // pretended earlier.
5263 rangeItems.AppendElement(std::move(info));
5267 return PaintRangePaintInfo(rangeItems, aSelection, Nothing(), area, aPoint,
5268 aScreenRect, aFlags);
5271 void AddDisplayItemToBottom(nsDisplayListBuilder* aBuilder,
5272 nsDisplayList* aList, nsDisplayItem* aItem) {
5273 if (!aItem) {
5274 return;
5277 nsDisplayList list(aBuilder);
5278 list.AppendToTop(aItem);
5279 list.AppendToTop(aList);
5280 aList->AppendToTop(&list);
5283 void PresShell::AddPrintPreviewBackgroundItem(nsDisplayListBuilder* aBuilder,
5284 nsDisplayList* aList,
5285 nsIFrame* aFrame,
5286 const nsRect& aBounds) {
5287 nsDisplayItem* item = MakeDisplayItem<nsDisplaySolidColor>(
5288 aBuilder, aFrame, aBounds, NS_RGB(115, 115, 115));
5289 AddDisplayItemToBottom(aBuilder, aList, item);
5292 static bool AddCanvasBackgroundColor(const nsDisplayList* aList,
5293 nsIFrame* aCanvasFrame, nscolor aColor,
5294 bool aCSSBackgroundColor) {
5295 for (nsDisplayItem* i : *aList) {
5296 const DisplayItemType type = i->GetType();
5298 if (i->Frame() == aCanvasFrame &&
5299 type == DisplayItemType::TYPE_CANVAS_BACKGROUND_COLOR) {
5300 auto* bg = static_cast<nsDisplayCanvasBackgroundColor*>(i);
5301 bg->SetExtraBackgroundColor(aColor);
5302 return true;
5305 const bool isBlendContainer =
5306 type == DisplayItemType::TYPE_BLEND_CONTAINER ||
5307 type == DisplayItemType::TYPE_TABLE_BLEND_CONTAINER;
5309 nsDisplayList* sublist = i->GetSameCoordinateSystemChildren();
5310 if (sublist && !(isBlendContainer && !aCSSBackgroundColor) &&
5311 AddCanvasBackgroundColor(sublist, aCanvasFrame, aColor,
5312 aCSSBackgroundColor))
5313 return true;
5315 return false;
5318 void PresShell::AddCanvasBackgroundColorItem(
5319 nsDisplayListBuilder* aBuilder, nsDisplayList* aList, nsIFrame* aFrame,
5320 const nsRect& aBounds, nscolor aBackstopColor,
5321 AddCanvasBackgroundColorFlags aFlags) {
5322 if (aBounds.IsEmpty()) {
5323 return;
5325 // We don't want to add an item for the canvas background color if the frame
5326 // (sub)tree we are painting doesn't include any canvas frames. There isn't
5327 // an easy way to check this directly, but if we check if the root of the
5328 // (sub)tree we are painting is a canvas frame that should cover us in all
5329 // cases (it will usually be a viewport frame when we have a canvas frame in
5330 // the (sub)tree).
5331 if (!(aFlags & AddCanvasBackgroundColorFlags::ForceDraw) &&
5332 !aFrame->IsViewportFrame() && !aFrame->IsPageContentFrame()) {
5333 return;
5336 nscolor bgcolor = NS_ComposeColors(aBackstopColor, mCanvasBackgroundColor);
5337 if (NS_GET_A(bgcolor) == 0) return;
5339 // To make layers work better, we want to avoid having a big non-scrolled
5340 // color background behind a scrolled transparent background. Instead,
5341 // we'll try to move the color background into the scrolled content
5342 // by making nsDisplayCanvasBackground paint it.
5343 bool addedScrollingBackgroundColor = false;
5344 if (!aFrame->GetParent()) {
5345 nsIScrollableFrame* sf =
5346 aFrame->PresShell()->GetRootScrollFrameAsScrollable();
5347 if (sf) {
5348 nsCanvasFrame* canvasFrame = do_QueryFrame(sf->GetScrolledFrame());
5349 if (canvasFrame && canvasFrame->IsVisibleForPainting()) {
5350 // TODO: We should be able to set canvas background color during display
5351 // list building to avoid calling this function.
5352 addedScrollingBackgroundColor = AddCanvasBackgroundColor(
5353 aList, canvasFrame, bgcolor, mHasCSSBackgroundColor);
5358 // With async scrolling, we'd like to have two instances of the background
5359 // color: one that scrolls with the content (for the reasons stated above),
5360 // and one underneath which does not scroll with the content, but which can
5361 // be shown during checkerboarding and overscroll.
5362 // We can only do that if the color is opaque.
5363 bool forceUnscrolledItem =
5364 nsLayoutUtils::UsesAsyncScrolling(aFrame) && NS_GET_A(bgcolor) == 255;
5366 if (!addedScrollingBackgroundColor || forceUnscrolledItem) {
5367 nsDisplaySolidColor* item = MakeDisplayItem<nsDisplaySolidColor>(
5368 aBuilder, aFrame, aBounds, bgcolor);
5369 if (addedScrollingBackgroundColor &&
5370 mPresContext->IsRootContentDocumentCrossProcess()) {
5371 item->SetIsCheckerboardBackground();
5373 AddDisplayItemToBottom(aBuilder, aList, item);
5377 bool PresShell::IsTransparentContainerElement() const {
5378 nsPresContext* pc = GetPresContext();
5379 if (!pc->IsRootContentDocumentCrossProcess()) {
5380 if (pc->IsChrome()) {
5381 return true;
5383 // Frames are transparent except if their used embedder color-scheme is
5384 // mismatched, in which case we use an opaque background to avoid
5385 // black-on-black or white-on-white text, see
5386 // https://github.com/w3c/csswg-drafts/issues/4772
5387 if (BrowsingContext* bc = pc->Document()->GetBrowsingContext()) {
5388 switch (bc->GetEmbedderColorSchemes().mUsed) {
5389 case dom::PrefersColorSchemeOverride::Light:
5390 return pc->DefaultBackgroundColorScheme() == ColorScheme::Light;
5391 case dom::PrefersColorSchemeOverride::Dark:
5392 return pc->DefaultBackgroundColorScheme() == ColorScheme::Dark;
5393 case dom::PrefersColorSchemeOverride::None:
5394 case dom::PrefersColorSchemeOverride::EndGuard_:
5395 break;
5398 return true;
5401 nsIDocShell* docShell = pc->GetDocShell();
5402 if (!docShell) {
5403 return false;
5405 nsPIDOMWindowOuter* pwin = docShell->GetWindow();
5406 if (!pwin) {
5407 return false;
5409 if (Element* containerElement = pwin->GetFrameElementInternal()) {
5410 return containerElement->HasAttr(nsGkAtoms::transparent);
5412 if (BrowserChild* tab = BrowserChild::GetFrom(docShell)) {
5413 // Check if presShell is the top PresShell. Only the top can influence the
5414 // canvas background color.
5415 return this == tab->GetTopLevelPresShell() && tab->IsTransparent();
5417 return false;
5420 nscolor PresShell::GetDefaultBackgroundColorToDraw() const {
5421 if (!mPresContext) {
5422 return NS_RGB(255, 255, 255);
5424 return mPresContext->DefaultBackgroundColor();
5427 void PresShell::UpdateCanvasBackground() {
5428 auto canvasBg = ComputeCanvasBackground();
5429 mCanvasBackgroundColor = canvasBg.mColor;
5430 mHasCSSBackgroundColor = canvasBg.mCSSSpecified;
5433 PresShell::CanvasBackground PresShell::ComputeCanvasBackground() const {
5434 // If we have a frame tree and it has style information that
5435 // specifies the background color of the canvas, update our local
5436 // cache of that color.
5437 nsIFrame* rootStyleFrame = FrameConstructor()->GetRootElementStyleFrame();
5438 if (!rootStyleFrame) {
5439 // If the root element of the document (ie html) has style 'display: none'
5440 // then the document's background color does not get drawn; return the color
5441 // we actually draw.
5442 return {GetDefaultBackgroundColorToDraw(), false};
5445 const ComputedStyle* bgStyle =
5446 nsCSSRendering::FindRootFrameBackground(rootStyleFrame);
5447 // XXX We should really be passing the canvasframe, not the root element
5448 // style frame but we don't have access to the canvasframe here. It isn't
5449 // a problem because only a few frames can return something other than true
5450 // and none of them would be a canvas frame or root element style frame.
5451 nscolor color = NS_RGBA(0, 0, 0, 0);
5452 bool drawBackgroundImage = false;
5453 bool drawBackgroundColor = false;
5454 const nsStyleDisplay* disp = rootStyleFrame->StyleDisplay();
5455 StyleAppearance appearance = disp->EffectiveAppearance();
5456 if (rootStyleFrame->IsThemed(disp) &&
5457 appearance != StyleAppearance::MozWinBorderlessGlass) {
5458 // Ignore the CSS background-color if -moz-appearance is used and it is
5459 // not one of the glass values. (Windows 7 Glass has traditionally not
5460 // overridden background colors, so we preserve that behavior for now.)
5461 } else {
5462 color = nsCSSRendering::DetermineBackgroundColor(
5463 mPresContext, bgStyle, rootStyleFrame, drawBackgroundImage,
5464 drawBackgroundColor);
5466 if (!IsTransparentContainerElement()) {
5467 color = NS_ComposeColors(GetDefaultBackgroundColorToDraw(), color);
5469 return {color, drawBackgroundColor};
5472 nscolor PresShell::ComputeBackstopColor(nsView* aDisplayRoot) {
5473 nsIWidget* widget = aDisplayRoot->GetWidget();
5474 if (widget &&
5475 (widget->GetTransparencyMode() != widget::TransparencyMode::Opaque ||
5476 widget->WidgetPaintsBackground())) {
5477 // Within a transparent widget, so the backstop color must be
5478 // totally transparent.
5479 return NS_RGBA(0, 0, 0, 0);
5481 // Within an opaque widget (or no widget at all), so the backstop
5482 // color must be totally opaque. The user's default background
5483 // as reported by the prescontext is guaranteed to be opaque.
5484 return GetDefaultBackgroundColorToDraw();
5487 struct PaintParams {
5488 nscolor mBackgroundColor;
5491 WindowRenderer* PresShell::GetWindowRenderer() {
5492 NS_ASSERTION(mViewManager, "Should have view manager");
5494 nsView* rootView = mViewManager->GetRootView();
5495 if (rootView) {
5496 if (nsIWidget* widget = rootView->GetWidget()) {
5497 return widget->GetWindowRenderer();
5500 return nullptr;
5503 bool PresShell::AsyncPanZoomEnabled() {
5504 NS_ASSERTION(mViewManager, "Should have view manager");
5505 nsView* rootView = mViewManager->GetRootView();
5506 if (rootView) {
5507 if (nsIWidget* widget = rootView->GetWidget()) {
5508 return widget->AsyncPanZoomEnabled();
5511 return gfxPlatform::AsyncPanZoomEnabled();
5514 nsresult PresShell::SetResolutionAndScaleTo(float aResolution,
5515 ResolutionChangeOrigin aOrigin) {
5516 if (!(aResolution > 0.0)) {
5517 return NS_ERROR_ILLEGAL_VALUE;
5519 if (aResolution == mResolution.valueOr(0.0)) {
5520 MOZ_ASSERT(mResolution.isSome());
5521 return NS_OK;
5524 // GetResolution handles mResolution being nothing by returning 1 so this
5525 // is checking that the resolution is actually changing.
5526 bool resolutionUpdated = (aResolution != GetResolution());
5528 mLastResolutionChangeOrigin = aOrigin;
5530 RenderingState state(this);
5531 state.mResolution = Some(aResolution);
5532 SetRenderingState(state);
5533 if (mMobileViewportManager) {
5534 mMobileViewportManager->ResolutionUpdated(aOrigin);
5536 // Changing the resolution changes the visual viewport size which may
5537 // make the current visual viewport offset out-of-bounds (if the size
5538 // increased). APZ will reconcile this by sending a clamped visual
5539 // viewport offset on the next repaint, but to avoid main-thread code
5540 // observing an out-of-bounds offset until then, reclamp it here.
5541 if (IsVisualViewportOffsetSet()) {
5542 SetVisualViewportOffset(GetVisualViewportOffset(),
5543 GetLayoutViewportOffset());
5545 if (aOrigin == ResolutionChangeOrigin::Apz) {
5546 mResolutionUpdatedByApz = true;
5547 } else if (resolutionUpdated) {
5548 mResolutionUpdated = true;
5551 if (auto* window = nsGlobalWindowInner::Cast(mDocument->GetInnerWindow())) {
5552 window->VisualViewport()->PostResizeEvent();
5555 return NS_OK;
5558 float PresShell::GetCumulativeResolution() const {
5559 float resolution = GetResolution();
5560 nsPresContext* parentCtx = GetPresContext()->GetParentPresContext();
5561 if (parentCtx) {
5562 resolution *= parentCtx->PresShell()->GetCumulativeResolution();
5564 return resolution;
5567 void PresShell::SetRestoreResolution(float aResolution,
5568 LayoutDeviceIntSize aDisplaySize) {
5569 if (mMobileViewportManager) {
5570 mMobileViewportManager->SetRestoreResolution(aResolution, aDisplaySize);
5574 void PresShell::SetRenderingState(const RenderingState& aState) {
5575 if (GetResolution() != aState.mResolution.valueOr(1.f)) {
5576 if (nsIFrame* frame = GetRootFrame()) {
5577 frame->SchedulePaint();
5581 mRenderingStateFlags = aState.mRenderingStateFlags;
5582 mResolution = aState.mResolution;
5583 #ifdef ACCESSIBILITY
5584 if (nsAccessibilityService* accService = GetAccService()) {
5585 accService->NotifyOfResolutionChange(this, GetResolution());
5587 #endif
5590 void PresShell::SynthesizeMouseMove(bool aFromScroll) {
5591 if (!StaticPrefs::layout_reflow_synthMouseMove()) return;
5593 if (mPaintingSuppressed || !mIsActive || !mPresContext) {
5594 return;
5597 if (!mPresContext->IsRoot()) {
5598 if (PresShell* rootPresShell = GetRootPresShell()) {
5599 rootPresShell->SynthesizeMouseMove(aFromScroll);
5601 return;
5604 if (mMouseLocation == nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE))
5605 return;
5607 if (!mSynthMouseMoveEvent.IsPending()) {
5608 RefPtr<nsSynthMouseMoveEvent> ev =
5609 new nsSynthMouseMoveEvent(this, aFromScroll);
5611 GetPresContext()->RefreshDriver()->AddRefreshObserver(
5612 ev, FlushType::Display, "Synthetic mouse move event");
5613 mSynthMouseMoveEvent = std::move(ev);
5617 static nsView* FindFloatingViewContaining(nsPresContext* aRootPresContext,
5618 nsIWidget* aRootWidget,
5619 const LayoutDeviceIntPoint& aPt) {
5620 nsIFrame* popupFrame = nsLayoutUtils::GetPopupFrameForPoint(
5621 aRootPresContext, aRootWidget, aPt,
5622 nsLayoutUtils::GetPopupFrameForPointFlags::OnlyReturnFramesWithWidgets);
5623 return popupFrame ? popupFrame->GetView() : nullptr;
5627 * This finds the first view with a frame that contains the given point in a
5628 * postorder traversal of the view tree, assuming that the point is not in a
5629 * floating view. It assumes that only floating views extend outside the bounds
5630 * of their parents.
5632 * This methods should only be called if FindFloatingViewContaining returns
5633 * null.
5635 * aPt is relative aRelativeToView with the viewport type
5636 * aRelativeToViewportType. aRelativeToView will always have a frame. If aView
5637 * has a frame then aRelativeToView will be aView. (The reason aRelativeToView
5638 * and aView are separate is because we need to traverse into views without
5639 * frames (ie the inner view of a subdocument frame) but we can only easily
5640 * transform between views using TransformPoint which takes frames.)
5642 static nsView* FindViewContaining(nsView* aRelativeToView,
5643 ViewportType aRelativeToViewportType,
5644 nsView* aView, nsPoint aPt) {
5645 MOZ_ASSERT(aRelativeToView->GetFrame());
5647 if (aView->GetVisibility() == ViewVisibility::Hide) {
5648 return nullptr;
5651 nsIFrame* frame = aView->GetFrame();
5652 if (frame) {
5653 if (!frame->PresShell()->IsActive() ||
5654 !frame->IsVisibleConsideringAncestors(
5655 nsIFrame::VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY)) {
5656 return nullptr;
5659 // We start out in visual coords and then if we cross the zoom boundary we
5660 // become in layout coords. The zoom boundary always occurs in a document
5661 // with IsRootContentDocumentCrossProcess. The root view of such a document
5662 // is outside the zoom boundary and any child view must be inside the zoom
5663 // boundary because we only create views for certain kinds of frames and
5664 // none of them can be between the root frame and the zoom boundary.
5665 bool crossingZoomBoundary = false;
5666 if (aRelativeToViewportType == ViewportType::Visual) {
5667 if (!aRelativeToView->GetParent() ||
5668 aRelativeToView->GetViewManager() !=
5669 aRelativeToView->GetParent()->GetViewManager()) {
5670 if (aRelativeToView->GetFrame()
5671 ->PresContext()
5672 ->IsRootContentDocumentCrossProcess()) {
5673 crossingZoomBoundary = true;
5678 ViewportType nextRelativeToViewportType = aRelativeToViewportType;
5679 if (crossingZoomBoundary) {
5680 nextRelativeToViewportType = ViewportType::Layout;
5683 nsLayoutUtils::TransformResult result = nsLayoutUtils::TransformPoint(
5684 RelativeTo{aRelativeToView->GetFrame(), aRelativeToViewportType},
5685 RelativeTo{frame, nextRelativeToViewportType}, aPt);
5686 if (result != nsLayoutUtils::TRANSFORM_SUCCEEDED) {
5687 return nullptr;
5690 // Even though aPt is in visual coordinates until we cross the zoom boundary
5691 // it is valid to compare it to view coords (which are in layout coords)
5692 // because visual coords are the same as layout coords for every view
5693 // outside of the zoom boundary except for the root view of the root content
5694 // document.
5695 // For the root view of the root content document, its bounds don't
5696 // actually correspond to what is visible when we have a
5697 // MobileViewportManager. So we skip the hit test. This is okay because the
5698 // point has already been hit test: 1) if we are the root view in the
5699 // process then the point comes from a real mouse event so it must have been
5700 // over our widget, or 2) if we are the root of a subdocument then
5701 // hittesting against the view of the subdocument frame that contains us
5702 // already happened and succeeded before getting here.
5703 if (!crossingZoomBoundary) {
5704 if (!aView->GetDimensions().Contains(aPt)) {
5705 return nullptr;
5709 aRelativeToView = aView;
5710 aRelativeToViewportType = nextRelativeToViewportType;
5713 for (nsView* v = aView->GetFirstChild(); v; v = v->GetNextSibling()) {
5714 nsView* r =
5715 FindViewContaining(aRelativeToView, aRelativeToViewportType, v, aPt);
5716 if (r) return r;
5719 return frame ? aView : nullptr;
5722 static BrowserBridgeChild* GetChildBrowser(nsView* aView) {
5723 if (!aView) {
5724 return nullptr;
5726 nsIFrame* frame = aView->GetFrame();
5727 if (!frame && aView->GetParent()) {
5728 // If frame is null then view is an anonymous inner view, and we want
5729 // the frame from the corresponding outer view.
5730 frame = aView->GetParent()->GetFrame();
5732 if (!frame || !frame->GetContent()) {
5733 return nullptr;
5735 return BrowserBridgeChild::GetFrom(frame->GetContent());
5738 void PresShell::ProcessSynthMouseMoveEvent(bool aFromScroll) {
5739 // If drag session has started, we shouldn't synthesize mousemove event.
5740 nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
5741 if (dragSession) {
5742 mSynthMouseMoveEvent.Forget();
5743 return;
5746 // allow new event to be posted while handling this one only if the
5747 // source of the event is a scroll (to prevent infinite reflow loops)
5748 if (aFromScroll) {
5749 mSynthMouseMoveEvent.Forget();
5752 nsView* rootView = mViewManager ? mViewManager->GetRootView() : nullptr;
5753 if (mMouseLocation == nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE) ||
5754 !rootView || !rootView->HasWidget() || !mPresContext) {
5755 mSynthMouseMoveEvent.Forget();
5756 return;
5759 NS_ASSERTION(mPresContext->IsRoot(), "Only a root pres shell should be here");
5761 // Hold a ref to ourselves so DispatchEvent won't destroy us (since
5762 // we need to access members after we call DispatchEvent).
5763 RefPtr<PresShell> kungFuDeathGrip(this);
5765 #ifdef DEBUG_MOUSE_LOCATION
5766 printf("[ps=%p]synthesizing mouse move to (%d,%d)\n", this, mMouseLocation.x,
5767 mMouseLocation.y);
5768 #endif
5770 int32_t APD = mPresContext->AppUnitsPerDevPixel();
5772 // We need a widget to put in the event we are going to dispatch so we look
5773 // for a view that has a widget and the mouse location is over. We first look
5774 // for floating views, if there isn't one we use the root view. |view| holds
5775 // that view.
5776 nsView* view = nullptr;
5778 // The appunits per devpixel ratio of |view|.
5779 int32_t viewAPD;
5781 // mRefPoint will be mMouseLocation relative to the widget of |view|, the
5782 // widget we will put in the event we dispatch, in viewAPD appunits
5783 nsPoint refpoint(0, 0);
5785 // We always dispatch the event to the pres shell that contains the view that
5786 // the mouse is over. pointVM is the VM of that pres shell.
5787 nsViewManager* pointVM = nullptr;
5789 if (rootView->GetFrame()) {
5790 view = FindFloatingViewContaining(
5791 mPresContext, rootView->GetWidget(),
5792 LayoutDeviceIntPoint::FromAppUnitsToNearest(
5793 mMouseLocation + rootView->ViewToWidgetOffset(), APD));
5796 nsView* pointView = view;
5797 if (!view) {
5798 view = rootView;
5799 if (rootView->GetFrame()) {
5800 pointView = FindViewContaining(rootView, ViewportType::Visual, rootView,
5801 mMouseLocation);
5802 } else {
5803 pointView = rootView;
5805 // pointView can be null in situations related to mouse capture
5806 pointVM = (pointView ? pointView : view)->GetViewManager();
5807 refpoint = mMouseLocation + rootView->ViewToWidgetOffset();
5808 viewAPD = APD;
5809 } else {
5810 pointVM = view->GetViewManager();
5811 nsIFrame* frame = view->GetFrame();
5812 NS_ASSERTION(frame, "floating views can't be anonymous");
5813 viewAPD = frame->PresContext()->AppUnitsPerDevPixel();
5814 refpoint = mMouseLocation;
5815 DebugOnly<nsLayoutUtils::TransformResult> result =
5816 nsLayoutUtils::TransformPoint(
5817 RelativeTo{rootView->GetFrame(), ViewportType::Visual},
5818 RelativeTo{frame, ViewportType::Layout}, refpoint);
5819 MOZ_ASSERT(result == nsLayoutUtils::TRANSFORM_SUCCEEDED);
5820 refpoint += view->ViewToWidgetOffset();
5822 NS_ASSERTION(view->GetWidget(), "view should have a widget here");
5823 WidgetMouseEvent event(true, eMouseMove, view->GetWidget(),
5824 WidgetMouseEvent::eSynthesized);
5825 event.mRefPoint =
5826 LayoutDeviceIntPoint::FromAppUnitsToNearest(refpoint, viewAPD);
5827 // XXX set event.mModifiers ?
5828 // XXX mnakano I think that we should get the latest information from widget.
5830 if (BrowserBridgeChild* bbc = GetChildBrowser(pointView)) {
5831 // If we have a BrowserBridgeChild, we're going to be dispatching this
5832 // mouse event into an OOP iframe of the current document.
5833 event.mLayersId = bbc->GetLayersId();
5834 bbc->SendDispatchSynthesizedMouseEvent(event);
5835 } else if (RefPtr<PresShell> presShell = pointVM->GetPresShell()) {
5836 // Since this gets run in a refresh tick there isn't an InputAPZContext on
5837 // the stack from the nsBaseWidget. We need to simulate one with at least
5838 // the correct target guid, so that the correct callback transform gets
5839 // applied if this event goes to a child process. The input block id is set
5840 // to 0 because this is a synthetic event which doesn't really belong to any
5841 // input block. Same for the APZ response field.
5842 InputAPZContext apzContext(mMouseEventTargetGuid, 0, nsEventStatus_eIgnore);
5843 presShell->DispatchSynthMouseMove(&event);
5846 if (!aFromScroll) {
5847 mSynthMouseMoveEvent.Forget();
5851 /* static */
5852 void PresShell::MarkFramesInListApproximatelyVisible(
5853 const nsDisplayList& aList) {
5854 for (nsDisplayItem* item : aList) {
5855 nsDisplayList* sublist = item->GetChildren();
5856 if (sublist) {
5857 MarkFramesInListApproximatelyVisible(*sublist);
5858 continue;
5861 nsIFrame* frame = item->Frame();
5862 MOZ_ASSERT(frame);
5864 if (!frame->TrackingVisibility()) {
5865 continue;
5868 // Use the presshell containing the frame.
5869 PresShell* presShell = frame->PresShell();
5870 MOZ_ASSERT(!presShell->AssumeAllFramesVisible());
5871 if (presShell->mApproximatelyVisibleFrames.EnsureInserted(frame)) {
5872 // The frame was added to mApproximatelyVisibleFrames, so increment its
5873 // visible count.
5874 frame->IncApproximateVisibleCount();
5879 /* static */
5880 void PresShell::DecApproximateVisibleCount(
5881 VisibleFrames& aFrames, const Maybe<OnNonvisible>& aNonvisibleAction
5882 /* = Nothing() */) {
5883 for (nsIFrame* frame : aFrames) {
5884 // Decrement the frame's visible count if we're still tracking its
5885 // visibility. (We may not be, if the frame disabled visibility tracking
5886 // after we added it to the visible frames list.)
5887 if (frame->TrackingVisibility()) {
5888 frame->DecApproximateVisibleCount(aNonvisibleAction);
5893 void PresShell::RebuildApproximateFrameVisibilityDisplayList(
5894 const nsDisplayList& aList) {
5895 MOZ_ASSERT(!mApproximateFrameVisibilityVisited, "already visited?");
5896 mApproximateFrameVisibilityVisited = true;
5898 // Remove the entries of the mApproximatelyVisibleFrames hashtable and put
5899 // them in oldApproxVisibleFrames.
5900 VisibleFrames oldApproximatelyVisibleFrames =
5901 std::move(mApproximatelyVisibleFrames);
5903 MarkFramesInListApproximatelyVisible(aList);
5905 DecApproximateVisibleCount(oldApproximatelyVisibleFrames);
5908 /* static */
5909 void PresShell::ClearApproximateFrameVisibilityVisited(nsView* aView,
5910 bool aClear) {
5911 nsViewManager* vm = aView->GetViewManager();
5912 if (aClear) {
5913 PresShell* presShell = vm->GetPresShell();
5914 if (!presShell->mApproximateFrameVisibilityVisited) {
5915 presShell->ClearApproximatelyVisibleFramesList();
5917 presShell->mApproximateFrameVisibilityVisited = false;
5919 for (nsView* v = aView->GetFirstChild(); v; v = v->GetNextSibling()) {
5920 ClearApproximateFrameVisibilityVisited(v, v->GetViewManager() != vm);
5924 void PresShell::ClearApproximatelyVisibleFramesList(
5925 const Maybe<OnNonvisible>& aNonvisibleAction
5926 /* = Nothing() */) {
5927 DecApproximateVisibleCount(mApproximatelyVisibleFrames, aNonvisibleAction);
5928 mApproximatelyVisibleFrames.Clear();
5931 void PresShell::MarkFramesInSubtreeApproximatelyVisible(
5932 nsIFrame* aFrame, const nsRect& aRect, bool aRemoveOnly /* = false */) {
5933 MOZ_DIAGNOSTIC_ASSERT(aFrame, "aFrame arg should be a valid frame pointer");
5934 MOZ_ASSERT(aFrame->PresShell() == this, "wrong presshell");
5936 if (aFrame->TrackingVisibility() && aFrame->StyleVisibility()->IsVisible() &&
5937 (!aRemoveOnly ||
5938 aFrame->GetVisibility() == Visibility::ApproximatelyVisible)) {
5939 MOZ_ASSERT(!AssumeAllFramesVisible());
5940 if (mApproximatelyVisibleFrames.EnsureInserted(aFrame)) {
5941 // The frame was added to mApproximatelyVisibleFrames, so increment its
5942 // visible count.
5943 aFrame->IncApproximateVisibleCount();
5947 nsSubDocumentFrame* subdocFrame = do_QueryFrame(aFrame);
5948 if (subdocFrame) {
5949 PresShell* presShell = subdocFrame->GetSubdocumentPresShellForPainting(
5950 nsSubDocumentFrame::IGNORE_PAINT_SUPPRESSION);
5951 if (presShell && !presShell->AssumeAllFramesVisible()) {
5952 nsRect rect = aRect;
5953 nsIFrame* root = presShell->GetRootFrame();
5954 if (root) {
5955 rect.MoveBy(aFrame->GetOffsetToCrossDoc(root));
5956 } else {
5957 rect.MoveBy(-aFrame->GetContentRectRelativeToSelf().TopLeft());
5959 rect = rect.ScaleToOtherAppUnitsRoundOut(
5960 aFrame->PresContext()->AppUnitsPerDevPixel(),
5961 presShell->GetPresContext()->AppUnitsPerDevPixel());
5963 presShell->RebuildApproximateFrameVisibility(&rect);
5965 return;
5968 nsRect rect = aRect;
5970 nsIScrollableFrame* scrollFrame = do_QueryFrame(aFrame);
5971 if (scrollFrame) {
5972 bool ignoreDisplayPort = false;
5973 if (DisplayPortUtils::IsMissingDisplayPortBaseRect(aFrame->GetContent())) {
5974 // We can properly set the base rect for root scroll frames on top level
5975 // and root content documents. Otherwise the base rect we compute might
5976 // be way too big without the limiting that
5977 // ScrollFrameHelper::DecideScrollableLayer does, so we just ignore the
5978 // displayport in that case.
5979 nsPresContext* pc = aFrame->PresContext();
5980 if (scrollFrame->IsRootScrollFrameOfDocument() &&
5981 (pc->IsRootContentDocumentCrossProcess() ||
5982 (pc->IsChrome() && !pc->GetParentPresContext()))) {
5983 nsRect baseRect(
5984 nsPoint(), nsLayoutUtils::CalculateCompositionSizeForFrame(aFrame));
5985 DisplayPortUtils::SetDisplayPortBase(aFrame->GetContent(), baseRect);
5986 } else {
5987 ignoreDisplayPort = true;
5991 nsRect displayPort;
5992 bool usingDisplayport =
5993 !ignoreDisplayPort &&
5994 DisplayPortUtils::GetDisplayPortForVisibilityTesting(
5995 aFrame->GetContent(), &displayPort);
5997 scrollFrame->NotifyApproximateFrameVisibilityUpdate(!usingDisplayport);
5999 if (usingDisplayport) {
6000 rect = displayPort;
6001 } else {
6002 rect = rect.Intersect(scrollFrame->GetScrollPortRect());
6004 rect = scrollFrame->ExpandRectToNearlyVisible(rect);
6007 bool preserves3DChildren = aFrame->Extend3DContext();
6009 for (const auto& [list, listID] : aFrame->ChildLists()) {
6010 if (listID == FrameChildListID::Popup) {
6011 // We assume all frames in popups are visible, so we skip them here.
6012 continue;
6015 for (nsIFrame* child : list) {
6016 // Note: This assert should be trivially satisfied, just by virtue of how
6017 // nsFrameList and its iterator works (with nullptr being an end-of-list
6018 // sentinel which should terminate the loop). But we do somehow get
6019 // crash reports inside this loop that suggest `child` is null...
6020 MOZ_DIAGNOSTIC_ASSERT(child, "shouldn't have null values in child lists");
6021 nsRect r = rect - child->GetPosition();
6022 if (!r.IntersectRect(r, child->InkOverflowRect())) {
6023 continue;
6025 if (child->IsTransformed()) {
6026 // for children of a preserve3d element we just pass down the same dirty
6027 // rect
6028 if (!preserves3DChildren ||
6029 !child->Combines3DTransformWithAncestors()) {
6030 const nsRect overflow = child->InkOverflowRectRelativeToSelf();
6031 nsRect out;
6032 if (nsDisplayTransform::UntransformRect(r, overflow, child, &out)) {
6033 r = out;
6034 } else {
6035 r.SetEmpty();
6039 MarkFramesInSubtreeApproximatelyVisible(child, r, aRemoveOnly);
6044 void PresShell::RebuildApproximateFrameVisibility(
6045 nsRect* aRect, bool aRemoveOnly /* = false */) {
6046 MOZ_ASSERT(!mApproximateFrameVisibilityVisited, "already visited?");
6047 mApproximateFrameVisibilityVisited = true;
6049 nsIFrame* rootFrame = GetRootFrame();
6050 if (!rootFrame) {
6051 return;
6054 // Remove the entries of the mApproximatelyVisibleFrames hashtable and put
6055 // them in oldApproximatelyVisibleFrames.
6056 VisibleFrames oldApproximatelyVisibleFrames =
6057 std::move(mApproximatelyVisibleFrames);
6059 nsRect vis(nsPoint(0, 0), rootFrame->GetSize());
6060 if (aRect) {
6061 vis = *aRect;
6064 // If we are in-process root but not the top level content, we need to take
6065 // the intersection with the iframe visible rect.
6066 if (mPresContext->IsRootContentDocumentInProcess() &&
6067 !mPresContext->IsRootContentDocumentCrossProcess()) {
6068 // There are two possibilities that we can't get the iframe's visible
6069 // rect other than the iframe is out side of ancestors' display ports.
6070 // a) the BrowserChild is being torn down
6071 // b) the visible rect hasn't been delivered the BrowserChild
6072 // In both cases we consider the visible rect is empty.
6073 Maybe<nsRect> visibleRect;
6074 if (BrowserChild* browserChild = BrowserChild::GetFrom(this)) {
6075 visibleRect = browserChild->GetVisibleRect();
6077 vis = vis.Intersect(visibleRect.valueOr(nsRect()));
6080 MarkFramesInSubtreeApproximatelyVisible(rootFrame, vis, aRemoveOnly);
6082 DecApproximateVisibleCount(oldApproximatelyVisibleFrames);
6085 void PresShell::UpdateApproximateFrameVisibility() {
6086 DoUpdateApproximateFrameVisibility(/* aRemoveOnly = */ false);
6089 void PresShell::DoUpdateApproximateFrameVisibility(bool aRemoveOnly) {
6090 MOZ_ASSERT(
6091 !mPresContext || mPresContext->IsRootContentDocumentInProcess(),
6092 "Updating approximate frame visibility on a non-root content document?");
6094 mUpdateApproximateFrameVisibilityEvent.Revoke();
6096 if (mHaveShutDown || mIsDestroying) {
6097 return;
6100 // call update on that frame
6101 nsIFrame* rootFrame = GetRootFrame();
6102 if (!rootFrame) {
6103 ClearApproximatelyVisibleFramesList(Some(OnNonvisible::DiscardImages));
6104 return;
6107 RebuildApproximateFrameVisibility(/* aRect = */ nullptr, aRemoveOnly);
6108 ClearApproximateFrameVisibilityVisited(rootFrame->GetView(), true);
6110 #ifdef DEBUG_FRAME_VISIBILITY_DISPLAY_LIST
6111 // This can be used to debug the frame walker by comparing beforeFrameList
6112 // and mApproximatelyVisibleFrames in RebuildFrameVisibilityDisplayList to see
6113 // if they produce the same results (mApproximatelyVisibleFrames holds the
6114 // frames the display list thinks are visible, beforeFrameList holds the
6115 // frames the frame walker thinks are visible).
6116 nsDisplayListBuilder builder(
6117 rootFrame, nsDisplayListBuilderMode::FRAME_VISIBILITY, false);
6118 nsRect updateRect(nsPoint(0, 0), rootFrame->GetSize());
6119 nsIFrame* rootScroll = GetRootScrollFrame();
6120 if (rootScroll) {
6121 nsIContent* content = rootScroll->GetContent();
6122 if (content) {
6123 Unused << nsLayoutUtils::GetDisplayPortForVisibilityTesting(
6124 content, &updateRect, RelativeTo::ScrollFrame);
6127 if (IgnoringViewportScrolling()) {
6128 builder.SetIgnoreScrollFrame(rootScroll);
6131 builder.IgnorePaintSuppression();
6132 builder.EnterPresShell(rootFrame);
6133 nsDisplayList list;
6134 rootFrame->BuildDisplayListForStackingContext(&builder, updateRect, &list);
6135 builder.LeavePresShell(rootFrame, &list);
6137 RebuildApproximateFrameVisibilityDisplayList(list);
6139 ClearApproximateFrameVisibilityVisited(rootFrame->GetView(), true);
6141 list.DeleteAll(&builder);
6142 #endif
6145 bool PresShell::AssumeAllFramesVisible() {
6146 if (!StaticPrefs::layout_framevisibility_enabled() || !mPresContext ||
6147 !mDocument) {
6148 return true;
6151 // We assume all frames are visible in print, print preview, chrome, and
6152 // resource docs and don't keep track of them.
6153 if (mPresContext->Type() == nsPresContext::eContext_PrintPreview ||
6154 mPresContext->Type() == nsPresContext::eContext_Print ||
6155 mPresContext->IsChrome() || mDocument->IsResourceDoc()) {
6156 return true;
6159 // If we're assuming all frames are visible in the top level content
6160 // document, we need to in subdocuments as well. Otherwise we can get in a
6161 // situation where things like animations won't work in subdocuments because
6162 // their frames appear not to be visible, since we won't schedule an image
6163 // visibility update if the top level content document is assuming all
6164 // frames are visible.
6166 // Note that it's not safe to call IsRootContentDocumentInProcess() if we're
6167 // currently being destroyed, so we have to check that first.
6168 if (!mHaveShutDown && !mIsDestroying &&
6169 !mPresContext->IsRootContentDocumentInProcess()) {
6170 nsPresContext* presContext =
6171 mPresContext->GetInProcessRootContentDocumentPresContext();
6172 if (presContext && presContext->PresShell()->AssumeAllFramesVisible()) {
6173 return true;
6177 return false;
6180 void PresShell::ScheduleApproximateFrameVisibilityUpdateSoon() {
6181 if (AssumeAllFramesVisible()) {
6182 return;
6185 if (!mPresContext) {
6186 return;
6189 nsRefreshDriver* refreshDriver = mPresContext->RefreshDriver();
6190 if (!refreshDriver) {
6191 return;
6194 // Ask the refresh driver to update frame visibility soon.
6195 refreshDriver->ScheduleFrameVisibilityUpdate();
6198 void PresShell::ScheduleApproximateFrameVisibilityUpdateNow() {
6199 if (AssumeAllFramesVisible()) {
6200 return;
6203 if (!mPresContext->IsRootContentDocumentInProcess()) {
6204 nsPresContext* presContext =
6205 mPresContext->GetInProcessRootContentDocumentPresContext();
6206 if (!presContext) return;
6207 MOZ_ASSERT(presContext->IsRootContentDocumentInProcess(),
6208 "Didn't get a root prescontext from "
6209 "GetInProcessRootContentDocumentPresContext?");
6210 presContext->PresShell()->ScheduleApproximateFrameVisibilityUpdateNow();
6211 return;
6214 if (mHaveShutDown || mIsDestroying) {
6215 return;
6218 if (mUpdateApproximateFrameVisibilityEvent.IsPending()) {
6219 return;
6222 RefPtr<nsRunnableMethod<PresShell>> event =
6223 NewRunnableMethod("PresShell::UpdateApproximateFrameVisibility", this,
6224 &PresShell::UpdateApproximateFrameVisibility);
6225 nsresult rv = mDocument->Dispatch(TaskCategory::Other, do_AddRef(event));
6227 if (NS_SUCCEEDED(rv)) {
6228 mUpdateApproximateFrameVisibilityEvent = std::move(event);
6232 void PresShell::EnsureFrameInApproximatelyVisibleList(nsIFrame* aFrame) {
6233 if (!aFrame->TrackingVisibility()) {
6234 return;
6237 if (AssumeAllFramesVisible()) {
6238 aFrame->IncApproximateVisibleCount();
6239 return;
6242 #ifdef DEBUG
6243 // Make sure it's in this pres shell.
6244 nsCOMPtr<nsIContent> content = aFrame->GetContent();
6245 if (content) {
6246 PresShell* presShell = content->OwnerDoc()->GetPresShell();
6247 MOZ_ASSERT(!presShell || presShell == this, "wrong shell");
6249 #endif
6251 if (mApproximatelyVisibleFrames.EnsureInserted(aFrame)) {
6252 // We inserted a new entry.
6253 aFrame->IncApproximateVisibleCount();
6257 void PresShell::RemoveFrameFromApproximatelyVisibleList(nsIFrame* aFrame) {
6258 #ifdef DEBUG
6259 // Make sure it's in this pres shell.
6260 nsCOMPtr<nsIContent> content = aFrame->GetContent();
6261 if (content) {
6262 PresShell* presShell = content->OwnerDoc()->GetPresShell();
6263 MOZ_ASSERT(!presShell || presShell == this, "wrong shell");
6265 #endif
6267 if (AssumeAllFramesVisible()) {
6268 MOZ_ASSERT(mApproximatelyVisibleFrames.Count() == 0,
6269 "Shouldn't have any frames in the table");
6270 return;
6273 if (mApproximatelyVisibleFrames.EnsureRemoved(aFrame) &&
6274 aFrame->TrackingVisibility()) {
6275 // aFrame was in the hashtable, and we're still tracking its visibility,
6276 // so we need to decrement its visible count.
6277 aFrame->DecApproximateVisibleCount();
6281 void PresShell::PaintAndRequestComposite(nsView* aView, PaintFlags aFlags) {
6282 if (!mIsActive) {
6283 return;
6286 WindowRenderer* renderer = aView->GetWidget()->GetWindowRenderer();
6287 NS_ASSERTION(renderer, "Must be in paint event");
6288 if (renderer->AsFallback()) {
6289 // The fallback renderer doesn't do any retaining, so we
6290 // just need to notify the view and widget that we're invalid, and
6291 // we'll do a paint+composite from the PaintWindow callback.
6292 GetViewManager()->InvalidateView(aView);
6293 return;
6296 // Otherwise we're a retained WebRenderLayerManager, so we want to call
6297 // Paint to update with any changes and push those to WR.
6298 PaintInternalFlags flags = PaintInternalFlags::None;
6299 if (aFlags & PaintFlags::PaintSyncDecodeImages) {
6300 flags |= PaintInternalFlags::PaintSyncDecodeImages;
6302 PaintInternal(aView, flags);
6305 void PresShell::SyncPaintFallback(nsView* aView) {
6306 if (!mIsActive) {
6307 return;
6310 WindowRenderer* renderer = aView->GetWidget()->GetWindowRenderer();
6311 NS_ASSERTION(renderer->AsFallback(),
6312 "Can't do Sync paint for remote renderers");
6313 if (!renderer->AsFallback()) {
6314 return;
6317 PaintInternal(aView, PaintInternalFlags::PaintComposite);
6318 GetPresContext()->NotifyDidPaintForSubtree();
6321 void PresShell::PaintInternal(nsView* aViewToPaint, PaintInternalFlags aFlags) {
6322 nsCString url;
6323 nsIURI* uri = mDocument->GetDocumentURI();
6324 Document* contentRoot = GetPrimaryContentDocument();
6325 if (contentRoot) {
6326 uri = contentRoot->GetDocumentURI();
6328 url = uri ? uri->GetSpecOrDefault() : "N/A"_ns;
6329 AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING_RELEVANT_FOR_JS(
6330 "Paint", GRAPHICS, Substring(url, std::min(size_t(128), url.Length())));
6332 Maybe<js::AutoAssertNoContentJS> nojs;
6334 // On Android, Flash can call into content JS during painting, so we can't
6335 // assert there. However, we don't rely on this assertion on Android because
6336 // we don't paint while JS is running.
6337 #if !defined(MOZ_WIDGET_ANDROID)
6338 if (!(aFlags & PaintInternalFlags::PaintComposite)) {
6339 // We need to allow content JS when the flag is set since we may trigger
6340 // MozAfterPaint events in content in those cases.
6341 nojs.emplace(dom::danger::GetJSContext());
6343 #endif
6345 NS_ASSERTION(!mIsDestroying, "painting a destroyed PresShell");
6346 NS_ASSERTION(aViewToPaint, "null view");
6348 MOZ_ASSERT(!mApproximateFrameVisibilityVisited, "Should have been cleared");
6350 if (!mIsActive) {
6351 return;
6354 if (StaticPrefs::apz_keyboard_enabled_AtStartup()) {
6355 // Update the focus target for async keyboard scrolling. This will be
6356 // forwarded to APZ by nsDisplayList::PaintRoot. We need to to do this
6357 // before we enter the paint phase because dispatching eVoid events can
6358 // cause layout to happen.
6359 mAPZFocusTarget = FocusTarget(this, mAPZFocusSequenceNumber);
6362 nsPresContext* presContext = GetPresContext();
6363 AUTO_LAYOUT_PHASE_ENTRY_POINT(presContext, Paint);
6365 nsIFrame* frame = aViewToPaint->GetFrame();
6367 WindowRenderer* renderer = aViewToPaint->GetWidget()->GetWindowRenderer();
6368 NS_ASSERTION(renderer, "Must be in paint event");
6369 WebRenderLayerManager* layerManager = renderer->AsWebRender();
6371 // Whether or not we should set first paint when painting is suppressed
6372 // is debatable. For now we'll do it because B2G relied on first paint
6373 // to configure the viewport and we only want to do that when we have
6374 // real content to paint. See Bug 798245
6375 if (mIsFirstPaint && !mPaintingSuppressed) {
6376 MOZ_LOG(gLog, LogLevel::Debug,
6377 ("PresShell::Paint, first paint, this=%p", this));
6379 if (layerManager) {
6380 layerManager->SetIsFirstPaint();
6382 mIsFirstPaint = false;
6385 if (!renderer->BeginTransaction(url)) {
6386 return;
6389 // Send an updated focus target with this transaction. Be sure to do this
6390 // before we paint in the case this is an empty transaction.
6391 if (layerManager) {
6392 layerManager->SetFocusTarget(mAPZFocusTarget);
6395 if (frame) {
6396 if (!(aFlags & PaintInternalFlags::PaintSyncDecodeImages) &&
6397 !frame->HasAnyStateBits(NS_FRAME_UPDATE_LAYER_TREE)) {
6398 if (layerManager) {
6399 layerManager->SetTransactionIdAllocator(presContext->RefreshDriver());
6402 if (renderer->EndEmptyTransaction(
6403 (aFlags & PaintInternalFlags::PaintComposite)
6404 ? WindowRenderer::END_DEFAULT
6405 : WindowRenderer::END_NO_COMPOSITE)) {
6406 return;
6409 frame->RemoveStateBits(NS_FRAME_UPDATE_LAYER_TREE);
6412 nscolor bgcolor = ComputeBackstopColor(aViewToPaint);
6413 PaintFrameFlags flags =
6414 PaintFrameFlags::WidgetLayers | PaintFrameFlags::ExistingTransaction;
6416 // We force sync-decode for printing / print-preview (printing already does
6417 // this from nsPageSequenceFrame::PrintNextSheet).
6418 // We also force sync-decoding via pref for reftests.
6419 if (aFlags & PaintInternalFlags::PaintSyncDecodeImages ||
6420 mDocument->IsStaticDocument() ||
6421 StaticPrefs::image_decode_sync_enabled()) {
6422 flags |= PaintFrameFlags::SyncDecodeImages;
6424 if (renderer->GetBackendType() == layers::LayersBackend::LAYERS_WR) {
6425 flags |= PaintFrameFlags::ForWebRender;
6428 if (frame) {
6429 // We can paint directly into the widget using its layer manager.
6430 nsLayoutUtils::PaintFrame(nullptr, frame, nsRegion(), bgcolor,
6431 nsDisplayListBuilderMode::Painting, flags);
6432 return;
6435 bgcolor = NS_ComposeColors(bgcolor, mCanvasBackgroundColor);
6437 if (renderer->GetBackendType() == layers::LayersBackend::LAYERS_WR) {
6438 LayoutDeviceRect bounds = LayoutDeviceRect::FromAppUnits(
6439 presContext->GetVisibleArea(), presContext->AppUnitsPerDevPixel());
6440 WebRenderBackgroundData data(wr::ToLayoutRect(bounds),
6441 wr::ToColorF(ToDeviceColor(bgcolor)));
6442 WrFiltersHolder wrFilters;
6444 layerManager->SetTransactionIdAllocator(presContext->RefreshDriver());
6445 layerManager->EndTransactionWithoutLayer(nullptr, nullptr,
6446 std::move(wrFilters), &data, 0);
6447 return;
6450 FallbackRenderer* fallback = renderer->AsFallback();
6451 MOZ_ASSERT(fallback);
6453 if (aFlags & PaintInternalFlags::PaintComposite) {
6454 nsIntRect bounds = presContext->GetVisibleArea().ToOutsidePixels(
6455 presContext->AppUnitsPerDevPixel());
6456 fallback->EndTransactionWithColor(bounds, ToDeviceColor(bgcolor));
6460 // static
6461 void PresShell::SetCapturingContent(nsIContent* aContent, CaptureFlags aFlags,
6462 WidgetEvent* aEvent) {
6463 // If capture was set for pointer lock, don't unlock unless we are coming
6464 // out of pointer lock explicitly.
6465 if (!aContent && sCapturingContentInfo.mPointerLock &&
6466 !(aFlags & CaptureFlags::PointerLock)) {
6467 return;
6470 sCapturingContentInfo.mContent = nullptr;
6471 sCapturingContentInfo.mRemoteTarget = nullptr;
6473 // only set capturing content if allowed or the
6474 // CaptureFlags::IgnoreAllowedState or CaptureFlags::PointerLock are used.
6475 if ((aFlags & CaptureFlags::IgnoreAllowedState) ||
6476 sCapturingContentInfo.mAllowed || (aFlags & CaptureFlags::PointerLock)) {
6477 if (aContent) {
6478 sCapturingContentInfo.mContent = aContent;
6480 if (aEvent) {
6481 MOZ_ASSERT(XRE_IsParentProcess());
6482 MOZ_ASSERT(aEvent->mMessage == eMouseDown);
6483 MOZ_ASSERT(aEvent->HasBeenPostedToRemoteProcess());
6484 sCapturingContentInfo.mRemoteTarget =
6485 BrowserParent::GetLastMouseRemoteTarget();
6486 MOZ_ASSERT(sCapturingContentInfo.mRemoteTarget);
6488 // CaptureFlags::PointerLock is the same as
6489 // CaptureFlags::RetargetToElement & CaptureFlags::IgnoreAllowedState.
6490 sCapturingContentInfo.mRetargetToElement =
6491 !!(aFlags & CaptureFlags::RetargetToElement) ||
6492 !!(aFlags & CaptureFlags::PointerLock);
6493 sCapturingContentInfo.mPreventDrag =
6494 !!(aFlags & CaptureFlags::PreventDragStart);
6495 sCapturingContentInfo.mPointerLock = !!(aFlags & CaptureFlags::PointerLock);
6499 nsIContent* PresShell::GetCurrentEventContent() {
6500 if (mCurrentEventContent &&
6501 mCurrentEventContent->GetComposedDoc() != mDocument) {
6502 mCurrentEventContent = nullptr;
6503 mCurrentEventFrame = nullptr;
6505 return mCurrentEventContent;
6508 nsIFrame* PresShell::GetCurrentEventFrame() {
6509 if (MOZ_UNLIKELY(mIsDestroying)) {
6510 return nullptr;
6513 // GetCurrentEventContent() makes sure the content is still in the
6514 // same document that this pres shell belongs to. If not, then the
6515 // frame shouldn't get an event, nor should we even assume its safe
6516 // to try and find the frame.
6517 nsIContent* content = GetCurrentEventContent();
6518 if (!mCurrentEventFrame && content) {
6519 mCurrentEventFrame = content->GetPrimaryFrame();
6520 MOZ_ASSERT(!mCurrentEventFrame ||
6521 mCurrentEventFrame->PresContext()->GetPresShell() == this);
6523 return mCurrentEventFrame;
6526 already_AddRefed<nsIContent> PresShell::GetEventTargetContent(
6527 WidgetEvent* aEvent) {
6528 nsCOMPtr<nsIContent> content = GetCurrentEventContent();
6529 if (!content) {
6530 nsIFrame* currentEventFrame = GetCurrentEventFrame();
6531 if (currentEventFrame) {
6532 currentEventFrame->GetContentForEvent(aEvent, getter_AddRefs(content));
6533 NS_ASSERTION(!content || content->GetComposedDoc() == mDocument,
6534 "handing out content from a different doc");
6537 return content.forget();
6540 void PresShell::PushCurrentEventInfo(nsIFrame* aFrame, nsIContent* aContent) {
6541 if (mCurrentEventFrame || mCurrentEventContent) {
6542 mCurrentEventFrameStack.InsertElementAt(0, mCurrentEventFrame);
6543 mCurrentEventContentStack.InsertObjectAt(mCurrentEventContent, 0);
6545 mCurrentEventFrame = aFrame;
6546 mCurrentEventContent = aContent;
6549 void PresShell::PopCurrentEventInfo() {
6550 mCurrentEventFrame = nullptr;
6551 mCurrentEventContent = nullptr;
6553 if (0 != mCurrentEventFrameStack.Length()) {
6554 mCurrentEventFrame = mCurrentEventFrameStack.ElementAt(0);
6555 mCurrentEventFrameStack.RemoveElementAt(0);
6556 mCurrentEventContent = mCurrentEventContentStack.ObjectAt(0);
6557 mCurrentEventContentStack.RemoveObjectAt(0);
6559 // Don't use it if it has moved to a different document.
6560 if (mCurrentEventContent &&
6561 mCurrentEventContent->GetComposedDoc() != mDocument) {
6562 mCurrentEventContent = nullptr;
6563 mCurrentEventFrame = nullptr;
6568 // static
6569 bool PresShell::EventHandler::InZombieDocument(nsIContent* aContent) {
6570 // If a content node points to a null document, or the document is not
6571 // attached to a window, then it is possibly in a zombie document,
6572 // about to be replaced by a newly loading document.
6573 // Such documents cannot handle DOM events.
6574 // It might actually be in a node not attached to any document,
6575 // in which case there is not parent presshell to retarget it to.
6576 Document* doc = aContent->GetComposedDoc();
6577 return !doc || !doc->GetWindow();
6580 already_AddRefed<nsPIDOMWindowOuter> PresShell::GetRootWindow() {
6581 nsCOMPtr<nsPIDOMWindowOuter> window = mDocument->GetWindow();
6582 if (window) {
6583 nsCOMPtr<nsPIDOMWindowOuter> rootWindow = window->GetPrivateRoot();
6584 NS_ASSERTION(rootWindow, "nsPIDOMWindow::GetPrivateRoot() returns NULL");
6585 return rootWindow.forget();
6588 // If we don't have DOM window, we're zombie, we should find the root window
6589 // with our parent shell.
6590 RefPtr<PresShell> parentPresShell = GetParentPresShellForEventHandling();
6591 NS_ENSURE_TRUE(parentPresShell, nullptr);
6592 return parentPresShell->GetRootWindow();
6595 already_AddRefed<nsPIDOMWindowOuter>
6596 PresShell::GetFocusedDOMWindowInOurWindow() {
6597 nsCOMPtr<nsPIDOMWindowOuter> rootWindow = GetRootWindow();
6598 NS_ENSURE_TRUE(rootWindow, nullptr);
6599 nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
6600 nsFocusManager::GetFocusedDescendant(rootWindow,
6601 nsFocusManager::eIncludeAllDescendants,
6602 getter_AddRefs(focusedWindow));
6603 return focusedWindow.forget();
6606 already_AddRefed<nsIContent> PresShell::GetFocusedContentInOurWindow() const {
6607 nsFocusManager* fm = nsFocusManager::GetFocusManager();
6608 if (fm && mDocument) {
6609 RefPtr<Element> focusedElement;
6610 fm->GetFocusedElementForWindow(mDocument->GetWindow(), false, nullptr,
6611 getter_AddRefs(focusedElement));
6612 return focusedElement.forget();
6614 return nullptr;
6617 already_AddRefed<PresShell> PresShell::GetParentPresShellForEventHandling() {
6618 if (!mPresContext) {
6619 return nullptr;
6622 // Now, find the parent pres shell and send the event there
6623 RefPtr<nsDocShell> docShell = mPresContext->GetDocShell();
6624 if (!docShell) {
6625 docShell = mForwardingContainer.get();
6628 // Might have gone away, or never been around to start with
6629 if (!docShell) {
6630 return nullptr;
6633 BrowsingContext* bc = docShell->GetBrowsingContext();
6634 if (!bc) {
6635 return nullptr;
6638 RefPtr<BrowsingContext> parentBC;
6639 if (XRE_IsParentProcess()) {
6640 parentBC = bc->Canonical()->GetParentCrossChromeBoundary();
6641 } else {
6642 parentBC = bc->GetParent();
6645 RefPtr<nsIDocShell> parentDocShell =
6646 parentBC ? parentBC->GetDocShell() : nullptr;
6647 if (!parentDocShell) {
6648 return nullptr;
6651 RefPtr<PresShell> parentPresShell = parentDocShell->GetPresShell();
6652 return parentPresShell.forget();
6655 nsresult PresShell::EventHandler::RetargetEventToParent(
6656 WidgetGUIEvent* aGUIEvent, nsEventStatus* aEventStatus) {
6657 // Send this events straight up to the parent pres shell.
6658 // We do this for keystroke events in zombie documents or if either a frame
6659 // or a root content is not present.
6660 // That way at least the UI key bindings can work.
6662 RefPtr<PresShell> parentPresShell = GetParentPresShellForEventHandling();
6663 NS_ENSURE_TRUE(parentPresShell, NS_ERROR_FAILURE);
6665 // Fake the event as though it's from the parent pres shell's root frame.
6666 return parentPresShell->HandleEvent(parentPresShell->GetRootFrame(),
6667 aGUIEvent, true, aEventStatus);
6670 void PresShell::DisableNonTestMouseEvents(bool aDisable) {
6671 sDisableNonTestMouseEvents = aDisable;
6674 bool PresShell::MouseLocationWasSetBySynthesizedMouseEventForTests() const {
6675 if (!mPresContext) {
6676 return false;
6678 if (mPresContext->IsRoot()) {
6679 return mMouseLocationWasSetBySynthesizedMouseEventForTests;
6681 PresShell* rootPresShell = GetRootPresShell();
6682 return rootPresShell &&
6683 rootPresShell->mMouseLocationWasSetBySynthesizedMouseEventForTests;
6686 nsPoint PresShell::GetEventLocation(const WidgetMouseEvent& aEvent) const {
6687 nsIFrame* rootFrame = GetRootFrame();
6688 if (rootFrame) {
6689 RelativeTo relativeTo{rootFrame};
6690 if (rootFrame->PresContext()->IsRootContentDocumentCrossProcess()) {
6691 relativeTo.mViewportType = ViewportType::Visual;
6693 return nsLayoutUtils::GetEventCoordinatesRelativeTo(&aEvent, relativeTo);
6696 nsView* rootView = mViewManager->GetRootView();
6697 return nsLayoutUtils::TranslateWidgetToView(mPresContext, aEvent.mWidget,
6698 aEvent.mRefPoint, rootView);
6701 void PresShell::RecordPointerLocation(WidgetGUIEvent* aEvent) {
6702 if (!mPresContext) {
6703 return;
6706 if (!mPresContext->IsRoot()) {
6707 PresShell* rootPresShell = GetRootPresShell();
6708 if (rootPresShell) {
6709 rootPresShell->RecordPointerLocation(aEvent);
6711 return;
6714 if ((aEvent->mMessage == eMouseMove &&
6715 aEvent->AsMouseEvent()->mReason == WidgetMouseEvent::eReal) ||
6716 aEvent->mMessage == eMouseEnterIntoWidget ||
6717 aEvent->mMessage == eMouseDown || aEvent->mMessage == eMouseUp) {
6718 mMouseLocation = GetEventLocation(*aEvent->AsMouseEvent());
6719 mMouseEventTargetGuid = InputAPZContext::GetTargetLayerGuid();
6720 mMouseLocationWasSetBySynthesizedMouseEventForTests =
6721 aEvent->mFlags.mIsSynthesizedForTests;
6722 #ifdef DEBUG_MOUSE_LOCATION
6723 if (aEvent->mMessage == eMouseEnterIntoWidget) {
6724 printf("[ps=%p]got mouse enter for %p\n", this, aEvent->mWidget);
6726 printf("[ps=%p]setting mouse location to (%d,%d)\n", this, mMouseLocation.x,
6727 mMouseLocation.y);
6728 #endif
6729 if (aEvent->mMessage == eMouseEnterIntoWidget) {
6730 SynthesizeMouseMove(false);
6732 } else if (aEvent->mMessage == eMouseExitFromWidget) {
6733 // Although we only care about the mouse moving into an area for which this
6734 // pres shell doesn't receive mouse move events, we don't check which widget
6735 // the mouse exit was for since this seems to vary by platform. Hopefully
6736 // this won't matter at all since we'll get the mouse move or enter after
6737 // the mouse exit when the mouse moves from one of our widgets into another.
6738 mMouseLocation = nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
6739 mMouseEventTargetGuid = InputAPZContext::GetTargetLayerGuid();
6740 mMouseLocationWasSetBySynthesizedMouseEventForTests =
6741 aEvent->mFlags.mIsSynthesizedForTests;
6742 #ifdef DEBUG_MOUSE_LOCATION
6743 printf("[ps=%p]got mouse exit for %p\n", this, aEvent->mWidget);
6744 printf("[ps=%p]clearing mouse location\n", this);
6745 #endif
6746 } else if ((aEvent->mMessage == ePointerMove &&
6747 aEvent->AsMouseEvent()->mReason == WidgetMouseEvent::eReal) ||
6748 aEvent->mMessage == ePointerDown ||
6749 aEvent->mMessage == ePointerUp) {
6750 // TODO: instead, encapsulate `mMouseLocation` and
6751 // `mLastOverWindowPointerLocation` in a struct.
6752 mLastOverWindowPointerLocation = GetEventLocation(*aEvent->AsMouseEvent());
6756 void PresShell::nsSynthMouseMoveEvent::Revoke() {
6757 if (mPresShell) {
6758 mPresShell->GetPresContext()->RefreshDriver()->RemoveRefreshObserver(
6759 this, FlushType::Display);
6760 mPresShell = nullptr;
6764 // static
6765 nsIFrame* PresShell::EventHandler::GetNearestFrameContainingPresShell(
6766 PresShell* aPresShell) {
6767 nsViewManager* vm = aPresShell->GetViewManager();
6768 if (!vm) {
6769 return nullptr;
6771 nsView* view = vm->GetRootView();
6772 while (view && !view->GetFrame()) {
6773 view = view->GetParent();
6776 nsIFrame* frame = nullptr;
6777 if (view) {
6778 frame = view->GetFrame();
6781 return frame;
6784 static CallState FlushThrottledStyles(Document& aDocument) {
6785 PresShell* presShell = aDocument.GetPresShell();
6786 if (presShell && presShell->IsVisible()) {
6787 if (nsPresContext* presContext = presShell->GetPresContext()) {
6788 presContext->RestyleManager()->UpdateOnlyAnimationStyles();
6792 aDocument.EnumerateSubDocuments(FlushThrottledStyles);
6793 return CallState::Continue;
6796 bool PresShell::CanDispatchEvent(const WidgetGUIEvent* aEvent) const {
6797 bool rv =
6798 mPresContext && !mHaveShutDown && nsContentUtils::IsSafeToRunScript();
6799 if (aEvent) {
6800 rv &= (aEvent && aEvent->mWidget && !aEvent->mWidget->Destroyed());
6802 return rv;
6805 /* static */
6806 PresShell* PresShell::GetShellForEventTarget(nsIFrame* aFrame,
6807 nsIContent* aContent) {
6808 if (aFrame) {
6809 return aFrame->PresShell();
6811 if (aContent) {
6812 Document* doc = aContent->GetComposedDoc();
6813 if (!doc) {
6814 return nullptr;
6816 return doc->GetPresShell();
6818 return nullptr;
6821 /* static */
6822 PresShell* PresShell::GetShellForTouchEvent(WidgetGUIEvent* aEvent) {
6823 switch (aEvent->mMessage) {
6824 case eTouchMove:
6825 case eTouchCancel:
6826 case eTouchEnd: {
6827 // get the correct shell to dispatch to
6828 WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
6829 for (dom::Touch* touch : touchEvent->mTouches) {
6830 if (!touch) {
6831 return nullptr;
6834 RefPtr<dom::Touch> oldTouch =
6835 TouchManager::GetCapturedTouch(touch->Identifier());
6836 if (!oldTouch) {
6837 return nullptr;
6840 nsCOMPtr<nsIContent> content = do_QueryInterface(oldTouch->GetTarget());
6841 if (!content) {
6842 return nullptr;
6845 nsIFrame* contentFrame = content->GetPrimaryFrame();
6846 if (!contentFrame) {
6847 return nullptr;
6850 PresShell* presShell = contentFrame->PresContext()->GetPresShell();
6851 if (presShell) {
6852 return presShell;
6855 return nullptr;
6857 default:
6858 return nullptr;
6862 nsresult PresShell::HandleEvent(nsIFrame* aFrameForPresShell,
6863 WidgetGUIEvent* aGUIEvent,
6864 bool aDontRetargetEvents,
6865 nsEventStatus* aEventStatus) {
6866 MOZ_ASSERT(aGUIEvent);
6867 // If it's synthesized in the parent process and our mouse location was set
6868 // by a mouse event which was synthesized for tests because the test does not
6869 // want to change `:hover` state with the synthesized mouse event for native
6870 // mouse cursor position.
6871 if (aGUIEvent->mMessage == eMouseMove &&
6872 aGUIEvent->CameFromAnotherProcess() && XRE_IsContentProcess() &&
6873 !aGUIEvent->mFlags.mIsSynthesizedForTests &&
6874 MouseLocationWasSetBySynthesizedMouseEventForTests() &&
6875 aGUIEvent->AsMouseEvent()->mReason == WidgetMouseEvent::eSynthesized) {
6876 return NS_OK;
6878 EventHandler eventHandler(*this);
6879 return eventHandler.HandleEvent(aFrameForPresShell, aGUIEvent,
6880 aDontRetargetEvents, aEventStatus);
6883 nsresult PresShell::EventHandler::HandleEvent(nsIFrame* aFrameForPresShell,
6884 WidgetGUIEvent* aGUIEvent,
6885 bool aDontRetargetEvents,
6886 nsEventStatus* aEventStatus) {
6887 MOZ_ASSERT(aGUIEvent);
6888 MOZ_DIAGNOSTIC_ASSERT(aGUIEvent->IsTrusted());
6889 MOZ_ASSERT(aEventStatus);
6891 NS_ASSERTION(aFrameForPresShell, "aFrameForPresShell should be not null");
6893 // Update the latest focus sequence number with this new sequence number;
6894 // the next transasction that gets sent to the compositor will carry this over
6895 if (mPresShell->mAPZFocusSequenceNumber < aGUIEvent->mFocusSequenceNumber) {
6896 mPresShell->mAPZFocusSequenceNumber = aGUIEvent->mFocusSequenceNumber;
6899 if (mPresShell->IsDestroying() ||
6900 (PresShell::sDisableNonTestMouseEvents &&
6901 !aGUIEvent->mFlags.mIsSynthesizedForTests &&
6902 aGUIEvent->HasMouseEventMessage())) {
6903 return NS_OK;
6906 mPresShell->RecordPointerLocation(aGUIEvent);
6908 if (MaybeHandleEventWithAccessibleCaret(aFrameForPresShell, aGUIEvent,
6909 aEventStatus)) {
6910 // Handled by AccessibleCaretEventHub.
6911 return NS_OK;
6914 if (MaybeDiscardEvent(aGUIEvent)) {
6915 // Cannot handle the event for now.
6916 return NS_OK;
6919 if (!aDontRetargetEvents) {
6920 // If aGUIEvent should be handled in another PresShell, we should call its
6921 // HandleEvent() and do nothing here.
6922 nsresult rv = NS_OK;
6923 if (MaybeHandleEventWithAnotherPresShell(aFrameForPresShell, aGUIEvent,
6924 aEventStatus, &rv)) {
6925 // Handled by another PresShell or nobody can handle the event.
6926 return rv;
6930 if (MaybeDiscardOrDelayKeyboardEvent(aGUIEvent)) {
6931 // The event is discarded or put into the delayed event queue.
6932 return NS_OK;
6935 if (aGUIEvent->IsUsingCoordinates()) {
6936 return HandleEventUsingCoordinates(aFrameForPresShell, aGUIEvent,
6937 aEventStatus, aDontRetargetEvents);
6940 // Activation events need to be dispatched even if no frame was found, since
6941 // we don't want the focus to be out of sync.
6942 if (!aFrameForPresShell) {
6943 if (!NS_EVENT_NEEDS_FRAME(aGUIEvent)) {
6944 // Push nullptr for both current event target content and frame since
6945 // there is no frame but the event does not require a frame.
6946 AutoCurrentEventInfoSetter eventInfoSetter(*this);
6947 return HandleEventWithCurrentEventInfo(aGUIEvent, aEventStatus, true,
6948 nullptr);
6951 if (aGUIEvent->HasKeyEventMessage()) {
6952 // Keypress events in new blank tabs should not be completely thrown away.
6953 // Retarget them -- the parent chrome shell might make use of them.
6954 return RetargetEventToParent(aGUIEvent, aEventStatus);
6957 return NS_OK;
6960 if (aGUIEvent->IsTargetedAtFocusedContent()) {
6961 return HandleEventAtFocusedContent(aGUIEvent, aEventStatus);
6964 return HandleEventWithFrameForPresShell(aFrameForPresShell, aGUIEvent,
6965 aEventStatus);
6968 nsresult PresShell::EventHandler::HandleEventUsingCoordinates(
6969 nsIFrame* aFrameForPresShell, WidgetGUIEvent* aGUIEvent,
6970 nsEventStatus* aEventStatus, bool aDontRetargetEvents) {
6971 MOZ_ASSERT(aGUIEvent);
6972 MOZ_ASSERT(aGUIEvent->IsUsingCoordinates());
6973 MOZ_ASSERT(aEventStatus);
6975 // Flush pending notifications to handle the event with the latest layout.
6976 // But if it causes destroying the frame for mPresShell, stop handling the
6977 // event. (why?)
6978 AutoWeakFrame weakFrame(aFrameForPresShell);
6979 MaybeFlushPendingNotifications(aGUIEvent);
6980 if (!weakFrame.IsAlive()) {
6981 *aEventStatus = nsEventStatus_eIgnore;
6982 return NS_OK;
6985 // XXX Retrieving capturing content here. However, some of the following
6986 // methods allow to run script. So, isn't it possible the capturing
6987 // content outdated?
6988 nsCOMPtr<nsIContent> capturingContent =
6989 EventHandler::GetCapturingContentFor(aGUIEvent);
6991 if (GetDocument() && aGUIEvent->mClass == eTouchEventClass) {
6992 PointerLockManager::Unlock();
6995 nsIFrame* frameForPresShell = MaybeFlushThrottledStyles(aFrameForPresShell);
6996 if (NS_WARN_IF(!frameForPresShell)) {
6997 return NS_OK;
7000 bool isCapturingContentIgnored = false;
7001 bool isCaptureRetargeted = false;
7002 nsIFrame* rootFrameToHandleEvent = ComputeRootFrameToHandleEvent(
7003 frameForPresShell, aGUIEvent, capturingContent,
7004 &isCapturingContentIgnored, &isCaptureRetargeted);
7005 if (isCapturingContentIgnored) {
7006 capturingContent = nullptr;
7009 // The order to generate pointer event is
7010 // 1. check pending pointer capture.
7011 // 2. check if there is a capturing content.
7012 // 3. hit test
7013 // 4. dispatch pointer events
7014 // 5. check whether the targets of all Touch instances are in the same
7015 // document and suppress invalid instances.
7016 // 6. dispatch mouse or touch events.
7018 // Try to keep frame for following check, because frame can be damaged
7019 // during MaybeProcessPointerCapture.
7021 AutoWeakFrame frameKeeper(rootFrameToHandleEvent);
7022 PointerEventHandler::MaybeProcessPointerCapture(aGUIEvent);
7023 // Prevent application crashes, in case damaged frame.
7024 if (!frameKeeper.IsAlive()) {
7025 NS_WARNING("Nothing to handle this event!");
7026 return NS_OK;
7030 // Only capture mouse events and pointer events.
7031 RefPtr<Element> pointerCapturingElement =
7032 PointerEventHandler::GetPointerCapturingElement(aGUIEvent);
7034 if (pointerCapturingElement) {
7035 rootFrameToHandleEvent = pointerCapturingElement->GetPrimaryFrame();
7036 if (!rootFrameToHandleEvent) {
7037 return HandleEventWithPointerCapturingContentWithoutItsFrame(
7038 aFrameForPresShell, aGUIEvent, pointerCapturingElement, aEventStatus);
7042 WidgetMouseEvent* mouseEvent = aGUIEvent->AsMouseEvent();
7043 bool isWindowLevelMouseExit =
7044 (aGUIEvent->mMessage == eMouseExitFromWidget) &&
7045 (mouseEvent &&
7046 (mouseEvent->mExitFrom.value() == WidgetMouseEvent::ePlatformTopLevel ||
7047 mouseEvent->mExitFrom.value() == WidgetMouseEvent::ePuppet));
7049 // Get the frame at the event point. However, don't do this if we're
7050 // capturing and retargeting the event because the captured frame will
7051 // be used instead below. Also keep using the root frame if we're dealing
7052 // with a window-level mouse exit event since we want to start sending
7053 // mouse out events at the root EventStateManager.
7054 EventTargetData eventTargetData(rootFrameToHandleEvent);
7055 if (!isCaptureRetargeted && !isWindowLevelMouseExit &&
7056 !pointerCapturingElement) {
7057 if (!ComputeEventTargetFrameAndPresShellAtEventPoint(
7058 rootFrameToHandleEvent, aGUIEvent, &eventTargetData)) {
7059 *aEventStatus = nsEventStatus_eIgnore;
7060 return NS_OK;
7064 // if a node is capturing the mouse, check if the event needs to be
7065 // retargeted at the capturing content instead. This will be the case when
7066 // capture retargeting is being used, no frame was found or the frame's
7067 // content is not a descendant of the capturing content.
7068 if (capturingContent && !pointerCapturingElement &&
7069 (PresShell::sCapturingContentInfo.mRetargetToElement ||
7070 !eventTargetData.mFrame->GetContent() ||
7071 !nsContentUtils::ContentIsCrossDocDescendantOf(
7072 eventTargetData.mFrame->GetContent(), capturingContent))) {
7073 // A check was already done above to ensure that capturingContent is
7074 // in this presshell.
7075 NS_ASSERTION(capturingContent->OwnerDoc() == GetDocument(),
7076 "Unexpected document");
7077 nsIFrame* capturingFrame = capturingContent->GetPrimaryFrame();
7078 if (capturingFrame) {
7079 eventTargetData.SetFrameAndComputePresShell(capturingFrame);
7083 if (NS_WARN_IF(!eventTargetData.mFrame)) {
7084 return NS_OK;
7087 // Suppress mouse event if it's being targeted at an element inside
7088 // a document which needs events suppressed
7089 if (MaybeDiscardOrDelayMouseEvent(eventTargetData.mFrame, aGUIEvent)) {
7090 return NS_OK;
7093 // Check if we have an active EventStateManager which isn't the
7094 // EventStateManager of the current PresContext. If that is the case, and
7095 // mouse is over some ancestor document, forward event handling to the
7096 // active document. This way content can get mouse events even when mouse
7097 // is over the chrome or outside the window.
7098 if (eventTargetData.MaybeRetargetToActiveDocument(aGUIEvent) &&
7099 NS_WARN_IF(!eventTargetData.mFrame)) {
7100 return NS_OK;
7103 if (!eventTargetData.ComputeElementFromFrame(aGUIEvent)) {
7104 return NS_OK;
7106 // Note that even if ComputeElementFromFrame() returns true,
7107 // eventTargetData.mContent can be nullptr here.
7109 // Dispatch a pointer event if Pointer Events is enabled. Note that if
7110 // pointer event listeners change the layout, eventTargetData is
7111 // automatically updated.
7112 if (!DispatchPrecedingPointerEvent(
7113 aFrameForPresShell, aGUIEvent, pointerCapturingElement,
7114 aDontRetargetEvents, &eventTargetData, aEventStatus)) {
7115 return NS_OK;
7118 // frame could be null after dispatching pointer events.
7119 // XXX Despite of this comment, we update the event target data outside
7120 // DispatchPrecedingPointerEvent(). Can we make it call
7121 // UpdateTouchEventTarget()?
7122 eventTargetData.UpdateTouchEventTarget(aGUIEvent);
7124 // Handle the event in the correct shell.
7125 // We pass the subshell's root frame as the frame to start from. This is
7126 // the only correct alternative; if the event was captured then it
7127 // must have been captured by us or some ancestor shell and we
7128 // now ask the subshell to dispatch it normally.
7129 EventHandler eventHandler(*eventTargetData.mPresShell);
7130 AutoCurrentEventInfoSetter eventInfoSetter(eventHandler, eventTargetData);
7131 // eventTargetData is on the stack and is guaranteed to keep its
7132 // mOverrideClickTarget alive, so we can just use MOZ_KnownLive here.
7133 nsresult rv = eventHandler.HandleEventWithCurrentEventInfo(
7134 aGUIEvent, aEventStatus, true,
7135 MOZ_KnownLive(eventTargetData.mOverrideClickTarget));
7136 return rv;
7139 bool PresShell::EventHandler::MaybeFlushPendingNotifications(
7140 WidgetGUIEvent* aGUIEvent) {
7141 MOZ_ASSERT(aGUIEvent);
7143 switch (aGUIEvent->mMessage) {
7144 case eMouseDown:
7145 case eMouseUp: {
7146 RefPtr<nsPresContext> presContext = mPresShell->GetPresContext();
7147 if (NS_WARN_IF(!presContext)) {
7148 return false;
7150 uint64_t framesConstructedCount = presContext->FramesConstructedCount();
7151 uint64_t framesReflowedCount = presContext->FramesReflowedCount();
7153 MOZ_KnownLive(mPresShell)->FlushPendingNotifications(FlushType::Layout);
7154 return framesConstructedCount != presContext->FramesConstructedCount() ||
7155 framesReflowedCount != presContext->FramesReflowedCount();
7157 default:
7158 return false;
7162 // The type of coordinates to use for hit-testing input events
7163 // that are relative to the RCD's viewport frame.
7164 // On most platforms, use visual coordinates so that scrollbars
7165 // can be targeted.
7166 // On mobile, use layout coordinates because hit-testing in
7167 // visual coordinates clashes with mobile viewport sizing, where
7168 // the ViewportFrame is sized to the initial containing block
7169 // (ICB) size, which is in layout coordinates. This is fine
7170 // because we don't need to be able to target scrollbars on mobile
7171 // (scrollbar dragging isn't supported).
7172 static ViewportType ViewportTypeForInputEventsRelativeToRoot() {
7173 #ifdef MOZ_WIDGET_ANDROID
7174 return ViewportType::Layout;
7175 #else
7176 return ViewportType::Visual;
7177 #endif
7180 nsIFrame* PresShell::EventHandler::GetFrameToHandleNonTouchEvent(
7181 nsIFrame* aRootFrameToHandleEvent, WidgetGUIEvent* aGUIEvent) {
7182 MOZ_ASSERT(aGUIEvent);
7183 MOZ_ASSERT(aGUIEvent->mClass != eTouchEventClass);
7185 ViewportType viewportType = ViewportType::Layout;
7186 if (aRootFrameToHandleEvent->Type() == LayoutFrameType::Viewport) {
7187 nsPresContext* pc = aRootFrameToHandleEvent->PresContext();
7188 if (pc->IsChrome()) {
7189 viewportType = ViewportType::Visual;
7190 } else if (pc->IsRootContentDocumentCrossProcess()) {
7191 viewportType = ViewportTypeForInputEventsRelativeToRoot();
7194 RelativeTo relativeTo{aRootFrameToHandleEvent, viewportType};
7195 nsPoint eventPoint =
7196 nsLayoutUtils::GetEventCoordinatesRelativeTo(aGUIEvent, relativeTo);
7198 uint32_t flags = 0;
7199 if (aGUIEvent->mClass == eMouseEventClass) {
7200 WidgetMouseEvent* mouseEvent = aGUIEvent->AsMouseEvent();
7201 if (mouseEvent && mouseEvent->mIgnoreRootScrollFrame) {
7202 flags |= INPUT_IGNORE_ROOT_SCROLL_FRAME;
7206 nsIFrame* targetFrame =
7207 FindFrameTargetedByInputEvent(aGUIEvent, relativeTo, eventPoint, flags);
7208 if (!targetFrame) {
7209 return aRootFrameToHandleEvent;
7212 if (targetFrame->PresShell() == mPresShell) {
7213 // If found target is in mPresShell, we've already found it in the latest
7214 // layout so that we can use it.
7215 return targetFrame;
7218 // If target is in a child document, we've not flushed its layout yet.
7219 PresShell* childPresShell = targetFrame->PresShell();
7220 EventHandler childEventHandler(*childPresShell);
7221 AutoWeakFrame weakFrame(aRootFrameToHandleEvent);
7222 bool layoutChanged =
7223 childEventHandler.MaybeFlushPendingNotifications(aGUIEvent);
7224 if (!weakFrame.IsAlive()) {
7225 // Stop handling the event if the root frame to handle event is destroyed
7226 // by the reflow. (but why?)
7227 return nullptr;
7229 if (!layoutChanged) {
7230 // If the layout in the child PresShell hasn't been changed, we don't
7231 // need to recompute the target.
7232 return targetFrame;
7235 // Finally, we need to recompute the target with the latest layout.
7236 targetFrame =
7237 FindFrameTargetedByInputEvent(aGUIEvent, relativeTo, eventPoint, flags);
7239 return targetFrame ? targetFrame : aRootFrameToHandleEvent;
7242 bool PresShell::EventHandler::ComputeEventTargetFrameAndPresShellAtEventPoint(
7243 nsIFrame* aRootFrameToHandleEvent, WidgetGUIEvent* aGUIEvent,
7244 EventTargetData* aEventTargetData) {
7245 MOZ_ASSERT(aRootFrameToHandleEvent);
7246 MOZ_ASSERT(aGUIEvent);
7247 MOZ_ASSERT(aEventTargetData);
7249 if (aGUIEvent->mClass == eTouchEventClass) {
7250 nsIFrame* targetFrameAtTouchEvent = TouchManager::SetupTarget(
7251 aGUIEvent->AsTouchEvent(), aRootFrameToHandleEvent);
7252 aEventTargetData->SetFrameAndComputePresShell(targetFrameAtTouchEvent);
7253 return true;
7256 nsIFrame* targetFrame =
7257 GetFrameToHandleNonTouchEvent(aRootFrameToHandleEvent, aGUIEvent);
7258 aEventTargetData->SetFrameAndComputePresShell(targetFrame);
7259 return !!aEventTargetData->mFrame;
7262 bool PresShell::EventHandler::DispatchPrecedingPointerEvent(
7263 nsIFrame* aFrameForPresShell, WidgetGUIEvent* aGUIEvent,
7264 nsIContent* aPointerCapturingContent, bool aDontRetargetEvents,
7265 EventTargetData* aEventTargetData, nsEventStatus* aEventStatus) {
7266 MOZ_ASSERT(aFrameForPresShell);
7267 MOZ_ASSERT(aGUIEvent);
7268 MOZ_ASSERT(aEventTargetData);
7269 MOZ_ASSERT(aEventStatus);
7271 // Dispatch pointer events from the mouse or touch events. Regarding
7272 // pointer events from mouse, we should dispatch those pointer events to
7273 // the same target as the source mouse events. We pass the frame found
7274 // in hit test to PointerEventHandler and dispatch pointer events to it.
7276 // Regarding pointer events from touch, the behavior is different. Touch
7277 // events are dispatched to the same target as the target of touchstart.
7278 // Multiple touch points must be dispatched to the same document. Pointer
7279 // events from touch can be dispatched to different documents. We Pass the
7280 // original frame to PointerEventHandler, reentry PresShell::HandleEvent,
7281 // and do hit test for each point.
7282 nsIFrame* targetFrame = aGUIEvent->mClass == eTouchEventClass
7283 ? aFrameForPresShell
7284 : aEventTargetData->mFrame;
7286 if (aPointerCapturingContent) {
7287 aEventTargetData->mOverrideClickTarget =
7288 GetOverrideClickTarget(aGUIEvent, aFrameForPresShell);
7289 aEventTargetData->mPresShell =
7290 PresShell::GetShellForEventTarget(nullptr, aPointerCapturingContent);
7291 if (!aEventTargetData->mPresShell) {
7292 // If we can't process event for the capturing content, release
7293 // the capture.
7294 PointerEventHandler::ReleaseIfCaptureByDescendant(
7295 aPointerCapturingContent);
7296 return false;
7299 targetFrame = aPointerCapturingContent->GetPrimaryFrame();
7300 aEventTargetData->mFrame = targetFrame;
7303 AutoWeakFrame weakTargetFrame(targetFrame);
7304 AutoWeakFrame weakFrame(aEventTargetData->mFrame);
7305 nsCOMPtr<nsIContent> content(aEventTargetData->mContent);
7306 RefPtr<PresShell> presShell(aEventTargetData->mPresShell);
7307 nsCOMPtr<nsIContent> targetContent;
7308 PointerEventHandler::DispatchPointerFromMouseOrTouch(
7309 presShell, aEventTargetData->mFrame, content, aGUIEvent,
7310 aDontRetargetEvents, aEventStatus, getter_AddRefs(targetContent));
7312 // If the target frame is alive, the caller should keep handling the event
7313 // unless event target frame is destroyed.
7314 if (weakTargetFrame.IsAlive()) {
7315 return weakFrame.IsAlive();
7318 // If the event is not a mouse event, the caller should keep handling the
7319 // event unless event target frame is destroyed. Note that this case is
7320 // not defined by the spec.
7321 if (aGUIEvent->mClass != eMouseEventClass) {
7322 return weakFrame.IsAlive();
7325 // Spec defines that mouse events must be dispatched to the same target as
7326 // the pointer event. If the target is no longer participating in its
7327 // ownerDocument's tree, fire the event at the original target's nearest
7328 // ancestor node
7329 if (!targetContent) {
7330 return false;
7333 // XXX Why don't we reset aEventTargetData->mContent here?
7334 aEventTargetData->mFrame = targetContent->GetPrimaryFrame();
7335 aEventTargetData->mPresShell = PresShell::GetShellForEventTarget(
7336 aEventTargetData->mFrame, targetContent);
7338 // If new target PresShel is not found, we cannot keep handling the event.
7339 return !!aEventTargetData->mPresShell;
7342 bool PresShell::EventHandler::MaybeHandleEventWithAccessibleCaret(
7343 nsIFrame* aFrameForPresShell, WidgetGUIEvent* aGUIEvent,
7344 nsEventStatus* aEventStatus) {
7345 MOZ_ASSERT(aGUIEvent);
7346 MOZ_ASSERT(aEventStatus);
7348 // Don't dispatch event to AccessibleCaretEventHub when the event status
7349 // is nsEventStatus_eConsumeNoDefault. This might be happened when content
7350 // preventDefault on the pointer events. In such case, we also call
7351 // preventDefault on mouse events to stop default behaviors.
7352 if (*aEventStatus == nsEventStatus_eConsumeNoDefault) {
7353 return false;
7356 if (!AccessibleCaretEnabled(GetDocument()->GetDocShell())) {
7357 return false;
7360 // AccessibleCaretEventHub handles only mouse, touch, and keyboard events.
7361 if (aGUIEvent->mClass != eMouseEventClass &&
7362 aGUIEvent->mClass != eTouchEventClass &&
7363 aGUIEvent->mClass != eKeyboardEventClass) {
7364 return false;
7367 // First, try the event hub at the event point to handle a long press to
7368 // select a word in an unfocused window.
7369 do {
7370 EventTargetData eventTargetData(nullptr);
7371 if (!ComputeEventTargetFrameAndPresShellAtEventPoint(
7372 aFrameForPresShell, aGUIEvent, &eventTargetData)) {
7373 break;
7376 if (!eventTargetData.mPresShell) {
7377 break;
7380 RefPtr<AccessibleCaretEventHub> eventHub =
7381 eventTargetData.mPresShell->GetAccessibleCaretEventHub();
7382 if (!eventHub) {
7383 break;
7386 *aEventStatus = eventHub->HandleEvent(aGUIEvent);
7387 if (*aEventStatus != nsEventStatus_eConsumeNoDefault) {
7388 break;
7391 // If the event is consumed, cancel APZC panning by setting
7392 // mMultipleActionsPrevented.
7393 aGUIEvent->mFlags.mMultipleActionsPrevented = true;
7394 return true;
7395 } while (false);
7397 // Then, we target the event to the event hub at the focused window.
7398 nsCOMPtr<nsPIDOMWindowOuter> window = GetFocusedDOMWindowInOurWindow();
7399 if (!window) {
7400 return false;
7402 RefPtr<Document> retargetEventDoc = window->GetExtantDoc();
7403 if (!retargetEventDoc) {
7404 return false;
7406 RefPtr<PresShell> presShell = retargetEventDoc->GetPresShell();
7407 if (!presShell) {
7408 return false;
7411 RefPtr<AccessibleCaretEventHub> eventHub =
7412 presShell->GetAccessibleCaretEventHub();
7413 if (!eventHub) {
7414 return false;
7416 *aEventStatus = eventHub->HandleEvent(aGUIEvent);
7417 if (*aEventStatus != nsEventStatus_eConsumeNoDefault) {
7418 return false;
7420 // If the event is consumed, cancel APZC panning by setting
7421 // mMultipleActionsPrevented.
7422 aGUIEvent->mFlags.mMultipleActionsPrevented = true;
7423 return true;
7426 bool PresShell::EventHandler::MaybeDiscardEvent(WidgetGUIEvent* aGUIEvent) {
7427 MOZ_ASSERT(aGUIEvent);
7429 // If it is safe to dispatch events now, don't discard the event.
7430 if (nsContentUtils::IsSafeToRunScript()) {
7431 return false;
7434 // If the event does not cause dispatching DOM event (i.e., internal event),
7435 // we can keep handling it even when it's not safe to run script.
7436 if (!aGUIEvent->IsAllowedToDispatchDOMEvent()) {
7437 return false;
7440 // If the event is a composition event, we need to let IMEStateManager know
7441 // it's discarded because it needs to listen all composition events to manage
7442 // TextComposition instance.
7443 if (aGUIEvent->mClass == eCompositionEventClass) {
7444 IMEStateManager::OnCompositionEventDiscarded(
7445 aGUIEvent->AsCompositionEvent());
7448 #ifdef DEBUG
7449 if (aGUIEvent->IsIMERelatedEvent()) {
7450 nsPrintfCString warning("%s event is discarded",
7451 ToChar(aGUIEvent->mMessage));
7452 NS_WARNING(warning.get());
7454 #endif // #ifdef DEBUG
7456 nsContentUtils::WarnScriptWasIgnored(GetDocument());
7457 return true;
7460 // static
7461 nsIContent* PresShell::EventHandler::GetCapturingContentFor(
7462 WidgetGUIEvent* aGUIEvent) {
7463 return (aGUIEvent->mClass == ePointerEventClass ||
7464 aGUIEvent->mClass == eWheelEventClass ||
7465 aGUIEvent->HasMouseEventMessage())
7466 ? PresShell::GetCapturingContent()
7467 : nullptr;
7470 bool PresShell::EventHandler::GetRetargetEventDocument(
7471 WidgetGUIEvent* aGUIEvent, Document** aRetargetEventDocument) {
7472 MOZ_ASSERT(aGUIEvent);
7473 MOZ_ASSERT(aRetargetEventDocument);
7475 *aRetargetEventDocument = nullptr;
7477 // key and IME related events should not cross top level window boundary.
7478 // Basically, such input events should be fired only on focused widget.
7479 // However, some IMEs might need to clean up composition after focused
7480 // window is deactivated. And also some tests on MozMill want to test key
7481 // handling on deactivated window because MozMill window can be activated
7482 // during tests. So, there is no merit the events should be redirected to
7483 // active window. So, the events should be handled on the last focused
7484 // content in the last focused DOM window in same top level window.
7485 // Note, if no DOM window has been focused yet, we can discard the events.
7486 if (aGUIEvent->IsTargetedAtFocusedWindow()) {
7487 nsCOMPtr<nsPIDOMWindowOuter> window = GetFocusedDOMWindowInOurWindow();
7488 // No DOM window in same top level window has not been focused yet,
7489 // discard the events.
7490 if (!window) {
7491 return false;
7494 RefPtr<Document> retargetEventDoc = window->GetExtantDoc();
7495 if (!retargetEventDoc) {
7496 return false;
7498 retargetEventDoc.forget(aRetargetEventDocument);
7499 return true;
7502 nsIContent* capturingContent =
7503 EventHandler::GetCapturingContentFor(aGUIEvent);
7504 if (capturingContent) {
7505 // if the mouse is being captured then retarget the mouse event at the
7506 // document that is being captured.
7507 RefPtr<Document> retargetEventDoc = capturingContent->GetComposedDoc();
7508 retargetEventDoc.forget(aRetargetEventDocument);
7509 return true;
7512 #ifdef ANDROID
7513 if (aGUIEvent->mClass == eTouchEventClass ||
7514 aGUIEvent->mClass == eMouseEventClass ||
7515 aGUIEvent->mClass == eWheelEventClass) {
7516 RefPtr<Document> retargetEventDoc = mPresShell->GetPrimaryContentDocument();
7517 retargetEventDoc.forget(aRetargetEventDocument);
7518 return true;
7520 #endif // #ifdef ANDROID
7522 // When we don't find another document to handle the event, we need to keep
7523 // handling the event by ourselves.
7524 return true;
7527 nsIFrame* PresShell::EventHandler::GetFrameForHandlingEventWith(
7528 WidgetGUIEvent* aGUIEvent, Document* aRetargetDocument,
7529 nsIFrame* aFrameForPresShell) {
7530 MOZ_ASSERT(aGUIEvent);
7531 MOZ_ASSERT(aRetargetDocument);
7533 RefPtr<PresShell> retargetPresShell = aRetargetDocument->GetPresShell();
7534 // Even if the document doesn't have PresShell, i.e., it's invisible, we
7535 // need to dispatch only KeyboardEvent in its nearest visible document
7536 // because key focus shouldn't be caught by invisible document.
7537 if (!retargetPresShell) {
7538 if (!aGUIEvent->HasKeyEventMessage()) {
7539 return nullptr;
7541 Document* retargetEventDoc = aRetargetDocument;
7542 while (!retargetPresShell) {
7543 retargetEventDoc = retargetEventDoc->GetInProcessParentDocument();
7544 if (!retargetEventDoc) {
7545 return nullptr;
7547 retargetPresShell = retargetEventDoc->GetPresShell();
7551 // If the found PresShell is this instance, caller needs to keep handling
7552 // aGUIEvent by itself. Therefore, return the given frame which was set
7553 // to aFrame of HandleEvent().
7554 if (retargetPresShell == mPresShell) {
7555 return aFrameForPresShell;
7558 // Use root frame of the new PresShell if there is.
7559 nsIFrame* rootFrame = retargetPresShell->GetRootFrame();
7560 if (rootFrame) {
7561 return rootFrame;
7564 // Otherwise, and if aGUIEvent requires content of PresShell, caller should
7565 // stop handling the event.
7566 if (aGUIEvent->mMessage == eQueryTextContent ||
7567 aGUIEvent->IsContentCommandEvent()) {
7568 return nullptr;
7571 // Otherwise, use nearest ancestor frame which includes the PresShell.
7572 return GetNearestFrameContainingPresShell(retargetPresShell);
7575 bool PresShell::EventHandler::MaybeHandleEventWithAnotherPresShell(
7576 nsIFrame* aFrameForPresShell, WidgetGUIEvent* aGUIEvent,
7577 nsEventStatus* aEventStatus, nsresult* aRv) {
7578 MOZ_ASSERT(aGUIEvent);
7579 MOZ_ASSERT(aEventStatus);
7580 MOZ_ASSERT(aRv);
7582 *aRv = NS_OK;
7584 RefPtr<Document> retargetEventDoc;
7585 if (!GetRetargetEventDocument(aGUIEvent, getter_AddRefs(retargetEventDoc))) {
7586 // Nobody can handle this event. So, treat as handled by somebody to make
7587 // caller do nothing anymore.
7588 return true;
7591 // If there is no proper retarget document, the caller should handle the
7592 // event by itself.
7593 if (!retargetEventDoc) {
7594 return false;
7597 nsIFrame* frame = GetFrameForHandlingEventWith(aGUIEvent, retargetEventDoc,
7598 aFrameForPresShell);
7599 if (!frame) {
7600 // Nobody can handle this event. So, treat as handled by somebody to make
7601 // caller do nothing anymore.
7602 return true;
7605 // If we reached same frame as set to HandleEvent(), the caller should handle
7606 // the event by itself.
7607 if (frame == aFrameForPresShell) {
7608 return false;
7611 // We need to handle aGUIEvent with another PresShell.
7612 RefPtr<PresShell> presShell = frame->PresContext()->PresShell();
7613 *aRv = presShell->HandleEvent(frame, aGUIEvent, true, aEventStatus);
7614 return true;
7617 bool PresShell::EventHandler::MaybeDiscardOrDelayKeyboardEvent(
7618 WidgetGUIEvent* aGUIEvent) {
7619 MOZ_ASSERT(aGUIEvent);
7621 if (aGUIEvent->mClass != eKeyboardEventClass) {
7622 return false;
7625 Document* document = GetDocument();
7626 if (!document || !document->EventHandlingSuppressed()) {
7627 return false;
7630 MOZ_ASSERT_IF(InputTaskManager::CanSuspendInputEvent(),
7631 !InputTaskManager::Get()->IsSuspended());
7633 if (aGUIEvent->mMessage == eKeyDown) {
7634 mPresShell->mNoDelayedKeyEvents = true;
7635 } else if (!mPresShell->mNoDelayedKeyEvents) {
7636 UniquePtr<DelayedKeyEvent> delayedKeyEvent =
7637 MakeUnique<DelayedKeyEvent>(aGUIEvent->AsKeyboardEvent());
7638 mPresShell->mDelayedEvents.AppendElement(std::move(delayedKeyEvent));
7640 aGUIEvent->mFlags.mIsSuppressedOrDelayed = true;
7641 return true;
7644 bool PresShell::EventHandler::MaybeDiscardOrDelayMouseEvent(
7645 nsIFrame* aFrameToHandleEvent, WidgetGUIEvent* aGUIEvent) {
7646 MOZ_ASSERT(aFrameToHandleEvent);
7647 MOZ_ASSERT(aGUIEvent);
7649 if (aGUIEvent->mClass != eMouseEventClass) {
7650 return false;
7653 if (!aFrameToHandleEvent->PresContext()
7654 ->Document()
7655 ->EventHandlingSuppressed()) {
7656 return false;
7659 MOZ_ASSERT_IF(InputTaskManager::CanSuspendInputEvent() &&
7660 aGUIEvent->mMessage != eMouseMove,
7661 !InputTaskManager::Get()->IsSuspended());
7663 RefPtr<PresShell> ps = aFrameToHandleEvent->PresShell();
7665 if (aGUIEvent->mMessage == eMouseDown) {
7666 ps->mNoDelayedMouseEvents = true;
7667 } else if (!ps->mNoDelayedMouseEvents &&
7668 (aGUIEvent->mMessage == eMouseUp ||
7669 // contextmenu is triggered after right mouseup on Windows and
7670 // right mousedown on other platforms.
7671 aGUIEvent->mMessage == eContextMenu ||
7672 aGUIEvent->mMessage == eMouseExitFromWidget)) {
7673 UniquePtr<DelayedMouseEvent> delayedMouseEvent =
7674 MakeUnique<DelayedMouseEvent>(aGUIEvent->AsMouseEvent());
7675 ps->mDelayedEvents.AppendElement(std::move(delayedMouseEvent));
7678 // If there is a suppressed event listener associated with the document,
7679 // notify it about the suppressed mouse event. This allows devtools
7680 // features to continue receiving mouse events even when the devtools
7681 // debugger has paused execution in a page.
7682 RefPtr<EventListener> suppressedListener = aFrameToHandleEvent->PresContext()
7683 ->Document()
7684 ->GetSuppressedEventListener();
7685 if (!suppressedListener ||
7686 aGUIEvent->AsMouseEvent()->mReason == WidgetMouseEvent::eSynthesized) {
7687 return true;
7690 nsCOMPtr<nsIContent> targetContent;
7691 aFrameToHandleEvent->GetContentForEvent(aGUIEvent,
7692 getter_AddRefs(targetContent));
7693 if (targetContent) {
7694 aGUIEvent->mTarget = targetContent;
7697 nsCOMPtr<EventTarget> eventTarget = aGUIEvent->mTarget;
7698 RefPtr<Event> event = EventDispatcher::CreateEvent(
7699 eventTarget, aFrameToHandleEvent->PresContext(), aGUIEvent, u""_ns);
7701 suppressedListener->HandleEvent(*event);
7702 return true;
7705 nsIFrame* PresShell::EventHandler::MaybeFlushThrottledStyles(
7706 nsIFrame* aFrameForPresShell) {
7707 if (!GetDocument()) {
7708 // XXX Only when mPresShell has document, we'll try to look for a frame
7709 // containing mPresShell even if given frame is nullptr. Does this
7710 // make sense?
7711 return aFrameForPresShell;
7714 PresShell* rootPresShell = mPresShell->GetRootPresShell();
7715 if (NS_WARN_IF(!rootPresShell)) {
7716 return nullptr;
7718 Document* rootDocument = rootPresShell->GetDocument();
7719 if (NS_WARN_IF(!rootDocument)) {
7720 return nullptr;
7723 AutoWeakFrame weakFrameForPresShell(aFrameForPresShell);
7724 { // scope for scriptBlocker.
7725 nsAutoScriptBlocker scriptBlocker;
7726 FlushThrottledStyles(*rootDocument);
7729 if (weakFrameForPresShell.IsAlive()) {
7730 return aFrameForPresShell;
7733 return GetNearestFrameContainingPresShell(mPresShell);
7736 nsIFrame* PresShell::EventHandler::ComputeRootFrameToHandleEvent(
7737 nsIFrame* aFrameForPresShell, WidgetGUIEvent* aGUIEvent,
7738 nsIContent* aCapturingContent, bool* aIsCapturingContentIgnored,
7739 bool* aIsCaptureRetargeted) {
7740 MOZ_ASSERT(aFrameForPresShell);
7741 MOZ_ASSERT(aGUIEvent);
7742 MOZ_ASSERT(aIsCapturingContentIgnored);
7743 MOZ_ASSERT(aIsCaptureRetargeted);
7745 nsIFrame* rootFrameToHandleEvent = ComputeRootFrameToHandleEventWithPopup(
7746 aFrameForPresShell, aGUIEvent, aCapturingContent,
7747 aIsCapturingContentIgnored);
7748 if (*aIsCapturingContentIgnored) {
7749 // If the capturing content is ignored, we don't need to respect it.
7750 return rootFrameToHandleEvent;
7753 if (!aCapturingContent) {
7754 return rootFrameToHandleEvent;
7757 // If we have capturing content, let's compute root frame with it again.
7758 return ComputeRootFrameToHandleEventWithCapturingContent(
7759 rootFrameToHandleEvent, aCapturingContent, aIsCapturingContentIgnored,
7760 aIsCaptureRetargeted);
7763 nsIFrame* PresShell::EventHandler::ComputeRootFrameToHandleEventWithPopup(
7764 nsIFrame* aRootFrameToHandleEvent, WidgetGUIEvent* aGUIEvent,
7765 nsIContent* aCapturingContent, bool* aIsCapturingContentIgnored) {
7766 MOZ_ASSERT(aRootFrameToHandleEvent);
7767 MOZ_ASSERT(aGUIEvent);
7768 MOZ_ASSERT(aIsCapturingContentIgnored);
7770 *aIsCapturingContentIgnored = false;
7772 nsPresContext* framePresContext = aRootFrameToHandleEvent->PresContext();
7773 nsPresContext* rootPresContext = framePresContext->GetRootPresContext();
7774 NS_ASSERTION(rootPresContext == GetPresContext()->GetRootPresContext(),
7775 "How did we end up outside the connected "
7776 "prescontext/viewmanager hierarchy?");
7777 nsIFrame* popupFrame = nsLayoutUtils::GetPopupFrameForEventCoordinates(
7778 rootPresContext, aGUIEvent);
7779 if (!popupFrame) {
7780 return aRootFrameToHandleEvent;
7783 // If a remote browser is currently capturing input break out if we
7784 // detect a chrome generated popup.
7785 // XXXedgar, do we need to check fission OOP iframe?
7786 if (aCapturingContent &&
7787 EventStateManager::IsTopLevelRemoteTarget(aCapturingContent)) {
7788 *aIsCapturingContentIgnored = true;
7791 // If the popupFrame is an ancestor of the 'frame', the frame should
7792 // handle the event, otherwise, the popup should handle it.
7793 if (nsContentUtils::ContentIsCrossDocDescendantOf(
7794 framePresContext->GetPresShell()->GetDocument(),
7795 popupFrame->GetContent())) {
7796 return aRootFrameToHandleEvent;
7799 // If we aren't starting our event dispatch from the root frame of the
7800 // root prescontext, then someone must be capturing the mouse. In that
7801 // case we only want to use the popup list if the capture is
7802 // inside the popup.
7803 if (framePresContext == rootPresContext &&
7804 aRootFrameToHandleEvent == FrameConstructor()->GetRootFrame()) {
7805 return popupFrame;
7808 if (aCapturingContent && !*aIsCapturingContentIgnored &&
7809 aCapturingContent->IsInclusiveDescendantOf(popupFrame->GetContent())) {
7810 return popupFrame;
7813 return aRootFrameToHandleEvent;
7816 nsIFrame*
7817 PresShell::EventHandler::ComputeRootFrameToHandleEventWithCapturingContent(
7818 nsIFrame* aRootFrameToHandleEvent, nsIContent* aCapturingContent,
7819 bool* aIsCapturingContentIgnored, bool* aIsCaptureRetargeted) {
7820 MOZ_ASSERT(aRootFrameToHandleEvent);
7821 MOZ_ASSERT(aCapturingContent);
7822 MOZ_ASSERT(aIsCapturingContentIgnored);
7823 MOZ_ASSERT(aIsCaptureRetargeted);
7825 *aIsCapturingContentIgnored = false;
7826 *aIsCaptureRetargeted = false;
7828 // If a capture is active, determine if the BrowsingContext is active. If
7829 // not, clear the capture and target the mouse event normally instead. This
7830 // would occur if the mouse button is held down while a tab change occurs.
7831 // If the BrowsingContext is active, look for a scrolling container.
7832 BrowsingContext* bc = GetPresContext()->Document()->GetBrowsingContext();
7833 if (!bc || !bc->IsActive()) {
7834 ClearMouseCapture();
7835 *aIsCapturingContentIgnored = true;
7836 return aRootFrameToHandleEvent;
7839 if (PresShell::sCapturingContentInfo.mRetargetToElement) {
7840 *aIsCaptureRetargeted = true;
7841 return aRootFrameToHandleEvent;
7844 // A check was already done above to ensure that aCapturingContent is
7845 // in this presshell.
7846 NS_ASSERTION(aCapturingContent->OwnerDoc() == GetDocument(),
7847 "Unexpected document");
7848 nsIFrame* captureFrame = aCapturingContent->GetPrimaryFrame();
7849 if (!captureFrame) {
7850 return aRootFrameToHandleEvent;
7853 // scrollable frames should use the scrolling container as the root instead
7854 // of the document
7855 nsIScrollableFrame* scrollFrame = do_QueryFrame(captureFrame);
7856 return scrollFrame ? scrollFrame->GetScrolledFrame()
7857 : aRootFrameToHandleEvent;
7860 nsresult
7861 PresShell::EventHandler::HandleEventWithPointerCapturingContentWithoutItsFrame(
7862 nsIFrame* aFrameForPresShell, WidgetGUIEvent* aGUIEvent,
7863 nsIContent* aPointerCapturingContent, nsEventStatus* aEventStatus) {
7864 MOZ_ASSERT(aGUIEvent);
7865 MOZ_ASSERT(aPointerCapturingContent);
7866 MOZ_ASSERT(!aPointerCapturingContent->GetPrimaryFrame(),
7867 "Handle the event with frame rather than only with the content");
7868 MOZ_ASSERT(aEventStatus);
7870 RefPtr<PresShell> presShellForCapturingContent =
7871 PresShell::GetShellForEventTarget(nullptr, aPointerCapturingContent);
7872 if (!presShellForCapturingContent) {
7873 // If we can't process event for the capturing content, release
7874 // the capture.
7875 PointerEventHandler::ReleaseIfCaptureByDescendant(aPointerCapturingContent);
7876 return NS_OK;
7879 nsCOMPtr<nsIContent> overrideClickTarget =
7880 GetOverrideClickTarget(aGUIEvent, aFrameForPresShell);
7882 // Dispatch events to the capturing content even it's frame is
7883 // destroyed.
7884 PointerEventHandler::DispatchPointerFromMouseOrTouch(
7885 presShellForCapturingContent, nullptr, aPointerCapturingContent,
7886 aGUIEvent, false, aEventStatus, nullptr);
7888 if (presShellForCapturingContent == mPresShell) {
7889 return HandleEventWithTarget(aGUIEvent, nullptr, aPointerCapturingContent,
7890 aEventStatus, true, nullptr,
7891 overrideClickTarget);
7894 EventHandler eventHandlerForCapturingContent(
7895 std::move(presShellForCapturingContent));
7896 return eventHandlerForCapturingContent.HandleEventWithTarget(
7897 aGUIEvent, nullptr, aPointerCapturingContent, aEventStatus, true, nullptr,
7898 overrideClickTarget);
7901 nsresult PresShell::EventHandler::HandleEventAtFocusedContent(
7902 WidgetGUIEvent* aGUIEvent, nsEventStatus* aEventStatus) {
7903 MOZ_ASSERT(aGUIEvent);
7904 MOZ_ASSERT(aGUIEvent->IsTargetedAtFocusedContent());
7905 MOZ_ASSERT(aEventStatus);
7907 AutoCurrentEventInfoSetter eventInfoSetter(*this);
7909 RefPtr<Element> eventTargetElement =
7910 ComputeFocusedEventTargetElement(aGUIEvent);
7912 mPresShell->mCurrentEventFrame = nullptr;
7913 if (eventTargetElement) {
7914 nsresult rv = NS_OK;
7915 if (MaybeHandleEventWithAnotherPresShell(eventTargetElement, aGUIEvent,
7916 aEventStatus, &rv)) {
7917 return rv;
7921 // If we cannot handle the event with mPresShell, let's try to handle it
7922 // with parent PresShell.
7923 mPresShell->mCurrentEventContent = eventTargetElement;
7924 if (!mPresShell->GetCurrentEventContent() ||
7925 !mPresShell->GetCurrentEventFrame() ||
7926 InZombieDocument(mPresShell->mCurrentEventContent)) {
7927 return RetargetEventToParent(aGUIEvent, aEventStatus);
7930 nsresult rv =
7931 HandleEventWithCurrentEventInfo(aGUIEvent, aEventStatus, true, nullptr);
7932 return rv;
7935 Element* PresShell::EventHandler::ComputeFocusedEventTargetElement(
7936 WidgetGUIEvent* aGUIEvent) {
7937 MOZ_ASSERT(aGUIEvent);
7938 MOZ_ASSERT(aGUIEvent->IsTargetedAtFocusedContent());
7940 // key and IME related events go to the focused frame in this DOM window.
7941 nsPIDOMWindowOuter* window = GetDocument()->GetWindow();
7942 nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
7943 Element* eventTargetElement = nsFocusManager::GetFocusedDescendant(
7944 window, nsFocusManager::eOnlyCurrentWindow,
7945 getter_AddRefs(focusedWindow));
7947 // otherwise, if there is no focused content or the focused content has
7948 // no frame, just use the root content. This ensures that key events
7949 // still get sent to the window properly if nothing is focused or if a
7950 // frame goes away while it is focused.
7951 if (!eventTargetElement || !eventTargetElement->GetPrimaryFrame()) {
7952 eventTargetElement = GetDocument()->GetUnfocusedKeyEventTarget();
7955 switch (aGUIEvent->mMessage) {
7956 case eKeyDown:
7957 sLastKeyDownEventTargetElement = eventTargetElement;
7958 return eventTargetElement;
7959 case eKeyPress:
7960 case eKeyUp:
7961 if (!sLastKeyDownEventTargetElement) {
7962 return eventTargetElement;
7964 // If a different element is now focused for the keypress/keyup event
7965 // than what was focused during the keydown event, check if the new
7966 // focused element is not in a chrome document any more, and if so,
7967 // retarget the event back at the keydown target. This prevents a
7968 // content area from grabbing the focus from chrome in-between key
7969 // events.
7970 if (eventTargetElement) {
7971 bool keyDownIsChrome = nsContentUtils::IsChromeDoc(
7972 sLastKeyDownEventTargetElement->GetComposedDoc());
7973 if (keyDownIsChrome != nsContentUtils::IsChromeDoc(
7974 eventTargetElement->GetComposedDoc()) ||
7975 (keyDownIsChrome && BrowserParent::GetFrom(eventTargetElement))) {
7976 eventTargetElement = sLastKeyDownEventTargetElement;
7980 if (aGUIEvent->mMessage == eKeyUp) {
7981 sLastKeyDownEventTargetElement = nullptr;
7983 [[fallthrough]];
7984 default:
7985 return eventTargetElement;
7989 bool PresShell::EventHandler::MaybeHandleEventWithAnotherPresShell(
7990 Element* aEventTargetElement, WidgetGUIEvent* aGUIEvent,
7991 nsEventStatus* aEventStatus, nsresult* aRv) {
7992 MOZ_ASSERT(aEventTargetElement);
7993 MOZ_ASSERT(aGUIEvent);
7994 MOZ_ASSERT(!aGUIEvent->IsUsingCoordinates());
7995 MOZ_ASSERT(aEventStatus);
7996 MOZ_ASSERT(aRv);
7998 Document* eventTargetDocument = aEventTargetElement->OwnerDoc();
7999 if (!eventTargetDocument || eventTargetDocument == GetDocument()) {
8000 *aRv = NS_OK;
8001 return false;
8004 RefPtr<PresShell> eventTargetPresShell = eventTargetDocument->GetPresShell();
8005 if (!eventTargetPresShell) {
8006 *aRv = NS_OK;
8007 return true; // No PresShell can handle the event.
8010 EventHandler eventHandler(std::move(eventTargetPresShell));
8011 *aRv = eventHandler.HandleRetargetedEvent(aGUIEvent, aEventStatus,
8012 aEventTargetElement);
8013 return true;
8016 nsresult PresShell::EventHandler::HandleEventWithFrameForPresShell(
8017 nsIFrame* aFrameForPresShell, WidgetGUIEvent* aGUIEvent,
8018 nsEventStatus* aEventStatus) {
8019 MOZ_ASSERT(aGUIEvent);
8020 MOZ_ASSERT(!aGUIEvent->IsUsingCoordinates());
8021 MOZ_ASSERT(!aGUIEvent->IsTargetedAtFocusedContent());
8022 MOZ_ASSERT(aEventStatus);
8024 AutoCurrentEventInfoSetter eventInfoSetter(*this, aFrameForPresShell,
8025 nullptr);
8027 nsresult rv = NS_OK;
8028 if (mPresShell->GetCurrentEventFrame()) {
8029 rv =
8030 HandleEventWithCurrentEventInfo(aGUIEvent, aEventStatus, true, nullptr);
8033 return rv;
8036 Document* PresShell::GetPrimaryContentDocument() {
8037 nsPresContext* context = GetPresContext();
8038 if (!context || !context->IsRoot()) {
8039 return nullptr;
8042 nsCOMPtr<nsIDocShellTreeItem> shellAsTreeItem = context->GetDocShell();
8043 if (!shellAsTreeItem) {
8044 return nullptr;
8047 nsCOMPtr<nsIDocShellTreeOwner> owner;
8048 shellAsTreeItem->GetTreeOwner(getter_AddRefs(owner));
8049 if (!owner) {
8050 return nullptr;
8053 // now get the primary content shell (active tab)
8054 nsCOMPtr<nsIDocShellTreeItem> item;
8055 owner->GetPrimaryContentShell(getter_AddRefs(item));
8056 nsCOMPtr<nsIDocShell> childDocShell = do_QueryInterface(item);
8057 if (!childDocShell) {
8058 return nullptr;
8061 return childDocShell->GetExtantDocument();
8064 nsresult PresShell::EventHandler::HandleEventWithTarget(
8065 WidgetEvent* aEvent, nsIFrame* aNewEventFrame, nsIContent* aNewEventContent,
8066 nsEventStatus* aEventStatus, bool aIsHandlingNativeEvent,
8067 nsIContent** aTargetContent, nsIContent* aOverrideClickTarget) {
8068 MOZ_ASSERT(aEvent);
8069 MOZ_DIAGNOSTIC_ASSERT(aEvent->IsTrusted());
8071 #if DEBUG
8072 MOZ_ASSERT(!aNewEventFrame ||
8073 aNewEventFrame->PresContext()->GetPresShell() == mPresShell,
8074 "wrong shell");
8075 if (aNewEventContent) {
8076 Document* doc = aNewEventContent->GetComposedDoc();
8077 NS_ASSERTION(doc, "event for content that isn't in a document");
8078 // NOTE: We don't require that the document still have a PresShell.
8079 // See bug 1375940.
8081 #endif
8082 NS_ENSURE_STATE(!aNewEventContent ||
8083 aNewEventContent->GetComposedDoc() == GetDocument());
8084 if (aEvent->mClass == ePointerEventClass) {
8085 mPresShell->RecordPointerLocation(aEvent->AsMouseEvent());
8087 AutoPointerEventTargetUpdater updater(mPresShell, aEvent, aNewEventFrame,
8088 aTargetContent);
8089 AutoCurrentEventInfoSetter eventInfoSetter(*this, aNewEventFrame,
8090 aNewEventContent);
8091 nsresult rv = HandleEventWithCurrentEventInfo(aEvent, aEventStatus, false,
8092 aOverrideClickTarget);
8093 return rv;
8096 namespace {
8098 class MOZ_RAII AutoEventHandler final {
8099 public:
8100 AutoEventHandler(WidgetEvent* aEvent, Document* aDocument) : mEvent(aEvent) {
8101 MOZ_ASSERT(mEvent);
8102 MOZ_ASSERT(mEvent->IsTrusted());
8104 if (mEvent->mMessage == eMouseDown) {
8105 PresShell::ReleaseCapturingContent();
8106 PresShell::AllowMouseCapture(true);
8108 if (NeedsToUpdateCurrentMouseBtnState()) {
8109 WidgetMouseEvent* mouseEvent = mEvent->AsMouseEvent();
8110 if (mouseEvent) {
8111 EventStateManager::sCurrentMouseBtn = mouseEvent->mButton;
8116 ~AutoEventHandler() {
8117 if (mEvent->mMessage == eMouseDown) {
8118 PresShell::AllowMouseCapture(false);
8120 if (NeedsToUpdateCurrentMouseBtnState()) {
8121 EventStateManager::sCurrentMouseBtn = MouseButton::eNotPressed;
8125 protected:
8126 bool NeedsToUpdateCurrentMouseBtnState() const {
8127 return mEvent->mMessage == eMouseDown || mEvent->mMessage == eMouseUp ||
8128 mEvent->mMessage == ePointerDown || mEvent->mMessage == ePointerUp;
8131 WidgetEvent* mEvent;
8134 } // anonymous namespace
8136 nsresult PresShell::EventHandler::HandleEventWithCurrentEventInfo(
8137 WidgetEvent* aEvent, nsEventStatus* aEventStatus,
8138 bool aIsHandlingNativeEvent, nsIContent* aOverrideClickTarget) {
8139 MOZ_ASSERT(aEvent);
8140 MOZ_ASSERT(aEventStatus);
8142 RefPtr<EventStateManager> manager = GetPresContext()->EventStateManager();
8144 // If we cannot handle the event with mPresShell because of no target,
8145 // just record the response time.
8146 // XXX Is this intentional? In such case, the score is really good because
8147 // of nothing to do. So, it may make average and median better.
8148 if (NS_EVENT_NEEDS_FRAME(aEvent) && !mPresShell->GetCurrentEventFrame() &&
8149 !mPresShell->GetCurrentEventContent()) {
8150 RecordEventHandlingResponsePerformance(aEvent);
8151 return NS_OK;
8154 if (mPresShell->mCurrentEventContent && aEvent->IsTargetedAtFocusedWindow()) {
8155 if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) {
8156 // This may run script now. So, mPresShell might be destroyed after here.
8157 nsCOMPtr<nsIContent> currentEventContent =
8158 mPresShell->mCurrentEventContent;
8159 fm->FlushBeforeEventHandlingIfNeeded(currentEventContent);
8163 bool touchIsNew = false;
8164 if (!PrepareToDispatchEvent(aEvent, aEventStatus, &touchIsNew)) {
8165 return NS_OK;
8168 // We finished preparing to dispatch the event. So, let's record the
8169 // performance.
8170 RecordEventPreparationPerformance(aEvent);
8172 AutoHandlingUserInputStatePusher userInpStatePusher(
8173 UserActivation::IsUserInteractionEvent(aEvent), aEvent);
8174 AutoEventHandler eventHandler(aEvent, GetDocument());
8175 AutoPopupStatePusher popupStatePusher(
8176 PopupBlocker::GetEventPopupControlState(aEvent));
8178 // FIXME. If the event was reused, we need to clear the old target,
8179 // bug 329430
8180 aEvent->mTarget = nullptr;
8182 HandlingTimeAccumulator handlingTimeAccumulator(*this, aEvent);
8184 nsresult rv = DispatchEvent(manager, aEvent, touchIsNew, aEventStatus,
8185 aOverrideClickTarget);
8187 if (!mPresShell->IsDestroying() && aIsHandlingNativeEvent) {
8188 // Ensure that notifications to IME should be sent before getting next
8189 // native event from the event queue.
8190 // XXX Should we check the event message or event class instead of
8191 // using aIsHandlingNativeEvent?
8192 manager->TryToFlushPendingNotificationsToIME();
8195 FinalizeHandlingEvent(aEvent);
8197 RecordEventHandlingResponsePerformance(aEvent);
8199 return rv; // Result of DispatchEvent()
8202 nsresult PresShell::EventHandler::DispatchEvent(
8203 EventStateManager* aEventStateManager, WidgetEvent* aEvent,
8204 bool aTouchIsNew, nsEventStatus* aEventStatus,
8205 nsIContent* aOverrideClickTarget) {
8206 MOZ_ASSERT(aEventStateManager);
8207 MOZ_ASSERT(aEvent);
8208 MOZ_ASSERT(aEventStatus);
8210 // 1. Give event to event manager for pre event state changes and
8211 // generation of synthetic events.
8212 { // Scope for presContext
8213 RefPtr<nsPresContext> presContext = GetPresContext();
8214 nsCOMPtr<nsIContent> eventContent = mPresShell->mCurrentEventContent;
8215 nsresult rv = aEventStateManager->PreHandleEvent(
8216 presContext, aEvent, mPresShell->mCurrentEventFrame, eventContent,
8217 aEventStatus, aOverrideClickTarget);
8218 if (NS_FAILED(rv)) {
8219 return rv;
8223 // 2. Give event to the DOM for third party and JS use.
8224 bool wasHandlingKeyBoardEvent = nsContentUtils::IsHandlingKeyBoardEvent();
8225 if (aEvent->mClass == eKeyboardEventClass) {
8226 nsContentUtils::SetIsHandlingKeyBoardEvent(true);
8228 // If EventStateManager or something wants reply from remote process and
8229 // needs to win any other event listeners in chrome, the event is both
8230 // stopped its propagation and marked as "waiting reply from remote
8231 // process". In this case, PresShell shouldn't dispatch the event into
8232 // the DOM tree because they don't have a chance to stop propagation in
8233 // the system event group. On the other hand, if its propagation is not
8234 // stopped, that means that the event may be reserved by chrome. If it's
8235 // reserved by chrome, the event shouldn't be sent to any remote
8236 // processes. In this case, PresShell needs to dispatch the event to
8237 // the DOM tree for checking if it's reserved.
8238 if (aEvent->IsAllowedToDispatchDOMEvent() &&
8239 !(aEvent->PropagationStopped() &&
8240 aEvent->IsWaitingReplyFromRemoteProcess())) {
8241 MOZ_ASSERT(nsContentUtils::IsSafeToRunScript(),
8242 "Somebody changed aEvent to cause a DOM event!");
8243 nsPresShellEventCB eventCB(mPresShell);
8244 if (nsIFrame* target = mPresShell->GetCurrentEventFrame()) {
8245 if (target->OnlySystemGroupDispatch(aEvent->mMessage)) {
8246 aEvent->StopPropagation();
8249 if (aEvent->mClass == eTouchEventClass) {
8250 DispatchTouchEventToDOM(aEvent, aEventStatus, &eventCB, aTouchIsNew);
8251 } else {
8252 DispatchEventToDOM(aEvent, aEventStatus, &eventCB);
8256 nsContentUtils::SetIsHandlingKeyBoardEvent(wasHandlingKeyBoardEvent);
8258 if (mPresShell->IsDestroying()) {
8259 return NS_OK;
8262 // 3. Give event to event manager for post event state changes and
8263 // generation of synthetic events.
8264 // Refetch the prescontext, in case it changed.
8265 RefPtr<nsPresContext> presContext = GetPresContext();
8266 return aEventStateManager->PostHandleEvent(
8267 presContext, aEvent, mPresShell->GetCurrentEventFrame(), aEventStatus,
8268 aOverrideClickTarget);
8271 bool PresShell::EventHandler::PrepareToDispatchEvent(
8272 WidgetEvent* aEvent, nsEventStatus* aEventStatus, bool* aTouchIsNew) {
8273 MOZ_ASSERT(aEvent->IsTrusted());
8274 MOZ_ASSERT(aEventStatus);
8275 MOZ_ASSERT(aTouchIsNew);
8277 *aTouchIsNew = false;
8278 if (aEvent->IsUserAction()) {
8279 mPresShell->mHasHandledUserInput = true;
8282 switch (aEvent->mMessage) {
8283 case eKeyPress:
8284 case eKeyDown:
8285 case eKeyUp: {
8286 WidgetKeyboardEvent* keyboardEvent = aEvent->AsKeyboardEvent();
8287 MaybeHandleKeyboardEventBeforeDispatch(keyboardEvent);
8288 return true;
8290 case eMouseMove: {
8291 bool allowCapture = EventStateManager::GetActiveEventStateManager() &&
8292 GetPresContext() &&
8293 GetPresContext()->EventStateManager() ==
8294 EventStateManager::GetActiveEventStateManager();
8295 PresShell::AllowMouseCapture(allowCapture);
8296 return true;
8298 case eDrop: {
8299 nsCOMPtr<nsIDragSession> session = nsContentUtils::GetDragSession();
8300 if (session) {
8301 bool onlyChromeDrop = false;
8302 session->GetOnlyChromeDrop(&onlyChromeDrop);
8303 if (onlyChromeDrop) {
8304 aEvent->mFlags.mOnlyChromeDispatch = true;
8307 return true;
8309 case eDragExit: {
8310 if (!StaticPrefs::dom_event_dragexit_enabled()) {
8311 aEvent->mFlags.mOnlyChromeDispatch = true;
8313 return true;
8315 case eContextMenu: {
8316 // If we cannot open context menu even though eContextMenu is fired, we
8317 // should stop dispatching it into the DOM.
8318 WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
8319 if (mouseEvent->IsContextMenuKeyEvent() &&
8320 !AdjustContextMenuKeyEvent(mouseEvent)) {
8321 return false;
8324 // If "Shift" state is active, context menu should be forcibly opened even
8325 // if web apps want to prevent it since we respect our users' intention.
8326 // In this case, we don't fire "contextmenu" event on web content because
8327 // of not cancelable.
8328 if (mouseEvent->IsShift()) {
8329 aEvent->mFlags.mOnlyChromeDispatch = true;
8330 aEvent->mFlags.mRetargetToNonNativeAnonymous = true;
8332 return true;
8334 case eTouchStart:
8335 case eTouchMove:
8336 case eTouchEnd:
8337 case eTouchCancel:
8338 case eTouchPointerCancel:
8339 return mPresShell->mTouchManager.PreHandleEvent(
8340 aEvent, aEventStatus, *aTouchIsNew, mPresShell->mCurrentEventContent);
8341 default:
8342 return true;
8346 void PresShell::EventHandler::FinalizeHandlingEvent(WidgetEvent* aEvent) {
8347 switch (aEvent->mMessage) {
8348 case eKeyPress:
8349 case eKeyDown:
8350 case eKeyUp: {
8351 if (aEvent->AsKeyboardEvent()->mKeyCode == NS_VK_ESCAPE) {
8352 if (aEvent->mMessage == eKeyUp) {
8353 // Reset this flag after key up is handled.
8354 mPresShell->mIsLastChromeOnlyEscapeKeyConsumed = false;
8355 } else {
8356 if (aEvent->mFlags.mOnlyChromeDispatch &&
8357 aEvent->mFlags.mDefaultPreventedByChrome) {
8358 mPresShell->mIsLastChromeOnlyEscapeKeyConsumed = true;
8360 if (aEvent->mMessage == eKeyDown &&
8361 !aEvent->mFlags.mDefaultPrevented) {
8362 if (Document* doc = GetDocument()) {
8363 doc->TryCancelDialog();
8368 if (aEvent->mMessage == eKeyDown) {
8369 mPresShell->mIsLastKeyDownCanceled = aEvent->mFlags.mDefaultPrevented;
8371 return;
8373 case eMouseUp:
8374 // reset the capturing content now that the mouse button is up
8375 PresShell::ReleaseCapturingContent();
8376 return;
8377 case eMouseMove:
8378 PresShell::AllowMouseCapture(false);
8379 return;
8380 case eDrag:
8381 case eDragEnd:
8382 case eDragEnter:
8383 case eDragExit:
8384 case eDragLeave:
8385 case eDragOver:
8386 case eDrop: {
8387 // After any drag event other than dragstart (which is handled
8388 // separately, as we need to collect the data first), the DataTransfer
8389 // needs to be made protected, and then disconnected.
8390 DataTransfer* dataTransfer = aEvent->AsDragEvent()->mDataTransfer;
8391 if (dataTransfer) {
8392 dataTransfer->Disconnect();
8394 return;
8396 default:
8397 return;
8401 void PresShell::EventHandler::MaybeHandleKeyboardEventBeforeDispatch(
8402 WidgetKeyboardEvent* aKeyboardEvent) {
8403 MOZ_ASSERT(aKeyboardEvent);
8405 if (aKeyboardEvent->mKeyCode != NS_VK_ESCAPE) {
8406 return;
8409 // If we're in fullscreen mode, exit from it forcibly when Escape key is
8410 // pressed.
8411 Document* doc = mPresShell->GetCurrentEventContent()
8412 ? mPresShell->mCurrentEventContent->OwnerDoc()
8413 : nullptr;
8414 Document* root = nsContentUtils::GetInProcessSubtreeRootDocument(doc);
8415 if (root && root->GetFullscreenElement()) {
8416 // Prevent default action on ESC key press when exiting
8417 // DOM fullscreen mode. This prevents the browser ESC key
8418 // handler from stopping all loads in the document, which
8419 // would cause <video> loads to stop.
8420 // XXX We need to claim the Escape key event which will be
8421 // dispatched only into chrome is already consumed by
8422 // content because we need to prevent its default here
8423 // for some reasons (not sure) but we need to detect
8424 // if a chrome event handler will call PreventDefault()
8425 // again and check it later.
8426 aKeyboardEvent->PreventDefaultBeforeDispatch(CrossProcessForwarding::eStop);
8427 aKeyboardEvent->mFlags.mOnlyChromeDispatch = true;
8429 // The event listeners in chrome can prevent this ESC behavior by
8430 // calling prevent default on the preceding keydown/press events.
8431 if (!mPresShell->mIsLastChromeOnlyEscapeKeyConsumed &&
8432 aKeyboardEvent->mMessage == eKeyUp) {
8433 // ESC key released while in DOM fullscreen mode.
8434 // Fully exit all browser windows and documents from
8435 // fullscreen mode.
8436 Document::AsyncExitFullscreen(nullptr);
8440 nsCOMPtr<Document> pointerLockedDoc = PointerLockManager::GetLockedDocument();
8441 if (!mPresShell->mIsLastChromeOnlyEscapeKeyConsumed && pointerLockedDoc) {
8442 // XXX See above comment to understand the reason why this needs
8443 // to claim that the Escape key event is consumed by content
8444 // even though it will be dispatched only into chrome.
8445 aKeyboardEvent->PreventDefaultBeforeDispatch(CrossProcessForwarding::eStop);
8446 aKeyboardEvent->mFlags.mOnlyChromeDispatch = true;
8447 if (aKeyboardEvent->mMessage == eKeyUp) {
8448 PointerLockManager::Unlock();
8453 void PresShell::EventHandler::RecordEventPreparationPerformance(
8454 const WidgetEvent* aEvent) {
8455 MOZ_ASSERT(aEvent);
8457 switch (aEvent->mMessage) {
8458 case eKeyPress:
8459 case eKeyDown:
8460 case eKeyUp:
8461 if (aEvent->AsKeyboardEvent()->ShouldInteractionTimeRecorded()) {
8462 GetPresContext()->RecordInteractionTime(
8463 nsPresContext::InteractionType::KeyInteraction, aEvent->mTimeStamp);
8465 Telemetry::AccumulateTimeDelta(Telemetry::INPUT_EVENT_QUEUED_KEYBOARD_MS,
8466 aEvent->mTimeStamp);
8467 return;
8469 case eMouseDown:
8470 case eMouseUp:
8471 Telemetry::AccumulateTimeDelta(Telemetry::INPUT_EVENT_QUEUED_CLICK_MS,
8472 aEvent->mTimeStamp);
8473 [[fallthrough]];
8474 case ePointerDown:
8475 case ePointerUp:
8476 GetPresContext()->RecordInteractionTime(
8477 nsPresContext::InteractionType::ClickInteraction, aEvent->mTimeStamp);
8478 return;
8480 case eMouseMove:
8481 if (aEvent->mFlags.mHandledByAPZ) {
8482 Telemetry::AccumulateTimeDelta(
8483 Telemetry::INPUT_EVENT_QUEUED_APZ_MOUSE_MOVE_MS,
8484 aEvent->mTimeStamp);
8486 GetPresContext()->RecordInteractionTime(
8487 nsPresContext::InteractionType::MouseMoveInteraction,
8488 aEvent->mTimeStamp);
8489 return;
8491 case eWheel:
8492 if (aEvent->mFlags.mHandledByAPZ) {
8493 Telemetry::AccumulateTimeDelta(
8494 Telemetry::INPUT_EVENT_QUEUED_APZ_WHEEL_MS, aEvent->mTimeStamp);
8496 return;
8498 case eTouchMove:
8499 if (aEvent->mFlags.mHandledByAPZ) {
8500 Telemetry::AccumulateTimeDelta(
8501 Telemetry::INPUT_EVENT_QUEUED_APZ_TOUCH_MOVE_MS,
8502 aEvent->mTimeStamp);
8504 return;
8506 default:
8507 return;
8511 void PresShell::EventHandler::RecordEventHandlingResponsePerformance(
8512 const WidgetEvent* aEvent) {
8513 if (!Telemetry::CanRecordBase() || aEvent->mTimeStamp.IsNull() ||
8514 aEvent->mTimeStamp <= mPresShell->mLastOSWake ||
8515 !aEvent->AsInputEvent()) {
8516 return;
8519 TimeStamp now = TimeStamp::Now();
8520 double millis = (now - aEvent->mTimeStamp).ToMilliseconds();
8521 Telemetry::Accumulate(Telemetry::INPUT_EVENT_RESPONSE_MS, millis);
8522 if (GetDocument() &&
8523 GetDocument()->GetReadyStateEnum() != Document::READYSTATE_COMPLETE) {
8524 Telemetry::Accumulate(Telemetry::LOAD_INPUT_EVENT_RESPONSE_MS, millis);
8527 if (!sLastInputProcessed || sLastInputProcessed < aEvent->mTimeStamp) {
8528 if (sLastInputProcessed) {
8529 // This input event was created after we handled the last one.
8530 // Accumulate the previous events' coalesced duration.
8531 double lastMillis =
8532 (sLastInputProcessed - sLastInputCreated).ToMilliseconds();
8533 Telemetry::Accumulate(Telemetry::INPUT_EVENT_RESPONSE_COALESCED_MS,
8534 lastMillis);
8536 if (MOZ_UNLIKELY(!PresShell::sProcessInteractable)) {
8537 // For content process, we use the ready state of
8538 // top-level-content-document to know if the process has finished the
8539 // start-up.
8540 // For parent process, see the topic
8541 // 'sessionstore-one-or-no-tab-restored' in PresShell::Observe.
8542 if (XRE_IsContentProcess() && GetDocument() &&
8543 GetDocument()->IsTopLevelContentDocument()) {
8544 switch (GetDocument()->GetReadyStateEnum()) {
8545 case Document::READYSTATE_INTERACTIVE:
8546 case Document::READYSTATE_COMPLETE:
8547 PresShell::sProcessInteractable = true;
8548 break;
8549 default:
8550 break;
8554 if (MOZ_LIKELY(PresShell::sProcessInteractable)) {
8555 Telemetry::Accumulate(Telemetry::INPUT_EVENT_RESPONSE_POST_STARTUP_MS,
8556 lastMillis);
8557 } else {
8558 Telemetry::Accumulate(Telemetry::INPUT_EVENT_RESPONSE_STARTUP_MS,
8559 lastMillis);
8562 sLastInputCreated = aEvent->mTimeStamp;
8563 } else if (aEvent->mTimeStamp < sLastInputCreated) {
8564 // This event was created before the last input. May be processing out
8565 // of order, so coalesce backwards, too.
8566 sLastInputCreated = aEvent->mTimeStamp;
8568 sLastInputProcessed = now;
8571 // static
8572 nsIPrincipal*
8573 PresShell::EventHandler::GetDocumentPrincipalToCompareWithBlacklist(
8574 PresShell& aPresShell) {
8575 nsPresContext* presContext = aPresShell.GetPresContext();
8576 if (NS_WARN_IF(!presContext)) {
8577 return nullptr;
8579 return presContext->Document()->GetPrincipalForPrefBasedHacks();
8582 nsresult PresShell::EventHandler::DispatchEventToDOM(
8583 WidgetEvent* aEvent, nsEventStatus* aEventStatus,
8584 nsPresShellEventCB* aEventCB) {
8585 nsresult rv = NS_OK;
8586 nsCOMPtr<nsINode> eventTarget = mPresShell->mCurrentEventContent;
8587 nsPresShellEventCB* eventCBPtr = aEventCB;
8588 if (!eventTarget) {
8589 nsCOMPtr<nsIContent> targetContent;
8590 if (mPresShell->mCurrentEventFrame) {
8591 rv = mPresShell->mCurrentEventFrame->GetContentForEvent(
8592 aEvent, getter_AddRefs(targetContent));
8594 if (NS_SUCCEEDED(rv) && targetContent) {
8595 eventTarget = targetContent;
8596 } else if (GetDocument()) {
8597 eventTarget = GetDocument();
8598 // If we don't have any content, the callback wouldn't probably
8599 // do nothing.
8600 eventCBPtr = nullptr;
8603 if (eventTarget) {
8604 if (eventTarget->OwnerDoc()->ShouldResistFingerprinting() &&
8605 aEvent->IsBlockedForFingerprintingResistance()) {
8606 aEvent->mFlags.mOnlySystemGroupDispatchInContent = true;
8607 } else if (aEvent->mMessage == eKeyPress) {
8608 // If eKeyPress event is marked as not dispatched in the default event
8609 // group in web content, it's caused by non-printable key or key
8610 // combination. In this case, UI Events declares that browsers
8611 // shouldn't dispatch keypress event. However, some web apps may be
8612 // broken with this strict behavior due to historical issue.
8613 // Therefore, we need to keep dispatching keypress event for such keys
8614 // even with breaking the standard.
8615 // Similarly, the other browsers sets non-zero value of keyCode or
8616 // charCode of keypress event to the other. Therefore, we should
8617 // behave so, however, some web apps may be broken. On such web apps,
8618 // we should keep using legacy our behavior.
8619 if (!mPresShell->mInitializedWithKeyPressEventDispatchingBlacklist) {
8620 mPresShell->mInitializedWithKeyPressEventDispatchingBlacklist = true;
8621 nsCOMPtr<nsIPrincipal> principal =
8622 GetDocumentPrincipalToCompareWithBlacklist(*mPresShell);
8623 if (principal) {
8624 mPresShell->mForceDispatchKeyPressEventsForNonPrintableKeys =
8625 principal->IsURIInPrefList(
8626 "dom.keyboardevent.keypress.hack.dispatch_non_printable_"
8627 "keys") ||
8628 principal->IsURIInPrefList(
8629 "dom.keyboardevent.keypress.hack."
8630 "dispatch_non_printable_keys.addl");
8632 mPresShell->mForceUseLegacyKeyCodeAndCharCodeValues |=
8633 principal->IsURIInPrefList(
8634 "dom.keyboardevent.keypress.hack."
8635 "use_legacy_keycode_and_charcode") ||
8636 principal->IsURIInPrefList(
8637 "dom.keyboardevent.keypress.hack."
8638 "use_legacy_keycode_and_charcode.addl");
8641 if (mPresShell->mForceDispatchKeyPressEventsForNonPrintableKeys) {
8642 aEvent->mFlags.mOnlySystemGroupDispatchInContent = false;
8644 if (mPresShell->mForceUseLegacyKeyCodeAndCharCodeValues) {
8645 aEvent->AsKeyboardEvent()->mUseLegacyKeyCodeAndCharCodeValues = true;
8647 } else if (aEvent->mMessage == eMouseUp) {
8648 // Historically Firefox has dispatched click events for non-primary
8649 // buttons, but only on window and document (and inside input/textarea),
8650 // not on elements in general. The UI events spec forbids click (and
8651 // dblclick) for non-primary mouse buttons, and specifies auxclick
8652 // instead. In case of some websites that rely on non-primary click to
8653 // prevent new tab etc. and don't have auxclick code to do the same, we
8654 // need to revert to the historial non-standard behaviour
8655 if (!mPresShell->mInitializedWithClickEventDispatchingBlacklist) {
8656 mPresShell->mInitializedWithClickEventDispatchingBlacklist = true;
8658 nsCOMPtr<nsIPrincipal> principal =
8659 GetDocumentPrincipalToCompareWithBlacklist(*mPresShell);
8661 if (principal) {
8662 mPresShell->mForceUseLegacyNonPrimaryDispatch =
8663 principal->IsURIInPrefList(
8664 "dom.mouseevent.click.hack.use_legacy_non-primary_dispatch");
8667 if (mPresShell->mForceUseLegacyNonPrimaryDispatch) {
8668 aEvent->AsMouseEvent()->mUseLegacyNonPrimaryDispatch = true;
8672 if (aEvent->mClass == eCompositionEventClass) {
8673 RefPtr<nsPresContext> presContext = GetPresContext();
8674 RefPtr<BrowserParent> browserParent =
8675 IMEStateManager::GetActiveBrowserParent();
8676 IMEStateManager::DispatchCompositionEvent(
8677 eventTarget, presContext, browserParent, aEvent->AsCompositionEvent(),
8678 aEventStatus, eventCBPtr);
8679 } else {
8680 RefPtr<nsPresContext> presContext = GetPresContext();
8681 EventDispatcher::Dispatch(eventTarget, presContext, aEvent, nullptr,
8682 aEventStatus, eventCBPtr);
8685 return rv;
8688 void PresShell::EventHandler::DispatchTouchEventToDOM(
8689 WidgetEvent* aEvent, nsEventStatus* aEventStatus,
8690 nsPresShellEventCB* aEventCB, bool aTouchIsNew) {
8691 // calling preventDefault on touchstart or the first touchmove for a
8692 // point prevents mouse events. calling it on the touchend should
8693 // prevent click dispatching.
8694 bool canPrevent = (aEvent->mMessage == eTouchStart) ||
8695 (aEvent->mMessage == eTouchMove && aTouchIsNew) ||
8696 (aEvent->mMessage == eTouchEnd);
8697 bool preventDefault = false;
8698 nsEventStatus tmpStatus = nsEventStatus_eIgnore;
8699 WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
8701 // loop over all touches and dispatch events on any that have changed
8702 for (dom::Touch* touch : touchEvent->mTouches) {
8703 // We should remove all suppressed touch instances in
8704 // TouchManager::PreHandleEvent.
8705 MOZ_ASSERT(!touch->mIsTouchEventSuppressed);
8707 if (!touch || !touch->mChanged) {
8708 continue;
8711 nsCOMPtr<EventTarget> targetPtr = touch->mTarget;
8712 nsCOMPtr<nsIContent> content = do_QueryInterface(targetPtr);
8713 if (!content) {
8714 continue;
8717 Document* doc = content->OwnerDoc();
8718 nsIContent* capturingContent = PresShell::GetCapturingContent();
8719 if (capturingContent) {
8720 if (capturingContent->OwnerDoc() != doc) {
8721 // Wrong document, don't dispatch anything.
8722 continue;
8724 content = capturingContent;
8726 // copy the event
8727 MOZ_ASSERT(touchEvent->IsTrusted());
8728 WidgetTouchEvent newEvent(true, touchEvent->mMessage, touchEvent->mWidget);
8729 newEvent.AssignTouchEventData(*touchEvent, false);
8730 newEvent.mTarget = targetPtr;
8731 newEvent.mFlags.mHandledByAPZ = touchEvent->mFlags.mHandledByAPZ;
8733 RefPtr<PresShell> contentPresShell;
8734 if (doc == GetDocument()) {
8735 contentPresShell = doc->GetPresShell();
8736 if (contentPresShell) {
8737 // XXXsmaug huge hack. Pushing possibly capturing content,
8738 // even though event target is something else.
8739 contentPresShell->PushCurrentEventInfo(content->GetPrimaryFrame(),
8740 content);
8744 RefPtr<nsPresContext> presContext = doc->GetPresContext();
8745 if (!presContext) {
8746 if (contentPresShell) {
8747 contentPresShell->PopCurrentEventInfo();
8749 continue;
8752 tmpStatus = nsEventStatus_eIgnore;
8753 EventDispatcher::Dispatch(targetPtr, presContext, &newEvent, nullptr,
8754 &tmpStatus, aEventCB);
8755 if (nsEventStatus_eConsumeNoDefault == tmpStatus ||
8756 newEvent.mFlags.mMultipleActionsPrevented) {
8757 preventDefault = true;
8760 if (newEvent.mFlags.mMultipleActionsPrevented) {
8761 touchEvent->mFlags.mMultipleActionsPrevented = true;
8764 if (contentPresShell) {
8765 contentPresShell->PopCurrentEventInfo();
8769 if (preventDefault && canPrevent) {
8770 *aEventStatus = nsEventStatus_eConsumeNoDefault;
8771 } else {
8772 *aEventStatus = nsEventStatus_eIgnore;
8776 // Dispatch event to content only (NOT full processing)
8777 // See also HandleEventWithTarget which does full event processing.
8778 nsresult PresShell::HandleDOMEventWithTarget(nsIContent* aTargetContent,
8779 WidgetEvent* aEvent,
8780 nsEventStatus* aStatus) {
8781 nsresult rv = NS_OK;
8783 PushCurrentEventInfo(nullptr, aTargetContent);
8785 // Bug 41013: Check if the event should be dispatched to content.
8786 // It's possible that we are in the middle of destroying the window
8787 // and the js context is out of date. This check detects the case
8788 // that caused a crash in bug 41013, but there may be a better way
8789 // to handle this situation!
8790 nsCOMPtr<nsISupports> container = mPresContext->GetContainerWeak();
8791 if (container) {
8792 // Dispatch event to content
8793 rv = EventDispatcher::Dispatch(aTargetContent, mPresContext, aEvent,
8794 nullptr, aStatus);
8797 PopCurrentEventInfo();
8798 return rv;
8801 // See the method above.
8802 nsresult PresShell::HandleDOMEventWithTarget(nsIContent* aTargetContent,
8803 Event* aEvent,
8804 nsEventStatus* aStatus) {
8805 nsresult rv = NS_OK;
8807 PushCurrentEventInfo(nullptr, aTargetContent);
8808 nsCOMPtr<nsISupports> container = mPresContext->GetContainerWeak();
8809 if (container) {
8810 rv = EventDispatcher::DispatchDOMEvent(aTargetContent, nullptr, aEvent,
8811 mPresContext, aStatus);
8814 PopCurrentEventInfo();
8815 return rv;
8818 bool PresShell::EventHandler::AdjustContextMenuKeyEvent(
8819 WidgetMouseEvent* aMouseEvent) {
8820 // if a menu is open, open the context menu relative to the active item on the
8821 // menu.
8822 if (nsXULPopupManager* pm = nsXULPopupManager::GetInstance()) {
8823 nsIFrame* popupFrame = pm->GetTopPopup(widget::PopupType::Menu);
8824 if (popupFrame) {
8825 nsIFrame* itemFrame = (static_cast<nsMenuPopupFrame*>(popupFrame))
8826 ->GetCurrentMenuItemFrame();
8827 if (!itemFrame) itemFrame = popupFrame;
8829 nsCOMPtr<nsIWidget> widget = popupFrame->GetNearestWidget();
8830 aMouseEvent->mWidget = widget;
8831 LayoutDeviceIntPoint widgetPoint = widget->WidgetToScreenOffset();
8832 aMouseEvent->mRefPoint =
8833 LayoutDeviceIntPoint::FromAppUnitsToNearest(
8834 itemFrame->GetScreenRectInAppUnits().BottomLeft(),
8835 itemFrame->PresContext()->AppUnitsPerDevPixel()) -
8836 widgetPoint;
8838 mPresShell->mCurrentEventContent = itemFrame->GetContent();
8839 mPresShell->mCurrentEventFrame = itemFrame;
8841 return true;
8845 // If we're here because of the key-equiv for showing context menus, we
8846 // have to twiddle with the NS event to make sure the context menu comes
8847 // up in the upper left of the relevant content area before we create
8848 // the DOM event. Since we never call InitMouseEvent() on the event,
8849 // the client X/Y will be 0,0. We can make use of that if the widget is null.
8850 // Use the root view manager's widget since it's most likely to have one,
8851 // and the coordinates returned by GetCurrentItemAndPositionForElement
8852 // are relative to the widget of the root of the root view manager.
8853 nsRootPresContext* rootPC = GetPresContext()->GetRootPresContext();
8854 aMouseEvent->mRefPoint = LayoutDeviceIntPoint(0, 0);
8855 if (rootPC) {
8856 aMouseEvent->mWidget =
8857 rootPC->PresShell()->GetViewManager()->GetRootWidget();
8858 if (aMouseEvent->mWidget) {
8859 // default the refpoint to the topleft of our document
8860 nsPoint offset(0, 0);
8861 nsIFrame* rootFrame = FrameConstructor()->GetRootFrame();
8862 if (rootFrame) {
8863 nsView* view = rootFrame->GetClosestView(&offset);
8864 offset += view->GetOffsetToWidget(aMouseEvent->mWidget);
8865 aMouseEvent->mRefPoint = LayoutDeviceIntPoint::FromAppUnitsToNearest(
8866 offset, GetPresContext()->AppUnitsPerDevPixel());
8869 } else {
8870 aMouseEvent->mWidget = nullptr;
8873 // see if we should use the caret position for the popup
8874 LayoutDeviceIntPoint caretPoint;
8875 // Beware! This may flush notifications via synchronous
8876 // ScrollSelectionIntoView.
8877 if (PrepareToUseCaretPosition(MOZ_KnownLive(aMouseEvent->mWidget),
8878 caretPoint)) {
8879 // caret position is good
8880 int32_t devPixelRatio = GetPresContext()->AppUnitsPerDevPixel();
8881 caretPoint = LayoutDeviceIntPoint::FromAppUnitsToNearest(
8882 ViewportUtils::LayoutToVisual(
8883 LayoutDeviceIntPoint::ToAppUnits(caretPoint, devPixelRatio),
8884 GetPresContext()->PresShell()),
8885 devPixelRatio);
8886 aMouseEvent->mRefPoint = caretPoint;
8887 return true;
8890 // If we're here because of the key-equiv for showing context menus, we
8891 // have to reset the event target to the currently focused element. Get it
8892 // from the focus controller.
8893 RefPtr<Element> currentFocus;
8894 nsFocusManager* fm = nsFocusManager::GetFocusManager();
8895 if (fm) {
8896 currentFocus = fm->GetFocusedElement();
8899 // Reset event coordinates relative to focused frame in view
8900 if (currentFocus) {
8901 nsCOMPtr<nsIContent> currentPointElement;
8902 GetCurrentItemAndPositionForElement(
8903 currentFocus, getter_AddRefs(currentPointElement),
8904 aMouseEvent->mRefPoint, MOZ_KnownLive(aMouseEvent->mWidget));
8905 if (currentPointElement) {
8906 mPresShell->mCurrentEventContent = currentPointElement;
8907 mPresShell->mCurrentEventFrame = nullptr;
8908 mPresShell->GetCurrentEventFrame();
8912 return true;
8915 // PresShell::EventHandler::PrepareToUseCaretPosition
8917 // This checks to see if we should use the caret position for popup context
8918 // menus. Returns true if the caret position should be used, and the
8919 // coordinates of that position is returned in aTargetPt. This function
8920 // will also scroll the window as needed to make the caret visible.
8922 // The event widget should be the widget that generated the event, and
8923 // whose coordinate system the resulting event's mRefPoint should be
8924 // relative to. The returned point is in device pixels realtive to the
8925 // widget passed in.
8926 bool PresShell::EventHandler::PrepareToUseCaretPosition(
8927 nsIWidget* aEventWidget, LayoutDeviceIntPoint& aTargetPt) {
8928 nsresult rv;
8930 // check caret visibility
8931 RefPtr<nsCaret> caret = mPresShell->GetCaret();
8932 NS_ENSURE_TRUE(caret, false);
8934 bool caretVisible = caret->IsVisible();
8935 if (!caretVisible) return false;
8937 // caret selection, this is a temporary weak reference, so no refcounting is
8938 // needed
8939 Selection* domSelection = caret->GetSelection();
8940 NS_ENSURE_TRUE(domSelection, false);
8942 // since the match could be an anonymous textnode inside a
8943 // <textarea> or text <input>, we need to get the outer frame
8944 // note: frames are not refcounted
8945 nsIFrame* frame = nullptr; // may be nullptr
8946 nsINode* node = domSelection->GetFocusNode();
8947 NS_ENSURE_TRUE(node, false);
8948 nsCOMPtr<nsIContent> content = nsIContent::FromNode(node);
8949 if (content) {
8950 nsIContent* nonNative = content->FindFirstNonChromeOnlyAccessContent();
8951 content = nonNative;
8954 if (content) {
8955 // It seems like ScrollSelectionIntoView should be enough, but it's
8956 // not. The problem is that scrolling the selection into view when it is
8957 // below the current viewport will align the top line of the frame exactly
8958 // with the bottom of the window. This is fine, BUT, the popup event causes
8959 // the control to be re-focused which does this exact call to
8960 // ScrollContentIntoView, which has a one-pixel disagreement of whether the
8961 // frame is actually in view. The result is that the frame is aligned with
8962 // the top of the window, but the menu is still at the bottom.
8964 // Doing this call first forces the frame to be in view, eliminating the
8965 // problem. The only difference in the result is that if your cursor is in
8966 // an edit box below the current view, you'll get the edit box aligned with
8967 // the top of the window. This is arguably better behavior anyway.
8968 rv = MOZ_KnownLive(mPresShell)
8969 ->ScrollContentIntoView(
8970 content,
8971 ScrollAxis(WhereToScroll::Nearest, WhenToScroll::IfNotVisible),
8972 ScrollAxis(WhereToScroll::Nearest, WhenToScroll::IfNotVisible),
8973 ScrollFlags::ScrollOverflowHidden);
8974 NS_ENSURE_SUCCESS(rv, false);
8975 frame = content->GetPrimaryFrame();
8976 NS_WARNING_ASSERTION(frame, "No frame for focused content?");
8979 // Actually scroll the selection (ie caret) into view. Note that this must
8980 // be synchronous since we will be checking the caret position on the screen.
8982 // Be easy about errors, and just don't scroll in those cases. Better to have
8983 // the correct menu at a weird place than the wrong menu.
8984 // After ScrollSelectionIntoView(), the pending notifications might be
8985 // flushed and PresShell/PresContext/Frames may be dead. See bug 418470.
8986 nsCOMPtr<nsISelectionController> selCon;
8987 if (frame)
8988 frame->GetSelectionController(GetPresContext(), getter_AddRefs(selCon));
8989 else
8990 selCon = static_cast<nsISelectionController*>(mPresShell);
8991 if (selCon) {
8992 rv = selCon->ScrollSelectionIntoView(
8993 nsISelectionController::SELECTION_NORMAL,
8994 nsISelectionController::SELECTION_FOCUS_REGION,
8995 nsISelectionController::SCROLL_SYNCHRONOUS);
8996 NS_ENSURE_SUCCESS(rv, false);
8999 nsPresContext* presContext = GetPresContext();
9001 // get caret position relative to the closest view
9002 nsRect caretCoords;
9003 nsIFrame* caretFrame = caret->GetGeometry(&caretCoords);
9004 if (!caretFrame) return false;
9005 nsPoint viewOffset;
9006 nsView* view = caretFrame->GetClosestView(&viewOffset);
9007 if (!view) return false;
9008 // and then get the caret coords relative to the event widget
9009 if (aEventWidget) {
9010 viewOffset += view->GetOffsetToWidget(aEventWidget);
9012 caretCoords.MoveBy(viewOffset);
9014 // caret coordinates are in app units, convert to pixels
9015 aTargetPt.x =
9016 presContext->AppUnitsToDevPixels(caretCoords.x + caretCoords.width);
9017 aTargetPt.y =
9018 presContext->AppUnitsToDevPixels(caretCoords.y + caretCoords.height);
9020 // make sure rounding doesn't return a pixel which is outside the caret
9021 // (e.g. one line lower)
9022 aTargetPt.y -= 1;
9024 return true;
9027 void PresShell::EventHandler::GetCurrentItemAndPositionForElement(
9028 Element* aFocusedElement, nsIContent** aTargetToUse,
9029 LayoutDeviceIntPoint& aTargetPt, nsIWidget* aRootWidget) {
9030 nsCOMPtr<nsIContent> focusedContent = aFocusedElement;
9031 MOZ_KnownLive(mPresShell)
9032 ->ScrollContentIntoView(focusedContent, ScrollAxis(), ScrollAxis(),
9033 ScrollFlags::ScrollOverflowHidden);
9035 nsPresContext* presContext = GetPresContext();
9037 bool istree = false, checkLineHeight = true;
9038 nscoord extraTreeY = 0;
9040 // Set the position to just underneath the current item for multi-select
9041 // lists or just underneath the selected item for single-select lists. If
9042 // the element is not a list, or there is no selection, leave the position
9043 // as is.
9044 nsCOMPtr<Element> item;
9045 nsCOMPtr<nsIDOMXULMultiSelectControlElement> multiSelect =
9046 aFocusedElement->AsXULMultiSelectControl();
9047 if (multiSelect) {
9048 checkLineHeight = false;
9050 int32_t currentIndex;
9051 multiSelect->GetCurrentIndex(&currentIndex);
9052 if (currentIndex >= 0) {
9053 RefPtr<XULTreeElement> tree = XULTreeElement::FromNode(focusedContent);
9054 // Tree view special case (tree items have no frames)
9055 // Get the focused row and add its coordinates, which are already in
9056 // pixels
9057 // XXX Boris, should we create a new interface so that this doesn't
9058 // need to know about trees? Something like nsINodelessChildCreator
9059 // which could provide the current focus coordinates?
9060 if (tree) {
9061 tree->EnsureRowIsVisible(currentIndex);
9062 int32_t firstVisibleRow = tree->GetFirstVisibleRow();
9063 int32_t rowHeight = tree->RowHeight();
9065 extraTreeY += nsPresContext::CSSPixelsToAppUnits(
9066 (currentIndex - firstVisibleRow + 1) * rowHeight);
9067 istree = true;
9069 RefPtr<nsTreeColumns> cols = tree->GetColumns();
9070 if (cols) {
9071 nsTreeColumn* col = cols->GetFirstColumn();
9072 if (col) {
9073 RefPtr<Element> colElement = col->Element();
9074 nsIFrame* frame = colElement->GetPrimaryFrame();
9075 if (frame) {
9076 extraTreeY += frame->GetSize().height;
9080 } else {
9081 multiSelect->GetCurrentItem(getter_AddRefs(item));
9084 } else {
9085 // don't check menulists as the selected item will be inside a popup.
9086 nsCOMPtr<nsIDOMXULMenuListElement> menulist =
9087 aFocusedElement->AsXULMenuList();
9088 if (!menulist) {
9089 nsCOMPtr<nsIDOMXULSelectControlElement> select =
9090 aFocusedElement->AsXULSelectControl();
9091 if (select) {
9092 checkLineHeight = false;
9093 select->GetSelectedItem(getter_AddRefs(item));
9098 if (item) {
9099 focusedContent = item;
9102 nsIFrame* frame = focusedContent->GetPrimaryFrame();
9103 if (frame) {
9104 NS_ASSERTION(
9105 frame->PresContext() == GetPresContext(),
9106 "handling event for focused content that is not in our document?");
9108 nsPoint frameOrigin(0, 0);
9110 // Get the frame's origin within its view
9111 nsView* view = frame->GetClosestView(&frameOrigin);
9112 NS_ASSERTION(view, "No view for frame");
9114 // View's origin relative the widget
9115 if (aRootWidget) {
9116 frameOrigin += view->GetOffsetToWidget(aRootWidget);
9119 // Start context menu down and to the right from top left of frame
9120 // use the lineheight. This is a good distance to move the context
9121 // menu away from the top left corner of the frame. If we always
9122 // used the frame height, the context menu could end up far away,
9123 // for example when we're focused on linked images.
9124 // On the other hand, we want to use the frame height if it's less
9125 // than the current line height, so that the context menu appears
9126 // associated with the correct frame.
9127 nscoord extra = 0;
9128 if (!istree) {
9129 extra = frame->GetSize().height;
9130 if (checkLineHeight) {
9131 nsIScrollableFrame* scrollFrame =
9132 nsLayoutUtils::GetNearestScrollableFrame(
9133 frame, nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN |
9134 nsLayoutUtils::SCROLLABLE_FIXEDPOS_FINDS_ROOT);
9135 if (scrollFrame) {
9136 nsSize scrollAmount = scrollFrame->GetLineScrollAmount();
9137 nsIFrame* f = do_QueryFrame(scrollFrame);
9138 int32_t APD = presContext->AppUnitsPerDevPixel();
9139 int32_t scrollAPD = f->PresContext()->AppUnitsPerDevPixel();
9140 scrollAmount = scrollAmount.ScaleToOtherAppUnits(scrollAPD, APD);
9141 if (extra > scrollAmount.height) {
9142 extra = scrollAmount.height;
9148 aTargetPt.x = presContext->AppUnitsToDevPixels(frameOrigin.x);
9149 aTargetPt.y =
9150 presContext->AppUnitsToDevPixels(frameOrigin.y + extra + extraTreeY);
9153 NS_IF_ADDREF(*aTargetToUse = focusedContent);
9156 bool PresShell::ShouldIgnoreInvalidation() {
9157 return mPaintingSuppressed || !mIsActive || mIsNeverPainting;
9160 void PresShell::WillPaint() {
9161 // Check the simplest things first. In particular, it's important to
9162 // check mIsActive before making any of the more expensive calls such
9163 // as GetRootPresContext, for the case of a browser with a large
9164 // number of tabs.
9165 // Don't bother doing anything if some viewmanager in our tree is painting
9166 // while we still have painting suppressed or we are not active.
9167 if (!mIsActive || mPaintingSuppressed || !IsVisible()) {
9168 return;
9171 nsRootPresContext* rootPresContext = mPresContext->GetRootPresContext();
9172 if (!rootPresContext) {
9173 // In some edge cases, such as when we don't have a root frame yet,
9174 // we can't find the root prescontext. There's nothing to do in that
9175 // case.
9176 return;
9179 rootPresContext->FlushWillPaintObservers();
9180 if (mIsDestroying) return;
9182 // Process reflows, if we have them, to reduce flicker due to invalidates and
9183 // reflow being interspersed. Note that we _do_ allow this to be
9184 // interruptible; if we can't do all the reflows it's better to flicker a bit
9185 // than to freeze up.
9186 FlushPendingNotifications(
9187 ChangesToFlush(FlushType::InterruptibleLayout, false));
9190 void PresShell::DidPaintWindow() {
9191 nsRootPresContext* rootPresContext = mPresContext->GetRootPresContext();
9192 if (rootPresContext != mPresContext) {
9193 // This could be a popup's presshell. No point in notifying XPConnect
9194 // about compositing of popups.
9195 return;
9198 if (!mHasReceivedPaintMessage) {
9199 mHasReceivedPaintMessage = true;
9201 nsCOMPtr<nsIObserverService> obsvc = services::GetObserverService();
9202 if (obsvc && mDocument) {
9203 nsPIDOMWindowOuter* window = mDocument->GetWindow();
9204 nsCOMPtr<nsIDOMChromeWindow> chromeWin(do_QueryInterface(window));
9205 if (chromeWin) {
9206 obsvc->NotifyObservers(chromeWin, "widget-first-paint", nullptr);
9212 bool PresShell::IsVisible() const {
9213 if (!mIsActive || !mViewManager) return false;
9215 nsView* view = mViewManager->GetRootView();
9216 if (!view) return true;
9218 // inner view of subdoc frame
9219 view = view->GetParent();
9220 if (!view) return true;
9222 // subdoc view
9223 view = view->GetParent();
9224 if (!view) return true;
9226 nsIFrame* frame = view->GetFrame();
9227 if (!frame) return true;
9229 return frame->IsVisibleConsideringAncestors(
9230 nsIFrame::VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY);
9233 void PresShell::SuppressDisplayport(bool aEnabled) {
9234 if (aEnabled) {
9235 mActiveSuppressDisplayport++;
9236 } else if (mActiveSuppressDisplayport > 0) {
9237 bool isSuppressed = IsDisplayportSuppressed();
9238 mActiveSuppressDisplayport--;
9239 if (isSuppressed && !IsDisplayportSuppressed()) {
9240 // We unsuppressed the displayport, trigger a paint
9241 if (nsIFrame* rootFrame = mFrameConstructor->GetRootFrame()) {
9242 rootFrame->SchedulePaint();
9248 static bool sDisplayPortSuppressionRespected = true;
9250 void PresShell::RespectDisplayportSuppression(bool aEnabled) {
9251 bool isSuppressed = IsDisplayportSuppressed();
9252 sDisplayPortSuppressionRespected = aEnabled;
9253 if (isSuppressed && !IsDisplayportSuppressed()) {
9254 // We unsuppressed the displayport, trigger a paint
9255 if (nsIFrame* rootFrame = mFrameConstructor->GetRootFrame()) {
9256 rootFrame->SchedulePaint();
9261 bool PresShell::IsDisplayportSuppressed() {
9262 return sDisplayPortSuppressionRespected && mActiveSuppressDisplayport > 0;
9265 static CallState FreezeSubDocument(Document& aDocument) {
9266 if (PresShell* presShell = aDocument.GetPresShell()) {
9267 presShell->Freeze();
9269 return CallState::Continue;
9272 void PresShell::Freeze(bool aIncludeSubDocuments) {
9273 mUpdateApproximateFrameVisibilityEvent.Revoke();
9275 MaybeReleaseCapturingContent();
9277 if (mCaret) {
9278 SetCaretEnabled(false);
9281 mPaintingSuppressed = true;
9283 if (aIncludeSubDocuments && mDocument) {
9284 mDocument->EnumerateSubDocuments(FreezeSubDocument);
9287 nsPresContext* presContext = GetPresContext();
9288 if (presContext) {
9289 presContext->DisableInteractionTimeRecording();
9290 if (presContext->RefreshDriver()->GetPresContext() == presContext) {
9291 presContext->RefreshDriver()->Freeze();
9295 mFrozen = true;
9296 if (mDocument) {
9297 UpdateImageLockingState();
9301 void PresShell::FireOrClearDelayedEvents(bool aFireEvents) {
9302 mNoDelayedMouseEvents = false;
9303 mNoDelayedKeyEvents = false;
9304 if (!aFireEvents) {
9305 mDelayedEvents.Clear();
9306 return;
9309 if (mDocument) {
9310 RefPtr<Document> doc = mDocument;
9311 while (!mIsDestroying && mDelayedEvents.Length() &&
9312 !doc->EventHandlingSuppressed()) {
9313 UniquePtr<DelayedEvent> ev = std::move(mDelayedEvents[0]);
9314 mDelayedEvents.RemoveElementAt(0);
9315 if (ev->IsKeyPressEvent() && mIsLastKeyDownCanceled) {
9316 continue;
9318 ev->Dispatch();
9320 if (!doc->EventHandlingSuppressed()) {
9321 mDelayedEvents.Clear();
9326 void PresShell::Thaw(bool aIncludeSubDocuments) {
9327 nsPresContext* presContext = GetPresContext();
9328 if (presContext &&
9329 presContext->RefreshDriver()->GetPresContext() == presContext) {
9330 presContext->RefreshDriver()->Thaw();
9333 if (aIncludeSubDocuments && mDocument) {
9334 mDocument->EnumerateSubDocuments([](Document& aSubDoc) {
9335 if (PresShell* presShell = aSubDoc.GetPresShell()) {
9336 presShell->Thaw();
9338 return CallState::Continue;
9342 // Get the activeness of our presshell, as this might have changed
9343 // while we were in the bfcache
9344 ActivenessMaybeChanged();
9346 // We're now unfrozen
9347 mFrozen = false;
9348 UpdateImageLockingState();
9350 UnsuppressPainting();
9353 //--------------------------------------------------------
9354 // Start of protected and private methods on the PresShell
9355 //--------------------------------------------------------
9357 void PresShell::MaybeScheduleReflow() {
9358 ASSERT_REFLOW_SCHEDULED_STATE();
9359 if (mObservingLayoutFlushes || mIsDestroying || mIsReflowing ||
9360 mDirtyRoots.IsEmpty())
9361 return;
9363 if (!mPresContext->HasPendingInterrupt() || !ScheduleReflowOffTimer()) {
9364 ScheduleReflow();
9367 ASSERT_REFLOW_SCHEDULED_STATE();
9370 void PresShell::ScheduleReflow() {
9371 ASSERT_REFLOW_SCHEDULED_STATE();
9372 DoObserveLayoutFlushes();
9373 ASSERT_REFLOW_SCHEDULED_STATE();
9376 void PresShell::WillCauseReflow() {
9377 nsContentUtils::AddScriptBlocker();
9378 ++mChangeNestCount;
9381 void PresShell::DidCauseReflow() {
9382 NS_ASSERTION(mChangeNestCount != 0, "Unexpected call to DidCauseReflow()");
9383 --mChangeNestCount;
9384 nsContentUtils::RemoveScriptBlocker();
9387 void PresShell::WillDoReflow() {
9388 mDocument->FlushUserFontSet();
9390 mPresContext->FlushCounterStyles();
9392 mPresContext->FlushFontFeatureValues();
9394 mPresContext->FlushFontPaletteValues();
9396 mLastReflowStart = GetPerformanceNowUnclamped();
9399 void PresShell::DidDoReflow(bool aInterruptible) {
9400 MOZ_ASSERT(mPendingDidDoReflow);
9401 if (!nsContentUtils::IsSafeToRunScript()) {
9402 // If we're reflowing while script-blocked (e.g. from container query
9403 // updates), defer our reflow callbacks until the end of our next layout
9404 // flush.
9405 SetNeedLayoutFlush();
9406 return;
9409 auto clearPendingDidDoReflow =
9410 MakeScopeExit([&] { mPendingDidDoReflow = false; });
9412 mHiddenContentInForcedLayout.Clear();
9414 HandlePostedReflowCallbacks(aInterruptible);
9416 if (mIsDestroying) {
9417 return;
9420 nsAutoScriptBlocker scriptBlocker;
9421 AutoAssertNoFlush noReentrantFlush(*this);
9422 if (nsCOMPtr<nsIDocShell> docShell = mPresContext->GetDocShell()) {
9423 DOMHighResTimeStamp now = GetPerformanceNowUnclamped();
9424 docShell->NotifyReflowObservers(aInterruptible, mLastReflowStart, now);
9427 if (!mPresContext->HasPendingInterrupt()) {
9428 // The ResizeObserver object may exist in the outer documents (e.g. observe
9429 // an element in the in-process iframe) or any other documents which can
9430 // access |mDocument|, so we have to schedule the resize observers for all
9431 // possible documents via browsing context tree.
9432 if (RefPtr<BrowsingContext> bc = mDocument->GetBrowsingContext()) {
9433 bc->Top()->PreOrderWalk([](BrowsingContext* aCur) {
9434 // Use extant document because we only want to schedule the observer to
9435 // its refresh driver and so don't need to ensure the content viewer.
9436 if (const Document* doc = aCur->GetExtantDocument()) {
9437 doc->ScheduleResizeObserversNotification();
9443 if (StaticPrefs::layout_reflow_synthMouseMove()) {
9444 SynthesizeMouseMove(false);
9447 mPresContext->NotifyMissingFonts();
9450 DOMHighResTimeStamp PresShell::GetPerformanceNowUnclamped() {
9451 DOMHighResTimeStamp now = 0;
9453 if (nsPIDOMWindowInner* window = mDocument->GetInnerWindow()) {
9454 Performance* perf = window->GetPerformance();
9456 if (perf) {
9457 now = perf->NowUnclamped();
9461 return now;
9464 void PresShell::sReflowContinueCallback(nsITimer* aTimer, void* aPresShell) {
9465 RefPtr<PresShell> self = static_cast<PresShell*>(aPresShell);
9467 MOZ_ASSERT(aTimer == self->mReflowContinueTimer, "Unexpected timer");
9468 self->mReflowContinueTimer = nullptr;
9469 self->ScheduleReflow();
9472 bool PresShell::ScheduleReflowOffTimer() {
9473 MOZ_ASSERT(!mObservingLayoutFlushes, "Shouldn't get here");
9474 ASSERT_REFLOW_SCHEDULED_STATE();
9476 if (!mReflowContinueTimer) {
9477 nsresult rv = NS_NewTimerWithFuncCallback(
9478 getter_AddRefs(mReflowContinueTimer), sReflowContinueCallback, this, 30,
9479 nsITimer::TYPE_ONE_SHOT, "sReflowContinueCallback",
9480 mDocument->EventTargetFor(TaskCategory::Other));
9481 return NS_SUCCEEDED(rv);
9483 return true;
9486 bool PresShell::DoReflow(nsIFrame* target, bool aInterruptible,
9487 OverflowChangedTracker* aOverflowTracker) {
9488 [[maybe_unused]] nsIURI* uri = mDocument->GetDocumentURI();
9489 AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING_RELEVANT_FOR_JS(
9490 "Reflow", LAYOUT_Reflow, uri ? uri->GetSpecOrDefault() : "N/A"_ns);
9492 LAYOUT_TELEMETRY_RECORD_BASE(Reflow);
9494 PerfStats::AutoMetricRecording<PerfStats::Metric::Reflowing> autoRecording;
9496 gfxTextPerfMetrics* tp = mPresContext->GetTextPerfMetrics();
9497 TimeStamp timeStart;
9498 if (tp) {
9499 tp->Accumulate();
9500 tp->reflowCount++;
9501 timeStart = TimeStamp::Now();
9504 // Schedule a paint, but don't actually mark this frame as changed for
9505 // retained DL building purposes. If any child frames get moved, then
9506 // they will schedule paint again. We could probaby skip this, and just
9507 // schedule a similar paint when a frame is deleted.
9508 target->SchedulePaint(nsIFrame::PAINT_DEFAULT, false);
9510 nsDocShell* docShell =
9511 static_cast<nsDocShell*>(GetPresContext()->GetDocShell());
9512 bool isTimelineRecording = TimelineConsumers::HasConsumer(docShell);
9514 if (isTimelineRecording) {
9515 TimelineConsumers::AddMarkerForDocShell(docShell, "Reflow",
9516 MarkerTracingType::START);
9519 Maybe<uint64_t> innerWindowID;
9520 if (auto* window = mDocument->GetInnerWindow()) {
9521 innerWindowID = Some(window->WindowID());
9523 AutoProfilerTracing tracingLayoutFlush(
9524 "Paint", "Reflow", geckoprofiler::category::LAYOUT,
9525 std::move(mReflowCause), innerWindowID);
9526 mReflowCause = nullptr;
9528 FlushPendingScrollAnchorSelections();
9530 if (mReflowContinueTimer) {
9531 mReflowContinueTimer->Cancel();
9532 mReflowContinueTimer = nullptr;
9535 const bool isRoot = target == mFrameConstructor->GetRootFrame();
9537 MOZ_ASSERT(isRoot || aOverflowTracker,
9538 "caller must provide overflow tracker when reflowing "
9539 "non-root frames");
9541 // CreateReferenceRenderingContext can return nullptr
9542 UniquePtr<gfxContext> rcx(CreateReferenceRenderingContext());
9544 #ifdef DEBUG
9545 mCurrentReflowRoot = target;
9546 #endif
9548 // If the target frame is the root of the frame hierarchy, then
9549 // use all the available space. If it's simply a `reflow root',
9550 // then use the target frame's size as the available space.
9551 WritingMode wm = target->GetWritingMode();
9552 LogicalSize size(wm);
9553 if (isRoot) {
9554 size = LogicalSize(wm, mPresContext->GetVisibleArea().Size());
9555 } else {
9556 size = target->GetLogicalSize();
9559 OverflowAreas oldOverflow; // initialized and used only when !isRoot
9560 if (!isRoot) {
9561 oldOverflow = target->GetOverflowAreas();
9564 NS_ASSERTION(!target->GetNextInFlow() && !target->GetPrevInFlow(),
9565 "reflow roots should never split");
9567 // Don't pass size directly to the reflow input, since a
9568 // constrained height implies page/column breaking.
9569 LogicalSize reflowSize(wm, size.ISize(wm), NS_UNCONSTRAINEDSIZE);
9570 ReflowInput reflowInput(mPresContext, target, rcx.get(), reflowSize,
9571 ReflowInput::InitFlag::CallerWillInit);
9572 reflowInput.mOrthogonalLimit = size.BSize(wm);
9574 if (isRoot) {
9575 reflowInput.Init(mPresContext);
9577 // When the root frame is being reflowed with unconstrained block-size
9578 // (which happens when we're called from
9579 // nsDocumentViewer::SizeToContent), we're effectively doing a
9580 // resize in the block direction, since it changes the meaning of
9581 // percentage block-sizes even if no block-sizes actually changed.
9582 // The same applies when we reflow again after that computation. This is
9583 // an unusual case, and isn't caught by ReflowInput::InitResizeFlags.
9584 bool hasUnconstrainedBSize = size.BSize(wm) == NS_UNCONSTRAINEDSIZE;
9586 if (hasUnconstrainedBSize || mLastRootReflowHadUnconstrainedBSize) {
9587 reflowInput.SetBResize(true);
9590 mLastRootReflowHadUnconstrainedBSize = hasUnconstrainedBSize;
9591 } else {
9592 // Initialize reflow input with current used border and padding,
9593 // in case this was set specially by the parent frame when the reflow root
9594 // was reflowed by its parent.
9595 reflowInput.Init(mPresContext, Nothing(),
9596 Some(target->GetLogicalUsedBorder(wm)),
9597 Some(target->GetLogicalUsedPadding(wm)));
9600 // fix the computed height
9601 NS_ASSERTION(reflowInput.ComputedPhysicalMargin() == nsMargin(0, 0, 0, 0),
9602 "reflow input should not set margin for reflow roots");
9603 if (size.BSize(wm) != NS_UNCONSTRAINEDSIZE) {
9604 nscoord computedBSize =
9605 size.BSize(wm) -
9606 reflowInput.ComputedLogicalBorderPadding(wm).BStartEnd(wm);
9607 computedBSize = std::max(computedBSize, 0);
9608 reflowInput.SetComputedBSize(computedBSize);
9610 NS_ASSERTION(
9611 reflowInput.ComputedISize() ==
9612 size.ISize(wm) -
9613 reflowInput.ComputedLogicalBorderPadding(wm).IStartEnd(wm),
9614 "reflow input computed incorrect inline size");
9616 mPresContext->ReflowStarted(aInterruptible);
9617 mIsReflowing = true;
9619 nsReflowStatus status;
9620 ReflowOutput desiredSize(reflowInput);
9621 target->Reflow(mPresContext, desiredSize, reflowInput, status);
9623 // If an incremental reflow is initiated at a frame other than the
9624 // root frame, then its desired size had better not change! If it's
9625 // initiated at the root, then the size better not change unless its
9626 // height was unconstrained to start with.
9627 nsRect boundsRelativeToTarget =
9628 nsRect(0, 0, desiredSize.Width(), desiredSize.Height());
9629 NS_ASSERTION((isRoot && size.BSize(wm) == NS_UNCONSTRAINEDSIZE) ||
9630 (desiredSize.ISize(wm) == size.ISize(wm) &&
9631 desiredSize.BSize(wm) == size.BSize(wm)),
9632 "non-root frame's desired size changed during an "
9633 "incremental reflow");
9634 NS_ASSERTION(status.IsEmpty(), "reflow roots should never split");
9636 target->SetSize(boundsRelativeToTarget.Size());
9638 // Always use boundsRelativeToTarget here, not desiredSize.InkOverflowRect(),
9639 // because for root frames (where they could be different, since root frames
9640 // are allowed to have overflow) the root view bounds need to match the
9641 // viewport bounds; the view manager "window dimensions" code depends on it.
9642 if (target->HasView()) {
9643 nsContainerFrame::SyncFrameViewAfterReflow(
9644 mPresContext, target, target->GetView(), boundsRelativeToTarget);
9645 if (target->IsViewportFrame()) {
9646 SyncWindowProperties(/* aSync = */ false);
9650 target->DidReflow(mPresContext, nullptr);
9651 if (target->IsInScrollAnchorChain()) {
9652 ScrollAnchorContainer* container = ScrollAnchorContainer::FindFor(target);
9653 PostPendingScrollAnchorAdjustment(container);
9655 if (isRoot && size.BSize(wm) == NS_UNCONSTRAINEDSIZE) {
9656 mPresContext->SetVisibleArea(boundsRelativeToTarget);
9659 #ifdef DEBUG
9660 mCurrentReflowRoot = nullptr;
9661 #endif
9663 if (!isRoot && oldOverflow != target->GetOverflowAreas()) {
9664 // The overflow area changed. Propagate this change to ancestors.
9665 aOverflowTracker->AddFrame(target->GetParent(),
9666 OverflowChangedTracker::CHILDREN_CHANGED);
9669 NS_ASSERTION(
9670 mPresContext->HasPendingInterrupt() || mFramesToDirty.Count() == 0,
9671 "Why do we need to dirty anything if not interrupted?");
9673 mIsReflowing = false;
9674 bool interrupted = mPresContext->HasPendingInterrupt();
9675 if (interrupted) {
9676 // Make sure target gets reflowed again.
9677 for (const auto& key : mFramesToDirty) {
9678 // Mark frames dirty until target frame.
9679 for (nsIFrame* f = key; f && !f->IsSubtreeDirty(); f = f->GetParent()) {
9680 f->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
9681 if (f->IsFlexItem()) {
9682 nsFlexContainerFrame::MarkCachedFlexMeasurementsDirty(f);
9685 if (f == target) {
9686 break;
9691 NS_ASSERTION(target->IsSubtreeDirty(), "Why is the target not dirty?");
9692 mDirtyRoots.Add(target);
9693 SetNeedLayoutFlush();
9695 // Clear mFramesToDirty after we've done the target->IsSubtreeDirty()
9696 // assertion so that if it fails it's easier to see what's going on.
9697 #ifdef NOISY_INTERRUPTIBLE_REFLOW
9698 printf("mFramesToDirty.Count() == %u\n", mFramesToDirty.Count());
9699 #endif /* NOISY_INTERRUPTIBLE_REFLOW */
9700 mFramesToDirty.Clear();
9702 // Any FlushPendingNotifications with interruptible reflows
9703 // should be suppressed now. We don't want to do extra reflow work
9704 // before our reflow event happens.
9705 mWasLastReflowInterrupted = true;
9706 MaybeScheduleReflow();
9709 // dump text perf metrics for reflows with significant text processing
9710 if (tp) {
9711 if (tp->current.numChars > 100) {
9712 TimeDuration reflowTime = TimeStamp::Now() - timeStart;
9713 LogTextPerfStats(tp, this, tp->current, reflowTime.ToMilliseconds(),
9714 eLog_reflow, nullptr);
9716 tp->Accumulate();
9719 if (isTimelineRecording) {
9720 TimelineConsumers::AddMarkerForDocShell(docShell, "Reflow",
9721 MarkerTracingType::END);
9724 return !interrupted;
9727 #ifdef DEBUG
9728 void PresShell::DoVerifyReflow() {
9729 if (GetVerifyReflowEnable()) {
9730 // First synchronously render what we have so far so that we can
9731 // see it.
9732 nsView* rootView = mViewManager->GetRootView();
9733 mViewManager->InvalidateView(rootView);
9735 FlushPendingNotifications(FlushType::Layout);
9736 mInVerifyReflow = true;
9737 bool ok = VerifyIncrementalReflow();
9738 mInVerifyReflow = false;
9739 if (VerifyReflowFlags::All & gVerifyReflowFlags) {
9740 printf("ProcessReflowCommands: finished (%s)\n", ok ? "ok" : "failed");
9743 if (!mDirtyRoots.IsEmpty()) {
9744 printf("XXX yikes! reflow commands queued during verify-reflow\n");
9748 #endif
9750 // used with Telemetry metrics
9751 #define NS_LONG_REFLOW_TIME_MS 5000
9753 bool PresShell::ProcessReflowCommands(bool aInterruptible) {
9754 if (mDirtyRoots.IsEmpty() && !mShouldUnsuppressPainting &&
9755 !mPendingDidDoReflow) {
9756 // Nothing to do; bail out
9757 return true;
9760 const bool wasProcessingReflowCommands = mProcessingReflowCommands;
9761 auto restoreProcessingReflowCommands = MakeScopeExit(
9762 [&] { mProcessingReflowCommands = wasProcessingReflowCommands; });
9763 mProcessingReflowCommands = true;
9765 auto timerStart = mozilla::TimeStamp::Now();
9766 bool interrupted = false;
9767 if (!mDirtyRoots.IsEmpty()) {
9768 #ifdef DEBUG
9769 if (VerifyReflowFlags::DumpCommands & gVerifyReflowFlags) {
9770 printf("ProcessReflowCommands: begin incremental reflow\n");
9772 #endif
9774 // If reflow is interruptible, then make a note of our deadline.
9775 const PRIntervalTime deadline =
9776 aInterruptible
9777 ? PR_IntervalNow() + PR_MicrosecondsToInterval(gMaxRCProcessingTime)
9778 : (PRIntervalTime)0;
9780 // Scope for the reflow entry point
9781 nsAutoScriptBlocker scriptBlocker;
9782 WillDoReflow();
9783 AUTO_LAYOUT_PHASE_ENTRY_POINT(GetPresContext(), Reflow);
9784 nsViewManager::AutoDisableRefresh refreshBlocker(mViewManager);
9786 OverflowChangedTracker overflowTracker;
9788 do {
9789 // Send an incremental reflow notification to the target frame.
9790 nsIFrame* target = mDirtyRoots.PopShallowestRoot();
9792 if (!target->IsSubtreeDirty()) {
9793 // It's not dirty anymore, which probably means the notification
9794 // was posted in the middle of a reflow (perhaps with a reflow
9795 // root in the middle). Don't do anything.
9796 continue;
9799 interrupted = !DoReflow(target, aInterruptible, &overflowTracker);
9801 // Keep going until we're out of reflow commands, or we've run
9802 // past our deadline, or we're interrupted.
9803 } while (!interrupted && !mDirtyRoots.IsEmpty() &&
9804 (!aInterruptible || PR_IntervalNow() < deadline));
9806 interrupted = !mDirtyRoots.IsEmpty();
9808 overflowTracker.Flush();
9810 if (!interrupted) {
9811 // We didn't get interrupted. Go ahead and perform scroll anchor
9812 // adjustments.
9813 FlushPendingScrollAnchorAdjustments();
9815 mPendingDidDoReflow = true;
9818 // Exiting the scriptblocker might have killed us. If we were processing
9819 // scroll commands, let the outermost call deal with it.
9820 if (!mIsDestroying && mPendingDidDoReflow && !wasProcessingReflowCommands) {
9821 DidDoReflow(aInterruptible);
9824 // DidDoReflow might have killed us
9825 if (!mIsDestroying) {
9826 #ifdef DEBUG
9827 if (VerifyReflowFlags::DumpCommands & gVerifyReflowFlags) {
9828 printf("\nPresShell::ProcessReflowCommands() finished: this=%p\n",
9829 (void*)this);
9831 DoVerifyReflow();
9832 #endif
9834 // If any new reflow commands were enqueued during the reflow, schedule
9835 // another reflow event to process them. Note that we want to do this
9836 // after DidDoReflow(), since that method can change whether there are
9837 // dirty roots around by flushing, and there's no point in posting a
9838 // reflow event just to have the flush revoke it.
9839 if (!mDirtyRoots.IsEmpty()) {
9840 MaybeScheduleReflow();
9841 // And record that we might need flushing
9842 SetNeedLayoutFlush();
9846 if (!mIsDestroying && mShouldUnsuppressPainting && mDirtyRoots.IsEmpty()) {
9847 // We only unlock if we're out of reflows. It's pointless
9848 // to unlock if reflows are still pending, since reflows
9849 // are just going to thrash the frames around some more. By
9850 // waiting we avoid an overeager "jitter" effect.
9851 mShouldUnsuppressPainting = false;
9852 UnsuppressAndInvalidate();
9855 if (mDocument->GetRootElement()) {
9856 TimeDuration elapsed = TimeStamp::Now() - timerStart;
9857 int32_t intElapsed = int32_t(elapsed.ToMilliseconds());
9859 if (intElapsed > NS_LONG_REFLOW_TIME_MS) {
9860 Telemetry::Accumulate(Telemetry::LONG_REFLOW_INTERRUPTIBLE,
9861 aInterruptible ? 1 : 0);
9865 return !interrupted;
9868 bool PresShell::DoFlushLayout(bool aInterruptible) {
9869 mFrameConstructor->RecalcQuotesAndCounters();
9870 return ProcessReflowCommands(aInterruptible);
9873 void PresShell::WindowSizeMoveDone() {
9874 if (mPresContext) {
9875 EventStateManager::ClearGlobalActiveContent(nullptr);
9876 ClearMouseCapture();
9880 NS_IMETHODIMP
9881 PresShell::Observe(nsISupports* aSubject, const char* aTopic,
9882 const char16_t* aData) {
9883 if (mIsDestroying) {
9884 NS_WARNING("our observers should have been unregistered by now");
9885 return NS_OK;
9888 if (!nsCRT::strcmp(aTopic, "memory-pressure")) {
9889 if (!AssumeAllFramesVisible() &&
9890 mPresContext->IsRootContentDocumentInProcess()) {
9891 DoUpdateApproximateFrameVisibility(/* aRemoveOnly = */ true);
9893 return NS_OK;
9896 if (!nsCRT::strcmp(aTopic, NS_WIDGET_WAKE_OBSERVER_TOPIC)) {
9897 mLastOSWake = TimeStamp::Now();
9898 return NS_OK;
9901 // For parent process, user may expect the UI is interactable after a
9902 // tab (previously opened page or home page) has restored.
9903 if (!nsCRT::strcmp(aTopic, "sessionstore-one-or-no-tab-restored")) {
9904 MOZ_ASSERT(XRE_IsParentProcess());
9905 sProcessInteractable = true;
9907 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
9908 if (os) {
9909 os->RemoveObserver(this, "sessionstore-one-or-no-tab-restored");
9911 return NS_OK;
9914 if (!nsCRT::strcmp(aTopic, "font-info-updated")) {
9915 // See how gfxPlatform::ForceGlobalReflow encodes this.
9916 bool needsReframe = aData && !!aData[0];
9917 mPresContext->ForceReflowForFontInfoUpdate(needsReframe);
9918 return NS_OK;
9921 // The "look-and-feel-changed" notification for JS observers will be
9922 // dispatched HandleGlobalThemeChange once LookAndFeel caches are cleared.
9923 if (!nsCRT::strcmp(aTopic, "internal-look-and-feel-changed")) {
9924 // See how LookAndFeel::NotifyChangedAllWindows encodes this.
9925 auto kind = widget::ThemeChangeKind(aData[0]);
9926 mPresContext->ThemeChanged(kind);
9927 return NS_OK;
9930 NS_WARNING("unrecognized topic in PresShell::Observe");
9931 return NS_ERROR_FAILURE;
9934 bool PresShell::AddRefreshObserver(nsARefreshObserver* aObserver,
9935 FlushType aFlushType,
9936 const char* aObserverDescription) {
9937 nsPresContext* presContext = GetPresContext();
9938 if (MOZ_UNLIKELY(!presContext)) {
9939 return false;
9941 presContext->RefreshDriver()->AddRefreshObserver(aObserver, aFlushType,
9942 aObserverDescription);
9943 return true;
9946 bool PresShell::RemoveRefreshObserver(nsARefreshObserver* aObserver,
9947 FlushType aFlushType) {
9948 nsPresContext* presContext = GetPresContext();
9949 return presContext && presContext->RefreshDriver()->RemoveRefreshObserver(
9950 aObserver, aFlushType);
9953 bool PresShell::AddPostRefreshObserver(nsAPostRefreshObserver* aObserver) {
9954 nsPresContext* presContext = GetPresContext();
9955 if (!presContext) {
9956 return false;
9958 presContext->RefreshDriver()->AddPostRefreshObserver(aObserver);
9959 return true;
9962 bool PresShell::RemovePostRefreshObserver(nsAPostRefreshObserver* aObserver) {
9963 nsPresContext* presContext = GetPresContext();
9964 if (!presContext) {
9965 return false;
9967 presContext->RefreshDriver()->RemovePostRefreshObserver(aObserver);
9968 return true;
9971 void PresShell::DoObserveStyleFlushes() {
9972 MOZ_ASSERT(!ObservingStyleFlushes());
9973 mObservingStyleFlushes = true;
9975 if (MOZ_LIKELY(!mDocument->GetBFCacheEntry())) {
9976 mPresContext->RefreshDriver()->AddStyleFlushObserver(this);
9980 void PresShell::DoObserveLayoutFlushes() {
9981 MOZ_ASSERT(!ObservingLayoutFlushes());
9982 mObservingLayoutFlushes = true;
9984 if (MOZ_LIKELY(!mDocument->GetBFCacheEntry())) {
9985 mPresContext->RefreshDriver()->AddLayoutFlushObserver(this);
9989 //------------------------------------------------------
9990 // End of protected and private methods on the PresShell
9991 //------------------------------------------------------
9993 //------------------------------------------------------------------
9994 //-- Delayed event Classes Impls
9995 //------------------------------------------------------------------
9997 PresShell::DelayedInputEvent::DelayedInputEvent()
9998 : DelayedEvent(), mEvent(nullptr) {}
10000 PresShell::DelayedInputEvent::~DelayedInputEvent() { delete mEvent; }
10002 void PresShell::DelayedInputEvent::Dispatch() {
10003 if (!mEvent || !mEvent->mWidget) {
10004 return;
10006 nsCOMPtr<nsIWidget> widget = mEvent->mWidget;
10007 nsEventStatus status;
10008 widget->DispatchEvent(mEvent, status);
10011 PresShell::DelayedMouseEvent::DelayedMouseEvent(WidgetMouseEvent* aEvent)
10012 : DelayedInputEvent() {
10013 MOZ_DIAGNOSTIC_ASSERT(aEvent->IsTrusted());
10014 WidgetMouseEvent* mouseEvent =
10015 new WidgetMouseEvent(true, aEvent->mMessage, aEvent->mWidget,
10016 aEvent->mReason, aEvent->mContextMenuTrigger);
10017 mouseEvent->AssignMouseEventData(*aEvent, false);
10018 mEvent = mouseEvent;
10021 PresShell::DelayedKeyEvent::DelayedKeyEvent(WidgetKeyboardEvent* aEvent)
10022 : DelayedInputEvent() {
10023 MOZ_DIAGNOSTIC_ASSERT(aEvent->IsTrusted());
10024 WidgetKeyboardEvent* keyEvent =
10025 new WidgetKeyboardEvent(true, aEvent->mMessage, aEvent->mWidget);
10026 keyEvent->AssignKeyEventData(*aEvent, false);
10027 keyEvent->mFlags.mIsSynthesizedForTests =
10028 aEvent->mFlags.mIsSynthesizedForTests;
10029 keyEvent->mFlags.mIsSuppressedOrDelayed = true;
10030 mEvent = keyEvent;
10033 bool PresShell::DelayedKeyEvent::IsKeyPressEvent() {
10034 return mEvent->mMessage == eKeyPress;
10037 // Start of DEBUG only code
10039 #ifdef DEBUG
10041 static void LogVerifyMessage(nsIFrame* k1, nsIFrame* k2, const char* aMsg) {
10042 nsAutoString n1, n2;
10043 if (k1) {
10044 k1->GetFrameName(n1);
10045 } else {
10046 n1.AssignLiteral(u"(null)");
10049 if (k2) {
10050 k2->GetFrameName(n2);
10051 } else {
10052 n2.AssignLiteral(u"(null)");
10055 printf("verifyreflow: %s %p != %s %p %s\n",
10056 NS_LossyConvertUTF16toASCII(n1).get(), (void*)k1,
10057 NS_LossyConvertUTF16toASCII(n2).get(), (void*)k2, aMsg);
10060 static void LogVerifyMessage(nsIFrame* k1, nsIFrame* k2, const char* aMsg,
10061 const nsRect& r1, const nsRect& r2) {
10062 printf("VerifyReflow Error:\n");
10063 nsAutoString name;
10065 if (k1) {
10066 k1->GetFrameName(name);
10067 printf(" %s %p ", NS_LossyConvertUTF16toASCII(name).get(), (void*)k1);
10069 printf("{%d, %d, %d, %d} != \n", r1.x, r1.y, r1.width, r1.height);
10071 if (k2) {
10072 k2->GetFrameName(name);
10073 printf(" %s %p ", NS_LossyConvertUTF16toASCII(name).get(), (void*)k2);
10075 printf("{%d, %d, %d, %d}\n %s\n", r2.x, r2.y, r2.width, r2.height, aMsg);
10078 static void LogVerifyMessage(nsIFrame* k1, nsIFrame* k2, const char* aMsg,
10079 const nsIntRect& r1, const nsIntRect& r2) {
10080 printf("VerifyReflow Error:\n");
10081 nsAutoString name;
10083 if (k1) {
10084 k1->GetFrameName(name);
10085 printf(" %s %p ", NS_LossyConvertUTF16toASCII(name).get(), (void*)k1);
10087 printf("{%d, %d, %d, %d} != \n", r1.x, r1.y, r1.width, r1.height);
10089 if (k2) {
10090 k2->GetFrameName(name);
10091 printf(" %s %p ", NS_LossyConvertUTF16toASCII(name).get(), (void*)k2);
10093 printf("{%d, %d, %d, %d}\n %s\n", r2.x, r2.y, r2.width, r2.height, aMsg);
10096 static bool CompareTrees(nsPresContext* aFirstPresContext,
10097 nsIFrame* aFirstFrame,
10098 nsPresContext* aSecondPresContext,
10099 nsIFrame* aSecondFrame) {
10100 if (!aFirstPresContext || !aFirstFrame || !aSecondPresContext ||
10101 !aSecondFrame)
10102 return true;
10103 // XXX Evil hack to reduce false positives; I can't seem to figure
10104 // out how to flush scrollbar changes correctly
10105 // if (aFirstFrame->IsScrollbarFrame())
10106 // return true;
10107 bool ok = true;
10108 const auto& childLists1 = aFirstFrame->ChildLists();
10109 const auto& childLists2 = aSecondFrame->ChildLists();
10110 auto iterLists1 = childLists1.begin();
10111 auto iterLists2 = childLists2.begin();
10112 do {
10113 const nsFrameList& kids1 = iterLists1 != childLists1.end()
10114 ? iterLists1->mList
10115 : nsFrameList::EmptyList();
10116 const nsFrameList& kids2 = iterLists2 != childLists2.end()
10117 ? iterLists2->mList
10118 : nsFrameList::EmptyList();
10119 int32_t l1 = kids1.GetLength();
10120 int32_t l2 = kids2.GetLength();
10121 if (l1 != l2) {
10122 ok = false;
10123 LogVerifyMessage(kids1.FirstChild(), kids2.FirstChild(),
10124 "child counts don't match: ");
10125 printf("%d != %d\n", l1, l2);
10126 if (!(VerifyReflowFlags::All & gVerifyReflowFlags)) {
10127 break;
10131 LayoutDeviceIntRect r1, r2;
10132 nsView* v1;
10133 nsView* v2;
10134 for (auto kids1Iter = kids1.begin(), kids2Iter = kids2.begin();;
10135 ++kids1Iter, ++kids2Iter) {
10136 nsIFrame* k1 = *kids1Iter;
10137 nsIFrame* k2 = *kids2Iter;
10138 if (((nullptr == k1) && (nullptr != k2)) ||
10139 ((nullptr != k1) && (nullptr == k2))) {
10140 ok = false;
10141 LogVerifyMessage(k1, k2, "child lists are different\n");
10142 break;
10143 } else if (nullptr != k1) {
10144 // Verify that the frames are the same size
10145 if (!k1->GetRect().IsEqualInterior(k2->GetRect())) {
10146 ok = false;
10147 LogVerifyMessage(k1, k2, "(frame rects)", k1->GetRect(),
10148 k2->GetRect());
10151 // Make sure either both have views or neither have views; if they
10152 // do have views, make sure the views are the same size. If the
10153 // views have widgets, make sure they both do or neither does. If
10154 // they do, make sure the widgets are the same size.
10155 v1 = k1->GetView();
10156 v2 = k2->GetView();
10157 if (((nullptr == v1) && (nullptr != v2)) ||
10158 ((nullptr != v1) && (nullptr == v2))) {
10159 ok = false;
10160 LogVerifyMessage(k1, k2, "child views are not matched\n");
10161 } else if (nullptr != v1) {
10162 if (!v1->GetBounds().IsEqualInterior(v2->GetBounds())) {
10163 LogVerifyMessage(k1, k2, "(view rects)", v1->GetBounds(),
10164 v2->GetBounds());
10167 nsIWidget* w1 = v1->GetWidget();
10168 nsIWidget* w2 = v2->GetWidget();
10169 if (((nullptr == w1) && (nullptr != w2)) ||
10170 ((nullptr != w1) && (nullptr == w2))) {
10171 ok = false;
10172 LogVerifyMessage(k1, k2, "child widgets are not matched\n");
10173 } else if (nullptr != w1) {
10174 r1 = w1->GetBounds();
10175 r2 = w2->GetBounds();
10176 if (!r1.IsEqualEdges(r2)) {
10177 LogVerifyMessage(k1, k2, "(widget rects)", r1.ToUnknownRect(),
10178 r2.ToUnknownRect());
10182 if (!ok && !(VerifyReflowFlags::All & gVerifyReflowFlags)) {
10183 break;
10186 // XXX Should perhaps compare their float managers.
10188 // Compare the sub-trees too
10189 if (!CompareTrees(aFirstPresContext, k1, aSecondPresContext, k2)) {
10190 ok = false;
10191 if (!(VerifyReflowFlags::All & gVerifyReflowFlags)) {
10192 break;
10195 } else {
10196 break;
10199 if (!ok && (!(VerifyReflowFlags::All & gVerifyReflowFlags))) {
10200 break;
10203 ++iterLists1;
10204 ++iterLists2;
10205 const bool lists1Done = iterLists1 == childLists1.end();
10206 const bool lists2Done = iterLists2 == childLists2.end();
10207 if (lists1Done != lists2Done ||
10208 (!lists1Done && iterLists1->mID != iterLists2->mID)) {
10209 if (!(VerifyReflowFlags::All & gVerifyReflowFlags)) {
10210 ok = false;
10212 LogVerifyMessage(kids1.FirstChild(), kids2.FirstChild(),
10213 "child list names are not matched: ");
10214 fprintf(stdout, "%s != %s\n",
10215 !lists1Done ? ChildListName(iterLists1->mID) : "(null)",
10216 !lists2Done ? ChildListName(iterLists2->mID) : "(null)");
10217 break;
10219 } while (ok && iterLists1 != childLists1.end());
10221 return ok;
10223 #endif
10225 #if 0
10226 static nsIFrame*
10227 FindTopFrame(nsIFrame* aRoot)
10229 if (aRoot) {
10230 nsIContent* content = aRoot->GetContent();
10231 if (content) {
10232 nsAtom* tag;
10233 content->GetTag(tag);
10234 if (nullptr != tag) {
10235 NS_RELEASE(tag);
10236 return aRoot;
10240 // Try one of the children
10241 for (nsIFrame* kid : aRoot->PrincipalChildList()) {
10242 nsIFrame* result = FindTopFrame(kid);
10243 if (nullptr != result) {
10244 return result;
10248 return nullptr;
10250 #endif
10252 #ifdef DEBUG
10254 // After an incremental reflow, we verify the correctness by doing a
10255 // full reflow into a fresh frame tree.
10256 bool PresShell::VerifyIncrementalReflow() {
10257 if (VerifyReflowFlags::Noisy & gVerifyReflowFlags) {
10258 printf("Building Verification Tree...\n");
10261 // Create a presentation context to view the new frame tree
10262 RefPtr<nsPresContext> cx = new nsRootPresContext(
10263 mDocument, mPresContext->IsPaginated()
10264 ? nsPresContext::eContext_PrintPreview
10265 : nsPresContext::eContext_Galley);
10266 NS_ENSURE_TRUE(cx, false);
10268 nsDeviceContext* dc = mPresContext->DeviceContext();
10269 nsresult rv = cx->Init(dc);
10270 NS_ENSURE_SUCCESS(rv, false);
10272 // Get our scrolling preference
10273 nsView* rootView = mViewManager->GetRootView();
10274 NS_ENSURE_TRUE(rootView->HasWidget(), false);
10275 nsIWidget* parentWidget = rootView->GetWidget();
10277 // Create a new view manager.
10278 RefPtr<nsViewManager> vm = new nsViewManager();
10279 NS_ENSURE_TRUE(vm, false);
10280 rv = vm->Init(dc);
10281 NS_ENSURE_SUCCESS(rv, false);
10283 // Create a child window of the parent that is our "root view/window"
10284 // Create a view
10285 nsRect tbounds = mPresContext->GetVisibleArea();
10286 nsView* view = vm->CreateView(tbounds, nullptr);
10287 NS_ENSURE_TRUE(view, false);
10289 // now create the widget for the view
10290 rv = view->CreateWidgetForParent(parentWidget, nullptr, true);
10291 NS_ENSURE_SUCCESS(rv, false);
10293 // Setup hierarchical relationship in view manager
10294 vm->SetRootView(view);
10296 // Make the new presentation context the same size as our
10297 // presentation context.
10298 cx->SetVisibleArea(mPresContext->GetVisibleArea());
10300 RefPtr<PresShell> presShell = mDocument->CreatePresShell(cx, vm);
10301 NS_ENSURE_TRUE(presShell, false);
10303 // Note that after we create the shell, we must make sure to destroy it
10304 presShell->SetVerifyReflowEnable(
10305 false); // turn off verify reflow while we're
10306 // reflowing the test frame tree
10307 vm->SetPresShell(presShell);
10309 nsAutoCauseReflowNotifier crNotifier(this);
10310 presShell->Initialize();
10312 presShell->FlushPendingNotifications(FlushType::Layout);
10313 presShell->SetVerifyReflowEnable(
10314 true); // turn on verify reflow again now that
10315 // we're done reflowing the test frame tree
10316 // Force the non-primary presshell to unsuppress; it doesn't want to normally
10317 // because it thinks it's hidden
10318 presShell->mPaintingSuppressed = false;
10319 if (VerifyReflowFlags::Noisy & gVerifyReflowFlags) {
10320 printf("Verification Tree built, comparing...\n");
10323 // Now that the document has been reflowed, use its frame tree to
10324 // compare against our frame tree.
10325 nsIFrame* root1 = mFrameConstructor->GetRootFrame();
10326 nsIFrame* root2 = presShell->GetRootFrame();
10327 bool ok = CompareTrees(mPresContext, root1, cx, root2);
10328 if (!ok && (VerifyReflowFlags::Noisy & gVerifyReflowFlags)) {
10329 printf("Verify reflow failed, primary tree:\n");
10330 root1->List(stdout);
10331 printf("Verification tree:\n");
10332 root2->List(stdout);
10335 # if 0
10336 // Sample code for dumping page to png
10337 // XXX Needs to be made more flexible
10338 if (!ok) {
10339 nsString stra;
10340 static int num = 0;
10341 stra.AppendLiteral("C:\\mozilla\\mozilla\\debug\\filea");
10342 stra.AppendInt(num);
10343 stra.AppendLiteral(".png");
10344 gfxUtils::WriteAsPNG(presShell, stra);
10345 nsString strb;
10346 strb.AppendLiteral("C:\\mozilla\\mozilla\\debug\\fileb");
10347 strb.AppendInt(num);
10348 strb.AppendLiteral(".png");
10349 gfxUtils::WriteAsPNG(presShell, strb);
10350 ++num;
10352 # endif
10354 presShell->EndObservingDocument();
10355 presShell->Destroy();
10356 if (VerifyReflowFlags::Noisy & gVerifyReflowFlags) {
10357 printf("Finished Verifying Reflow...\n");
10360 return ok;
10363 // Layout debugging hooks
10364 void PresShell::ListComputedStyles(FILE* out, int32_t aIndent) {
10365 nsIFrame* rootFrame = GetRootFrame();
10366 if (rootFrame) {
10367 rootFrame->Style()->List(out, aIndent);
10370 // The root element's frame's ComputedStyle is the root of a separate tree.
10371 Element* rootElement = mDocument->GetRootElement();
10372 if (rootElement) {
10373 nsIFrame* rootElementFrame = rootElement->GetPrimaryFrame();
10374 if (rootElementFrame) {
10375 rootElementFrame->Style()->List(out, aIndent);
10379 #endif
10381 #if defined(DEBUG) || defined(MOZ_LAYOUT_DEBUGGER)
10382 void PresShell::ListStyleSheets(FILE* out, int32_t aIndent) {
10383 auto ListStyleSheetsAtOrigin = [this, out, aIndent](StyleOrigin origin) {
10384 int32_t sheetCount = StyleSet()->SheetCount(origin);
10385 for (int32_t i = 0; i < sheetCount; ++i) {
10386 StyleSet()->SheetAt(origin, i)->List(out, aIndent);
10390 ListStyleSheetsAtOrigin(StyleOrigin::UserAgent);
10391 ListStyleSheetsAtOrigin(StyleOrigin::User);
10392 ListStyleSheetsAtOrigin(StyleOrigin::Author);
10394 #endif
10396 //=============================================================
10397 //=============================================================
10398 //-- Debug Reflow Counts
10399 //=============================================================
10400 //=============================================================
10401 #ifdef MOZ_REFLOW_PERF
10402 //-------------------------------------------------------------
10403 void PresShell::DumpReflows() {
10404 if (mReflowCountMgr) {
10405 nsAutoCString uriStr;
10406 if (mDocument) {
10407 nsIURI* uri = mDocument->GetDocumentURI();
10408 if (uri) {
10409 uri->GetPathQueryRef(uriStr);
10412 mReflowCountMgr->DisplayTotals(uriStr.get());
10413 mReflowCountMgr->DisplayHTMLTotals(uriStr.get());
10414 mReflowCountMgr->DisplayDiffsInTotals();
10418 //-------------------------------------------------------------
10419 void PresShell::CountReflows(const char* aName, nsIFrame* aFrame) {
10420 if (mReflowCountMgr) {
10421 mReflowCountMgr->Add(aName, aFrame);
10425 //-------------------------------------------------------------
10426 void PresShell::PaintCount(const char* aName, gfxContext* aRenderingContext,
10427 nsPresContext* aPresContext, nsIFrame* aFrame,
10428 const nsPoint& aOffset, uint32_t aColor) {
10429 if (mReflowCountMgr) {
10430 mReflowCountMgr->PaintCount(aName, aRenderingContext, aPresContext, aFrame,
10431 aOffset, aColor);
10435 //-------------------------------------------------------------
10436 void PresShell::SetPaintFrameCount(bool aPaintFrameCounts) {
10437 if (mReflowCountMgr) {
10438 mReflowCountMgr->SetPaintFrameCounts(aPaintFrameCounts);
10442 bool PresShell::IsPaintingFrameCounts() {
10443 if (mReflowCountMgr) return mReflowCountMgr->IsPaintingFrameCounts();
10444 return false;
10447 //------------------------------------------------------------------
10448 //-- Reflow Counter Classes Impls
10449 //------------------------------------------------------------------
10451 //------------------------------------------------------------------
10452 ReflowCounter::ReflowCounter(ReflowCountMgr* aMgr) : mMgr(aMgr) {
10453 ClearTotals();
10454 SetTotalsCache();
10457 //------------------------------------------------------------------
10458 ReflowCounter::~ReflowCounter() = default;
10460 //------------------------------------------------------------------
10461 void ReflowCounter::ClearTotals() { mTotal = 0; }
10463 //------------------------------------------------------------------
10464 void ReflowCounter::SetTotalsCache() { mCacheTotal = mTotal; }
10466 //------------------------------------------------------------------
10467 void ReflowCounter::CalcDiffInTotals() { mCacheTotal = mTotal - mCacheTotal; }
10469 //------------------------------------------------------------------
10470 void ReflowCounter::DisplayTotals(const char* aStr) {
10471 DisplayTotals(mTotal, aStr ? aStr : "Totals");
10474 //------------------------------------------------------------------
10475 void ReflowCounter::DisplayDiffTotals(const char* aStr) {
10476 DisplayTotals(mCacheTotal, aStr ? aStr : "Diff Totals");
10479 //------------------------------------------------------------------
10480 void ReflowCounter::DisplayHTMLTotals(const char* aStr) {
10481 DisplayHTMLTotals(mTotal, aStr ? aStr : "Totals");
10484 //------------------------------------------------------------------
10485 void ReflowCounter::DisplayTotals(uint32_t aTotal, const char* aTitle) {
10486 // figure total
10487 if (aTotal == 0) {
10488 return;
10490 ReflowCounter* gTots = (ReflowCounter*)mMgr->LookUp(kGrandTotalsStr);
10492 printf("%25s\t", aTitle);
10493 printf("%d\t", aTotal);
10494 if (gTots != this && aTotal > 0) {
10495 gTots->Add(aTotal);
10499 //------------------------------------------------------------------
10500 void ReflowCounter::DisplayHTMLTotals(uint32_t aTotal, const char* aTitle) {
10501 if (aTotal == 0) {
10502 return;
10505 ReflowCounter* gTots = (ReflowCounter*)mMgr->LookUp(kGrandTotalsStr);
10506 FILE* fd = mMgr->GetOutFile();
10507 if (!fd) {
10508 return;
10511 fprintf(fd, "<tr><td><center>%s</center></td>", aTitle);
10512 fprintf(fd, "<td><center>%d</center></td></tr>\n", aTotal);
10514 if (gTots != this && aTotal > 0) {
10515 gTots->Add(aTotal);
10519 //------------------------------------------------------------------
10520 //-- ReflowCountMgr
10521 //------------------------------------------------------------------
10523 # define KEY_BUF_SIZE_FOR_PTR \
10524 24 // adequate char[] buffer to sprintf a pointer
10526 ReflowCountMgr::ReflowCountMgr() : mCounts(10), mIndiFrameCounts(10) {
10527 mCycledOnce = false;
10528 mDumpFrameCounts = false;
10529 mDumpFrameByFrameCounts = false;
10530 mPaintFrameByFrameCounts = false;
10533 //------------------------------------------------------------------
10534 ReflowCountMgr::~ReflowCountMgr() = default;
10536 //------------------------------------------------------------------
10537 ReflowCounter* ReflowCountMgr::LookUp(const char* aName) {
10538 return mCounts.Get(aName);
10541 //------------------------------------------------------------------
10542 void ReflowCountMgr::Add(const char* aName, nsIFrame* aFrame) {
10543 NS_ASSERTION(aName != nullptr, "Name shouldn't be null!");
10545 if (mDumpFrameCounts) {
10546 auto* const counter = mCounts.GetOrInsertNew(aName, this);
10547 counter->Add();
10550 if ((mDumpFrameByFrameCounts || mPaintFrameByFrameCounts) &&
10551 aFrame != nullptr) {
10552 char key[KEY_BUF_SIZE_FOR_PTR];
10553 SprintfLiteral(key, "%p", (void*)aFrame);
10554 auto* const counter =
10555 mIndiFrameCounts
10556 .LookupOrInsertWith(key,
10557 [&aName, &aFrame, this]() {
10558 auto counter =
10559 MakeUnique<IndiReflowCounter>(this);
10560 counter->mFrame = aFrame;
10561 counter->mName.AssignASCII(aName);
10562 return counter;
10564 .get();
10565 // this eliminates extra counts from super classes
10566 if (counter && counter->mName.EqualsASCII(aName)) {
10567 counter->mCount++;
10568 counter->mCounter.Add(1);
10573 //------------------------------------------------------------------
10574 void ReflowCountMgr::PaintCount(const char* aName,
10575 gfxContext* aRenderingContext,
10576 nsPresContext* aPresContext, nsIFrame* aFrame,
10577 const nsPoint& aOffset, uint32_t aColor) {
10578 if (mPaintFrameByFrameCounts && aFrame != nullptr) {
10579 char key[KEY_BUF_SIZE_FOR_PTR];
10580 SprintfLiteral(key, "%p", (void*)aFrame);
10581 IndiReflowCounter* counter = mIndiFrameCounts.Get(key);
10582 if (counter != nullptr && counter->mName.EqualsASCII(aName)) {
10583 DrawTarget* drawTarget = aRenderingContext->GetDrawTarget();
10584 int32_t appUnitsPerDevPixel = aPresContext->AppUnitsPerDevPixel();
10586 aRenderingContext->Save();
10587 gfxPoint devPixelOffset =
10588 nsLayoutUtils::PointToGfxPoint(aOffset, appUnitsPerDevPixel);
10589 aRenderingContext->SetMatrixDouble(
10590 aRenderingContext->CurrentMatrixDouble().PreTranslate(
10591 devPixelOffset));
10593 // We don't care about the document language or user fonts here;
10594 // just get a default Latin font.
10595 nsFont font(StyleGenericFontFamily::Serif, Length::FromPixels(11));
10596 nsFontMetrics::Params params;
10597 params.language = nsGkAtoms::x_western;
10598 params.textPerf = aPresContext->GetTextPerfMetrics();
10599 params.featureValueLookup = aPresContext->GetFontFeatureValuesLookup();
10600 RefPtr<nsFontMetrics> fm = aPresContext->GetMetricsFor(font, params);
10602 char buf[16];
10603 int len = SprintfLiteral(buf, "%d", counter->mCount);
10604 nscoord x = 0, y = fm->MaxAscent();
10605 nscoord width, height = fm->MaxHeight();
10606 fm->SetTextRunRTL(false);
10607 width = fm->GetWidth(buf, len, drawTarget);
10609 sRGBColor color;
10610 sRGBColor color2;
10611 if (aColor != 0) {
10612 color = sRGBColor::FromABGR(aColor);
10613 color2 = sRGBColor(0.f, 0.f, 0.f);
10614 } else {
10615 gfx::Float rc = 0.f, gc = 0.f, bc = 0.f;
10616 if (counter->mCount < 5) {
10617 rc = 1.f;
10618 gc = 1.f;
10619 } else if (counter->mCount < 11) {
10620 gc = 1.f;
10621 } else {
10622 rc = 1.f;
10624 color = sRGBColor(rc, gc, bc);
10625 color2 = sRGBColor(rc / 2, gc / 2, bc / 2);
10628 nsRect rect(0, 0, width + 15, height + 15);
10629 Rect devPxRect =
10630 NSRectToSnappedRect(rect, appUnitsPerDevPixel, *drawTarget);
10631 ColorPattern black(ToDeviceColor(sRGBColor::OpaqueBlack()));
10632 drawTarget->FillRect(devPxRect, black);
10634 aRenderingContext->SetColor(color2);
10635 fm->DrawString(buf, len, x + 15, y + 15, aRenderingContext);
10636 aRenderingContext->SetColor(color);
10637 fm->DrawString(buf, len, x, y, aRenderingContext);
10639 aRenderingContext->Restore();
10644 //------------------------------------------------------------------
10645 void ReflowCountMgr::DoGrandTotals() {
10646 mCounts.WithEntryHandle(kGrandTotalsStr, [this](auto&& entry) {
10647 if (!entry) {
10648 entry.Insert(MakeUnique<ReflowCounter>(this));
10649 } else {
10650 entry.Data()->ClearTotals();
10654 printf("\t\t\t\tTotal\n");
10655 for (uint32_t i = 0; i < 78; i++) {
10656 printf("-");
10658 printf("\n");
10659 for (const auto& entry : mCounts) {
10660 entry.GetData()->DisplayTotals(entry.GetKey());
10664 static void RecurseIndiTotals(
10665 nsPresContext* aPresContext,
10666 nsClassHashtable<nsCharPtrHashKey, IndiReflowCounter>& aHT,
10667 nsIFrame* aParentFrame, int32_t aLevel) {
10668 if (aParentFrame == nullptr) {
10669 return;
10672 char key[KEY_BUF_SIZE_FOR_PTR];
10673 SprintfLiteral(key, "%p", (void*)aParentFrame);
10674 IndiReflowCounter* counter = aHT.Get(key);
10675 if (counter) {
10676 counter->mHasBeenOutput = true;
10677 char* name = ToNewCString(counter->mName);
10678 for (int32_t i = 0; i < aLevel; i++) printf(" ");
10679 printf("%s - %p [%d][", name, (void*)aParentFrame, counter->mCount);
10680 printf("%d", counter->mCounter.GetTotal());
10681 printf("]\n");
10682 free(name);
10685 for (nsIFrame* child : aParentFrame->PrincipalChildList()) {
10686 RecurseIndiTotals(aPresContext, aHT, child, aLevel + 1);
10690 //------------------------------------------------------------------
10691 void ReflowCountMgr::DoIndiTotalsTree() {
10692 printf("\n------------------------------------------------\n");
10693 printf("-- Individual Frame Counts\n");
10694 printf("------------------------------------------------\n");
10696 if (mPresShell) {
10697 nsIFrame* rootFrame = mPresShell->GetRootFrame();
10698 RecurseIndiTotals(mPresContext, mIndiFrameCounts, rootFrame, 0);
10699 printf("------------------------------------------------\n");
10700 printf("-- Individual Counts of Frames not in Root Tree\n");
10701 printf("------------------------------------------------\n");
10702 for (const auto& counter : mIndiFrameCounts.Values()) {
10703 if (!counter->mHasBeenOutput) {
10704 char* name = ToNewCString(counter->mName);
10705 printf("%s - %p [%d][", name, (void*)counter->mFrame,
10706 counter->mCount);
10707 printf("%d", counter->mCounter.GetTotal());
10708 printf("]\n");
10709 free(name);
10715 //------------------------------------------------------------------
10716 void ReflowCountMgr::DoGrandHTMLTotals() {
10717 mCounts.WithEntryHandle(kGrandTotalsStr, [this](auto&& entry) {
10718 if (!entry) {
10719 entry.Insert(MakeUnique<ReflowCounter>(this));
10720 } else {
10721 entry.Data()->ClearTotals();
10725 static const char* title[] = {"Class", "Reflows"};
10726 fprintf(mFD, "<tr>");
10727 for (uint32_t i = 0; i < ArrayLength(title); i++) {
10728 fprintf(mFD, "<td><center><b>%s<b></center></td>", title[i]);
10730 fprintf(mFD, "</tr>\n");
10732 for (const auto& entry : mCounts) {
10733 entry.GetData()->DisplayHTMLTotals(entry.GetKey());
10737 //------------------------------------
10738 void ReflowCountMgr::DisplayTotals(const char* aStr) {
10739 # ifdef DEBUG_rods
10740 printf("%s\n", aStr ? aStr : "No name");
10741 # endif
10742 if (mDumpFrameCounts) {
10743 DoGrandTotals();
10745 if (mDumpFrameByFrameCounts) {
10746 DoIndiTotalsTree();
10749 //------------------------------------
10750 void ReflowCountMgr::DisplayHTMLTotals(const char* aStr) {
10751 # ifdef WIN32x // XXX NOT XP!
10752 char name[1024];
10754 char* sptr = strrchr(aStr, '/');
10755 if (sptr) {
10756 sptr++;
10757 strcpy(name, sptr);
10758 char* eptr = strrchr(name, '.');
10759 if (eptr) {
10760 *eptr = 0;
10762 strcat(name, "_stats.html");
10764 mFD = fopen(name, "w");
10765 if (mFD) {
10766 fprintf(mFD, "<html><head><title>Reflow Stats</title></head><body>\n");
10767 const char* title = aStr ? aStr : "No name";
10768 fprintf(mFD,
10769 "<center><b>%s</b><br><table border=1 "
10770 "style=\"background-color:#e0e0e0\">",
10771 title);
10772 DoGrandHTMLTotals();
10773 fprintf(mFD, "</center></table>\n");
10774 fprintf(mFD, "</body></html>\n");
10775 fclose(mFD);
10776 mFD = nullptr;
10778 # endif // not XP!
10781 //------------------------------------------------------------------
10782 void ReflowCountMgr::ClearTotals() {
10783 for (const auto& data : mCounts.Values()) {
10784 data->ClearTotals();
10788 //------------------------------------------------------------------
10789 void ReflowCountMgr::ClearGrandTotals() {
10790 mCounts.WithEntryHandle(kGrandTotalsStr, [&](auto&& entry) {
10791 if (!entry) {
10792 entry.Insert(MakeUnique<ReflowCounter>(this));
10793 } else {
10794 entry.Data()->ClearTotals();
10795 entry.Data()->SetTotalsCache();
10800 //------------------------------------------------------------------
10801 void ReflowCountMgr::DisplayDiffsInTotals() {
10802 if (mCycledOnce) {
10803 printf("Differences\n");
10804 for (int32_t i = 0; i < 78; i++) {
10805 printf("-");
10807 printf("\n");
10808 ClearGrandTotals();
10811 for (const auto& entry : mCounts) {
10812 if (mCycledOnce) {
10813 entry.GetData()->CalcDiffInTotals();
10814 entry.GetData()->DisplayDiffTotals(entry.GetKey());
10816 entry.GetData()->SetTotalsCache();
10819 mCycledOnce = true;
10822 #endif // MOZ_REFLOW_PERF
10824 nsIFrame* PresShell::GetAbsoluteContainingBlock(nsIFrame* aFrame) {
10825 return FrameConstructor()->GetAbsoluteContainingBlock(
10826 aFrame, nsCSSFrameConstructor::ABS_POS);
10829 void PresShell::ActivenessMaybeChanged() {
10830 if (!mDocument) {
10831 return;
10833 auto activeness = ComputeActiveness();
10834 SetIsActive(activeness.mShouldBeActive, activeness.mIsInActiveTab);
10837 // A PresShell being active means that it is visible (or close to be visible, if
10838 // the front-end is warming it). That means that when it is active we always
10839 // tick its refresh driver at full speed if needed.
10841 // However we also want to track whether we're in the active tab (represented by
10842 // the browsing context activeness) for the refresh driver to be able to treat
10843 // invisible-but-in-the-active-tab frames slightly differently in some
10844 // circumstances (give them a throttled or unthrottled refresh driver after a
10845 // while). mIsInActiveTab should ~always be GetBrowsingContext()->IsActive().
10847 // Image documents behave specially in the sense that they are always "active"
10848 // and never "in the active tab". However these documents tick manually so
10849 // there's not much to worry about there.
10850 auto PresShell::ComputeActiveness() const -> Activeness {
10851 MOZ_LOG(gLog, LogLevel::Debug,
10852 ("PresShell::ShouldBeActive(%s, %d, %d)\n",
10853 mDocument->GetDocumentURI()
10854 ? mDocument->GetDocumentURI()->GetSpecOrDefault().get()
10855 : "(no uri)",
10856 mIsActive, mIsInActiveTab));
10858 Document* doc = mDocument;
10860 if (doc->IsBeingUsedAsImage()) {
10861 // Documents used as an image can remain active. They do not tick their
10862 // refresh driver if not painted, and they can't run script or such so they
10863 // can't really observe much else.
10865 // Image docs can be displayed in multiple docs at the same time so the "in
10866 // active tab" bool doesn't make much sense for them.
10867 return {true, false};
10870 if (Document* displayDoc = doc->GetDisplayDocument()) {
10871 // Ok, we're an external resource document -- we need to use our display
10872 // document's docshell to determine "IsActive" status, since we lack
10873 // a browsing context of our own.
10874 MOZ_ASSERT(!doc->GetBrowsingContext(),
10875 "external resource doc shouldn't have its own BC");
10876 doc = displayDoc;
10879 BrowsingContext* bc = doc->GetBrowsingContext();
10880 const bool inActiveTab = bc && bc->IsActive();
10882 MOZ_LOG(gLog, LogLevel::Debug,
10883 (" > BrowsingContext %p active: %d", bc, inActiveTab));
10885 Document* root = nsContentUtils::GetInProcessSubtreeRootDocument(doc);
10886 if (auto* browserChild = BrowserChild::GetFrom(root->GetDocShell())) {
10887 // We might want to activate a tab even though the browsing-context is not
10888 // active if the BrowserChild is considered visible. This serves two
10889 // purposes:
10891 // * For top-level tabs, we use this for tab warming. The browsing-context
10892 // might still be inactive, but we want to activate the pres shell and
10893 // the refresh driver.
10895 // * For oop iframes, we do want to throttle them if they're not visible.
10897 // TODO(emilio): Consider unifying the in-process vs. fission iframe
10898 // throttling code (in-process throttling for non-visible iframes lives
10899 // right now in Document::ShouldThrottleFrameRequests(), but that only
10900 // throttles rAF).
10901 if (!browserChild->IsVisible()) {
10902 MOZ_LOG(gLog, LogLevel::Debug,
10903 (" > BrowserChild %p is not visible", browserChild));
10904 return {false, inActiveTab};
10907 // If the browser is visible but just due to be preserving layers
10908 // artificially, we do want to fall back to the browsing context activeness
10909 // instead. Otherwise we do want to be active for the use cases above.
10910 if (!browserChild->IsPreservingLayers()) {
10911 MOZ_LOG(gLog, LogLevel::Debug,
10912 (" > BrowserChild %p is visible and not preserving layers",
10913 browserChild));
10914 return {true, inActiveTab};
10916 MOZ_LOG(
10917 gLog, LogLevel::Debug,
10918 (" > BrowserChild %p is visible and preserving layers", browserChild));
10920 return {inActiveTab, inActiveTab};
10923 void PresShell::SetIsActive(bool aIsActive, bool aIsInActiveTab) {
10924 MOZ_ASSERT(mDocument, "should only be called with a document");
10926 const bool activityChanged = mIsActive != aIsActive;
10927 const bool inActiveTabChanged = mIsInActiveTab != aIsInActiveTab;
10929 mIsActive = aIsActive;
10930 mIsInActiveTab = aIsInActiveTab;
10932 nsPresContext* presContext = GetPresContext();
10933 if (presContext &&
10934 presContext->RefreshDriver()->GetPresContext() == presContext) {
10935 presContext->RefreshDriver()->SetActivity(aIsActive, aIsInActiveTab);
10938 if (activityChanged || inActiveTabChanged) {
10939 // Propagate state-change to my resource documents' PresShells and other
10940 // subdocuments.
10942 // Note that it is fine to not propagate to fission iframes. Those will
10943 // become active / inactive as needed as a result of they getting painted /
10944 // not painted eventually.
10945 auto recurse = [aIsActive, aIsInActiveTab](Document& aSubDoc) {
10946 if (PresShell* presShell = aSubDoc.GetPresShell()) {
10947 presShell->SetIsActive(aIsActive, aIsInActiveTab);
10949 return CallState::Continue;
10951 mDocument->EnumerateExternalResources(recurse);
10952 mDocument->EnumerateSubDocuments(recurse);
10955 UpdateImageLockingState();
10957 if (activityChanged) {
10958 #if defined(MOZ_WIDGET_ANDROID)
10959 if (!aIsActive && presContext &&
10960 presContext->IsRootContentDocumentCrossProcess()) {
10961 if (BrowserChild* browserChild = BrowserChild::GetFrom(this)) {
10962 // Reset the dynamic toolbar offset state.
10963 presContext->UpdateDynamicToolbarOffset(0);
10966 #endif
10969 if (aIsActive) {
10970 #ifdef ACCESSIBILITY
10971 if (nsAccessibilityService* accService = GetAccService()) {
10972 accService->PresShellActivated(this);
10974 #endif
10975 if (nsIFrame* rootFrame = GetRootFrame()) {
10976 rootFrame->SchedulePaint();
10981 RefPtr<MobileViewportManager> PresShell::GetMobileViewportManager() const {
10982 return mMobileViewportManager;
10985 Maybe<MobileViewportManager::ManagerType> UseMobileViewportManager(
10986 PresShell* aPresShell, Document* aDocument) {
10987 // If we're not using APZ, we won't be able to zoom, so there is no
10988 // point in having an MVM.
10989 if (nsPresContext* presContext = aPresShell->GetPresContext()) {
10990 if (nsIWidget* widget = presContext->GetNearestWidget()) {
10991 if (!widget->AsyncPanZoomEnabled()) {
10992 return Nothing();
10996 if (nsLayoutUtils::ShouldHandleMetaViewport(aDocument)) {
10997 return Some(MobileViewportManager::ManagerType::VisualAndMetaViewport);
10999 if (StaticPrefs::apz_mvm_force_enabled() ||
11000 nsLayoutUtils::AllowZoomingForDocument(aDocument)) {
11001 return Some(MobileViewportManager::ManagerType::VisualViewportOnly);
11003 return Nothing();
11006 void PresShell::MaybeRecreateMobileViewportManager(bool aAfterInitialization) {
11007 // Determine if we require a MobileViewportManager, and what kind if so. We
11008 // need one any time we allow resolution zooming for a document, and any time
11009 // we want to obey <meta name="viewport"> tags for it.
11010 Maybe<MobileViewportManager::ManagerType> mvmType =
11011 UseMobileViewportManager(this, mDocument);
11013 if (mvmType.isNothing() && !mMobileViewportManager) {
11014 // We don't need one and don't have it. So we're done.
11015 return;
11017 if (mvmType && mMobileViewportManager &&
11018 *mvmType == mMobileViewportManager->GetManagerType()) {
11019 // We need one and we have one of the correct type, so we're done.
11020 return;
11023 if (mMobileViewportManager) {
11024 // We have one, but we need to either destroy it completely to replace it
11025 // with another one of the correct type. So either way, let's destroy the
11026 // one we have.
11027 mMobileViewportManager->Destroy();
11028 mMobileViewportManager = nullptr;
11029 mMVMContext = nullptr;
11031 ResetVisualViewportSize();
11033 // After we clear out the MVM and the MVMContext, also reset the
11034 // resolution to its pre-MVM value.
11035 SetResolutionAndScaleTo(mDocument->GetSavedResolutionBeforeMVM(),
11036 ResolutionChangeOrigin::MainThreadRestore);
11038 if (aAfterInitialization) {
11039 // Force a reflow to our correct view manager size.
11040 ForceResizeReflowWithCurrentDimensions();
11044 if (mvmType) {
11045 // Let's create the MVM of the type that we need. At this point we shouldn't
11046 // have one.
11047 MOZ_ASSERT(!mMobileViewportManager);
11049 if (mPresContext->IsRootContentDocumentCrossProcess()) {
11050 // Store the resolution so we can restore to this resolution when
11051 // the MVM is destroyed.
11052 mDocument->SetSavedResolutionBeforeMVM(mResolution.valueOr(1.0f));
11054 mMVMContext = new GeckoMVMContext(mDocument, this);
11055 mMobileViewportManager = new MobileViewportManager(mMVMContext, *mvmType);
11056 if (MOZ_UNLIKELY(
11057 MOZ_LOG_TEST(MobileViewportManager::gLog, LogLevel::Debug))) {
11058 nsIURI* uri = mDocument->GetDocumentURI();
11059 MOZ_LOG(MobileViewportManager::gLog, LogLevel::Debug,
11060 ("Created MVM %p (type %d) for URI %s",
11061 mMobileViewportManager.get(), (int)*mvmType,
11062 uri ? uri->GetSpecOrDefault().get() : "(null)"));
11065 if (aAfterInitialization) {
11066 // Setting the initial viewport will trigger a reflow.
11067 mMobileViewportManager->SetInitialViewport();
11073 bool PresShell::UsesMobileViewportSizing() const {
11074 return mMobileViewportManager != nullptr &&
11075 nsLayoutUtils::ShouldHandleMetaViewport(mDocument);
11079 * Determines the current image locking state. Called when one of the
11080 * dependent factors changes.
11082 void PresShell::UpdateImageLockingState() {
11083 // We're locked if we're both thawed and active.
11084 const bool locked = !mFrozen && mIsActive;
11085 auto* tracker = mDocument->ImageTracker();
11086 if (locked == tracker->GetLockingState()) {
11087 return;
11090 tracker->SetLockingState(locked);
11091 if (locked) {
11092 // Request decodes for visible image frames; we want to start decoding as
11093 // quickly as possible when we get foregrounded to minimize flashing.
11094 for (const auto& key : mApproximatelyVisibleFrames) {
11095 if (nsImageFrame* imageFrame = do_QueryFrame(key)) {
11096 imageFrame->MaybeDecodeForPredictedSize();
11102 PresShell* PresShell::GetRootPresShell() const {
11103 if (mPresContext) {
11104 nsPresContext* rootPresContext = mPresContext->GetRootPresContext();
11105 if (rootPresContext) {
11106 return rootPresContext->PresShell();
11109 return nullptr;
11112 void PresShell::AddSizeOfIncludingThis(nsWindowSizes& aSizes) const {
11113 MallocSizeOf mallocSizeOf = aSizes.mState.mMallocSizeOf;
11114 mFrameArena.AddSizeOfExcludingThis(aSizes, Arena::ArenaKind::PresShell);
11115 aSizes.mLayoutPresShellSize += mallocSizeOf(this);
11116 if (mCaret) {
11117 aSizes.mLayoutPresShellSize += mCaret->SizeOfIncludingThis(mallocSizeOf);
11119 aSizes.mLayoutPresShellSize +=
11120 mApproximatelyVisibleFrames.ShallowSizeOfExcludingThis(mallocSizeOf) +
11121 mFramesToDirty.ShallowSizeOfExcludingThis(mallocSizeOf) +
11122 mPendingScrollAnchorSelection.ShallowSizeOfExcludingThis(mallocSizeOf) +
11123 mPendingScrollAnchorAdjustment.ShallowSizeOfExcludingThis(mallocSizeOf);
11125 aSizes.mLayoutTextRunsSize += SizeOfTextRuns(mallocSizeOf);
11127 aSizes.mLayoutPresContextSize +=
11128 mPresContext->SizeOfIncludingThis(mallocSizeOf);
11130 mFrameConstructor->AddSizeOfIncludingThis(aSizes);
11133 size_t PresShell::SizeOfTextRuns(MallocSizeOf aMallocSizeOf) const {
11134 nsIFrame* rootFrame = mFrameConstructor->GetRootFrame();
11135 if (!rootFrame) {
11136 return 0;
11139 // clear the TEXT_RUN_MEMORY_ACCOUNTED flags
11140 nsLayoutUtils::SizeOfTextRunsForFrames(rootFrame, nullptr,
11141 /* clear = */ true);
11143 // collect the total memory in use for textruns
11144 return nsLayoutUtils::SizeOfTextRunsForFrames(rootFrame, aMallocSizeOf,
11145 /* clear = */ false);
11148 void PresShell::MarkFixedFramesForReflow(IntrinsicDirty aIntrinsicDirty) {
11149 nsIFrame* rootFrame = mFrameConstructor->GetRootFrame();
11150 if (rootFrame) {
11151 const nsFrameList& childList =
11152 rootFrame->GetChildList(FrameChildListID::Fixed);
11153 for (nsIFrame* childFrame : childList) {
11154 FrameNeedsReflow(childFrame, aIntrinsicDirty, NS_FRAME_IS_DIRTY);
11159 static void AppendSubtree(nsIDocShell* aDocShell,
11160 nsTArray<nsCOMPtr<nsIContentViewer>>& aArray) {
11161 if (nsCOMPtr<nsIContentViewer> cv = aDocShell->GetContentViewer()) {
11162 aArray.AppendElement(cv);
11165 int32_t n = aDocShell->GetInProcessChildCount();
11166 for (int32_t i = 0; i < n; i++) {
11167 nsCOMPtr<nsIDocShellTreeItem> childItem;
11168 aDocShell->GetInProcessChildAt(i, getter_AddRefs(childItem));
11169 if (childItem) {
11170 nsCOMPtr<nsIDocShell> child(do_QueryInterface(childItem));
11171 AppendSubtree(child, aArray);
11176 void PresShell::MaybeReflowForInflationScreenSizeChange() {
11177 nsPresContext* pc = GetPresContext();
11178 const bool fontInflationWasEnabled = FontSizeInflationEnabled();
11179 RecomputeFontSizeInflationEnabled();
11180 bool changed = false;
11181 if (FontSizeInflationEnabled() && FontSizeInflationMinTwips() != 0) {
11182 pc->ScreenSizeInchesForFontInflation(&changed);
11185 changed = changed || fontInflationWasEnabled != FontSizeInflationEnabled();
11186 if (!changed) {
11187 return;
11189 if (nsCOMPtr<nsIDocShell> docShell = pc->GetDocShell()) {
11190 nsTArray<nsCOMPtr<nsIContentViewer>> array;
11191 AppendSubtree(docShell, array);
11192 for (uint32_t i = 0, iEnd = array.Length(); i < iEnd; ++i) {
11193 nsCOMPtr<nsIContentViewer> cv = array[i];
11194 if (RefPtr<PresShell> descendantPresShell = cv->GetPresShell()) {
11195 nsIFrame* rootFrame = descendantPresShell->GetRootFrame();
11196 if (rootFrame) {
11197 descendantPresShell->FrameNeedsReflow(
11198 rootFrame, IntrinsicDirty::FrameAncestorsAndDescendants,
11199 NS_FRAME_IS_DIRTY);
11206 void PresShell::CompleteChangeToVisualViewportSize() {
11207 // This can get called during reflow, if the caller wants to get the latest
11208 // visual viewport size after scrollbars have been added/removed. In such a
11209 // case, we don't need to mark things as dirty because the things that we
11210 // would mark dirty either just got updated (the root scrollframe's
11211 // scrollbars), or will be laid out later during this reflow cycle (fixed-pos
11212 // items). Callers that update the visual viewport during a reflow are
11213 // responsible for maintaining these invariants.
11214 if (!mIsReflowing) {
11215 if (nsIScrollableFrame* rootScrollFrame =
11216 GetRootScrollFrameAsScrollable()) {
11217 rootScrollFrame->MarkScrollbarsDirtyForReflow();
11219 MarkFixedFramesForReflow(IntrinsicDirty::None);
11222 MaybeReflowForInflationScreenSizeChange();
11224 if (auto* window = nsGlobalWindowInner::Cast(mDocument->GetInnerWindow())) {
11225 window->VisualViewport()->PostResizeEvent();
11229 void PresShell::SetVisualViewportSize(nscoord aWidth, nscoord aHeight) {
11230 MOZ_ASSERT(aWidth >= 0.0 && aHeight >= 0.0);
11232 if (!mVisualViewportSizeSet || mVisualViewportSize.width != aWidth ||
11233 mVisualViewportSize.height != aHeight) {
11234 mVisualViewportSizeSet = true;
11235 mVisualViewportSize.width = aWidth;
11236 mVisualViewportSize.height = aHeight;
11238 CompleteChangeToVisualViewportSize();
11242 void PresShell::ResetVisualViewportSize() {
11243 if (mVisualViewportSizeSet) {
11244 mVisualViewportSizeSet = false;
11245 mVisualViewportSize.width = 0;
11246 mVisualViewportSize.height = 0;
11248 CompleteChangeToVisualViewportSize();
11252 bool PresShell::SetVisualViewportOffset(const nsPoint& aScrollOffset,
11253 const nsPoint& aPrevLayoutScrollPos) {
11254 nsPoint newOffset = aScrollOffset;
11255 nsIScrollableFrame* rootScrollFrame = GetRootScrollFrameAsScrollable();
11256 if (rootScrollFrame) {
11257 // See the comment in nsHTMLScrollFrame::Reflow above the call to
11258 // SetVisualViewportOffset for why we need to do this.
11259 nsRect scrollRange = rootScrollFrame->GetScrollRangeForUserInputEvents();
11260 if (!scrollRange.Contains(newOffset)) {
11261 newOffset.x = std::min(newOffset.x, scrollRange.XMost());
11262 newOffset.x = std::max(newOffset.x, scrollRange.x);
11263 newOffset.y = std::min(newOffset.y, scrollRange.YMost());
11264 newOffset.y = std::max(newOffset.y, scrollRange.y);
11268 // Careful here not to call GetVisualViewportOffset to get the previous visual
11269 // viewport offset because if mVisualViewportOffset is nothing then we'll get
11270 // the layout scroll position directly from the scroll frame and it has likely
11271 // already been updated.
11272 nsPoint prevOffset = aPrevLayoutScrollPos;
11273 if (mVisualViewportOffset.isSome()) {
11274 prevOffset = *mVisualViewportOffset;
11276 if (prevOffset == newOffset) {
11277 return false;
11280 mVisualViewportOffset = Some(newOffset);
11282 if (auto* window = nsGlobalWindowInner::Cast(mDocument->GetInnerWindow())) {
11283 window->VisualViewport()->PostScrollEvent(prevOffset, aPrevLayoutScrollPos);
11286 if (IsVisualViewportSizeSet() && rootScrollFrame) {
11287 rootScrollFrame->Anchor()->UserScrolled();
11290 if (gfxPlatform::UseDesktopZoomingScrollbars()) {
11291 if (nsIScrollableFrame* rootScrollFrame =
11292 GetRootScrollFrameAsScrollable()) {
11293 rootScrollFrame->UpdateScrollbarPosition();
11297 return true;
11300 void PresShell::ResetVisualViewportOffset() { mVisualViewportOffset.reset(); }
11302 void PresShell::ScrollToVisual(const nsPoint& aVisualViewportOffset,
11303 FrameMetrics::ScrollOffsetUpdateType aUpdateType,
11304 ScrollMode aMode) {
11305 MOZ_ASSERT(aMode == ScrollMode::Instant || aMode == ScrollMode::SmoothMsd);
11307 if (aMode == ScrollMode::SmoothMsd) {
11308 if (nsIScrollableFrame* sf = GetRootScrollFrameAsScrollable()) {
11309 if (sf->SmoothScrollVisual(aVisualViewportOffset, aUpdateType)) {
11310 return;
11315 // If the caller asked for instant scroll, or if we failed
11316 // to do a smooth scroll, do an instant scroll.
11317 SetPendingVisualScrollUpdate(aVisualViewportOffset, aUpdateType);
11320 void PresShell::SetPendingVisualScrollUpdate(
11321 const nsPoint& aVisualViewportOffset,
11322 FrameMetrics::ScrollOffsetUpdateType aUpdateType) {
11323 mPendingVisualScrollUpdate =
11324 Some(VisualScrollUpdate{aVisualViewportOffset, aUpdateType});
11326 // The pending update is picked up during the next paint.
11327 // Schedule a paint to make sure one will happen.
11328 if (nsIFrame* rootFrame = GetRootFrame()) {
11329 rootFrame->SchedulePaint();
11333 void PresShell::ClearPendingVisualScrollUpdate() {
11334 if (mPendingVisualScrollUpdate && mPendingVisualScrollUpdate->mAcknowledged) {
11335 mPendingVisualScrollUpdate = mozilla::Nothing();
11339 void PresShell::AcknowledgePendingVisualScrollUpdate() {
11340 MOZ_ASSERT(mPendingVisualScrollUpdate);
11341 mPendingVisualScrollUpdate->mAcknowledged = true;
11344 nsPoint PresShell::GetVisualViewportOffsetRelativeToLayoutViewport() const {
11345 return GetVisualViewportOffset() - GetLayoutViewportOffset();
11348 nsPoint PresShell::GetLayoutViewportOffset() const {
11349 nsPoint result;
11350 if (nsIScrollableFrame* sf = GetRootScrollFrameAsScrollable()) {
11351 result = sf->GetScrollPosition();
11353 return result;
11356 nsSize PresShell::GetLayoutViewportSize() const {
11357 nsSize result;
11358 if (nsIScrollableFrame* sf = GetRootScrollFrameAsScrollable()) {
11359 result = sf->GetScrollPortRect().Size();
11361 return result;
11364 nsSize PresShell::GetVisualViewportSizeUpdatedByDynamicToolbar() const {
11365 NS_ASSERTION(mVisualViewportSizeSet,
11366 "asking for visual viewport size when its not set?");
11367 if (!mMobileViewportManager) {
11368 return mVisualViewportSize;
11371 MOZ_ASSERT(GetDynamicToolbarState() == DynamicToolbarState::InTransition ||
11372 GetDynamicToolbarState() == DynamicToolbarState::Collapsed);
11374 nsSize sizeUpdatedByDynamicToolbar =
11375 mMobileViewportManager->GetVisualViewportSizeUpdatedByDynamicToolbar();
11376 return sizeUpdatedByDynamicToolbar == nsSize() ? mVisualViewportSize
11377 : sizeUpdatedByDynamicToolbar;
11380 void PresShell::RecomputeFontSizeInflationEnabled() {
11381 mFontSizeInflationEnabled = DetermineFontSizeInflationState();
11383 // Divide by 100 to convert the pref from a percentage to a fraction.
11384 float fontScale = StaticPrefs::font_size_systemFontScale() / 100.0f;
11385 if (fontScale == 0.0f) {
11386 return;
11389 MOZ_ASSERT(mDocument);
11390 MOZ_ASSERT(mPresContext);
11391 if (mFontSizeInflationEnabled || mDocument->IsSyntheticDocument()) {
11392 mPresContext->SetSystemFontScale(1.0f);
11393 } else {
11394 mPresContext->SetSystemFontScale(fontScale);
11398 bool PresShell::DetermineFontSizeInflationState() {
11399 MOZ_ASSERT(mPresContext, "our pres context should not be null");
11400 if (mPresContext->IsChrome()) {
11401 return false;
11404 if (FontSizeInflationEmPerLine() == 0 && FontSizeInflationMinTwips() == 0) {
11405 return false;
11408 // Force-enabling font inflation always trumps the heuristics here.
11409 if (!FontSizeInflationForceEnabled()) {
11410 if (BrowserChild* tab = BrowserChild::GetFrom(this)) {
11411 // We're in a child process. Cancel inflation if we're not
11412 // async-pan zoomed.
11413 if (!tab->AsyncPanZoomEnabled()) {
11414 return false;
11416 } else if (XRE_IsParentProcess()) {
11417 // We're in the master process. Cancel inflation if it's been
11418 // explicitly disabled.
11419 if (FontSizeInflationDisabledInMasterProcess()) {
11420 return false;
11425 Maybe<LayoutDeviceIntSize> displaySize;
11426 // The MVM already caches the top-level content viewer size and is therefore
11427 // the fastest way of getting that data.
11428 if (mPresContext->IsRootContentDocumentCrossProcess()) {
11429 if (mMobileViewportManager) {
11430 displaySize = Some(mMobileViewportManager->DisplaySize());
11432 } else if (PresShell* rootPresShell = GetRootPresShell()) {
11433 // With any luck, we can get at the root content document without any cross-
11434 // process shenanigans.
11435 if (auto mvm = rootPresShell->GetMobileViewportManager()) {
11436 displaySize = Some(mvm->DisplaySize());
11440 if (!displaySize) {
11441 // Unfortunately, it looks like the root content document lives in a
11442 // different process. For consistency's sake it would be best to always use
11443 // the content viewer size of the root content document, but it's not worth
11444 // the effort, because this only makes a difference in the case of pages
11445 // with an explicitly sized viewport (neither "width=device-width" nor a
11446 // completely missing viewport tag) being loaded within a frame, which is
11447 // hopefully a relatively exotic case.
11448 // More to the point, these viewport size and zoom-based calculations don't
11449 // really make sense for frames anyway, so instead of creating a way to
11450 // access the content viewer size of the top level document cross-process,
11451 // we probably rather want frames to simply inherit the font inflation state
11452 // of their top-level parent and should therefore invest any time spent on
11453 // getting things to work cross-process into that (bug 1724311).
11455 // Until we get around to that though, we just use the content viewer size
11456 // of however high we can get within the same process.
11458 // (This also serves as a fallback code path if the MVM isn't available,
11459 // e.g. when debugging in non-e10s mode on Desktop.)
11460 nsPresContext* topContext =
11461 mPresContext->GetInProcessRootContentDocumentPresContext();
11462 LayoutDeviceIntSize result;
11463 if (!nsLayoutUtils::GetContentViewerSize(topContext, result)) {
11464 return false;
11466 displaySize = Some(result);
11469 ScreenIntSize screenSize = ViewAs<ScreenPixel>(
11470 displaySize.value(),
11471 PixelCastJustification::LayoutDeviceIsScreenForBounds);
11472 nsViewportInfo vInf = GetDocument()->GetViewportInfo(screenSize);
11474 CSSToScreenScale defaultScale =
11475 mPresContext->CSSToDevPixelScale() * LayoutDeviceToScreenScale(1.0);
11477 if (vInf.GetDefaultZoom() >= defaultScale || vInf.IsAutoSizeEnabled()) {
11478 return false;
11481 return true;
11484 static nsIWidget* GetPresContextContainerWidget(nsPresContext* aPresContext) {
11485 nsCOMPtr<nsISupports> container = aPresContext->Document()->GetContainer();
11486 nsCOMPtr<nsIBaseWindow> baseWindow = do_QueryInterface(container);
11487 if (!baseWindow) {
11488 return nullptr;
11491 nsCOMPtr<nsIWidget> mainWidget;
11492 baseWindow->GetMainWidget(getter_AddRefs(mainWidget));
11493 return mainWidget;
11496 static bool IsTopLevelWidget(nsIWidget* aWidget) {
11497 using WindowType = mozilla::widget::WindowType;
11499 auto windowType = aWidget->GetWindowType();
11500 return windowType == WindowType::TopLevel ||
11501 windowType == WindowType::Dialog || windowType == WindowType::Popup ||
11502 windowType == WindowType::Sheet;
11505 PresShell::WindowSizeConstraints PresShell::GetWindowSizeConstraints() {
11506 nsSize minSize(0, 0);
11507 nsSize maxSize(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
11508 nsIFrame* rootFrame = FrameConstructor()->GetRootElementStyleFrame();
11509 if (!rootFrame || !mPresContext) {
11510 return {minSize, maxSize};
11512 if (rootFrame->IsXULBoxFrame()) {
11513 UniquePtr<gfxContext> rcx(CreateReferenceRenderingContext());
11514 if (!rcx) {
11515 return {minSize, maxSize};
11517 nsBoxLayoutState state(mPresContext, rcx.get());
11518 minSize = rootFrame->GetXULMinSize(state);
11519 maxSize = rootFrame->GetXULMaxSize(state);
11520 } else {
11521 const auto* pos = rootFrame->StylePosition();
11522 if (pos->mMinWidth.ConvertsToLength()) {
11523 minSize.width = pos->mMinWidth.ToLength();
11525 if (pos->mMinHeight.ConvertsToLength()) {
11526 minSize.height = pos->mMinHeight.ToLength();
11528 if (pos->mMaxWidth.ConvertsToLength()) {
11529 maxSize.width = pos->mMaxWidth.ToLength();
11531 if (pos->mMaxHeight.ConvertsToLength()) {
11532 maxSize.height = pos->mMaxHeight.ToLength();
11535 return {minSize, maxSize};
11538 void PresShell::SyncWindowProperties(bool aSync) {
11539 nsView* view = mViewManager->GetRootView();
11540 if (!view || !view->HasWidget()) {
11541 return;
11543 RefPtr pc = mPresContext;
11544 if (!pc) {
11545 return;
11548 nsCOMPtr<nsIWidget> windowWidget = GetPresContextContainerWidget(pc);
11549 if (!windowWidget || !IsTopLevelWidget(windowWidget)) {
11550 return;
11553 nsIFrame* rootFrame = FrameConstructor()->GetRootElementStyleFrame();
11554 if (!rootFrame) {
11555 return;
11558 if (!aSync) {
11559 view->SetNeedsWindowPropertiesSync();
11560 return;
11563 AutoWeakFrame weak(rootFrame);
11564 if (!GetRootScrollFrame()) {
11565 // Scrollframes use native widgets which don't work well with
11566 // translucent windows, at least in Windows XP. So if the document
11567 // has a root scrollrame it's useless to try to make it transparent,
11568 // we'll just get something broken.
11569 // We can change this to allow translucent toplevel HTML documents
11570 // (e.g. to do something like Dashboard widgets), once we
11571 // have broad support for translucent scrolled documents, but be
11572 // careful because apparently some Firefox extensions expect
11573 // openDialog("something.html") to produce an opaque window
11574 // even if the HTML doesn't have a background-color set.
11575 auto* canvas = GetCanvasFrame();
11576 widget::TransparencyMode mode = nsLayoutUtils::GetFrameTransparency(
11577 canvas ? canvas : rootFrame, rootFrame);
11578 StyleWindowShadow shadow = rootFrame->StyleUIReset()->mWindowShadow;
11579 nsCOMPtr<nsIWidget> viewWidget = view->GetWidget();
11580 viewWidget->SetTransparencyMode(mode);
11581 windowWidget->SetWindowShadowStyle(shadow);
11583 // For macOS, apply color scheme overrides to the top level window widget.
11584 if (auto scheme = pc->GetOverriddenOrEmbedderColorScheme()) {
11585 windowWidget->SetColorScheme(scheme);
11589 if (!weak.IsAlive()) {
11590 return;
11593 const auto& constraints = GetWindowSizeConstraints();
11594 nsContainerFrame::SetSizeConstraints(pc, windowWidget, constraints.mMinSize,
11595 constraints.mMaxSize);
11598 nsresult PresShell::HasRuleProcessorUsedByMultipleStyleSets(uint32_t aSheetType,
11599 bool* aRetVal) {
11600 *aRetVal = false;
11601 return NS_OK;
11604 void PresShell::NotifyStyleSheetServiceSheetAdded(StyleSheet* aSheet,
11605 uint32_t aSheetType) {
11606 switch (aSheetType) {
11607 case nsIStyleSheetService::AGENT_SHEET:
11608 AddAgentSheet(aSheet);
11609 break;
11610 case nsIStyleSheetService::USER_SHEET:
11611 AddUserSheet(aSheet);
11612 break;
11613 case nsIStyleSheetService::AUTHOR_SHEET:
11614 AddAuthorSheet(aSheet);
11615 break;
11616 default:
11617 MOZ_ASSERT_UNREACHABLE("unexpected aSheetType value");
11618 break;
11622 void PresShell::NotifyStyleSheetServiceSheetRemoved(StyleSheet* aSheet,
11623 uint32_t aSheetType) {
11624 StyleSet()->RemoveStyleSheet(*aSheet);
11625 mDocument->ApplicableStylesChanged();
11628 void PresShell::SetIsUnderHiddenEmbedderElement(
11629 bool aUnderHiddenEmbedderElement) {
11630 if (mUnderHiddenEmbedderElement == aUnderHiddenEmbedderElement) {
11631 return;
11634 mUnderHiddenEmbedderElement = aUnderHiddenEmbedderElement;
11636 if (nsCOMPtr<nsIDocShell> docShell = mPresContext->GetDocShell()) {
11637 BrowsingContext* bc = docShell->GetBrowsingContext();
11639 // Propagate to children.
11640 for (BrowsingContext* child : bc->Children()) {
11641 Element* embedderElement = child->GetEmbedderElement();
11642 if (!embedderElement) {
11643 // TODO: We shouldn't need to null check here since `child` and the
11644 // element returned by `child->GetEmbedderElement()` are in our
11645 // process (the actual browsing context represented by `child` may not
11646 // be, but that doesn't matter). However, there are currently a very
11647 // small number of crashes due to `embedderElement` being null, somehow
11648 // - see bug 1551241. For now we wallpaper the crash.
11649 continue;
11652 bool embedderFrameIsHidden = true;
11653 if (auto embedderFrame = embedderElement->GetPrimaryFrame()) {
11654 embedderFrameIsHidden = !embedderFrame->StyleVisibility()->IsVisible();
11657 if (nsIDocShell* childDocShell = child->GetDocShell()) {
11658 PresShell* presShell = childDocShell->GetPresShell();
11659 if (!presShell) {
11660 continue;
11662 presShell->SetIsUnderHiddenEmbedderElement(
11663 aUnderHiddenEmbedderElement || embedderFrameIsHidden);
11664 } else {
11665 BrowserBridgeChild* bridgeChild =
11666 BrowserBridgeChild::GetFrom(embedderElement);
11667 bridgeChild->SetIsUnderHiddenEmbedderElement(
11668 aUnderHiddenEmbedderElement || embedderFrameIsHidden);
11674 nsIContent* PresShell::EventHandler::GetOverrideClickTarget(
11675 WidgetGUIEvent* aGUIEvent, nsIFrame* aFrame) {
11676 if (aGUIEvent->mMessage != eMouseUp) {
11677 return nullptr;
11680 MOZ_ASSERT(aGUIEvent->mClass == eMouseEventClass);
11681 WidgetMouseEvent* mouseEvent = aGUIEvent->AsMouseEvent();
11683 uint32_t flags = 0;
11684 RelativeTo relativeTo{aFrame};
11685 nsPoint eventPoint =
11686 nsLayoutUtils::GetEventCoordinatesRelativeTo(aGUIEvent, relativeTo);
11687 if (mouseEvent->mIgnoreRootScrollFrame) {
11688 flags |= INPUT_IGNORE_ROOT_SCROLL_FRAME;
11691 nsIFrame* target =
11692 FindFrameTargetedByInputEvent(aGUIEvent, relativeTo, eventPoint, flags);
11693 if (!target) {
11694 return nullptr;
11697 nsIContent* overrideClickTarget = target->GetContent();
11698 while (overrideClickTarget && !overrideClickTarget->IsElement()) {
11699 overrideClickTarget = overrideClickTarget->GetFlattenedTreeParent();
11701 return overrideClickTarget;
11704 /******************************************************************************
11705 * PresShell::EventHandler::EventTargetData
11706 ******************************************************************************/
11708 void PresShell::EventHandler::EventTargetData::SetFrameAndComputePresShell(
11709 nsIFrame* aFrameToHandleEvent) {
11710 if (aFrameToHandleEvent) {
11711 mFrame = aFrameToHandleEvent;
11712 mPresShell = aFrameToHandleEvent->PresShell();
11713 } else {
11714 mFrame = nullptr;
11715 mPresShell = nullptr;
11719 void PresShell::EventHandler::EventTargetData::
11720 SetFrameAndComputePresShellAndContent(nsIFrame* aFrameToHandleEvent,
11721 WidgetGUIEvent* aGUIEvent) {
11722 MOZ_ASSERT(aFrameToHandleEvent);
11723 MOZ_ASSERT(aGUIEvent);
11725 SetFrameAndComputePresShell(aFrameToHandleEvent);
11726 SetContentForEventFromFrame(aGUIEvent);
11729 void PresShell::EventHandler::EventTargetData::SetContentForEventFromFrame(
11730 WidgetGUIEvent* aGUIEvent) {
11731 MOZ_ASSERT(mFrame);
11732 mContent = nullptr;
11733 mFrame->GetContentForEvent(aGUIEvent, getter_AddRefs(mContent));
11736 nsIContent* PresShell::EventHandler::EventTargetData::GetFrameContent() const {
11737 return mFrame ? mFrame->GetContent() : nullptr;
11740 bool PresShell::EventHandler::EventTargetData::MaybeRetargetToActiveDocument(
11741 WidgetGUIEvent* aGUIEvent) {
11742 MOZ_ASSERT(aGUIEvent);
11743 MOZ_ASSERT(mFrame);
11744 MOZ_ASSERT(mPresShell);
11745 MOZ_ASSERT(!mContent, "Doesn't support to retarget the content");
11747 EventStateManager* activeESM =
11748 EventStateManager::GetActiveEventStateManager();
11749 if (!activeESM) {
11750 return false;
11753 if (aGUIEvent->mClass != ePointerEventClass &&
11754 !aGUIEvent->HasMouseEventMessage()) {
11755 return false;
11758 if (activeESM == GetEventStateManager()) {
11759 return false;
11762 nsPresContext* activePresContext = activeESM->GetPresContext();
11763 if (!activePresContext) {
11764 return false;
11767 PresShell* activePresShell = activePresContext->GetPresShell();
11768 if (!activePresShell) {
11769 return false;
11772 // Note, currently for backwards compatibility we don't forward mouse events
11773 // to the active document when mouse is over some subdocument.
11774 if (!nsContentUtils::ContentIsCrossDocDescendantOf(
11775 activePresShell->GetDocument(), GetDocument())) {
11776 return false;
11779 SetFrameAndComputePresShell(activePresShell->GetRootFrame());
11780 return true;
11783 bool PresShell::EventHandler::EventTargetData::ComputeElementFromFrame(
11784 WidgetGUIEvent* aGUIEvent) {
11785 MOZ_ASSERT(aGUIEvent);
11786 MOZ_ASSERT(aGUIEvent->IsUsingCoordinates());
11787 MOZ_ASSERT(mPresShell);
11788 MOZ_ASSERT(mFrame);
11790 SetContentForEventFromFrame(aGUIEvent);
11792 // If there is no content for this frame, target it anyway. Some frames can
11793 // be targeted but do not have content, particularly windows with scrolling
11794 // off.
11795 if (!mContent) {
11796 return true;
11799 // Bug 103055, bug 185889: mouse events apply to *elements*, not all nodes.
11800 // Thus we get the nearest element parent here.
11801 // XXX we leave the frame the same even if we find an element parent, so that
11802 // the text frame will receive the event (selection and friends are the ones
11803 // who care about that anyway)
11805 // We use weak pointers because during this tight loop, the node
11806 // will *not* go away. And this happens on every mousemove.
11807 nsIContent* content = mContent;
11808 while (content && !content->IsElement()) {
11809 content = content->GetFlattenedTreeParent();
11811 mContent = content;
11813 // If we found an element, target it. Otherwise, target *nothing*.
11814 return !!mContent;
11817 void PresShell::EventHandler::EventTargetData::UpdateTouchEventTarget(
11818 WidgetGUIEvent* aGUIEvent) {
11819 MOZ_ASSERT(aGUIEvent);
11821 if (aGUIEvent->mClass != eTouchEventClass) {
11822 return;
11825 if (aGUIEvent->mMessage == eTouchStart) {
11826 WidgetTouchEvent* touchEvent = aGUIEvent->AsTouchEvent();
11827 nsIFrame* newFrame =
11828 TouchManager::SuppressInvalidPointsAndGetTargetedFrame(touchEvent);
11829 if (!newFrame) {
11830 return; // XXX Why don't we stop handling the event in this case?
11832 SetFrameAndComputePresShellAndContent(newFrame, aGUIEvent);
11833 return;
11836 PresShell* newPresShell = PresShell::GetShellForTouchEvent(aGUIEvent);
11837 if (!newPresShell) {
11838 return; // XXX Why don't we stop handling the event in this case?
11841 // Touch events (except touchstart) are dispatching to the captured
11842 // element. Get correct shell from it.
11843 mPresShell = newPresShell;
11846 /******************************************************************************
11847 * PresShell::EventHandler::HandlingTimeAccumulator
11848 ******************************************************************************/
11850 PresShell::EventHandler::HandlingTimeAccumulator::HandlingTimeAccumulator(
11851 const PresShell::EventHandler& aEventHandler, const WidgetEvent* aEvent)
11852 : mEventHandler(aEventHandler),
11853 mEvent(aEvent),
11854 mHandlingStartTime(TimeStamp::Now()) {
11855 MOZ_ASSERT(mEvent);
11856 MOZ_ASSERT(mEvent->IsTrusted());
11859 PresShell::EventHandler::HandlingTimeAccumulator::~HandlingTimeAccumulator() {
11860 if (mEvent->mTimeStamp <= mEventHandler.mPresShell->mLastOSWake) {
11861 return;
11864 switch (mEvent->mMessage) {
11865 case eKeyPress:
11866 case eKeyDown:
11867 case eKeyUp:
11868 Telemetry::AccumulateTimeDelta(Telemetry::INPUT_EVENT_HANDLED_KEYBOARD_MS,
11869 mHandlingStartTime);
11870 return;
11871 case eMouseDown:
11872 Telemetry::AccumulateTimeDelta(
11873 Telemetry::INPUT_EVENT_HANDLED_MOUSE_DOWN_MS, mHandlingStartTime);
11874 return;
11875 case eMouseUp:
11876 Telemetry::AccumulateTimeDelta(Telemetry::INPUT_EVENT_HANDLED_MOUSE_UP_MS,
11877 mHandlingStartTime);
11878 return;
11879 case eMouseMove:
11880 if (mEvent->mFlags.mHandledByAPZ) {
11881 Telemetry::AccumulateTimeDelta(
11882 Telemetry::INPUT_EVENT_HANDLED_APZ_MOUSE_MOVE_MS,
11883 mHandlingStartTime);
11885 return;
11886 case eWheel:
11887 if (mEvent->mFlags.mHandledByAPZ) {
11888 Telemetry::AccumulateTimeDelta(
11889 Telemetry::INPUT_EVENT_HANDLED_APZ_WHEEL_MS, mHandlingStartTime);
11891 return;
11892 case eTouchMove:
11893 if (mEvent->mFlags.mHandledByAPZ) {
11894 Telemetry::AccumulateTimeDelta(
11895 Telemetry::INPUT_EVENT_HANDLED_APZ_TOUCH_MOVE_MS,
11896 mHandlingStartTime);
11898 return;
11899 default:
11900 return;
11904 void PresShell::EndPaint() {
11905 ClearPendingVisualScrollUpdate();
11907 if (mDocument) {
11908 mDocument->EnumerateSubDocuments([](Document& aSubDoc) {
11909 if (PresShell* presShell = aSubDoc.GetPresShell()) {
11910 presShell->EndPaint();
11912 return CallState::Continue;
11917 void PresShell::PingPerTickTelemetry(FlushType aFlushType) {
11918 mLayoutTelemetry.PingPerTickTelemetry(aFlushType);
11921 bool PresShell::GetZoomableByAPZ() const {
11922 return mZoomConstraintsClient && mZoomConstraintsClient->GetAllowZoom();
11925 void PresShell::EnsureReflowIfFrameHasHiddenContent(nsIFrame* aFrame) {
11926 if (!aFrame || !aFrame->IsSubtreeDirty() ||
11927 !StaticPrefs::layout_css_content_visibility_enabled()) {
11928 return;
11931 // Flushing notifications below might trigger more layouts, which might,
11932 // in turn, trigger layout of other hidden content. We keep a local set
11933 // of hidden content we are laying out to handle recursive calls.
11934 nsTHashSet<nsIContent*> hiddenContentInForcedLayout;
11936 MOZ_ASSERT(mHiddenContentInForcedLayout.IsEmpty());
11937 nsIFrame* topmostFrameWithContentHidden = nullptr;
11938 for (nsIFrame* cur = aFrame->GetInFlowParent(); cur;
11939 cur = cur->GetInFlowParent()) {
11940 if (cur->HidesContent()) {
11941 topmostFrameWithContentHidden = cur;
11942 mHiddenContentInForcedLayout.Insert(cur->GetContent());
11946 if (mHiddenContentInForcedLayout.IsEmpty()) {
11947 return;
11950 // Queue and immediately flush a reflow for this node.
11951 MOZ_ASSERT(topmostFrameWithContentHidden);
11952 FrameNeedsReflow(topmostFrameWithContentHidden, IntrinsicDirty::None,
11953 NS_FRAME_IS_DIRTY);
11954 mDocument->FlushPendingNotifications(FlushType::Layout);
11956 mHiddenContentInForcedLayout.Clear();
11959 bool PresShell::IsForcingLayoutForHiddenContent(const nsIFrame* aFrame) const {
11960 return mHiddenContentInForcedLayout.Contains(aFrame->GetContent());
11963 void PresShell::UpdateRelevancyOfContentVisibilityAutoFrames() {
11964 if (mContentVisibilityRelevancyToUpdate.isEmpty()) {
11965 return;
11968 for (nsIFrame* frame : mContentVisibilityAutoFrames) {
11969 frame->UpdateIsRelevantContent(mContentVisibilityRelevancyToUpdate);
11972 mContentVisibilityRelevancyToUpdate.clear();
11975 void PresShell::ScheduleContentRelevancyUpdate(ContentRelevancyReason aReason) {
11976 if (MOZ_UNLIKELY(mIsDestroying)) {
11977 return;
11980 mContentVisibilityRelevancyToUpdate += aReason;
11982 SetNeedLayoutFlush();
11983 if (nsPresContext* presContext = GetPresContext()) {
11984 presContext->RefreshDriver()->EnsureContentRelevancyUpdateHappens();