Bug 1717887 Part 2: Make RenderThread backed by nsIThread, with a hang monitor. r...
[gecko.git] / layout / base / PresShell.cpp
blob241066d2cffe063822d35c58553c087d76c7909b
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/ArrayUtils.h"
14 #include "mozilla/Attributes.h"
15 #include "mozilla/AutoRestore.h"
16 #include "mozilla/ContentIterator.h"
17 #include "mozilla/DisplayPortUtils.h"
18 #include "mozilla/EventDispatcher.h"
19 #include "mozilla/EventStateManager.h"
20 #include "mozilla/EventStates.h"
21 #include "mozilla/GeckoMVMContext.h"
22 #include "mozilla/IMEStateManager.h"
23 #include "mozilla/MemoryReporting.h"
24 #include "mozilla/dom/BrowserChild.h"
25 #include "mozilla/Likely.h"
26 #include "mozilla/Logging.h"
27 #include "mozilla/MouseEvents.h"
28 #include "mozilla/PerfStats.h"
29 #include "mozilla/PointerLockManager.h"
30 #include "mozilla/PresShellInlines.h"
31 #include "mozilla/RangeUtils.h"
32 #include "mozilla/ScopeExit.h"
33 #include "mozilla/Sprintf.h"
34 #include "mozilla/StaticAnalysisFunctions.h"
35 #include "mozilla/StaticPrefs_apz.h"
36 #include "mozilla/StaticPrefs_browser.h"
37 #include "mozilla/StaticPrefs_dom.h"
38 #include "mozilla/StaticPrefs_font.h"
39 #include "mozilla/StaticPrefs_layout.h"
40 #include "mozilla/TextEvents.h"
41 #include "mozilla/TimeStamp.h"
42 #include "mozilla/TouchEvents.h"
43 #include "mozilla/UniquePtr.h"
44 #include "mozilla/Unused.h"
45 #include "mozilla/ViewportUtils.h"
46 #include <algorithm>
48 #ifdef XP_WIN
49 # include "winuser.h"
50 #endif
52 #include "gfxContext.h"
53 #include "gfxUserFontSet.h"
54 #include "nsContentList.h"
55 #include "nsPresContext.h"
56 #include "nsIContent.h"
57 #include "mozilla/dom/BrowserBridgeChild.h"
58 #include "mozilla/dom/BrowsingContext.h"
59 #include "mozilla/dom/CanonicalBrowsingContext.h"
60 #include "mozilla/dom/ContentChild.h"
61 #include "mozilla/dom/ContentParent.h"
62 #include "mozilla/dom/Element.h"
63 #include "mozilla/dom/PointerEventHandler.h"
64 #include "mozilla/dom/PopupBlocker.h"
65 #include "mozilla/dom/Document.h"
66 #include "mozilla/dom/DocumentInlines.h"
67 #include "mozilla/dom/UserActivation.h"
68 #include "nsAnimationManager.h"
69 #include "nsNameSpaceManager.h" // for Pref-related rule management (bugs 22963,20760,31816)
70 #include "nsFlexContainerFrame.h"
71 #include "nsIFrame.h"
72 #include "FrameLayerBuilder.h"
73 #include "nsViewManager.h"
74 #include "nsView.h"
75 #include "nsCRTGlue.h"
76 #include "prinrval.h"
77 #include "nsTArray.h"
78 #include "nsCOMArray.h"
79 #include "nsContainerFrame.h"
80 #include "mozilla/dom/Selection.h"
81 #include "nsGkAtoms.h"
82 #include "nsRange.h"
83 #include "nsWindowSizes.h"
84 #include "nsCOMPtr.h"
85 #include "nsReadableUtils.h"
86 #include "nsPageSequenceFrame.h"
87 #include "nsCaret.h"
88 #include "mozilla/AccessibleCaretEventHub.h"
89 #include "nsFrameManager.h"
90 #include "nsXPCOM.h"
91 #include "nsILayoutHistoryState.h"
92 #include "nsILineIterator.h" // for ScrollContentIntoView
93 #include "PLDHashTable.h"
94 #include "mozilla/dom/Touch.h"
95 #include "mozilla/dom/TouchEvent.h"
96 #include "mozilla/dom/PointerEventBinding.h"
97 #include "mozilla/dom/ShadowIncludingTreeIterator.h"
98 #include "nsIObserverService.h"
99 #include "nsDocShell.h" // for reflow observation
100 #include "nsIBaseWindow.h"
101 #include "nsError.h"
102 #include "nsLayoutUtils.h"
103 #include "nsViewportInfo.h"
104 #include "nsCSSRendering.h"
105 // for |#ifdef DEBUG| code
106 #include "prenv.h"
107 #include "nsDisplayList.h"
108 #include "nsRegion.h"
109 #include "nsAutoLayoutPhase.h"
110 #include "AutoProfilerStyleMarker.h"
111 #ifdef MOZ_REFLOW_PERF
112 # include "nsFontMetrics.h"
113 #endif
114 #include "MobileViewportManager.h"
115 #include "OverflowChangedTracker.h"
116 #include "PositionedEventTargeting.h"
118 #include "nsIReflowCallback.h"
120 #include "nsPIDOMWindow.h"
121 #include "nsFocusManager.h"
122 #include "nsNetUtil.h"
123 #include "nsThreadUtils.h"
124 #include "nsStyleSheetService.h"
125 #include "gfxUtils.h"
126 #include "mozilla/SMILAnimationController.h"
127 #include "mozilla/dom/SVGAnimationElement.h"
128 #include "mozilla/SVGObserverUtils.h"
129 #include "mozilla/SVGFragmentIdentifier.h"
130 #include "nsFrameSelection.h"
132 #include "mozilla/dom/Performance.h"
133 #include "nsRefreshDriver.h"
134 #include "nsDOMNavigationTiming.h"
136 // Drag & Drop, Clipboard
137 #include "nsIDocShellTreeItem.h"
138 #include "nsIURI.h"
139 #include "nsIScrollableFrame.h"
140 #include "nsITimer.h"
141 #ifdef ACCESSIBILITY
142 # include "nsAccessibilityService.h"
143 # include "mozilla/a11y/DocAccessible.h"
144 # ifdef DEBUG
145 # include "mozilla/a11y/Logging.h"
146 # endif
147 #endif
149 // For style data reconstruction
150 #include "nsStyleChangeList.h"
151 #include "nsCSSFrameConstructor.h"
152 #ifdef MOZ_XUL
153 # include "nsMenuFrame.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"
162 #endif // MOZ_XUL
164 #include "mozilla/layers/CompositorBridgeChild.h"
165 #include "ClientLayerManager.h"
166 #include "gfxPlatform.h"
167 #include "Layers.h"
168 #include "LayerTreeInvalidation.h"
169 #include "mozilla/css/ImageLoader.h"
170 #include "mozilla/dom/DocumentTimeline.h"
171 #include "mozilla/dom/ScriptSettings.h"
172 #include "mozilla/ErrorResult.h"
173 #include "mozilla/Preferences.h"
174 #include "mozilla/Telemetry.h"
175 #include "nsCanvasFrame.h"
176 #include "nsImageFrame.h"
177 #include "nsIScreen.h"
178 #include "nsIScreenManager.h"
179 #include "nsPlaceholderFrame.h"
180 #include "nsTransitionManager.h"
181 #include "ChildIterator.h"
182 #include "mozilla/RestyleManager.h"
183 #include "nsIDragSession.h"
184 #include "nsIFrameInlines.h"
185 #include "mozilla/gfx/2D.h"
186 #include "nsNetUtil.h"
187 #include "nsSubDocumentFrame.h"
188 #include "nsQueryObject.h"
189 #include "mozilla/GlobalStyleSheetCache.h"
190 #include "mozilla/layers/InputAPZContext.h"
191 #include "mozilla/layers/FocusTarget.h"
192 #include "mozilla/layers/WebRenderLayerManager.h"
193 #include "mozilla/layers/WebRenderUserData.h"
194 #include "mozilla/layout/ScrollAnchorContainer.h"
195 #include "mozilla/ProfilerLabels.h"
196 #include "mozilla/ProfilerMarkers.h"
197 #include "mozilla/ScrollTypes.h"
198 #include "mozilla/ServoBindings.h"
199 #include "mozilla/ServoStyleSet.h"
200 #include "mozilla/StyleSheet.h"
201 #include "mozilla/StyleSheetInlines.h"
202 #include "mozilla/InputTaskManager.h"
203 #include "mozilla/dom/ImageTracker.h"
204 #include "nsIDocShellTreeOwner.h"
205 #include "nsClassHashtable.h"
206 #include "nsHashKeys.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 MOZ_COUNT_CTOR(RangePaintInfo);
244 mBuilder.BeginFrame();
247 ~RangePaintInfo() {
248 mList.DeleteAll(&mBuilder);
249 mBuilder.EndFrame();
250 MOZ_COUNT_DTOR(RangePaintInfo);
254 #undef NOISY
256 // ----------------------------------------------------------------------
258 #ifdef DEBUG
259 // Set the environment variable GECKO_VERIFY_REFLOW_FLAGS to one or
260 // more of the following flags (comma separated) for handy debug
261 // output.
262 static VerifyReflowFlags gVerifyReflowFlags;
264 struct VerifyReflowFlagData {
265 const char* name;
266 VerifyReflowFlags bit;
269 static const VerifyReflowFlagData gFlags[] = {
270 // clang-format off
271 { "verify", VerifyReflowFlags::On },
272 { "reflow", VerifyReflowFlags::Noisy },
273 { "all", VerifyReflowFlags::All },
274 { "list-commands", VerifyReflowFlags::DumpCommands },
275 { "noisy-commands", VerifyReflowFlags::NoisyCommands },
276 { "really-noisy-commands", VerifyReflowFlags::ReallyNoisyCommands },
277 { "resize", VerifyReflowFlags::DuringResizeReflow },
278 // clang-format on
281 # define NUM_VERIFY_REFLOW_FLAGS (sizeof(gFlags) / sizeof(gFlags[0]))
283 static void ShowVerifyReflowFlags() {
284 printf("Here are the available GECKO_VERIFY_REFLOW_FLAGS:\n");
285 const VerifyReflowFlagData* flag = gFlags;
286 const VerifyReflowFlagData* limit = gFlags + NUM_VERIFY_REFLOW_FLAGS;
287 while (flag < limit) {
288 printf(" %s\n", flag->name);
289 ++flag;
291 printf("Note: GECKO_VERIFY_REFLOW_FLAGS is a comma separated list of flag\n");
292 printf("names (no whitespace)\n");
294 #endif
296 //========================================================================
297 //========================================================================
298 //========================================================================
299 #ifdef MOZ_REFLOW_PERF
300 class ReflowCountMgr;
302 static const char kGrandTotalsStr[] = "Grand Totals";
304 // Counting Class
305 class ReflowCounter {
306 public:
307 explicit ReflowCounter(ReflowCountMgr* aMgr = nullptr);
308 ~ReflowCounter();
310 void ClearTotals();
311 void DisplayTotals(const char* aStr);
312 void DisplayDiffTotals(const char* aStr);
313 void DisplayHTMLTotals(const char* aStr);
315 void Add() { mTotal++; }
316 void Add(uint32_t aTotal) { mTotal += aTotal; }
318 void CalcDiffInTotals();
319 void SetTotalsCache();
321 void SetMgr(ReflowCountMgr* aMgr) { mMgr = aMgr; }
323 uint32_t GetTotal() { return mTotal; }
325 protected:
326 void DisplayTotals(uint32_t aTotal, const char* aTitle);
327 void DisplayHTMLTotals(uint32_t aTotal, const char* aTitle);
329 uint32_t mTotal;
330 uint32_t mCacheTotal;
332 ReflowCountMgr* mMgr; // weak reference (don't delete)
335 // Counting Class
336 class IndiReflowCounter {
337 public:
338 explicit IndiReflowCounter(ReflowCountMgr* aMgr = nullptr)
339 : mFrame(nullptr),
340 mCount(0),
341 mMgr(aMgr),
342 mCounter(aMgr),
343 mHasBeenOutput(false) {}
344 virtual ~IndiReflowCounter() = default;
346 nsAutoString mName;
347 nsIFrame* mFrame; // weak reference (don't delete)
348 int32_t mCount;
350 ReflowCountMgr* mMgr; // weak reference (don't delete)
352 ReflowCounter mCounter;
353 bool mHasBeenOutput;
356 //--------------------
357 // Manager Class
358 //--------------------
359 class ReflowCountMgr {
360 public:
361 ReflowCountMgr();
362 virtual ~ReflowCountMgr();
364 void ClearTotals();
365 void ClearGrandTotals();
366 void DisplayTotals(const char* aStr);
367 void DisplayHTMLTotals(const char* aStr);
368 void DisplayDiffsInTotals();
370 void Add(const char* aName, nsIFrame* aFrame);
371 ReflowCounter* LookUp(const char* aName);
373 void PaintCount(const char* aName, gfxContext* aRenderingContext,
374 nsPresContext* aPresContext, nsIFrame* aFrame,
375 const nsPoint& aOffset, uint32_t aColor);
377 FILE* GetOutFile() { return mFD; }
379 void SetPresContext(nsPresContext* aPresContext) {
380 mPresContext = aPresContext; // weak reference
382 void SetPresShell(PresShell* aPresShell) {
383 mPresShell = aPresShell; // weak reference
386 void SetDumpFrameCounts(bool aVal) { mDumpFrameCounts = aVal; }
387 void SetDumpFrameByFrameCounts(bool aVal) { mDumpFrameByFrameCounts = aVal; }
388 void SetPaintFrameCounts(bool aVal) { mPaintFrameByFrameCounts = aVal; }
390 bool IsPaintingFrameCounts() { return mPaintFrameByFrameCounts; }
392 protected:
393 void DisplayTotals(uint32_t aTotal, uint32_t* aDupArray, char* aTitle);
394 void DisplayHTMLTotals(uint32_t aTotal, uint32_t* aDupArray, char* aTitle);
396 void DoGrandTotals();
397 void DoIndiTotalsTree();
399 // HTML Output Methods
400 void DoGrandHTMLTotals();
402 nsClassHashtable<nsCharPtrHashKey, ReflowCounter> mCounts;
403 nsClassHashtable<nsCharPtrHashKey, IndiReflowCounter> mIndiFrameCounts;
404 FILE* mFD;
406 bool mDumpFrameCounts;
407 bool mDumpFrameByFrameCounts;
408 bool mPaintFrameByFrameCounts;
410 bool mCycledOnce;
412 // Root Frame for Individual Tracking
413 nsPresContext* mPresContext;
414 PresShell* mPresShell;
416 // ReflowCountMgr gReflowCountMgr;
418 #endif
419 //========================================================================
421 // comment out to hide caret
422 #define SHOW_CARET
424 // The upper bound on the amount of time to spend reflowing, in
425 // microseconds. When this bound is exceeded and reflow commands are
426 // still queued up, a reflow event is posted. The idea is for reflow
427 // to not hog the processor beyond the time specifed in
428 // gMaxRCProcessingTime. This data member is initialized from the
429 // layout.reflow.timeslice pref.
430 #define NS_MAX_REFLOW_TIME 1000000
431 static int32_t gMaxRCProcessingTime = -1;
433 struct nsCallbackEventRequest {
434 nsIReflowCallback* callback;
435 nsCallbackEventRequest* next;
438 // ----------------------------------------------------------------------------
440 // NOTE(emilio): It'd be nice for this to assert that our document isn't in the
441 // bfcache, but font pref changes don't care about that, and maybe / probably
442 // shouldn't.
443 #ifdef DEBUG
444 # define ASSERT_REFLOW_SCHEDULED_STATE() \
446 if (ObservingLayoutFlushes()) { \
447 MOZ_ASSERT( \
448 mDocument->GetBFCacheEntry() || \
449 mPresContext->RefreshDriver()->IsLayoutFlushObserver(this), \
450 "Unexpected state"); \
451 } else { \
452 MOZ_ASSERT( \
453 !mPresContext->RefreshDriver()->IsLayoutFlushObserver(this), \
454 "Unexpected state"); \
457 #else
458 # define ASSERT_REFLOW_SCHEDULED_STATE() /* nothing */
459 #endif
461 class nsAutoCauseReflowNotifier {
462 public:
463 MOZ_CAN_RUN_SCRIPT explicit nsAutoCauseReflowNotifier(PresShell* aPresShell)
464 : mPresShell(aPresShell) {
465 mPresShell->WillCauseReflow();
467 MOZ_CAN_RUN_SCRIPT ~nsAutoCauseReflowNotifier() {
468 // This check should not be needed. Currently the only place that seem
469 // to need it is the code that deals with bug 337586.
470 if (!mPresShell->mHaveShutDown) {
471 RefPtr<PresShell> presShell(mPresShell);
472 presShell->DidCauseReflow();
473 } else {
474 nsContentUtils::RemoveScriptBlocker();
478 PresShell* mPresShell;
481 class MOZ_STACK_CLASS nsPresShellEventCB : public EventDispatchingCallback {
482 public:
483 explicit nsPresShellEventCB(PresShell* aPresShell) : mPresShell(aPresShell) {}
485 MOZ_CAN_RUN_SCRIPT
486 virtual void HandleEvent(EventChainPostVisitor& aVisitor) override {
487 if (aVisitor.mPresContext && aVisitor.mEvent->mClass != eBasicEventClass) {
488 if (aVisitor.mEvent->mMessage == eMouseDown ||
489 aVisitor.mEvent->mMessage == eMouseUp) {
490 // Mouse-up and mouse-down events call nsIFrame::HandlePress/Release
491 // which call GetContentOffsetsFromPoint which requires up-to-date
492 // layout. Bring layout up-to-date now so that GetCurrentEventFrame()
493 // below will return a real frame and we don't have to worry about
494 // destroying it by flushing later.
495 MOZ_KnownLive(mPresShell)->FlushPendingNotifications(FlushType::Layout);
496 } else if (aVisitor.mEvent->mMessage == eWheel &&
497 aVisitor.mEventStatus != nsEventStatus_eConsumeNoDefault) {
498 nsIFrame* frame = mPresShell->GetCurrentEventFrame();
499 if (frame) {
500 // chrome (including addons) should be able to know if content
501 // handles both D3E "wheel" event and legacy mouse scroll events.
502 // We should dispatch legacy mouse events before dispatching the
503 // "wheel" event into system group.
504 RefPtr<EventStateManager> esm =
505 aVisitor.mPresContext->EventStateManager();
506 esm->DispatchLegacyMouseScrollEvents(
507 frame, aVisitor.mEvent->AsWheelEvent(), &aVisitor.mEventStatus);
510 nsIFrame* frame = mPresShell->GetCurrentEventFrame();
511 if (!frame && (aVisitor.mEvent->mMessage == eMouseUp ||
512 aVisitor.mEvent->mMessage == eTouchEnd)) {
513 // Redirect BUTTON_UP and TOUCH_END events to the root frame to ensure
514 // that capturing is released.
515 frame = mPresShell->GetRootFrame();
517 if (frame) {
518 frame->HandleEvent(aVisitor.mPresContext, aVisitor.mEvent->AsGUIEvent(),
519 &aVisitor.mEventStatus);
524 RefPtr<PresShell> mPresShell;
527 class nsBeforeFirstPaintDispatcher : public Runnable {
528 public:
529 explicit nsBeforeFirstPaintDispatcher(Document* aDocument)
530 : mozilla::Runnable("nsBeforeFirstPaintDispatcher"),
531 mDocument(aDocument) {}
533 // Fires the "before-first-paint" event so that interested parties (right now,
534 // the mobile browser) are aware of it.
535 NS_IMETHOD Run() override {
536 nsCOMPtr<nsIObserverService> observerService =
537 mozilla::services::GetObserverService();
538 if (observerService) {
539 observerService->NotifyObservers(ToSupports(mDocument),
540 "before-first-paint", nullptr);
542 return NS_OK;
545 private:
546 RefPtr<Document> mDocument;
549 // This is a helper class to track whether the targeted frame is destroyed after
550 // dispatching pointer events. In that case, we need the original targeted
551 // content so that we can dispatch the mouse events to it.
552 class MOZ_STACK_CLASS AutoPointerEventTargetUpdater final {
553 public:
554 AutoPointerEventTargetUpdater(PresShell* aShell, WidgetEvent* aEvent,
555 nsIFrame* aFrame, nsIContent** aTargetContent) {
556 MOZ_ASSERT(aEvent);
557 if (!aTargetContent || aEvent->mClass != ePointerEventClass) {
558 // Make the destructor happy.
559 mTargetContent = nullptr;
560 return;
562 MOZ_ASSERT(aShell);
563 MOZ_ASSERT(aFrame);
564 MOZ_ASSERT(!aFrame->GetContent() ||
565 aShell->GetDocument() == aFrame->GetContent()->OwnerDoc());
567 mShell = aShell;
568 mWeakFrame = aFrame;
569 mTargetContent = aTargetContent;
570 aShell->mPointerEventTarget = aFrame->GetContent();
573 ~AutoPointerEventTargetUpdater() {
574 if (!mTargetContent || !mShell || mWeakFrame.IsAlive()) {
575 return;
577 mShell->mPointerEventTarget.swap(*mTargetContent);
580 private:
581 RefPtr<PresShell> mShell;
582 AutoWeakFrame mWeakFrame;
583 nsIContent** mTargetContent;
586 void PresShell::DirtyRootsList::Add(nsIFrame* aFrame) {
587 // Is this root already scheduled for reflow?
588 // FIXME: This could possibly be changed to a uniqueness assertion, with some
589 // work in ResizeReflowIgnoreOverride (and maybe others?)
590 if (mList.Contains(aFrame)) {
591 // We don't expect frame to change depths.
592 MOZ_ASSERT(aFrame->GetDepthInFrameTree() ==
593 mList[mList.IndexOf(aFrame)].mDepth);
594 return;
597 mList.InsertElementSorted(
598 FrameAndDepth{aFrame, aFrame->GetDepthInFrameTree()},
599 FrameAndDepth::CompareByReverseDepth{});
602 void PresShell::DirtyRootsList::Remove(nsIFrame* aFrame) {
603 mList.RemoveElement(aFrame);
606 nsIFrame* PresShell::DirtyRootsList::PopShallowestRoot() {
607 // List is sorted in order of decreasing depth, so there are no deeper
608 // frames than the last one.
609 const FrameAndDepth& lastFAD = mList.PopLastElement();
610 nsIFrame* frame = lastFAD.mFrame;
611 // We don't expect frame to change depths.
612 MOZ_ASSERT(frame->GetDepthInFrameTree() == lastFAD.mDepth);
613 return frame;
616 void PresShell::DirtyRootsList::Clear() { mList.Clear(); }
618 bool PresShell::DirtyRootsList::Contains(nsIFrame* aFrame) const {
619 return mList.Contains(aFrame);
622 bool PresShell::DirtyRootsList::IsEmpty() const { return mList.IsEmpty(); }
624 bool PresShell::DirtyRootsList::FrameIsAncestorOfDirtyRoot(
625 nsIFrame* aFrame) const {
626 MOZ_ASSERT(aFrame);
628 // Look for a path from any dirty roots to aFrame, following GetParent().
629 // This check mirrors what FrameNeedsReflow() would have done if the reflow
630 // root didn't get in the way.
631 for (nsIFrame* dirtyFrame : mList) {
632 do {
633 if (dirtyFrame == aFrame) {
634 return true;
636 dirtyFrame = dirtyFrame->GetParent();
637 } while (dirtyFrame);
640 return false;
643 bool PresShell::sDisableNonTestMouseEvents = false;
645 LazyLogModule PresShell::gLog("PresShell");
647 TimeStamp PresShell::EventHandler::sLastInputCreated;
648 TimeStamp PresShell::EventHandler::sLastInputProcessed;
649 StaticRefPtr<Element> PresShell::EventHandler::sLastKeyDownEventTargetElement;
651 bool PresShell::sProcessInteractable = false;
653 static bool gVerifyReflowEnabled;
654 extern mozilla::LazyLogModule sApzMvmLog;
656 bool PresShell::GetVerifyReflowEnable() {
657 #ifdef DEBUG
658 static bool firstTime = true;
659 if (firstTime) {
660 firstTime = false;
661 char* flags = PR_GetEnv("GECKO_VERIFY_REFLOW_FLAGS");
662 if (flags) {
663 bool error = false;
665 for (;;) {
666 char* comma = PL_strchr(flags, ',');
667 if (comma) *comma = '\0';
669 bool found = false;
670 const VerifyReflowFlagData* flag = gFlags;
671 const VerifyReflowFlagData* limit = gFlags + NUM_VERIFY_REFLOW_FLAGS;
672 while (flag < limit) {
673 if (PL_strcasecmp(flag->name, flags) == 0) {
674 gVerifyReflowFlags |= flag->bit;
675 found = true;
676 break;
678 ++flag;
681 if (!found) error = true;
683 if (!comma) break;
685 *comma = ',';
686 flags = comma + 1;
689 if (error) ShowVerifyReflowFlags();
692 if (VerifyReflowFlags::On & gVerifyReflowFlags) {
693 gVerifyReflowEnabled = true;
695 printf("Note: verifyreflow is enabled");
696 if (VerifyReflowFlags::Noisy & gVerifyReflowFlags) {
697 printf(" (noisy)");
699 if (VerifyReflowFlags::All & gVerifyReflowFlags) {
700 printf(" (all)");
702 if (VerifyReflowFlags::DumpCommands & gVerifyReflowFlags) {
703 printf(" (show reflow commands)");
705 if (VerifyReflowFlags::NoisyCommands & gVerifyReflowFlags) {
706 printf(" (noisy reflow commands)");
707 if (VerifyReflowFlags::ReallyNoisyCommands & gVerifyReflowFlags) {
708 printf(" (REALLY noisy reflow commands)");
711 printf("\n");
714 #endif
715 return gVerifyReflowEnabled;
718 void PresShell::SetVerifyReflowEnable(bool aEnabled) {
719 gVerifyReflowEnabled = aEnabled;
722 void PresShell::AddAutoWeakFrame(AutoWeakFrame* aWeakFrame) {
723 if (aWeakFrame->GetFrame()) {
724 aWeakFrame->GetFrame()->AddStateBits(NS_FRAME_EXTERNAL_REFERENCE);
726 aWeakFrame->SetPreviousWeakFrame(mAutoWeakFrames);
727 mAutoWeakFrames = aWeakFrame;
730 void PresShell::AddWeakFrame(WeakFrame* aWeakFrame) {
731 if (aWeakFrame->GetFrame()) {
732 aWeakFrame->GetFrame()->AddStateBits(NS_FRAME_EXTERNAL_REFERENCE);
734 MOZ_ASSERT(!mWeakFrames.Contains(aWeakFrame));
735 mWeakFrames.Insert(aWeakFrame);
738 void PresShell::RemoveAutoWeakFrame(AutoWeakFrame* aWeakFrame) {
739 if (mAutoWeakFrames == aWeakFrame) {
740 mAutoWeakFrames = aWeakFrame->GetPreviousWeakFrame();
741 return;
743 AutoWeakFrame* nextWeak = mAutoWeakFrames;
744 while (nextWeak && nextWeak->GetPreviousWeakFrame() != aWeakFrame) {
745 nextWeak = nextWeak->GetPreviousWeakFrame();
747 if (nextWeak) {
748 nextWeak->SetPreviousWeakFrame(aWeakFrame->GetPreviousWeakFrame());
752 void PresShell::RemoveWeakFrame(WeakFrame* aWeakFrame) {
753 MOZ_ASSERT(mWeakFrames.Contains(aWeakFrame));
754 mWeakFrames.Remove(aWeakFrame);
757 already_AddRefed<nsFrameSelection> PresShell::FrameSelection() {
758 RefPtr<nsFrameSelection> ret = mSelection;
759 return ret.forget();
762 //----------------------------------------------------------------------
764 static uint32_t sNextPresShellId;
766 /* static */
767 bool PresShell::AccessibleCaretEnabled(nsIDocShell* aDocShell) {
768 // If the pref forces it on, then enable it.
769 if (StaticPrefs::layout_accessiblecaret_enabled()) {
770 return true;
772 // If the touch pref is on, and touch events are enabled (this depends
773 // on the specific device running), then enable it.
774 if (StaticPrefs::layout_accessiblecaret_enabled_on_touch() &&
775 dom::TouchEvent::PrefEnabled(aDocShell)) {
776 return true;
778 // Otherwise, disabled.
779 return false;
782 PresShell::PresShell(Document* aDocument)
783 : mDocument(aDocument),
784 mViewManager(nullptr),
785 mFrameManager(nullptr),
786 mAutoWeakFrames(nullptr),
787 #ifdef ACCESSIBILITY
788 mDocAccessible(nullptr),
789 #endif // #ifdef ACCESSIBILITY
790 mCurrentEventFrame(nullptr),
791 mMouseLocation(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE),
792 mLastResolutionChangeOrigin(ResolutionChangeOrigin::Apz),
793 mPaintCount(0),
794 mAPZFocusSequenceNumber(0),
795 mCanvasBackgroundColor(NS_RGBA(0, 0, 0, 0)),
796 mActiveSuppressDisplayport(0),
797 mPresShellId(sNextPresShellId++),
798 mFontSizeInflationEmPerLine(0),
799 mFontSizeInflationMinTwips(0),
800 mFontSizeInflationLineThreshold(0),
801 mSelectionFlags(nsISelectionDisplay::DISPLAY_TEXT |
802 nsISelectionDisplay::DISPLAY_IMAGES),
803 mChangeNestCount(0),
804 mRenderingStateFlags(RenderingStateFlags::None),
805 mInFlush(false),
806 mCaretEnabled(false),
807 mNeedLayoutFlush(true),
808 mNeedStyleFlush(true),
809 mNeedThrottledAnimationFlush(true),
810 mVisualViewportSizeSet(false),
811 mDidInitialize(false),
812 mIsDestroying(false),
813 mIsReflowing(false),
814 mIsObservingDocument(false),
815 mForbiddenToFlush(false),
816 mIsDocumentGone(false),
817 mHaveShutDown(false),
818 mPaintingSuppressed(false),
819 mLastRootReflowHadUnconstrainedBSize(false),
820 mShouldUnsuppressPainting(false),
821 mIgnoreFrameDestruction(false),
822 mIsActive(true),
823 mFrozen(false),
824 mIsFirstPaint(true),
825 mObservesMutationsForPrint(false),
826 mWasLastReflowInterrupted(false),
827 mObservingStyleFlushes(false),
828 mObservingLayoutFlushes(false),
829 mResizeEventPending(false),
830 mFontSizeInflationForceEnabled(false),
831 mFontSizeInflationDisabledInMasterProcess(false),
832 mFontSizeInflationEnabled(false),
833 mIsNeverPainting(false),
834 mResolutionUpdated(false),
835 mResolutionUpdatedByApz(false),
836 mUnderHiddenEmbedderElement(false),
837 mDocumentLoading(false),
838 mNoDelayedMouseEvents(false),
839 mNoDelayedKeyEvents(false),
840 mApproximateFrameVisibilityVisited(false),
841 mNextPaintCompressed(false),
842 mHasCSSBackgroundColor(true),
843 mIsLastChromeOnlyEscapeKeyConsumed(false),
844 mHasReceivedPaintMessage(false),
845 mIsLastKeyDownCanceled(false),
846 mHasHandledUserInput(false),
847 mForceDispatchKeyPressEventsForNonPrintableKeys(false),
848 mForceUseLegacyKeyCodeAndCharCodeValues(false),
849 mInitializedWithKeyPressEventDispatchingBlacklist(false),
850 mForceUseLegacyNonPrimaryDispatch(false),
851 mInitializedWithClickEventDispatchingBlacklist(false),
852 mMouseLocationWasSetBySynthesizedMouseEventForTests(false) {
853 MOZ_LOG(gLog, LogLevel::Debug, ("PresShell::PresShell this=%p", this));
854 MOZ_ASSERT(aDocument);
856 #ifdef MOZ_REFLOW_PERF
857 mReflowCountMgr = MakeUnique<ReflowCountMgr>();
858 mReflowCountMgr->SetPresContext(mPresContext);
859 mReflowCountMgr->SetPresShell(this);
860 #endif
861 mLastOSWake = mLoadBegin = TimeStamp::Now();
864 NS_INTERFACE_TABLE_HEAD(PresShell)
865 NS_INTERFACE_TABLE_BEGIN
866 // In most cases, PresShell should be treated as concrete class, but need to
867 // QI for weak reference. Therefore, the case needed by do_QueryReferent()
868 // should be tested first.
869 NS_INTERFACE_TABLE_ENTRY(PresShell, PresShell)
870 NS_INTERFACE_TABLE_ENTRY(PresShell, nsIDocumentObserver)
871 NS_INTERFACE_TABLE_ENTRY(PresShell, nsISelectionController)
872 NS_INTERFACE_TABLE_ENTRY(PresShell, nsISelectionDisplay)
873 NS_INTERFACE_TABLE_ENTRY(PresShell, nsIObserver)
874 NS_INTERFACE_TABLE_ENTRY(PresShell, nsISupportsWeakReference)
875 NS_INTERFACE_TABLE_ENTRY(PresShell, nsIMutationObserver)
876 NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(PresShell, nsISupports, nsIObserver)
877 NS_INTERFACE_TABLE_END
878 NS_INTERFACE_TABLE_TO_MAP_SEGUE
879 NS_INTERFACE_MAP_END
881 NS_IMPL_ADDREF(PresShell)
882 NS_IMPL_RELEASE(PresShell)
884 PresShell::~PresShell() {
885 MOZ_RELEASE_ASSERT(!mForbiddenToFlush,
886 "Flag should only be set temporarily, while doing things "
887 "that shouldn't cause destruction");
888 MOZ_LOG(gLog, LogLevel::Debug, ("PresShell::~PresShell this=%p", this));
890 if (!mHaveShutDown) {
891 MOZ_ASSERT_UNREACHABLE("Someone did not call PresShell::Destroy()");
892 Destroy();
895 NS_ASSERTION(mCurrentEventContentStack.Count() == 0,
896 "Huh, event content left on the stack in pres shell dtor!");
897 NS_ASSERTION(mFirstCallbackEventRequest == nullptr &&
898 mLastCallbackEventRequest == nullptr,
899 "post-reflow queues not empty. This means we're leaking");
901 MOZ_ASSERT(mAllocatedPointers.IsEmpty(),
902 "Some pres arena objects were not freed");
904 mFrameManager = nullptr;
905 mFrameConstructor = nullptr;
907 mCurrentEventContent = nullptr;
911 * Initialize the presentation shell. Create view manager and style
912 * manager.
913 * Note this can't be merged into our constructor because caret initialization
914 * calls AddRef() on us.
916 void PresShell::Init(nsPresContext* aPresContext, nsViewManager* aViewManager) {
917 MOZ_ASSERT(mDocument);
918 MOZ_ASSERT(aPresContext);
919 MOZ_ASSERT(aViewManager);
920 MOZ_ASSERT(!mViewManager, "already initialized");
922 mViewManager = aViewManager;
924 // mDocument is now set. It might have a display document whose "need layout/
925 // style" flush flags are not set, but ours will be set. To keep these
926 // consistent, call the flag setting functions to propagate those flags up
927 // to the display document.
928 SetNeedLayoutFlush();
929 SetNeedStyleFlush();
931 // Create our frame constructor.
932 mFrameConstructor = MakeUnique<nsCSSFrameConstructor>(mDocument, this);
934 mFrameManager = mFrameConstructor.get();
936 // The document viewer owns both view manager and pres shell.
937 mViewManager->SetPresShell(this);
939 // Bind the context to the presentation shell.
940 // FYI: We cannot initialize mPresContext in the constructor because we
941 // cannot call AttachPresShell() in it and once we initialize
942 // mPresContext, other objects may refer refresh driver or restyle
943 // manager via mPresContext and that causes hitting MOZ_ASSERT in some
944 // places. Therefore, we should initialize mPresContext here with
945 // const_cast hack since we want to guarantee that mPresContext lives
946 // as long as the PresShell.
947 const_cast<RefPtr<nsPresContext>&>(mPresContext) = aPresContext;
948 mPresContext->AttachPresShell(this);
950 mPresContext->DeviceContext()->InitFontCache();
952 // FIXME(emilio, bug 1544185): Some Android code somehow depends on the shell
953 // being eagerly registered as a style flush observer. This shouldn't be
954 // needed otherwise.
955 EnsureStyleFlush();
957 // Add the preference style sheet.
958 UpdatePreferenceStyles();
960 bool accessibleCaretEnabled =
961 AccessibleCaretEnabled(mDocument->GetDocShell());
962 if (accessibleCaretEnabled) {
963 // Need to happen before nsFrameSelection has been set up.
964 mAccessibleCaretEventHub = new AccessibleCaretEventHub(this);
967 mSelection = new nsFrameSelection(this, nullptr, accessibleCaretEnabled);
969 // Important: this has to happen after the selection has been set up
970 #ifdef SHOW_CARET
971 // make the caret
972 mCaret = new nsCaret();
973 mCaret->Init(this);
974 mOriginalCaret = mCaret;
976 // SetCaretEnabled(true); // make it show in browser windows
977 #endif
978 // set up selection to be displayed in document
979 // Don't enable selection for print media
980 nsPresContext::nsPresContextType type = mPresContext->Type();
981 if (type != nsPresContext::eContext_PrintPreview &&
982 type != nsPresContext::eContext_Print) {
983 SetDisplaySelection(nsISelectionController::SELECTION_DISABLED);
986 if (gMaxRCProcessingTime == -1) {
987 gMaxRCProcessingTime =
988 Preferences::GetInt("layout.reflow.timeslice", NS_MAX_REFLOW_TIME);
991 if (nsStyleSheetService* ss = nsStyleSheetService::GetInstance()) {
992 ss->RegisterPresShell(this);
996 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
997 if (os) {
998 os->AddObserver(this, "memory-pressure", false);
999 os->AddObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC, false);
1000 if (XRE_IsParentProcess() && !sProcessInteractable) {
1001 os->AddObserver(this, "sessionstore-one-or-no-tab-restored", false);
1003 os->AddObserver(this, "font-info-updated", false);
1004 os->AddObserver(this, "look-and-feel-changed", false);
1008 #ifdef MOZ_REFLOW_PERF
1009 if (mReflowCountMgr) {
1010 bool paintFrameCounts =
1011 Preferences::GetBool("layout.reflow.showframecounts");
1013 bool dumpFrameCounts =
1014 Preferences::GetBool("layout.reflow.dumpframecounts");
1016 bool dumpFrameByFrameCounts =
1017 Preferences::GetBool("layout.reflow.dumpframebyframecounts");
1019 mReflowCountMgr->SetDumpFrameCounts(dumpFrameCounts);
1020 mReflowCountMgr->SetDumpFrameByFrameCounts(dumpFrameByFrameCounts);
1021 mReflowCountMgr->SetPaintFrameCounts(paintFrameCounts);
1023 #endif
1025 if (mDocument->HasAnimationController()) {
1026 SMILAnimationController* animCtrl = mDocument->GetAnimationController();
1027 animCtrl->NotifyRefreshDriverCreated(GetPresContext()->RefreshDriver());
1030 for (DocumentTimeline* timeline : mDocument->Timelines()) {
1031 timeline->NotifyRefreshDriverCreated(GetPresContext()->RefreshDriver());
1034 // Get our activeness from the docShell.
1035 ActivenessMaybeChanged();
1037 // Setup our font inflation preferences.
1038 mFontSizeInflationEmPerLine = StaticPrefs::font_size_inflation_emPerLine();
1039 mFontSizeInflationMinTwips = StaticPrefs::font_size_inflation_minTwips();
1040 mFontSizeInflationLineThreshold =
1041 StaticPrefs::font_size_inflation_lineThreshold();
1042 mFontSizeInflationForceEnabled =
1043 StaticPrefs::font_size_inflation_forceEnabled();
1044 mFontSizeInflationDisabledInMasterProcess =
1045 StaticPrefs::font_size_inflation_disabledInMasterProcess();
1046 // We'll compute the font size inflation state in Initialize(), when we know
1047 // the document type.
1049 mTouchManager.Init(this, mDocument);
1051 if (mPresContext->IsRootContentDocumentCrossProcess()) {
1052 mZoomConstraintsClient = new ZoomConstraintsClient();
1053 mZoomConstraintsClient->Init(this, mDocument);
1055 // We call this to create mMobileViewportManager, if it is needed.
1056 MaybeRecreateMobileViewportManager(false);
1059 if (nsCOMPtr<nsIDocShell> docShell = mPresContext->GetDocShell()) {
1060 BrowsingContext* bc = docShell->GetBrowsingContext();
1061 bool embedderFrameIsHidden = true;
1062 if (Element* embedderElement = bc->GetEmbedderElement()) {
1063 if (auto embedderFrame = embedderElement->GetPrimaryFrame()) {
1064 embedderFrameIsHidden = !embedderFrame->StyleVisibility()->IsVisible();
1068 if (BrowsingContext* parent = bc->GetParent()) {
1069 if (nsCOMPtr<nsIDocShell> parentDocShell = parent->GetDocShell()) {
1070 if (PresShell* parentPresShell = parentDocShell->GetPresShell()) {
1071 mUnderHiddenEmbedderElement =
1072 parentPresShell->IsUnderHiddenEmbedderElement() ||
1073 embedderFrameIsHidden;
1080 enum TextPerfLogType { eLog_reflow, eLog_loaddone, eLog_totals };
1082 static void LogTextPerfStats(gfxTextPerfMetrics* aTextPerf,
1083 PresShell* aPresShell,
1084 const gfxTextPerfMetrics::TextCounts& aCounts,
1085 float aTime, TextPerfLogType aLogType,
1086 const char* aURL) {
1087 LogModule* tpLog = gfxPlatform::GetLog(eGfxLog_textperf);
1089 // ignore XUL contexts unless at debug level
1090 mozilla::LogLevel logLevel = LogLevel::Warning;
1091 if (aCounts.numContentTextRuns == 0) {
1092 logLevel = LogLevel::Debug;
1095 if (!MOZ_LOG_TEST(tpLog, logLevel)) {
1096 return;
1099 char prefix[256];
1101 switch (aLogType) {
1102 case eLog_reflow:
1103 SprintfLiteral(prefix, "(textperf-reflow) %p time-ms: %7.0f", aPresShell,
1104 aTime);
1105 break;
1106 case eLog_loaddone:
1107 SprintfLiteral(prefix, "(textperf-loaddone) %p time-ms: %7.0f",
1108 aPresShell, aTime);
1109 break;
1110 default:
1111 MOZ_ASSERT(aLogType == eLog_totals, "unknown textperf log type");
1112 SprintfLiteral(prefix, "(textperf-totals) %p", aPresShell);
1115 double hitRatio = 0.0;
1116 uint32_t lookups = aCounts.wordCacheHit + aCounts.wordCacheMiss;
1117 if (lookups) {
1118 hitRatio = double(aCounts.wordCacheHit) / double(lookups);
1121 if (aLogType == eLog_loaddone) {
1122 MOZ_LOG(
1123 tpLog, logLevel,
1124 ("%s reflow: %d chars: %d "
1125 "[%s] "
1126 "content-textruns: %d chrome-textruns: %d "
1127 "max-textrun-len: %d "
1128 "word-cache-lookups: %d word-cache-hit-ratio: %4.3f "
1129 "word-cache-space: %d word-cache-long: %d "
1130 "pref-fallbacks: %d system-fallbacks: %d "
1131 "textruns-const: %d textruns-destr: %d "
1132 "generic-lookups: %d "
1133 "cumulative-textruns-destr: %d\n",
1134 prefix, aTextPerf->reflowCount, aCounts.numChars, (aURL ? aURL : ""),
1135 aCounts.numContentTextRuns, aCounts.numChromeTextRuns,
1136 aCounts.maxTextRunLen, lookups, hitRatio, aCounts.wordCacheSpaceRules,
1137 aCounts.wordCacheLong, aCounts.fallbackPrefs, aCounts.fallbackSystem,
1138 aCounts.textrunConst, aCounts.textrunDestr, aCounts.genericLookups,
1139 aTextPerf->cumulative.textrunDestr));
1140 } else {
1141 MOZ_LOG(
1142 tpLog, logLevel,
1143 ("%s reflow: %d chars: %d "
1144 "content-textruns: %d chrome-textruns: %d "
1145 "max-textrun-len: %d "
1146 "word-cache-lookups: %d word-cache-hit-ratio: %4.3f "
1147 "word-cache-space: %d word-cache-long: %d "
1148 "pref-fallbacks: %d system-fallbacks: %d "
1149 "textruns-const: %d textruns-destr: %d "
1150 "generic-lookups: %d "
1151 "cumulative-textruns-destr: %d\n",
1152 prefix, aTextPerf->reflowCount, aCounts.numChars,
1153 aCounts.numContentTextRuns, aCounts.numChromeTextRuns,
1154 aCounts.maxTextRunLen, lookups, hitRatio, aCounts.wordCacheSpaceRules,
1155 aCounts.wordCacheLong, aCounts.fallbackPrefs, aCounts.fallbackSystem,
1156 aCounts.textrunConst, aCounts.textrunDestr, aCounts.genericLookups,
1157 aTextPerf->cumulative.textrunDestr));
1161 bool PresShell::InRDMPane() {
1162 if (Document* doc = GetDocument()) {
1163 if (BrowsingContext* bc = doc->GetBrowsingContext()) {
1164 return bc->InRDMPane();
1167 return false;
1170 void PresShell::Destroy() {
1171 // Do not add code before this line please!
1172 if (mHaveShutDown) {
1173 return;
1176 NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
1177 "destroy called on presshell while scripts not blocked");
1179 AUTO_PROFILER_LABEL("PresShell::Destroy", LAYOUT);
1181 // Try to determine if the page is the user had a meaningful opportunity to
1182 // zoom this page. This is not 100% accurate but should be "good enough" for
1183 // telemetry purposes.
1184 auto isUserZoomablePage = [&]() -> bool {
1185 if (mIsFirstPaint) {
1186 // Page was never painted, so it wasn't zoomable by the user. We get a
1187 // handful of these "transient" presShells.
1188 return false;
1190 if (!mPresContext->IsRootContentDocumentCrossProcess()) {
1191 // Not a root content document, so APZ doesn't support zooming it.
1192 return false;
1194 if (InRDMPane()) {
1195 // Responsive design mode is a special case that we want to ignore here.
1196 return false;
1198 if (mDocument && mDocument->IsInitialDocument()) {
1199 // Ignore initial about:blank page loads
1200 return false;
1202 if (XRE_IsContentProcess() &&
1203 IsExtensionRemoteType(ContentChild::GetSingleton()->GetRemoteType())) {
1204 // Also omit presShells from the extension process because they sometimes
1205 // can't be zoomed by the user.
1206 return false;
1208 // Otherwise assume the page is user-zoomable.
1209 return true;
1211 if (isUserZoomablePage()) {
1212 Telemetry::Accumulate(Telemetry::APZ_ZOOM_ACTIVITY,
1213 IsResolutionUpdatedByApz());
1216 // dump out cumulative text perf metrics
1217 gfxTextPerfMetrics* tp;
1218 if (mPresContext && (tp = mPresContext->GetTextPerfMetrics())) {
1219 tp->Accumulate();
1220 if (tp->cumulative.numChars > 0) {
1221 LogTextPerfStats(tp, this, tp->cumulative, 0.0, eLog_totals, nullptr);
1224 if (mPresContext) {
1225 if (gfxUserFontSet* fs = mPresContext->GetUserFontSet()) {
1226 uint32_t fontCount;
1227 uint64_t fontSize;
1228 fs->GetLoadStatistics(fontCount, fontSize);
1229 Telemetry::Accumulate(Telemetry::WEBFONT_PER_PAGE, fontCount);
1230 Telemetry::Accumulate(Telemetry::WEBFONT_SIZE_PER_PAGE,
1231 uint32_t(fontSize / 1024));
1232 } else {
1233 Telemetry::Accumulate(Telemetry::WEBFONT_PER_PAGE, 0);
1234 Telemetry::Accumulate(Telemetry::WEBFONT_SIZE_PER_PAGE, 0);
1236 const auto* stats = mPresContext->GetFontMatchingStats();
1237 if (stats) {
1238 Document* doc = GetDocument();
1239 if (doc && doc->IsContentDocument()) {
1240 nsIURI* uri = doc->GetDocumentURI();
1241 nsAutoCString path;
1242 if (uri && !uri->SchemeIs("about") && !uri->SchemeIs("chrome") &&
1243 !uri->SchemeIs("resource") &&
1244 !(uri->SchemeIs("moz-extension") &&
1245 (NS_SUCCEEDED(uri->GetFilePath(path)) &&
1246 StringEndsWith(path, "/_generated_background_page.html"_ns)))) {
1247 Telemetry::Accumulate(Telemetry::BASE_FONT_FAMILIES_PER_PAGE,
1248 stats->mBaseFonts);
1249 Telemetry::Accumulate(Telemetry::LANGPACK_FONT_FAMILIES_PER_PAGE,
1250 stats->mLangPackFonts);
1251 Telemetry::Accumulate(Telemetry::USER_FONT_FAMILIES_PER_PAGE,
1252 stats->mUserFonts);
1253 Telemetry::Accumulate(Telemetry::WEB_FONT_FAMILIES_PER_PAGE,
1254 stats->mWebFonts);
1255 Telemetry::Accumulate(
1256 Telemetry::FALLBACK_TO_PREFS_FONT,
1257 bool(stats->mFallbacks & FallbackTypes::FallbackToPrefsFont));
1258 Telemetry::Accumulate(
1259 Telemetry::FALLBACK_TO_BASE_FONT,
1260 bool(stats->mFallbacks & FallbackTypes::FallbackToBaseFont));
1261 Telemetry::Accumulate(
1262 Telemetry::FALLBACK_TO_LANGPACK_FONT,
1263 bool(stats->mFallbacks & FallbackTypes::FallbackToLangPackFont));
1264 Telemetry::Accumulate(
1265 Telemetry::FALLBACK_TO_USER_FONT,
1266 bool(stats->mFallbacks & FallbackTypes::FallbackToUserFont));
1267 Telemetry::Accumulate(
1268 Telemetry::MISSING_FONT,
1269 bool(stats->mFallbacks & FallbackTypes::MissingFont));
1270 Telemetry::Accumulate(
1271 Telemetry::MISSING_FONT_LANGPACK,
1272 bool(stats->mFallbacks & FallbackTypes::MissingFontLangPack));
1273 Telemetry::Accumulate(
1274 Telemetry::MISSING_FONT_USER,
1275 bool(stats->mFallbacks & FallbackTypes::MissingFontUser));
1279 mPresContext->CancelManagedPostRefreshObservers();
1282 #ifdef MOZ_REFLOW_PERF
1283 DumpReflows();
1284 mReflowCountMgr = nullptr;
1285 #endif
1287 if (mZoomConstraintsClient) {
1288 mZoomConstraintsClient->Destroy();
1289 mZoomConstraintsClient = nullptr;
1291 if (mMobileViewportManager) {
1292 mMobileViewportManager->Destroy();
1293 mMobileViewportManager = nullptr;
1294 mMVMContext = nullptr;
1297 #ifdef ACCESSIBILITY
1298 if (mDocAccessible) {
1299 # ifdef DEBUG
1300 if (a11y::logging::IsEnabled(a11y::logging::eDocDestroy))
1301 a11y::logging::DocDestroy("presshell destroyed", mDocument);
1302 # endif
1304 mDocAccessible->Shutdown();
1305 mDocAccessible = nullptr;
1307 #endif // ACCESSIBILITY
1309 MaybeReleaseCapturingContent();
1311 EventHandler::OnPresShellDestroy(mDocument);
1313 if (mContentToScrollTo) {
1314 mContentToScrollTo->RemoveProperty(nsGkAtoms::scrolling);
1315 mContentToScrollTo = nullptr;
1318 if (mPresContext) {
1319 // We need to notify the destroying the nsPresContext to ESM for
1320 // suppressing to use from ESM.
1321 mPresContext->EventStateManager()->NotifyDestroyPresContext(mPresContext);
1324 if (nsStyleSheetService* ss = nsStyleSheetService::GetInstance()) {
1325 ss->UnregisterPresShell(this);
1329 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
1330 if (os) {
1331 os->RemoveObserver(this, "memory-pressure");
1332 os->RemoveObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC);
1333 if (XRE_IsParentProcess()) {
1334 os->RemoveObserver(this, "sessionstore-one-or-no-tab-restored");
1336 os->RemoveObserver(this, "font-info-updated");
1337 os->RemoveObserver(this, "look-and-feel-changed");
1341 // If our paint suppression timer is still active, kill it.
1342 CancelPaintSuppressionTimer();
1344 // Same for our reflow continuation timer
1345 if (mReflowContinueTimer) {
1346 mReflowContinueTimer->Cancel();
1347 mReflowContinueTimer = nullptr;
1350 if (mDelayedPaintTimer) {
1351 mDelayedPaintTimer->Cancel();
1352 mDelayedPaintTimer = nullptr;
1355 mSynthMouseMoveEvent.Revoke();
1357 mUpdateApproximateFrameVisibilityEvent.Revoke();
1359 ClearApproximatelyVisibleFramesList(Some(OnNonvisible::DiscardImages));
1361 if (mCaret) {
1362 mCaret->Terminate();
1363 mCaret = nullptr;
1366 mFocusedFrameSelection = nullptr;
1368 if (mSelection) {
1369 RefPtr<nsFrameSelection> frameSelection = mSelection;
1370 frameSelection->DisconnectFromPresShell();
1373 // release our pref style sheet, if we have one still
1375 // TODO(emilio): Should we move the preference sheet tracking to the Document?
1376 RemovePreferenceStyles();
1378 mIsDestroying = true;
1380 // We can't release all the event content in
1381 // mCurrentEventContentStack here since there might be code on the
1382 // stack that will release the event content too. Double release
1383 // bad!
1385 // The frames will be torn down, so remove them from the current
1386 // event frame stack (since they'd be dangling references if we'd
1387 // leave them in) and null out the mCurrentEventFrame pointer as
1388 // well.
1390 mCurrentEventFrame = nullptr;
1392 int32_t i, count = mCurrentEventFrameStack.Length();
1393 for (i = 0; i < count; i++) {
1394 mCurrentEventFrameStack[i] = nullptr;
1397 mFramesToDirty.Clear();
1398 mPendingScrollAnchorSelection.Clear();
1399 mPendingScrollAnchorAdjustment.Clear();
1401 if (mViewManager) {
1402 // Clear the view manager's weak pointer back to |this| in case it
1403 // was leaked.
1404 mViewManager->SetPresShell(nullptr);
1405 mViewManager = nullptr;
1408 nsRefreshDriver* rd = GetPresContext()->RefreshDriver();
1410 // This shell must be removed from the document before the frame
1411 // hierarchy is torn down to avoid finding deleted frames through
1412 // this presshell while the frames are being torn down
1413 if (mDocument) {
1414 NS_ASSERTION(mDocument->GetPresShell() == this, "Wrong shell?");
1415 mDocument->ClearServoRestyleRoot();
1416 mDocument->DeletePresShell();
1418 if (mDocument->HasAnimationController()) {
1419 mDocument->GetAnimationController()->NotifyRefreshDriverDestroying(rd);
1421 for (DocumentTimeline* timeline : mDocument->Timelines()) {
1422 timeline->NotifyRefreshDriverDestroying(rd);
1426 if (mPresContext) {
1427 rd->CancelPendingAnimationEvents(mPresContext->AnimationEventDispatcher());
1430 // Revoke any pending events. We need to do this and cancel pending reflows
1431 // before we destroy the frame manager, since apparently frame destruction
1432 // sometimes spins the event queue when plug-ins are involved(!).
1433 // XXXmats is this still needed now that plugins are gone?
1434 StopObservingRefreshDriver();
1436 if (rd->GetPresContext() == GetPresContext()) {
1437 rd->RevokeViewManagerFlush();
1438 rd->ClearHasScheduleFlush();
1441 CancelAllPendingReflows();
1442 CancelPostedReflowCallbacks();
1444 // Destroy the frame manager. This will destroy the frame hierarchy
1445 mFrameConstructor->WillDestroyFrameTree();
1447 NS_WARNING_ASSERTION(!mAutoWeakFrames && mWeakFrames.IsEmpty(),
1448 "Weak frames alive after destroying FrameManager");
1449 while (mAutoWeakFrames) {
1450 mAutoWeakFrames->Clear(this);
1452 const nsTArray<WeakFrame*> weakFrames = ToArray(mWeakFrames);
1453 for (WeakFrame* weakFrame : weakFrames) {
1454 weakFrame->Clear(this);
1457 // Terminate AccessibleCaretEventHub after tearing down the frame tree so that
1458 // we don't need to remove caret element's frame in
1459 // AccessibleCaret::RemoveCaretElement().
1460 if (mAccessibleCaretEventHub) {
1461 mAccessibleCaretEventHub->Terminate();
1462 mAccessibleCaretEventHub = nullptr;
1465 if (mPresContext) {
1466 // We hold a reference to the pres context, and it holds a weak link back
1467 // to us. To avoid the pres context having a dangling reference, set its
1468 // pres shell to nullptr
1469 mPresContext->DetachPresShell();
1472 mHaveShutDown = true;
1474 mTouchManager.Destroy();
1477 void PresShell::StopObservingRefreshDriver() {
1478 nsRefreshDriver* rd = mPresContext->RefreshDriver();
1479 if (mResizeEventPending) {
1480 rd->RemoveResizeEventFlushObserver(this);
1482 if (mObservingLayoutFlushes) {
1483 rd->RemoveLayoutFlushObserver(this);
1485 if (mObservingStyleFlushes) {
1486 rd->RemoveStyleFlushObserver(this);
1490 void PresShell::StartObservingRefreshDriver() {
1491 nsRefreshDriver* rd = mPresContext->RefreshDriver();
1492 if (mResizeEventPending) {
1493 rd->AddResizeEventFlushObserver(this);
1495 if (mObservingLayoutFlushes) {
1496 rd->AddLayoutFlushObserver(this);
1498 if (mObservingStyleFlushes) {
1499 rd->AddStyleFlushObserver(this);
1503 nsRefreshDriver* PresShell::GetRefreshDriver() const {
1504 return mPresContext ? mPresContext->RefreshDriver() : nullptr;
1507 void PresShell::SetAuthorStyleDisabled(bool aStyleDisabled) {
1508 if (aStyleDisabled != StyleSet()->GetAuthorStyleDisabled()) {
1509 StyleSet()->SetAuthorStyleDisabled(aStyleDisabled);
1510 mDocument->ApplicableStylesChanged();
1512 nsCOMPtr<nsIObserverService> observerService =
1513 mozilla::services::GetObserverService();
1514 if (observerService) {
1515 observerService->NotifyObservers(
1516 ToSupports(mDocument), "author-style-disabled-changed", nullptr);
1521 bool PresShell::GetAuthorStyleDisabled() const {
1522 return StyleSet()->GetAuthorStyleDisabled();
1525 void PresShell::UpdatePreferenceStyles() {
1526 if (!mDocument) {
1527 return;
1530 // If the document doesn't have a window there's no need to notify
1531 // its presshell about changes to preferences since the document is
1532 // in a state where it doesn't matter any more (see
1533 // nsDocumentViewer::Close()).
1534 if (!mDocument->GetWindow()) {
1535 return;
1538 // Documents in chrome shells do not have any preference style rules applied.
1539 if (nsContentUtils::IsInChromeDocshell(mDocument)) {
1540 return;
1543 PreferenceSheet::EnsureInitialized();
1544 auto* cache = GlobalStyleSheetCache::Singleton();
1546 RefPtr<StyleSheet> newPrefSheet =
1547 PreferenceSheet::ShouldUseChromePrefs(*mDocument)
1548 ? cache->ChromePreferenceSheet()
1549 : cache->ContentPreferenceSheet();
1551 if (mPrefStyleSheet == newPrefSheet) {
1552 return;
1555 RemovePreferenceStyles();
1557 // NOTE(emilio): This sheet is added as an agent sheet, because we don't want
1558 // it to be modifiable from devtools and similar, see bugs 1239336 and
1559 // 1436782. I think it conceptually should be a user sheet, and could be
1560 // without too much trouble I'd think.
1561 StyleSet()->AppendStyleSheet(*newPrefSheet);
1562 mPrefStyleSheet = newPrefSheet;
1565 void PresShell::RemovePreferenceStyles() {
1566 if (mPrefStyleSheet) {
1567 StyleSet()->RemoveStyleSheet(*mPrefStyleSheet);
1568 mPrefStyleSheet = nullptr;
1572 void PresShell::AddUserSheet(StyleSheet* aSheet) {
1573 // Make sure this does what nsDocumentViewer::CreateStyleSet does wrt
1574 // ordering. We want this new sheet to come after all the existing stylesheet
1575 // service sheets (which are at the start), but before other user sheets; see
1576 // nsIStyleSheetService.idl for the ordering.
1578 nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance();
1579 nsTArray<RefPtr<StyleSheet>>& userSheets = *sheetService->UserStyleSheets();
1581 // Search for the place to insert the new user sheet. Since all of the
1582 // stylesheet service provided user sheets should be at the start of the style
1583 // set's list, and aSheet should be at the end of userSheets. Given that, we
1584 // can find the right place to insert the new sheet based on the length of
1585 // userSheets.
1586 MOZ_ASSERT(aSheet);
1587 MOZ_ASSERT(userSheets.LastElement() == aSheet);
1589 size_t index = userSheets.Length() - 1;
1591 // Assert that all of userSheets (except for the last, new element) matches up
1592 // with what's in the style set.
1593 for (size_t i = 0; i < index; ++i) {
1594 MOZ_ASSERT(StyleSet()->SheetAt(StyleOrigin::User, i) == userSheets[i]);
1597 if (index == static_cast<size_t>(StyleSet()->SheetCount(StyleOrigin::User))) {
1598 StyleSet()->AppendStyleSheet(*aSheet);
1599 } else {
1600 StyleSheet* ref = StyleSet()->SheetAt(StyleOrigin::User, index);
1601 StyleSet()->InsertStyleSheetBefore(*aSheet, *ref);
1604 mDocument->ApplicableStylesChanged();
1607 void PresShell::AddAgentSheet(StyleSheet* aSheet) {
1608 // Make sure this does what nsDocumentViewer::CreateStyleSet does
1609 // wrt ordering.
1610 StyleSet()->AppendStyleSheet(*aSheet);
1611 mDocument->ApplicableStylesChanged();
1614 void PresShell::AddAuthorSheet(StyleSheet* aSheet) {
1615 // Document specific "additional" Author sheets should be stronger than the
1616 // ones added with the StyleSheetService.
1617 StyleSheet* firstAuthorSheet = mDocument->GetFirstAdditionalAuthorSheet();
1618 if (firstAuthorSheet) {
1619 StyleSet()->InsertStyleSheetBefore(*aSheet, *firstAuthorSheet);
1620 } else {
1621 StyleSet()->AppendStyleSheet(*aSheet);
1624 mDocument->ApplicableStylesChanged();
1627 void PresShell::SelectionWillTakeFocus() {
1628 if (mSelection) {
1629 FrameSelectionWillTakeFocus(*mSelection);
1633 void PresShell::SelectionWillLoseFocus() {
1634 // Do nothing, the main selection is the default focused selection.
1637 // Selection repainting code relies on selection offsets being properly
1638 // adjusted (see bug 1626291), so we need to wait until the DOM is finished
1639 // notifying.
1640 static void RepaintNormalSelectionWhenSafe(nsFrameSelection& aFrameSelection) {
1641 if (nsContentUtils::IsSafeToRunScript()) {
1642 aFrameSelection.RepaintSelection(SelectionType::eNormal);
1643 return;
1646 // Note that importantly we don't defer changing the DisplaySelection. That'd
1647 // be potentially racy with other code that may change it.
1648 nsContentUtils::AddScriptRunner(NS_NewRunnableFunction(
1649 "RepaintNormalSelectionWhenSafe",
1650 [sel = RefPtr<nsFrameSelection>(&aFrameSelection)] {
1651 sel->RepaintSelection(SelectionType::eNormal);
1652 }));
1655 void PresShell::FrameSelectionWillLoseFocus(nsFrameSelection& aFrameSelection) {
1656 if (mFocusedFrameSelection != &aFrameSelection) {
1657 return;
1660 // Do nothing, the main selection is the default focused selection.
1661 if (&aFrameSelection == mSelection) {
1662 return;
1665 RefPtr<nsFrameSelection> old = std::move(mFocusedFrameSelection);
1666 MOZ_ASSERT(!mFocusedFrameSelection);
1668 if (old->GetDisplaySelection() != nsISelectionController::SELECTION_HIDDEN) {
1669 old->SetDisplaySelection(nsISelectionController::SELECTION_HIDDEN);
1670 RepaintNormalSelectionWhenSafe(*old);
1673 if (mSelection) {
1674 FrameSelectionWillTakeFocus(*mSelection);
1678 void PresShell::FrameSelectionWillTakeFocus(nsFrameSelection& aFrameSelection) {
1679 if (mFocusedFrameSelection == &aFrameSelection) {
1680 #ifdef XP_MACOSX
1681 // FIXME: Mac needs to update the global selection cache, even if the
1682 // document's focused selection doesn't change, and this is currently done
1683 // from RepaintSelection. Maybe we should move part of the global selection
1684 // handling here, or something of that sort, unclear.
1685 RepaintNormalSelectionWhenSafe(aFrameSelection);
1686 #endif
1687 return;
1690 RefPtr<nsFrameSelection> old = std::move(mFocusedFrameSelection);
1691 mFocusedFrameSelection = &aFrameSelection;
1693 if (old &&
1694 old->GetDisplaySelection() != nsISelectionController::SELECTION_HIDDEN) {
1695 old->SetDisplaySelection(nsISelectionController::SELECTION_HIDDEN);
1696 RepaintNormalSelectionWhenSafe(*old);
1699 if (aFrameSelection.GetDisplaySelection() !=
1700 nsISelectionController::SELECTION_ON) {
1701 aFrameSelection.SetDisplaySelection(nsISelectionController::SELECTION_ON);
1702 RepaintNormalSelectionWhenSafe(aFrameSelection);
1706 NS_IMETHODIMP
1707 PresShell::SetDisplaySelection(int16_t aToggle) {
1708 RefPtr<nsFrameSelection> frameSelection = mSelection;
1709 frameSelection->SetDisplaySelection(aToggle);
1710 return NS_OK;
1713 NS_IMETHODIMP
1714 PresShell::GetDisplaySelection(int16_t* aToggle) {
1715 RefPtr<nsFrameSelection> frameSelection = mSelection;
1716 *aToggle = frameSelection->GetDisplaySelection();
1717 return NS_OK;
1720 NS_IMETHODIMP
1721 PresShell::GetSelectionFromScript(RawSelectionType aRawSelectionType,
1722 Selection** aSelection) {
1723 if (!aSelection || !mSelection) return NS_ERROR_NULL_POINTER;
1725 RefPtr<nsFrameSelection> frameSelection = mSelection;
1726 RefPtr<Selection> selection =
1727 frameSelection->GetSelection(ToSelectionType(aRawSelectionType));
1729 if (!selection) {
1730 return NS_ERROR_INVALID_ARG;
1733 selection.forget(aSelection);
1734 return NS_OK;
1737 Selection* PresShell::GetSelection(RawSelectionType aRawSelectionType) {
1738 if (!mSelection) {
1739 return nullptr;
1742 RefPtr<nsFrameSelection> frameSelection = mSelection;
1743 return frameSelection->GetSelection(ToSelectionType(aRawSelectionType));
1746 Selection* PresShell::GetCurrentSelection(SelectionType aSelectionType) {
1747 if (!mSelection) {
1748 return nullptr;
1751 RefPtr<nsFrameSelection> frameSelection = mSelection;
1752 return frameSelection->GetSelection(aSelectionType);
1755 nsFrameSelection* PresShell::GetLastFocusedFrameSelection() {
1756 return mFocusedFrameSelection ? mFocusedFrameSelection : mSelection;
1759 NS_IMETHODIMP
1760 PresShell::ScrollSelectionIntoView(RawSelectionType aRawSelectionType,
1761 SelectionRegion aRegion, int16_t aFlags) {
1762 if (!mSelection) return NS_ERROR_NULL_POINTER;
1764 RefPtr<nsFrameSelection> frameSelection = mSelection;
1765 return frameSelection->ScrollSelectionIntoView(
1766 ToSelectionType(aRawSelectionType), aRegion, aFlags);
1769 NS_IMETHODIMP
1770 PresShell::RepaintSelection(RawSelectionType aRawSelectionType) {
1771 if (!mSelection) {
1772 return NS_ERROR_NULL_POINTER;
1775 if (MOZ_UNLIKELY(mIsDestroying)) {
1776 return NS_OK;
1779 RefPtr<nsFrameSelection> frameSelection = mSelection;
1780 return frameSelection->RepaintSelection(ToSelectionType(aRawSelectionType));
1783 // Make shell be a document observer
1784 void PresShell::BeginObservingDocument() {
1785 if (mDocument && !mIsDestroying) {
1786 mIsObservingDocument = true;
1787 if (mIsDocumentGone) {
1788 NS_WARNING(
1789 "Adding a presshell that was disconnected from the document "
1790 "as a document observer? Sounds wrong...");
1791 mIsDocumentGone = false;
1796 // Make shell stop being a document observer
1797 void PresShell::EndObservingDocument() {
1798 // XXXbz do we need to tell the frame constructor that the document
1799 // is gone, perhaps? Except for printing it's NOT gone, sometimes.
1800 mIsDocumentGone = true;
1801 mIsObservingDocument = false;
1804 #ifdef DEBUG_kipp
1805 char* nsPresShell_ReflowStackPointerTop;
1806 #endif
1808 void PresShell::InitPaintSuppressionTimer() {
1809 // Default to PAINTLOCK_EVENT_DELAY if we can't get the pref value.
1810 Document* doc = mDocument->GetDisplayDocument()
1811 ? mDocument->GetDisplayDocument()
1812 : mDocument.get();
1813 const bool inProcess = !doc->GetBrowsingContext() ||
1814 doc->GetBrowsingContext()->Top()->IsInProcess();
1815 int32_t delay = inProcess
1816 ? StaticPrefs::nglayout_initialpaint_delay()
1817 : StaticPrefs::nglayout_initialpaint_delay_in_oopif();
1818 mPaintSuppressionTimer->InitWithNamedFuncCallback(
1819 [](nsITimer* aTimer, void* aPresShell) {
1820 RefPtr<PresShell> self = static_cast<PresShell*>(aPresShell);
1821 self->UnsuppressPainting();
1823 this, delay, nsITimer::TYPE_ONE_SHOT,
1824 "PresShell::sPaintSuppressionCallback");
1827 nsresult PresShell::Initialize() {
1828 if (mIsDestroying) {
1829 return NS_OK;
1832 if (!mDocument) {
1833 // Nothing to do
1834 return NS_OK;
1837 MOZ_LOG(gLog, LogLevel::Debug, ("PresShell::Initialize this=%p", this));
1839 NS_ASSERTION(!mDidInitialize, "Why are we being called?");
1841 RefPtr<PresShell> kungFuDeathGrip(this);
1843 RecomputeFontSizeInflationEnabled();
1844 MOZ_DIAGNOSTIC_ASSERT(!mIsDestroying);
1846 // Ensure the pres context doesn't think it has changed, since we haven't even
1847 // started layout. This avoids spurious restyles / reflows afterwards.
1849 // Note that this is very intentionally before setting mDidInitialize so it
1850 // doesn't notify the document, or run media query change events.
1851 mPresContext->FlushPendingMediaFeatureValuesChanged();
1852 MOZ_DIAGNOSTIC_ASSERT(!mIsDestroying);
1854 mDidInitialize = true;
1856 #ifdef DEBUG
1857 if (VerifyReflowFlags::NoisyCommands & gVerifyReflowFlags) {
1858 if (mDocument) {
1859 nsIURI* uri = mDocument->GetDocumentURI();
1860 if (uri) {
1861 printf("*** PresShell::Initialize (this=%p, url='%s')\n", (void*)this,
1862 uri->GetSpecOrDefault().get());
1866 #endif
1868 // Get the root frame from the frame manager
1869 // XXXbz it would be nice to move this somewhere else... like frame manager
1870 // Init(), say. But we need to make sure our views are all set up by the
1871 // time we do this!
1872 nsIFrame* rootFrame = mFrameConstructor->GetRootFrame();
1873 NS_ASSERTION(!rootFrame, "How did that happen, exactly?");
1875 if (!rootFrame) {
1876 nsAutoScriptBlocker scriptBlocker;
1877 rootFrame = mFrameConstructor->ConstructRootFrame();
1878 mFrameConstructor->SetRootFrame(rootFrame);
1881 NS_ENSURE_STATE(!mHaveShutDown);
1883 if (!rootFrame) {
1884 return NS_ERROR_OUT_OF_MEMORY;
1887 if (Element* root = mDocument->GetRootElement()) {
1889 nsAutoCauseReflowNotifier reflowNotifier(this);
1890 // Have the style sheet processor construct frame for the root
1891 // content object down
1892 mFrameConstructor->ContentInserted(
1893 root, nsCSSFrameConstructor::InsertionKind::Sync);
1895 // Something in mFrameConstructor->ContentInserted may have caused
1896 // Destroy() to get called, bug 337586. Or, nsAutoCauseReflowNotifier
1897 // (which sets up a script blocker) going out of scope may have killed us
1898 // too
1899 NS_ENSURE_STATE(!mHaveShutDown);
1902 mDocument->TriggerAutoFocus();
1904 NS_ASSERTION(rootFrame, "How did that happen?");
1906 // Note: when the frame was created above it had the NS_FRAME_IS_DIRTY bit
1907 // set, but XBL processing could have caused a reflow which clears it.
1908 if (MOZ_LIKELY(rootFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY))) {
1909 // Unset the DIRTY bits so that FrameNeedsReflow() will work right.
1910 rootFrame->RemoveStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN);
1911 NS_ASSERTION(!mDirtyRoots.Contains(rootFrame),
1912 "Why is the root in mDirtyRoots already?");
1913 FrameNeedsReflow(rootFrame, IntrinsicDirty::Resize, NS_FRAME_IS_DIRTY);
1914 NS_ASSERTION(mDirtyRoots.Contains(rootFrame),
1915 "Should be in mDirtyRoots now");
1916 NS_ASSERTION(mObservingLayoutFlushes, "Why no reflow scheduled?");
1919 // Restore our root scroll position now if we're getting here after EndLoad
1920 // got called, since this is our one chance to do it. Note that we need not
1921 // have reflowed for this to work; when the scrollframe is finally reflowed
1922 // it'll pick up the position we store in it here.
1923 if (!mDocumentLoading) {
1924 RestoreRootScrollPosition();
1927 // For printing, we just immediately unsuppress.
1928 if (!mPresContext->IsPaginated()) {
1929 // Kick off a one-shot timer based off our pref value. When this timer
1930 // fires, if painting is still locked down, then we will go ahead and
1931 // trigger a full invalidate and allow painting to proceed normally.
1932 mPaintingSuppressed = true;
1933 // Don't suppress painting if the document isn't loading.
1934 Document::ReadyState readyState = mDocument->GetReadyStateEnum();
1935 if (readyState != Document::READYSTATE_COMPLETE) {
1936 mPaintSuppressionTimer = NS_NewTimer();
1938 if (!mPaintSuppressionTimer) {
1939 mPaintingSuppressed = false;
1940 } else {
1941 // Initialize the timer.
1942 mPaintSuppressionTimer->SetTarget(
1943 mDocument->EventTargetFor(TaskCategory::Other));
1944 InitPaintSuppressionTimer();
1948 // If we get here and painting is not suppressed, we still want to run the
1949 // unsuppression logic, so set mShouldUnsuppressPainting to true.
1950 if (!mPaintingSuppressed) {
1951 mShouldUnsuppressPainting = true;
1954 return NS_OK; // XXX this needs to be real. MMP
1957 nsresult PresShell::ResizeReflow(nscoord aWidth, nscoord aHeight,
1958 ResizeReflowOptions aOptions) {
1959 if (mZoomConstraintsClient) {
1960 // If we have a ZoomConstraintsClient and the available screen area
1961 // changed, then we might need to disable double-tap-to-zoom, so notify
1962 // the ZCC to update itself.
1963 mZoomConstraintsClient->ScreenSizeChanged();
1965 if (UsesMobileViewportSizing()) {
1966 // If we are using mobile viewport sizing, request a reflow from the MVM.
1967 // It can recompute the final CSS viewport and trigger a call to
1968 // ResizeReflowIgnoreOverride if it changed. We don't force adjusting
1969 // of resolution, because that is only necessary when we are destroying
1970 // the MVM.
1971 MOZ_ASSERT(mMobileViewportManager);
1972 mMobileViewportManager->RequestReflow(false);
1973 return NS_OK;
1976 return ResizeReflowIgnoreOverride(aWidth, aHeight, aOptions);
1979 void PresShell::SimpleResizeReflow(nscoord aWidth, nscoord aHeight,
1980 ResizeReflowOptions aOptions) {
1981 MOZ_ASSERT(aWidth != NS_UNCONSTRAINEDSIZE);
1982 MOZ_ASSERT(aHeight != NS_UNCONSTRAINEDSIZE);
1983 nsSize oldSize = mPresContext->GetVisibleArea().Size();
1984 mPresContext->SetVisibleArea(nsRect(0, 0, aWidth, aHeight));
1985 nsIFrame* rootFrame = GetRootFrame();
1986 if (!rootFrame) {
1987 return;
1989 WritingMode wm = rootFrame->GetWritingMode();
1990 bool isBSizeChanging =
1991 wm.IsVertical() ? oldSize.width != aWidth : oldSize.height != aHeight;
1992 if (isBSizeChanging) {
1993 nsLayoutUtils::MarkIntrinsicISizesDirtyIfDependentOnBSize(rootFrame);
1995 FrameNeedsReflow(rootFrame, IntrinsicDirty::Resize,
1996 NS_FRAME_HAS_DIRTY_CHILDREN);
1998 // For compat with the old code path which always reflowed as long as there
1999 // was a root frame.
2000 bool suppressReflow = (aOptions & ResizeReflowOptions::SuppressReflow) ||
2001 mPresContext->SuppressingResizeReflow();
2002 if (!suppressReflow) {
2003 mDocument->FlushPendingNotifications(FlushType::InterruptibleLayout);
2007 void PresShell::AddResizeEventFlushObserverIfNeeded() {
2008 if (!mIsDestroying && !mResizeEventPending &&
2009 MOZ_LIKELY(!mDocument->GetBFCacheEntry())) {
2010 mResizeEventPending = true;
2011 mPresContext->RefreshDriver()->AddResizeEventFlushObserver(this);
2015 nsresult PresShell::ResizeReflowIgnoreOverride(nscoord aWidth, nscoord aHeight,
2016 ResizeReflowOptions aOptions) {
2017 MOZ_ASSERT(!mIsReflowing, "Shouldn't be in reflow here!");
2019 // Historically we never fired resize events if there was no root frame by the
2020 // time this function got called.
2021 const bool initialized = mDidInitialize;
2022 RefPtr<PresShell> kungFuDeathGrip(this);
2024 auto postResizeEventIfNeeded = [this, initialized]() {
2025 if (initialized) {
2026 AddResizeEventFlushObserverIfNeeded();
2030 if (!(aOptions & ResizeReflowOptions::BSizeLimit)) {
2031 nsSize oldSize = mPresContext->GetVisibleArea().Size();
2032 if (oldSize == nsSize(aWidth, aHeight)) {
2033 return NS_OK;
2036 SimpleResizeReflow(aWidth, aHeight, aOptions);
2037 postResizeEventIfNeeded();
2038 return NS_OK;
2041 MOZ_ASSERT(!mPresContext->SuppressingResizeReflow() &&
2042 !(aOptions & ResizeReflowOptions::SuppressReflow),
2043 "Can't suppress resize reflow and shrink-wrap at the same time");
2045 // Make sure that style is flushed before setting the pres context
2046 // VisibleArea.
2048 // Otherwise we may end up with bogus viewport units resolved against the
2049 // unconstrained bsize, or restyling the whole document resolving viewport
2050 // units against targetWidth, which may end up doing wasteful work.
2051 mDocument->FlushPendingNotifications(FlushType::Frames);
2053 nsIFrame* rootFrame = GetRootFrame();
2054 if (mIsDestroying || !rootFrame) {
2055 // If we don't have a root frame yet, that means we haven't had our initial
2056 // reflow... If that's the case, and aWidth or aHeight is unconstrained,
2057 // ignore them altogether.
2058 if (aHeight == NS_UNCONSTRAINEDSIZE || aWidth == NS_UNCONSTRAINEDSIZE) {
2059 // We can't do the work needed for SizeToContent without a root
2060 // frame, and we want to return before setting the visible area.
2061 return NS_ERROR_NOT_AVAILABLE;
2064 mPresContext->SetVisibleArea(nsRect(0, 0, aWidth, aHeight));
2065 // There isn't anything useful we can do if the initial reflow hasn't
2066 // happened.
2067 return NS_OK;
2070 WritingMode wm = rootFrame->GetWritingMode();
2071 MOZ_ASSERT((wm.IsVertical() ? aHeight : aWidth) != NS_UNCONSTRAINEDSIZE,
2072 "unconstrained isize not allowed");
2074 nscoord targetWidth = aWidth;
2075 nscoord targetHeight = aHeight;
2076 if (wm.IsVertical()) {
2077 targetWidth = NS_UNCONSTRAINEDSIZE;
2078 } else {
2079 targetHeight = NS_UNCONSTRAINEDSIZE;
2082 mPresContext->SetVisibleArea(nsRect(0, 0, targetWidth, targetHeight));
2083 // XXX Do a full invalidate at the beginning so that invalidates along
2084 // the way don't have region accumulation issues?
2086 // For height:auto BSizes (i.e. layout-controlled), descendant
2087 // intrinsic sizes can't depend on them. So the only other case is
2088 // viewport-controlled BSizes which we handle here.
2089 nsLayoutUtils::MarkIntrinsicISizesDirtyIfDependentOnBSize(rootFrame);
2092 nsAutoCauseReflowNotifier crNotifier(this);
2093 WillDoReflow();
2095 // Kick off a top-down reflow
2096 AUTO_LAYOUT_PHASE_ENTRY_POINT(GetPresContext(), Reflow);
2097 nsViewManager::AutoDisableRefresh refreshBlocker(mViewManager);
2099 mDirtyRoots.Remove(rootFrame);
2100 DoReflow(rootFrame, true, nullptr);
2102 const bool reflowAgain =
2103 wm.IsVertical() ? mPresContext->GetVisibleArea().width > aWidth
2104 : mPresContext->GetVisibleArea().height > aHeight;
2106 if (reflowAgain) {
2107 mPresContext->SetVisibleArea(nsRect(0, 0, aWidth, aHeight));
2108 DoReflow(rootFrame, true, nullptr);
2112 // Now, we may have been destroyed by the destructor of
2113 // `nsAutoCauseReflowNotifier`.
2115 DidDoReflow(true);
2117 // the reflow above should've set our bsize if it was NS_UNCONSTRAINEDSIZE,
2118 // and the isize shouldn't be NS_UNCONSTRAINEDSIZE anyway.
2119 MOZ_DIAGNOSTIC_ASSERT(
2120 mPresContext->GetVisibleArea().width != NS_UNCONSTRAINEDSIZE,
2121 "width should not be NS_UNCONSTRAINEDSIZE after reflow");
2122 MOZ_DIAGNOSTIC_ASSERT(
2123 mPresContext->GetVisibleArea().height != NS_UNCONSTRAINEDSIZE,
2124 "height should not be NS_UNCONSTRAINEDSIZE after reflow");
2126 postResizeEventIfNeeded();
2127 return NS_OK; // XXX this needs to be real. MMP
2130 void PresShell::FireResizeEvent() {
2131 if (mIsDocumentGone) {
2132 return;
2135 // If event handling is suppressed, repost the resize event to the refresh
2136 // driver. The event is marked as delayed so that the refresh driver does not
2137 // continue ticking.
2138 if (mDocument->EventHandlingSuppressed()) {
2139 if (MOZ_LIKELY(!mDocument->GetBFCacheEntry())) {
2140 mDocument->SetHasDelayedRefreshEvent();
2141 mPresContext->RefreshDriver()->AddResizeEventFlushObserver(
2142 this, /* aDelayed = */ true);
2144 return;
2147 mResizeEventPending = false;
2149 // Send resize event from here.
2150 WidgetEvent event(true, mozilla::eResize);
2151 nsEventStatus status = nsEventStatus_eIgnore;
2153 if (nsPIDOMWindowOuter* window = mDocument->GetWindow()) {
2154 EventDispatcher::Dispatch(window, mPresContext, &event, nullptr, &status);
2158 static nsIContent* GetNativeAnonymousSubtreeRoot(nsIContent* aContent) {
2159 if (!aContent) {
2160 return nullptr;
2162 return aContent->GetClosestNativeAnonymousSubtreeRoot();
2165 void PresShell::NativeAnonymousContentRemoved(nsIContent* aAnonContent) {
2166 MOZ_ASSERT(aAnonContent->IsRootOfNativeAnonymousSubtree());
2167 if (nsIContent* root = GetNativeAnonymousSubtreeRoot(mCurrentEventContent)) {
2168 if (aAnonContent == root) {
2169 mCurrentEventContent = aAnonContent->GetFlattenedTreeParent();
2170 mCurrentEventFrame = nullptr;
2174 for (unsigned int i = 0; i < mCurrentEventContentStack.Length(); i++) {
2175 nsIContent* anon =
2176 GetNativeAnonymousSubtreeRoot(mCurrentEventContentStack.ElementAt(i));
2177 if (aAnonContent == anon) {
2178 mCurrentEventContentStack.ReplaceObjectAt(
2179 aAnonContent->GetFlattenedTreeParent(), i);
2180 mCurrentEventFrameStack[i] = nullptr;
2185 void PresShell::SetIgnoreFrameDestruction(bool aIgnore) {
2186 if (mDocument) {
2187 // We need to tell the ImageLoader to drop all its references to frames
2188 // because they're about to go away and it won't get notifications of that.
2189 mDocument->StyleImageLoader()->ClearFrames(mPresContext);
2191 mIgnoreFrameDestruction = aIgnore;
2194 void PresShell::NotifyDestroyingFrame(nsIFrame* aFrame) {
2195 // We must remove these from FrameLayerBuilder::DisplayItemData::mFrameList
2196 // here, otherwise the DisplayItemData destructor will use the destroyed frame
2197 // when it tries to remove it from the (array) value of this property.
2198 aFrame->RemoveDisplayItemDataForDeletion();
2200 if (!mIgnoreFrameDestruction) {
2201 if (aFrame->HasImageRequest()) {
2202 mDocument->StyleImageLoader()->DropRequestsForFrame(aFrame);
2205 mFrameConstructor->NotifyDestroyingFrame(aFrame);
2207 mDirtyRoots.Remove(aFrame);
2209 // Remove frame properties
2210 aFrame->RemoveAllProperties();
2212 if (aFrame == mCurrentEventFrame) {
2213 mCurrentEventContent = aFrame->GetContent();
2214 mCurrentEventFrame = nullptr;
2217 #ifdef DEBUG
2218 if (aFrame == mDrawEventTargetFrame) {
2219 mDrawEventTargetFrame = nullptr;
2221 #endif
2223 for (unsigned int i = 0; i < mCurrentEventFrameStack.Length(); i++) {
2224 if (aFrame == mCurrentEventFrameStack.ElementAt(i)) {
2225 // One of our stack frames was deleted. Get its content so that when we
2226 // pop it we can still get its new frame from its content
2227 nsIContent* currentEventContent = aFrame->GetContent();
2228 mCurrentEventContentStack.ReplaceObjectAt(currentEventContent, i);
2229 mCurrentEventFrameStack[i] = nullptr;
2233 mFramesToDirty.Remove(aFrame);
2235 nsIScrollableFrame* scrollableFrame = do_QueryFrame(aFrame);
2236 if (scrollableFrame) {
2237 mPendingScrollAnchorSelection.Remove(scrollableFrame);
2238 mPendingScrollAnchorAdjustment.Remove(scrollableFrame);
2243 already_AddRefed<nsCaret> PresShell::GetCaret() const {
2244 RefPtr<nsCaret> caret = mCaret;
2245 return caret.forget();
2248 already_AddRefed<AccessibleCaretEventHub>
2249 PresShell::GetAccessibleCaretEventHub() const {
2250 RefPtr<AccessibleCaretEventHub> eventHub = mAccessibleCaretEventHub;
2251 return eventHub.forget();
2254 void PresShell::SetCaret(nsCaret* aNewCaret) { mCaret = aNewCaret; }
2256 void PresShell::RestoreCaret() { mCaret = mOriginalCaret; }
2258 NS_IMETHODIMP PresShell::SetCaretEnabled(bool aInEnable) {
2259 bool oldEnabled = mCaretEnabled;
2261 mCaretEnabled = aInEnable;
2263 if (mCaretEnabled != oldEnabled) {
2264 MOZ_ASSERT(mCaret);
2265 if (mCaret) {
2266 mCaret->SetVisible(mCaretEnabled);
2270 return NS_OK;
2273 NS_IMETHODIMP PresShell::SetCaretReadOnly(bool aReadOnly) {
2274 if (mCaret) mCaret->SetCaretReadOnly(aReadOnly);
2275 return NS_OK;
2278 NS_IMETHODIMP PresShell::GetCaretEnabled(bool* aOutEnabled) {
2279 NS_ENSURE_ARG_POINTER(aOutEnabled);
2280 *aOutEnabled = mCaretEnabled;
2281 return NS_OK;
2284 NS_IMETHODIMP PresShell::SetCaretVisibilityDuringSelection(bool aVisibility) {
2285 if (mCaret) mCaret->SetVisibilityDuringSelection(aVisibility);
2286 return NS_OK;
2289 NS_IMETHODIMP PresShell::GetCaretVisible(bool* aOutIsVisible) {
2290 *aOutIsVisible = false;
2291 if (mCaret) {
2292 *aOutIsVisible = mCaret->IsVisible();
2294 return NS_OK;
2297 NS_IMETHODIMP PresShell::SetSelectionFlags(int16_t aFlags) {
2298 mSelectionFlags = aFlags;
2299 return NS_OK;
2302 NS_IMETHODIMP PresShell::GetSelectionFlags(int16_t* aFlags) {
2303 if (!aFlags) {
2304 return NS_ERROR_INVALID_ARG;
2307 *aFlags = mSelectionFlags;
2308 return NS_OK;
2311 // implementation of nsISelectionController
2313 NS_IMETHODIMP
2314 PresShell::PhysicalMove(int16_t aDirection, int16_t aAmount, bool aExtend) {
2315 RefPtr<nsFrameSelection> frameSelection = mSelection;
2316 return frameSelection->PhysicalMove(aDirection, aAmount, aExtend);
2319 NS_IMETHODIMP
2320 PresShell::CharacterMove(bool aForward, bool aExtend) {
2321 RefPtr<nsFrameSelection> frameSelection = mSelection;
2322 return frameSelection->CharacterMove(aForward, aExtend);
2325 NS_IMETHODIMP
2326 PresShell::WordMove(bool aForward, bool aExtend) {
2327 RefPtr<nsFrameSelection> frameSelection = mSelection;
2328 nsresult result = frameSelection->WordMove(aForward, aExtend);
2329 // if we can't go down/up any more we must then move caret completely to
2330 // end/beginning respectively.
2331 if (NS_FAILED(result)) result = CompleteMove(aForward, aExtend);
2332 return result;
2335 NS_IMETHODIMP
2336 PresShell::LineMove(bool aForward, bool aExtend) {
2337 RefPtr<nsFrameSelection> frameSelection = mSelection;
2338 nsresult result = frameSelection->LineMove(aForward, aExtend);
2339 // if we can't go down/up any more we must then move caret completely to
2340 // end/beginning respectively.
2341 if (NS_FAILED(result)) result = CompleteMove(aForward, aExtend);
2342 return result;
2345 NS_IMETHODIMP
2346 PresShell::IntraLineMove(bool aForward, bool aExtend) {
2347 RefPtr<nsFrameSelection> frameSelection = mSelection;
2348 return frameSelection->IntraLineMove(aForward, aExtend);
2351 NS_IMETHODIMP
2352 PresShell::PageMove(bool aForward, bool aExtend) {
2353 nsIFrame* frame = nullptr;
2354 if (!aExtend) {
2355 frame = do_QueryFrame(GetScrollableFrameToScroll(VerticalScrollDirection));
2356 // If there is no scrollable frame, get the frame to move caret instead.
2358 if (!frame || frame->PresContext() != mPresContext) {
2359 frame = mSelection->GetFrameToPageSelect();
2360 if (!frame) {
2361 return NS_OK;
2364 // We may scroll parent scrollable element of current selection limiter.
2365 // In such case, we don't want to scroll selection into view unless
2366 // selection is changed.
2367 RefPtr<nsFrameSelection> frameSelection = mSelection;
2368 return frameSelection->PageMove(
2369 aForward, aExtend, frame, nsFrameSelection::SelectionIntoView::IfChanged);
2372 NS_IMETHODIMP
2373 PresShell::ScrollPage(bool aForward) {
2374 nsIScrollableFrame* scrollFrame =
2375 GetScrollableFrameToScroll(VerticalScrollDirection);
2376 if (scrollFrame) {
2377 scrollFrame->ScrollBy(
2378 nsIntPoint(0, aForward ? 1 : -1), ScrollUnit::PAGES, ScrollMode::Smooth,
2379 nullptr, mozilla::ScrollOrigin::NotSpecified,
2380 nsIScrollableFrame::NOT_MOMENTUM, nsIScrollableFrame::ENABLE_SNAP);
2382 return NS_OK;
2385 NS_IMETHODIMP
2386 PresShell::ScrollLine(bool aForward) {
2387 nsIScrollableFrame* scrollFrame =
2388 GetScrollableFrameToScroll(VerticalScrollDirection);
2389 if (scrollFrame) {
2390 int32_t lineCount =
2391 Preferences::GetInt("toolkit.scrollbox.verticalScrollDistance",
2392 NS_DEFAULT_VERTICAL_SCROLL_DISTANCE);
2393 scrollFrame->ScrollBy(
2394 nsIntPoint(0, aForward ? lineCount : -lineCount), ScrollUnit::LINES,
2395 ScrollMode::Smooth, nullptr, mozilla::ScrollOrigin::NotSpecified,
2396 nsIScrollableFrame::NOT_MOMENTUM, nsIScrollableFrame::ENABLE_SNAP);
2398 return NS_OK;
2401 NS_IMETHODIMP
2402 PresShell::ScrollCharacter(bool aRight) {
2403 nsIScrollableFrame* scrollFrame =
2404 GetScrollableFrameToScroll(HorizontalScrollDirection);
2405 if (scrollFrame) {
2406 int32_t h =
2407 Preferences::GetInt("toolkit.scrollbox.horizontalScrollDistance",
2408 NS_DEFAULT_HORIZONTAL_SCROLL_DISTANCE);
2409 scrollFrame->ScrollBy(
2410 nsIntPoint(aRight ? h : -h, 0), ScrollUnit::LINES, ScrollMode::Smooth,
2411 nullptr, mozilla::ScrollOrigin::NotSpecified,
2412 nsIScrollableFrame::NOT_MOMENTUM, nsIScrollableFrame::ENABLE_SNAP);
2414 return NS_OK;
2417 NS_IMETHODIMP
2418 PresShell::CompleteScroll(bool aForward) {
2419 nsIScrollableFrame* scrollFrame =
2420 GetScrollableFrameToScroll(VerticalScrollDirection);
2421 if (scrollFrame) {
2422 scrollFrame->ScrollBy(
2423 nsIntPoint(0, aForward ? 1 : -1), ScrollUnit::WHOLE, ScrollMode::Smooth,
2424 nullptr, mozilla::ScrollOrigin::NotSpecified,
2425 nsIScrollableFrame::NOT_MOMENTUM, nsIScrollableFrame::ENABLE_SNAP);
2427 return NS_OK;
2430 NS_IMETHODIMP
2431 PresShell::CompleteMove(bool aForward, bool aExtend) {
2432 // Beware! This may flush notifications via synchronous
2433 // ScrollSelectionIntoView.
2434 RefPtr<nsFrameSelection> frameSelection = mSelection;
2435 nsIContent* limiter = frameSelection->GetAncestorLimiter();
2436 nsIFrame* frame = limiter ? limiter->GetPrimaryFrame()
2437 : FrameConstructor()->GetRootElementFrame();
2438 if (!frame) return NS_ERROR_FAILURE;
2439 nsIFrame::CaretPosition pos = frame->GetExtremeCaretPosition(!aForward);
2441 const nsFrameSelection::FocusMode focusMode =
2442 aExtend ? nsFrameSelection::FocusMode::kExtendSelection
2443 : nsFrameSelection::FocusMode::kCollapseToNewPoint;
2444 frameSelection->HandleClick(
2445 MOZ_KnownLive(pos.mResultContent) /* bug 1636889 */, pos.mContentOffset,
2446 pos.mContentOffset, focusMode,
2447 aForward ? CARET_ASSOCIATE_AFTER : CARET_ASSOCIATE_BEFORE);
2448 if (limiter) {
2449 // HandleClick resets ancestorLimiter, so set it again.
2450 frameSelection->SetAncestorLimiter(limiter);
2453 // After ScrollSelectionIntoView(), the pending notifications might be
2454 // flushed and PresShell/PresContext/Frames may be dead. See bug 418470.
2455 return ScrollSelectionIntoView(
2456 nsISelectionController::SELECTION_NORMAL,
2457 nsISelectionController::SELECTION_FOCUS_REGION,
2458 nsISelectionController::SCROLL_SYNCHRONOUS |
2459 nsISelectionController::SCROLL_FOR_CARET_MOVE);
2462 static void DoCheckVisibility(nsPresContext* aPresContext, nsIContent* aNode,
2463 int16_t aStartOffset, int16_t aEndOffset,
2464 bool* aRetval) {
2465 nsIFrame* frame = aNode->GetPrimaryFrame();
2466 if (!frame) {
2467 // No frame to look at so it must not be visible.
2468 return;
2471 // Start process now to go through all frames to find startOffset. Then check
2472 // chars after that to see if anything until EndOffset is visible.
2473 bool finished = false;
2474 frame->CheckVisibility(aPresContext, aStartOffset, aEndOffset, true,
2475 &finished, aRetval);
2476 // Don't worry about other return value.
2479 NS_IMETHODIMP
2480 PresShell::CheckVisibility(nsINode* node, int16_t startOffset,
2481 int16_t EndOffset, bool* _retval) {
2482 if (!node || startOffset > EndOffset || !_retval || startOffset < 0 ||
2483 EndOffset < 0)
2484 return NS_ERROR_INVALID_ARG;
2485 *_retval = false; // initialize return parameter
2486 nsCOMPtr<nsIContent> content(do_QueryInterface(node));
2487 if (!content) return NS_ERROR_FAILURE;
2489 DoCheckVisibility(mPresContext, content, startOffset, EndOffset, _retval);
2490 return NS_OK;
2493 nsresult PresShell::CheckVisibilityContent(nsIContent* aNode,
2494 int16_t aStartOffset,
2495 int16_t aEndOffset, bool* aRetval) {
2496 if (!aNode || aStartOffset > aEndOffset || !aRetval || aStartOffset < 0 ||
2497 aEndOffset < 0) {
2498 return NS_ERROR_INVALID_ARG;
2501 *aRetval = false;
2502 DoCheckVisibility(mPresContext, aNode, aStartOffset, aEndOffset, aRetval);
2503 return NS_OK;
2506 // end implementations nsISelectionController
2508 nsIFrame* PresShell::GetRootScrollFrame() const {
2509 nsIFrame* rootFrame = mFrameConstructor->GetRootFrame();
2510 // Ensure root frame is a viewport frame
2511 if (!rootFrame || !rootFrame->IsViewportFrame()) return nullptr;
2512 nsIFrame* theFrame = rootFrame->PrincipalChildList().FirstChild();
2513 if (!theFrame || !theFrame->IsScrollFrame()) return nullptr;
2514 return theFrame;
2517 nsIScrollableFrame* PresShell::GetRootScrollFrameAsScrollable() const {
2518 nsIFrame* frame = GetRootScrollFrame();
2519 if (!frame) return nullptr;
2520 nsIScrollableFrame* scrollableFrame = do_QueryFrame(frame);
2521 NS_ASSERTION(scrollableFrame,
2522 "All scroll frames must implement nsIScrollableFrame");
2523 return scrollableFrame;
2526 nsPageSequenceFrame* PresShell::GetPageSequenceFrame() const {
2527 return mFrameConstructor->GetPageSequenceFrame();
2530 nsCanvasFrame* PresShell::GetCanvasFrame() const {
2531 nsIFrame* frame = mFrameConstructor->GetDocElementContainingBlock();
2532 return do_QueryFrame(frame);
2535 void PresShell::RestoreRootScrollPosition() {
2536 nsIScrollableFrame* scrollableFrame = GetRootScrollFrameAsScrollable();
2537 if (scrollableFrame) {
2538 scrollableFrame->ScrollToRestoredPosition();
2542 void PresShell::MaybeReleaseCapturingContent() {
2543 RefPtr<nsFrameSelection> frameSelection = FrameSelection();
2544 if (frameSelection) {
2545 frameSelection->SetDragState(false);
2547 if (sCapturingContentInfo.mContent &&
2548 sCapturingContentInfo.mContent->OwnerDoc() == mDocument) {
2549 PresShell::ReleaseCapturingContent();
2553 void PresShell::BeginLoad(Document* aDocument) {
2554 mDocumentLoading = true;
2556 gfxTextPerfMetrics* tp = nullptr;
2557 if (mPresContext) {
2558 tp = mPresContext->GetTextPerfMetrics();
2561 bool shouldLog = MOZ_LOG_TEST(gLog, LogLevel::Debug);
2562 if (shouldLog || tp) {
2563 mLoadBegin = TimeStamp::Now();
2566 if (shouldLog) {
2567 nsIURI* uri = mDocument->GetDocumentURI();
2568 MOZ_LOG(gLog, LogLevel::Debug,
2569 ("(presshell) %p load begin [%s]\n", this,
2570 uri ? uri->GetSpecOrDefault().get() : ""));
2574 void PresShell::EndLoad(Document* aDocument) {
2575 MOZ_ASSERT(aDocument == mDocument, "Wrong document");
2577 RestoreRootScrollPosition();
2579 mDocumentLoading = false;
2582 bool PresShell::IsLayoutFlushObserver() {
2583 return GetPresContext()->RefreshDriver()->IsLayoutFlushObserver(this);
2586 void PresShell::LoadComplete() {
2587 gfxTextPerfMetrics* tp = nullptr;
2588 if (mPresContext) {
2589 tp = mPresContext->GetTextPerfMetrics();
2592 // log load
2593 bool shouldLog = MOZ_LOG_TEST(gLog, LogLevel::Debug);
2594 if (shouldLog || tp) {
2595 TimeDuration loadTime = TimeStamp::Now() - mLoadBegin;
2596 nsIURI* uri = mDocument->GetDocumentURI();
2597 nsAutoCString spec;
2598 if (uri) {
2599 spec = uri->GetSpecOrDefault();
2601 if (shouldLog) {
2602 MOZ_LOG(gLog, LogLevel::Debug,
2603 ("(presshell) %p load done time-ms: %9.2f [%s]\n", this,
2604 loadTime.ToMilliseconds(), spec.get()));
2606 if (tp) {
2607 tp->Accumulate();
2608 if (tp->cumulative.numChars > 0) {
2609 LogTextPerfStats(tp, this, tp->cumulative, loadTime.ToMilliseconds(),
2610 eLog_loaddone, spec.get());
2616 #ifdef DEBUG
2617 void PresShell::VerifyHasDirtyRootAncestor(nsIFrame* aFrame) {
2618 // XXXbz due to bug 372769, can't actually assert anything here...
2619 return;
2621 // XXXbz shouldn't need this part; remove it once FrameNeedsReflow
2622 // handles the root frame correctly.
2623 if (!aFrame->GetParent()) {
2624 return;
2627 // Make sure that there is a reflow root ancestor of |aFrame| that's
2628 // in mDirtyRoots already.
2629 while (aFrame && aFrame->HasAnyStateBits(NS_FRAME_HAS_DIRTY_CHILDREN)) {
2630 if ((aFrame->HasAnyStateBits(NS_FRAME_REFLOW_ROOT |
2631 NS_FRAME_DYNAMIC_REFLOW_ROOT) ||
2632 !aFrame->GetParent()) &&
2633 mDirtyRoots.Contains(aFrame)) {
2634 return;
2637 aFrame = aFrame->GetParent();
2640 MOZ_ASSERT_UNREACHABLE(
2641 "Frame has dirty bits set but isn't scheduled to be "
2642 "reflowed?");
2644 #endif
2646 void PresShell::PostPendingScrollAnchorSelection(
2647 mozilla::layout::ScrollAnchorContainer* aContainer) {
2648 mPendingScrollAnchorSelection.Insert(aContainer->ScrollableFrame());
2651 void PresShell::FlushPendingScrollAnchorSelections() {
2652 for (nsIScrollableFrame* scroll : mPendingScrollAnchorSelection) {
2653 scroll->Anchor()->SelectAnchor();
2655 mPendingScrollAnchorSelection.Clear();
2658 void PresShell::PostPendingScrollAnchorAdjustment(
2659 ScrollAnchorContainer* aContainer) {
2660 mPendingScrollAnchorAdjustment.Insert(aContainer->ScrollableFrame());
2663 void PresShell::FlushPendingScrollAnchorAdjustments() {
2664 for (nsIScrollableFrame* scroll : mPendingScrollAnchorAdjustment) {
2665 scroll->Anchor()->ApplyAdjustments();
2667 mPendingScrollAnchorAdjustment.Clear();
2670 void PresShell::FrameNeedsReflow(nsIFrame* aFrame,
2671 IntrinsicDirty aIntrinsicDirty,
2672 nsFrameState aBitToAdd,
2673 ReflowRootHandling aRootHandling) {
2674 MOZ_ASSERT(aBitToAdd == NS_FRAME_IS_DIRTY ||
2675 aBitToAdd == NS_FRAME_HAS_DIRTY_CHILDREN || !aBitToAdd,
2676 "Unexpected bits being added");
2678 // FIXME bug 478135
2679 NS_ASSERTION(!(aIntrinsicDirty == IntrinsicDirty::StyleChange &&
2680 aBitToAdd == NS_FRAME_HAS_DIRTY_CHILDREN),
2681 "bits don't correspond to style change reason");
2683 // FIXME bug 457400
2684 NS_ASSERTION(!mIsReflowing, "can't mark frame dirty during reflow");
2686 // If we've not yet done the initial reflow, then don't bother
2687 // enqueuing a reflow command yet.
2688 if (!mDidInitialize) return;
2690 // If we're already destroying, don't bother with this either.
2691 if (mIsDestroying) return;
2693 #ifdef DEBUG
2694 // printf("gShellCounter: %d\n", gShellCounter++);
2695 if (mInVerifyReflow) return;
2697 if (VerifyReflowFlags::NoisyCommands & gVerifyReflowFlags) {
2698 printf("\nPresShell@%p: frame %p needs reflow\n", (void*)this,
2699 (void*)aFrame);
2700 if (VerifyReflowFlags::ReallyNoisyCommands & gVerifyReflowFlags) {
2701 printf("Current content model:\n");
2702 Element* rootElement = mDocument->GetRootElement();
2703 if (rootElement) {
2704 rootElement->List(stdout, 0);
2708 #endif
2710 AutoTArray<nsIFrame*, 4> subtrees;
2711 subtrees.AppendElement(aFrame);
2713 do {
2714 nsIFrame* subtreeRoot = subtrees.PopLastElement();
2716 // Grab |wasDirty| now so we can go ahead and update the bits on
2717 // subtreeRoot.
2718 bool wasDirty = subtreeRoot->IsSubtreeDirty();
2719 subtreeRoot->AddStateBits(aBitToAdd);
2721 // Determine whether we need to keep looking for the next ancestor
2722 // reflow root if subtreeRoot itself is a reflow root.
2723 bool targetNeedsReflowFromParent;
2724 switch (aRootHandling) {
2725 case ReflowRootHandling::PositionOrSizeChange:
2726 targetNeedsReflowFromParent = true;
2727 break;
2728 case ReflowRootHandling::NoPositionOrSizeChange:
2729 targetNeedsReflowFromParent = false;
2730 break;
2731 case ReflowRootHandling::InferFromBitToAdd:
2732 targetNeedsReflowFromParent = (aBitToAdd == NS_FRAME_IS_DIRTY);
2733 break;
2736 #define FRAME_IS_REFLOW_ROOT(_f) \
2737 ((_f)->HasAnyStateBits(NS_FRAME_REFLOW_ROOT | \
2738 NS_FRAME_DYNAMIC_REFLOW_ROOT) && \
2739 ((_f) != subtreeRoot || !targetNeedsReflowFromParent))
2741 // Mark the intrinsic widths as dirty on the frame, all of its ancestors,
2742 // and all of its descendants, if needed:
2744 if (aIntrinsicDirty != IntrinsicDirty::Resize) {
2745 // Mark argument and all ancestors dirty. (Unless we hit a reflow
2746 // root that should contain the reflow. That root could be
2747 // subtreeRoot itself if it's not dirty, or it could be some
2748 // ancestor of subtreeRoot.)
2749 for (nsIFrame* a = subtreeRoot; a && !FRAME_IS_REFLOW_ROOT(a);
2750 a = a->GetParent()) {
2751 a->MarkIntrinsicISizesDirty();
2752 if (a->IsAbsolutelyPositioned()) {
2753 // If we get here, 'a' is abspos, so its subtree's intrinsic sizing
2754 // has no effect on its ancestors' intrinsic sizing. So, don't loop
2755 // upwards any further.
2756 break;
2761 const bool styleChange = (aIntrinsicDirty == IntrinsicDirty::StyleChange);
2762 const bool dirty = (aBitToAdd == NS_FRAME_IS_DIRTY);
2763 if (styleChange || dirty) {
2764 // Mark all descendants dirty (using an nsTArray stack rather than
2765 // recursion).
2766 // Note that ReflowInput::InitResizeFlags has some similar
2767 // code; see comments there for how and why it differs.
2768 AutoTArray<nsIFrame*, 32> stack;
2769 stack.AppendElement(subtreeRoot);
2771 do {
2772 nsIFrame* f = stack.PopLastElement();
2774 if (styleChange && f->IsPlaceholderFrame()) {
2775 // Call `GetOutOfFlowFrame` directly because we can get here from
2776 // frame destruction and the placeholder might be already torn down.
2777 if (nsIFrame* oof =
2778 static_cast<nsPlaceholderFrame*>(f)->GetOutOfFlowFrame()) {
2779 if (!nsLayoutUtils::IsProperAncestorFrame(subtreeRoot, oof)) {
2780 // We have another distinct subtree we need to mark.
2781 subtrees.AppendElement(oof);
2786 for (const auto& childList : f->ChildLists()) {
2787 for (nsIFrame* kid : childList.mList) {
2788 if (styleChange) {
2789 kid->MarkIntrinsicISizesDirty();
2791 if (dirty) {
2792 kid->AddStateBits(NS_FRAME_IS_DIRTY);
2794 stack.AppendElement(kid);
2797 } while (stack.Length() != 0);
2800 // Skip setting dirty bits up the tree if we weren't given a bit to add.
2801 if (!aBitToAdd) {
2802 continue;
2805 // Set NS_FRAME_HAS_DIRTY_CHILDREN bits (via nsIFrame::ChildIsDirty)
2806 // up the tree until we reach either a frame that's already dirty or
2807 // a reflow root.
2808 nsIFrame* f = subtreeRoot;
2809 for (;;) {
2810 if (FRAME_IS_REFLOW_ROOT(f) || !f->GetParent()) {
2811 // we've hit a reflow root or the root frame
2812 if (!wasDirty) {
2813 mDirtyRoots.Add(f);
2814 SetNeedLayoutFlush();
2816 #ifdef DEBUG
2817 else {
2818 VerifyHasDirtyRootAncestor(f);
2820 #endif
2822 break;
2825 nsIFrame* child = f;
2826 f = f->GetParent();
2827 wasDirty = f->IsSubtreeDirty();
2828 f->ChildIsDirty(child);
2829 NS_ASSERTION(f->HasAnyStateBits(NS_FRAME_HAS_DIRTY_CHILDREN),
2830 "ChildIsDirty didn't do its job");
2831 if (wasDirty) {
2832 // This frame was already marked dirty.
2833 #ifdef DEBUG
2834 VerifyHasDirtyRootAncestor(f);
2835 #endif
2836 break;
2839 } while (subtrees.Length() != 0);
2841 MaybeScheduleReflow();
2844 void PresShell::FrameNeedsToContinueReflow(nsIFrame* aFrame) {
2845 NS_ASSERTION(mIsReflowing, "Must be in reflow when marking path dirty.");
2846 MOZ_ASSERT(mCurrentReflowRoot, "Must have a current reflow root here");
2847 NS_ASSERTION(
2848 aFrame == mCurrentReflowRoot ||
2849 nsLayoutUtils::IsProperAncestorFrame(mCurrentReflowRoot, aFrame),
2850 "Frame passed in is not the descendant of mCurrentReflowRoot");
2851 NS_ASSERTION(aFrame->HasAnyStateBits(NS_FRAME_IN_REFLOW),
2852 "Frame passed in not in reflow?");
2854 mFramesToDirty.Insert(aFrame);
2857 already_AddRefed<nsIContent> PresShell::GetContentForScrolling() const {
2858 if (nsCOMPtr<nsIContent> focused = GetFocusedContentInOurWindow()) {
2859 return focused.forget();
2861 return GetSelectedContentForScrolling();
2864 already_AddRefed<nsIContent> PresShell::GetSelectedContentForScrolling() const {
2865 nsCOMPtr<nsIContent> selectedContent;
2866 if (mSelection) {
2867 Selection* domSelection = mSelection->GetSelection(SelectionType::eNormal);
2868 if (domSelection) {
2869 selectedContent =
2870 nsIContent::FromNodeOrNull(domSelection->GetFocusNode());
2873 return selectedContent.forget();
2876 nsIScrollableFrame* PresShell::GetScrollableFrameToScrollForContent(
2877 nsIContent* aContent, ScrollDirections aDirections) {
2878 nsIScrollableFrame* scrollFrame = nullptr;
2879 if (aContent) {
2880 nsIFrame* startFrame = aContent->GetPrimaryFrame();
2881 if (startFrame) {
2882 scrollFrame = startFrame->GetScrollTargetFrame();
2883 if (scrollFrame) {
2884 startFrame = scrollFrame->GetScrolledFrame();
2886 scrollFrame = nsLayoutUtils::GetNearestScrollableFrameForDirection(
2887 startFrame, aDirections);
2890 if (!scrollFrame) {
2891 scrollFrame = GetRootScrollFrameAsScrollable();
2892 if (!scrollFrame || !scrollFrame->GetScrolledFrame()) {
2893 return nullptr;
2895 scrollFrame = nsLayoutUtils::GetNearestScrollableFrameForDirection(
2896 scrollFrame->GetScrolledFrame(), aDirections);
2898 return scrollFrame;
2901 nsIScrollableFrame* PresShell::GetScrollableFrameToScroll(
2902 ScrollDirections aDirections) {
2903 nsCOMPtr<nsIContent> content = GetContentForScrolling();
2904 return GetScrollableFrameToScrollForContent(content.get(), aDirections);
2907 void PresShell::CancelAllPendingReflows() {
2908 mDirtyRoots.Clear();
2910 if (mObservingLayoutFlushes) {
2911 GetPresContext()->RefreshDriver()->RemoveLayoutFlushObserver(this);
2912 mObservingLayoutFlushes = false;
2915 ASSERT_REFLOW_SCHEDULED_STATE();
2918 static bool DestroyFramesAndStyleDataFor(
2919 Element* aElement, nsPresContext& aPresContext,
2920 RestyleManager::IncludeRoot aIncludeRoot) {
2921 bool didReconstruct =
2922 aPresContext.FrameConstructor()->DestroyFramesFor(aElement);
2923 RestyleManager::ClearServoDataFromSubtree(aElement, aIncludeRoot);
2924 return didReconstruct;
2927 void PresShell::SlotAssignmentWillChange(Element& aElement,
2928 HTMLSlotElement* aOldSlot,
2929 HTMLSlotElement* aNewSlot) {
2930 MOZ_ASSERT(aOldSlot != aNewSlot);
2932 if (MOZ_UNLIKELY(!mDidInitialize)) {
2933 return;
2936 // If the old slot is about to become empty and show fallback, let layout know
2937 // that it needs to do work.
2938 if (aOldSlot && aOldSlot->AssignedNodes().Length() == 1 &&
2939 aOldSlot->HasChildren()) {
2940 DestroyFramesForAndRestyle(aOldSlot);
2943 // Ensure the new element starts off clean.
2944 DestroyFramesAndStyleDataFor(&aElement, *mPresContext,
2945 RestyleManager::IncludeRoot::Yes);
2947 if (aNewSlot) {
2948 // If the new slot will stop showing fallback content, we need to reframe it
2949 // altogether.
2950 if (aNewSlot->AssignedNodes().IsEmpty() && aNewSlot->HasChildren()) {
2951 DestroyFramesForAndRestyle(aNewSlot);
2952 // Otherwise we just care about the element, but we need to ensure that
2953 // something takes care of traversing to the relevant slot, if needed.
2954 } else if (aNewSlot->HasServoData() &&
2955 !Servo_Element_IsDisplayNone(aNewSlot)) {
2956 // Set the reframe bits...
2957 aNewSlot->NoteDescendantsNeedFramesForServo();
2958 aElement.SetFlags(NODE_NEEDS_FRAME);
2959 // Now the style dirty bits. Note that we can't just do
2960 // aElement.NoteDirtyForServo(), because the new slot is not setup yet.
2961 aNewSlot->SetHasDirtyDescendantsForServo();
2962 aNewSlot->NoteDirtySubtreeForServo();
2967 #ifdef DEBUG
2968 static void AssertNoFramesInSubtree(nsIContent* aContent) {
2969 for (nsINode* node : ShadowIncludingTreeIterator(*aContent)) {
2970 nsIContent* c = nsIContent::FromNode(node);
2971 MOZ_ASSERT(!c->GetPrimaryFrame());
2974 #endif
2976 void PresShell::DestroyFramesForAndRestyle(Element* aElement) {
2977 #ifdef DEBUG
2978 auto postCondition =
2979 mozilla::MakeScopeExit([&]() { AssertNoFramesInSubtree(aElement); });
2980 #endif
2982 MOZ_ASSERT(aElement);
2983 if (MOZ_UNLIKELY(!mDidInitialize)) {
2984 return;
2987 if (!aElement->GetFlattenedTreeParentNode()) {
2988 // Nothing to do here, the element already is out of the frame tree.
2989 return;
2992 nsAutoScriptBlocker scriptBlocker;
2994 // Mark ourselves as not safe to flush while we're doing frame destruction.
2995 ++mChangeNestCount;
2997 const bool didReconstruct = FrameConstructor()->DestroyFramesFor(aElement);
2999 // Clear the style data from all the flattened tree descendants, but _not_
3000 // from us, since otherwise we wouldn't see the reframe.
3001 RestyleManager::ClearServoDataFromSubtree(aElement,
3002 RestyleManager::IncludeRoot::No);
3004 auto changeHint =
3005 didReconstruct ? nsChangeHint(0) : nsChangeHint_ReconstructFrame;
3007 mPresContext->RestyleManager()->PostRestyleEvent(
3008 aElement, RestyleHint::RestyleSubtree(), changeHint);
3010 --mChangeNestCount;
3013 void PresShell::PostRecreateFramesFor(Element* aElement) {
3014 if (MOZ_UNLIKELY(!mDidInitialize)) {
3015 // Nothing to do here. In fact, if we proceed and aElement is the root, we
3016 // will crash.
3017 return;
3020 mPresContext->RestyleManager()->PostRestyleEvent(
3021 aElement, RestyleHint{0}, nsChangeHint_ReconstructFrame);
3024 void PresShell::RestyleForAnimation(Element* aElement, RestyleHint aHint) {
3025 // Now that we no longer have separate non-animation and animation
3026 // restyles, this method having a distinct identity is less important,
3027 // but it still seems useful to offer as a "more public" API and as a
3028 // chokepoint for these restyles to go through.
3029 mPresContext->RestyleManager()->PostRestyleEvent(aElement, aHint,
3030 nsChangeHint(0));
3033 void PresShell::SetForwardingContainer(const WeakPtr<nsDocShell>& aContainer) {
3034 mForwardingContainer = aContainer;
3037 void PresShell::ClearFrameRefs(nsIFrame* aFrame) {
3038 mPresContext->EventStateManager()->ClearFrameRefs(aFrame);
3040 AutoWeakFrame* weakFrame = mAutoWeakFrames;
3041 while (weakFrame) {
3042 AutoWeakFrame* prev = weakFrame->GetPreviousWeakFrame();
3043 if (weakFrame->GetFrame() == aFrame) {
3044 // This removes weakFrame from mAutoWeakFrames.
3045 weakFrame->Clear(this);
3047 weakFrame = prev;
3050 AutoTArray<WeakFrame*, 4> toRemove;
3051 for (WeakFrame* weakFrame : mWeakFrames) {
3052 if (weakFrame->GetFrame() == aFrame) {
3053 toRemove.AppendElement(weakFrame);
3056 for (WeakFrame* weakFrame : toRemove) {
3057 weakFrame->Clear(this);
3061 already_AddRefed<gfxContext> PresShell::CreateReferenceRenderingContext() {
3062 nsDeviceContext* devCtx = mPresContext->DeviceContext();
3063 RefPtr<gfxContext> rc;
3064 if (mPresContext->IsScreen()) {
3065 rc = gfxContext::CreateOrNull(
3066 gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget().get());
3067 } else {
3068 // We assume the devCtx has positive width and height for this call.
3069 // However, width and height, may be outside of the reasonable range
3070 // so rc may still be null.
3071 rc = devCtx->CreateReferenceRenderingContext();
3074 return rc ? rc.forget() : nullptr;
3077 nsresult PresShell::GoToAnchor(const nsAString& aAnchorName, bool aScroll,
3078 ScrollFlags aAdditionalScrollFlags) {
3079 if (!mDocument) {
3080 return NS_ERROR_FAILURE;
3083 const Element* root = mDocument->GetRootElement();
3084 if (root && root->IsSVGElement(nsGkAtoms::svg)) {
3085 // We need to execute this even if there is an empty anchor name
3086 // so that any existing SVG fragment identifier effect is removed
3087 if (SVGFragmentIdentifier::ProcessFragmentIdentifier(mDocument,
3088 aAnchorName)) {
3089 return NS_OK;
3093 // Hold a reference to the ESM in case event dispatch tears us down.
3094 RefPtr<EventStateManager> esm = mPresContext->EventStateManager();
3096 if (aAnchorName.IsEmpty()) {
3097 NS_ASSERTION(!aScroll, "can't scroll to empty anchor name");
3098 esm->SetContentState(nullptr, NS_EVENT_STATE_URLTARGET);
3099 return NS_OK;
3102 nsresult rv = NS_OK;
3103 nsCOMPtr<nsIContent> content;
3105 // Search for an element with a matching "id" attribute
3106 if (mDocument) {
3107 content = mDocument->GetElementById(aAnchorName);
3110 // Search for an anchor element with a matching "name" attribute
3111 if (!content && mDocument->IsHTMLDocument()) {
3112 // Find a matching list of named nodes
3113 nsCOMPtr<nsINodeList> list = mDocument->GetElementsByName(aAnchorName);
3114 if (list) {
3115 // Loop through the named nodes looking for the first anchor
3116 uint32_t length = list->Length();
3117 for (uint32_t i = 0; i < length; i++) {
3118 nsIContent* node = list->Item(i);
3119 if (node->IsHTMLElement(nsGkAtoms::a)) {
3120 content = node;
3121 break;
3127 // Search for anchor in the HTML namespace with a matching name
3128 if (!content && !mDocument->IsHTMLDocument()) {
3129 constexpr auto nameSpace = u"http://www.w3.org/1999/xhtml"_ns;
3130 // Get the list of anchor elements
3131 nsCOMPtr<nsINodeList> list =
3132 mDocument->GetElementsByTagNameNS(nameSpace, u"a"_ns);
3133 // Loop through the anchors looking for the first one with the given name.
3134 for (uint32_t i = 0; true; i++) {
3135 nsIContent* node = list->Item(i);
3136 if (!node) { // End of list
3137 break;
3140 // Compare the name attribute
3141 if (node->IsElement() &&
3142 node->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name,
3143 aAnchorName, eCaseMatters)) {
3144 content = node;
3145 break;
3150 esm->SetContentState(content, NS_EVENT_STATE_URLTARGET);
3152 #ifdef ACCESSIBILITY
3153 nsIContent* anchorTarget = content;
3154 #endif
3156 nsIScrollableFrame* rootScroll = GetRootScrollFrameAsScrollable();
3157 if (rootScroll && rootScroll->DidHistoryRestore()) {
3158 // Scroll position restored from history trumps scrolling to anchor.
3159 aScroll = false;
3160 rootScroll->ClearDidHistoryRestore();
3163 if (content) {
3164 if (aScroll) {
3165 rv = ScrollContentIntoView(
3166 content, ScrollAxis(kScrollToTop, WhenToScroll::Always), ScrollAxis(),
3167 ScrollFlags::AnchorScrollFlags | aAdditionalScrollFlags);
3168 NS_ENSURE_SUCCESS(rv, rv);
3170 nsIScrollableFrame* rootScroll = GetRootScrollFrameAsScrollable();
3171 if (rootScroll) {
3172 mLastAnchorScrolledTo = content;
3173 mLastAnchorScrollPositionY = rootScroll->GetScrollPosition().y;
3177 // Should we select the target? This action is controlled by a
3178 // preference: the default is to not select.
3179 bool selectAnchor = Preferences::GetBool("layout.selectanchor");
3181 // Even if select anchor pref is false, we must still move the
3182 // caret there. That way tabbing will start from the new
3183 // location
3184 RefPtr<nsRange> jumpToRange = nsRange::Create(mDocument);
3185 while (content && content->GetFirstChild()) {
3186 content = content->GetFirstChild();
3188 jumpToRange->SelectNodeContents(*content, IgnoreErrors());
3189 // Select the anchor
3190 RefPtr<Selection> sel = mSelection->GetSelection(SelectionType::eNormal);
3191 if (sel) {
3192 sel->RemoveAllRanges(IgnoreErrors());
3193 sel->AddRangeAndSelectFramesAndNotifyListeners(*jumpToRange,
3194 IgnoreErrors());
3195 if (!selectAnchor) {
3196 // Use a caret (collapsed selection) at the start of the anchor
3197 sel->CollapseToStart(IgnoreErrors());
3200 // Selection is at anchor.
3201 // Now focus the document itself if focus is on an element within it.
3202 nsPIDOMWindowOuter* win = mDocument->GetWindow();
3204 nsFocusManager* fm = nsFocusManager::GetFocusManager();
3205 if (fm && win) {
3206 nsCOMPtr<mozIDOMWindowProxy> focusedWindow;
3207 fm->GetFocusedWindow(getter_AddRefs(focusedWindow));
3208 if (SameCOMIdentity(win, focusedWindow)) {
3209 fm->ClearFocus(focusedWindow);
3213 // If the target is an animation element, activate the animation
3214 nsCOMPtr<SVGAnimationElement> animationElement = do_QueryInterface(content);
3215 if (animationElement) {
3216 animationElement->ActivateByHyperlink();
3218 } else {
3219 rv = NS_ERROR_FAILURE;
3220 if (nsContentUtils::EqualsIgnoreASCIICase(aAnchorName, u"top"_ns)) {
3221 // Scroll to the top/left if aAnchorName is "top" and there is no element
3222 // with such a name or id.
3223 rv = NS_OK;
3224 nsIScrollableFrame* sf = GetRootScrollFrameAsScrollable();
3225 // Check |aScroll| after setting |rv| so we set |rv| to the same
3226 // thing whether or not |aScroll| is true.
3227 if (aScroll && sf) {
3228 ScrollMode scrollMode =
3229 sf->IsSmoothScroll() ? ScrollMode::SmoothMsd : ScrollMode::Instant;
3230 // Scroll to the top of the page
3231 sf->ScrollTo(nsPoint(0, 0), scrollMode);
3236 #ifdef ACCESSIBILITY
3237 if (anchorTarget) {
3238 if (nsAccessibilityService* accService = GetAccessibilityService()) {
3239 accService->NotifyOfAnchorJumpTo(anchorTarget);
3242 #endif // #ifdef ACCESSIBILITY
3244 return rv;
3247 nsresult PresShell::ScrollToAnchor() {
3248 nsCOMPtr<nsIContent> lastAnchor = std::move(mLastAnchorScrolledTo);
3249 if (!lastAnchor) {
3250 return NS_OK;
3253 NS_ASSERTION(mDidInitialize, "should have done initial reflow by now");
3254 nsIScrollableFrame* rootScroll = GetRootScrollFrameAsScrollable();
3255 if (!rootScroll ||
3256 mLastAnchorScrollPositionY != rootScroll->GetScrollPosition().y) {
3257 return NS_OK;
3259 return ScrollContentIntoView(lastAnchor,
3260 ScrollAxis(kScrollToTop, WhenToScroll::Always),
3261 ScrollAxis(), ScrollFlags::AnchorScrollFlags);
3265 * Helper (per-continuation) for ScrollContentIntoView.
3267 * @param aContainerFrame [in] the frame which aRect is relative to
3268 * @param aFrame [in] Frame whose bounds should be unioned
3269 * @param aUseWholeLineHeightForInlines [in] if true, then for inline frames
3270 * we should include the top of the line in the added rectangle
3271 * @param aRect [inout] rect into which its bounds should be unioned
3272 * @param aHaveRect [inout] whether aRect contains data yet
3273 * @param aPrevBlock [inout] the block aLines is a line iterator for
3274 * @param aLines [inout] the line iterator we're using
3275 * @param aCurLine [inout] the line to start looking from in this iterator
3277 static void AccumulateFrameBounds(nsIFrame* aContainerFrame, nsIFrame* aFrame,
3278 bool aUseWholeLineHeightForInlines,
3279 nsRect& aRect, bool& aHaveRect,
3280 nsIFrame*& aPrevBlock,
3281 nsAutoLineIterator& aLines,
3282 int32_t& aCurLine) {
3283 nsIFrame* frame = aFrame;
3284 nsRect frameBounds = nsRect(nsPoint(0, 0), aFrame->GetSize());
3286 // If this is an inline frame and either the bounds height is 0 (quirks
3287 // layout model) or aUseWholeLineHeightForInlines is set, we need to
3288 // change the top of the bounds to include the whole line.
3289 if (frameBounds.height == 0 || aUseWholeLineHeightForInlines) {
3290 nsIFrame* prevFrame = aFrame;
3291 nsIFrame* f = aFrame;
3293 while (f && f->IsFrameOfType(nsIFrame::eLineParticipant) &&
3294 !f->IsTransformed() && !f->IsAbsPosContainingBlock()) {
3295 prevFrame = f;
3296 f = prevFrame->GetParent();
3299 if (f != aFrame && f && f->IsBlockFrame()) {
3300 // find the line containing aFrame and increase the top of |offset|.
3301 if (f != aPrevBlock) {
3302 aLines = f->GetLineIterator();
3303 aPrevBlock = f;
3304 aCurLine = 0;
3306 if (aLines) {
3307 int32_t index = aLines->FindLineContaining(prevFrame, aCurLine);
3308 if (index >= 0) {
3309 auto line = aLines->GetLine(index).unwrap();
3310 frameBounds += frame->GetOffsetTo(f);
3311 frame = f;
3312 if (line.mLineBounds.y < frameBounds.y) {
3313 frameBounds.height = frameBounds.YMost() - line.mLineBounds.y;
3314 frameBounds.y = line.mLineBounds.y;
3321 nsRect transformedBounds = nsLayoutUtils::TransformFrameRectToAncestor(
3322 frame, frameBounds, aContainerFrame);
3324 if (aHaveRect) {
3325 // We can't use nsRect::UnionRect since it drops empty rects on
3326 // the floor, and we need to include them. (Thus we need
3327 // aHaveRect to know when to drop the initial value on the floor.)
3328 aRect = aRect.UnionEdges(transformedBounds);
3329 } else {
3330 aHaveRect = true;
3331 aRect = transformedBounds;
3335 static bool ComputeNeedToScroll(WhenToScroll aWhenToScroll, nscoord aLineSize,
3336 nscoord aRectMin, nscoord aRectMax,
3337 nscoord aViewMin, nscoord aViewMax) {
3338 // See how the rect should be positioned in a given axis.
3339 switch (aWhenToScroll) {
3340 case WhenToScroll::Always:
3341 // The caller wants the frame as visible as possible
3342 return true;
3343 case WhenToScroll::IfNotVisible:
3344 // Scroll only if no part of the frame is visible in this view.
3345 return aRectMax - aLineSize <= aViewMin ||
3346 aRectMin + aLineSize >= aViewMax;
3347 case WhenToScroll::IfNotFullyVisible:
3348 // Scroll only if part of the frame is hidden and more can fit in view
3349 return !(aRectMin >= aViewMin && aRectMax <= aViewMax) &&
3350 std::min(aViewMax, aRectMax) - std::max(aRectMin, aViewMin) <
3351 aViewMax - aViewMin;
3353 return false;
3356 static nscoord ComputeWhereToScroll(WhereToScroll aWhereToScroll,
3357 nscoord aOriginalCoord, nscoord aRectMin,
3358 nscoord aRectMax, nscoord aViewMin,
3359 nscoord aViewMax, nscoord* aRangeMin,
3360 nscoord* aRangeMax) {
3361 nscoord resultCoord = aOriginalCoord;
3362 nscoord scrollPortLength = aViewMax - aViewMin;
3363 if (kScrollMinimum == aWhereToScroll) {
3364 // Scroll the minimum amount necessary to show as much as possible of the
3365 // frame. If the frame is too large, don't hide any initially visible part
3366 // of it.
3367 nscoord min = std::min(aRectMin, aRectMax - scrollPortLength);
3368 nscoord max = std::max(aRectMin, aRectMax - scrollPortLength);
3369 resultCoord = std::min(std::max(aOriginalCoord, min), max);
3370 } else {
3371 nscoord frameAlignCoord = NSToCoordRound(
3372 aRectMin + (aRectMax - aRectMin) * (aWhereToScroll / 100.0f));
3373 resultCoord = NSToCoordRound(frameAlignCoord -
3374 scrollPortLength * (aWhereToScroll / 100.0f));
3376 // Force the scroll range to extend to include resultCoord.
3377 *aRangeMin = std::min(resultCoord, aRectMax - scrollPortLength);
3378 *aRangeMax = std::max(resultCoord, aRectMin);
3379 return resultCoord;
3383 * This function takes a scrollable frame, a rect in the coordinate system
3384 * of the scrolled frame, and a desired percentage-based scroll
3385 * position and attempts to scroll the rect to that position in the
3386 * visual viewport.
3388 * This needs to work even if aRect has a width or height of zero.
3390 static void ScrollToShowRect(nsIScrollableFrame* aFrameAsScrollable,
3391 const nsRect& aRect, const nsMargin& aMargin,
3392 ScrollAxis aVertical, ScrollAxis aHorizontal,
3393 ScrollFlags aScrollFlags) {
3394 nsPoint scrollPt = aFrameAsScrollable->GetVisualViewportOffset();
3395 const nsPoint originalScrollPt = scrollPt;
3396 const nsRect visibleRect(scrollPt,
3397 aFrameAsScrollable->GetVisualViewportSize());
3399 const nsMargin padding = aFrameAsScrollable->GetScrollPadding() + aMargin;
3401 const nsRect rectToScrollIntoView = [&] {
3402 nsRect r(aRect);
3403 r.Inflate(padding);
3404 return r.Intersect(aFrameAsScrollable->GetScrolledRect());
3405 }();
3407 nsSize lineSize;
3408 // Don't call GetLineScrollAmount unless we actually need it. Not only
3409 // does this save time, but it's not safe to call GetLineScrollAmount
3410 // during reflow (because it depends on font size inflation and doesn't
3411 // use the in-reflow-safe font-size inflation path). If we did call it,
3412 // it would assert and possible give the wrong result.
3413 if (aVertical.mWhenToScroll == WhenToScroll::IfNotVisible ||
3414 aHorizontal.mWhenToScroll == WhenToScroll::IfNotVisible) {
3415 lineSize = aFrameAsScrollable->GetLineScrollAmount();
3417 ScrollStyles ss = aFrameAsScrollable->GetScrollStyles();
3418 nsRect allowedRange(scrollPt, nsSize(0, 0));
3419 ScrollDirections directions =
3420 aFrameAsScrollable->GetAvailableScrollingDirections();
3422 if (((aScrollFlags & ScrollFlags::ScrollOverflowHidden) ||
3423 ss.mVertical != StyleOverflow::Hidden) &&
3424 (!aVertical.mOnlyIfPerceivedScrollableDirection ||
3425 (directions.contains(ScrollDirection::eVertical)))) {
3426 if (ComputeNeedToScroll(aVertical.mWhenToScroll, lineSize.height, aRect.y,
3427 aRect.YMost(), visibleRect.y + padding.top,
3428 visibleRect.YMost() - padding.bottom)) {
3429 nscoord maxHeight;
3430 scrollPt.y = ComputeWhereToScroll(
3431 aVertical.mWhereToScroll, scrollPt.y, rectToScrollIntoView.y,
3432 rectToScrollIntoView.YMost(), visibleRect.y, visibleRect.YMost(),
3433 &allowedRange.y, &maxHeight);
3434 allowedRange.height = maxHeight - allowedRange.y;
3438 if (((aScrollFlags & ScrollFlags::ScrollOverflowHidden) ||
3439 ss.mHorizontal != StyleOverflow::Hidden) &&
3440 (!aHorizontal.mOnlyIfPerceivedScrollableDirection ||
3441 (directions.contains(ScrollDirection::eHorizontal)))) {
3442 if (ComputeNeedToScroll(aHorizontal.mWhenToScroll, lineSize.width, aRect.x,
3443 aRect.XMost(), visibleRect.x + padding.left,
3444 visibleRect.XMost() - padding.right)) {
3445 nscoord maxWidth;
3446 scrollPt.x = ComputeWhereToScroll(
3447 aHorizontal.mWhereToScroll, scrollPt.x, rectToScrollIntoView.x,
3448 rectToScrollIntoView.XMost(), visibleRect.x, visibleRect.XMost(),
3449 &allowedRange.x, &maxWidth);
3450 allowedRange.width = maxWidth - allowedRange.x;
3454 // If we don't need to scroll, then don't try since it might cancel
3455 // a current smooth scroll operation.
3456 if (scrollPt == originalScrollPt) {
3457 return;
3460 ScrollMode scrollMode = ScrollMode::Instant;
3461 bool autoBehaviorIsSmooth = aFrameAsScrollable->IsSmoothScroll();
3462 bool smoothScroll =
3463 (aScrollFlags & ScrollFlags::ScrollSmooth) ||
3464 ((aScrollFlags & ScrollFlags::ScrollSmoothAuto) && autoBehaviorIsSmooth);
3465 if (StaticPrefs::layout_css_scroll_behavior_enabled() && smoothScroll) {
3466 scrollMode = ScrollMode::SmoothMsd;
3468 nsIFrame* frame = do_QueryFrame(aFrameAsScrollable);
3469 AutoWeakFrame weakFrame(frame);
3470 aFrameAsScrollable->ScrollTo(scrollPt, scrollMode, &allowedRange,
3471 aScrollFlags & ScrollFlags::ScrollSnap
3472 ? nsIScrollbarMediator::ENABLE_SNAP
3473 : nsIScrollbarMediator::DISABLE_SNAP);
3474 if (!weakFrame.IsAlive()) {
3475 return;
3478 // If this is the RCD-RSF, also call ScrollToVisual() since we want to
3479 // scroll the rect into view visually, and that may require scrolling
3480 // the visual viewport in scenarios where there is not enough layout
3481 // scroll range.
3482 if (aFrameAsScrollable->IsRootScrollFrameOfDocument() &&
3483 frame->PresContext()->IsRootContentDocumentCrossProcess()) {
3484 frame->PresShell()->ScrollToVisual(scrollPt, FrameMetrics::eMainThread,
3485 scrollMode);
3489 nsresult PresShell::ScrollContentIntoView(nsIContent* aContent,
3490 ScrollAxis aVertical,
3491 ScrollAxis aHorizontal,
3492 ScrollFlags aScrollFlags) {
3493 NS_ENSURE_TRUE(aContent, NS_ERROR_NULL_POINTER);
3494 RefPtr<Document> composedDoc = aContent->GetComposedDoc();
3495 NS_ENSURE_STATE(composedDoc);
3497 NS_ASSERTION(mDidInitialize, "should have done initial reflow by now");
3499 if (mContentToScrollTo) {
3500 mContentToScrollTo->RemoveProperty(nsGkAtoms::scrolling);
3502 mContentToScrollTo = aContent;
3503 ScrollIntoViewData* data = new ScrollIntoViewData();
3504 data->mContentScrollVAxis = aVertical;
3505 data->mContentScrollHAxis = aHorizontal;
3506 data->mContentToScrollToFlags = aScrollFlags;
3507 if (NS_FAILED(mContentToScrollTo->SetProperty(
3508 nsGkAtoms::scrolling, data,
3509 nsINode::DeleteProperty<PresShell::ScrollIntoViewData>))) {
3510 mContentToScrollTo = nullptr;
3513 // Flush layout and attempt to scroll in the process.
3514 if (PresShell* presShell = composedDoc->GetPresShell()) {
3515 presShell->SetNeedLayoutFlush();
3517 composedDoc->FlushPendingNotifications(FlushType::InterruptibleLayout);
3519 // If mContentToScrollTo is non-null, that means we interrupted the reflow
3520 // (or suppressed it altogether because we're suppressing interruptible
3521 // flushes right now) and won't necessarily get the position correct, but do
3522 // a best-effort scroll here. The other option would be to do this inside
3523 // FlushPendingNotifications, but I'm not sure the repeated scrolling that
3524 // could trigger if reflows keep getting interrupted would be more desirable
3525 // than a single best-effort scroll followed by one final scroll on the first
3526 // completed reflow.
3527 if (mContentToScrollTo) {
3528 DoScrollContentIntoView();
3530 return NS_OK;
3533 void PresShell::DoScrollContentIntoView() {
3534 NS_ASSERTION(mDidInitialize, "should have done initial reflow by now");
3536 nsIFrame* frame = mContentToScrollTo->GetPrimaryFrame();
3537 if (!frame) {
3538 mContentToScrollTo->RemoveProperty(nsGkAtoms::scrolling);
3539 mContentToScrollTo = nullptr;
3540 return;
3543 if (frame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
3544 // The reflow flush before this scroll got interrupted, and this frame's
3545 // coords and size are all zero, and it has no content showing anyway.
3546 // Don't bother scrolling to it. We'll try again when we finish up layout.
3547 return;
3550 // Make sure we skip 'frame' ... if it's scrollable, we should use its
3551 // scrollable ancestor as the container.
3552 nsIFrame* container = nsLayoutUtils::GetClosestFrameOfType(
3553 frame->GetParent(), LayoutFrameType::Scroll);
3554 if (!container) {
3555 // nothing can be scrolled
3556 return;
3559 ScrollIntoViewData* data = static_cast<ScrollIntoViewData*>(
3560 mContentToScrollTo->GetProperty(nsGkAtoms::scrolling));
3561 if (MOZ_UNLIKELY(!data)) {
3562 mContentToScrollTo = nullptr;
3563 return;
3566 // Get the scroll-margin here since |frame| is going to be changed to iterate
3567 // over all continuation frames below.
3568 const nsMargin scrollMargin = frame->StyleMargin()->GetScrollMargin();
3570 // This is a two-step process.
3571 // Step 1: Find the bounds of the rect we want to scroll into view. For
3572 // example, for an inline frame we may want to scroll in the whole
3573 // line, or we may want to scroll multiple lines into view.
3574 // Step 2: Walk container frame and its ancestors and scroll them
3575 // appropriately.
3576 // frameBounds is relative to container. We're assuming
3577 // that scrollframes don't split so every continuation of frame will
3578 // be a descendant of container. (Things would still mostly work
3579 // even if that assumption was false.)
3580 nsRect frameBounds;
3581 bool haveRect = false;
3582 bool useWholeLineHeightForInlines = data->mContentScrollVAxis.mWhenToScroll !=
3583 WhenToScroll::IfNotFullyVisible;
3584 // Reuse the same line iterator across calls to AccumulateFrameBounds. We set
3585 // it every time we detect a new block (stored in prevBlock).
3586 nsIFrame* prevBlock = nullptr;
3587 nsAutoLineIterator lines;
3588 // The last line we found a continuation on in |lines|. We assume that later
3589 // continuations cannot come on earlier lines.
3590 int32_t curLine = 0;
3591 do {
3592 AccumulateFrameBounds(container, frame, useWholeLineHeightForInlines,
3593 frameBounds, haveRect, prevBlock, lines, curLine);
3594 } while ((frame = frame->GetNextContinuation()));
3596 ScrollFrameRectIntoView(container, frameBounds, scrollMargin,
3597 data->mContentScrollVAxis, data->mContentScrollHAxis,
3598 data->mContentToScrollToFlags);
3601 bool PresShell::ScrollFrameRectIntoView(nsIFrame* aFrame, const nsRect& aRect,
3602 const nsMargin& aMargin,
3603 ScrollAxis aVertical,
3604 ScrollAxis aHorizontal,
3605 ScrollFlags aScrollFlags) {
3606 bool didScroll = false;
3607 // This function needs to work even if rect has a width or height of 0.
3608 nsRect rect = aRect;
3609 nsIFrame* container = aFrame;
3610 // Walk up the frame hierarchy scrolling the rect into view and
3611 // keeping rect relative to container
3612 do {
3613 nsIScrollableFrame* sf = do_QueryFrame(container);
3614 if (sf) {
3615 nsPoint oldPosition = sf->GetScrollPosition();
3616 nsRect targetRect = rect;
3617 // Inflate the scrolled rect by the container's padding in each dimension,
3618 // unless we have 'overflow-clip-box-*: content-box' in that dimension.
3619 auto* disp = container->StyleDisplay();
3620 if (disp->mOverflowClipBoxBlock == StyleOverflowClipBox::ContentBox ||
3621 disp->mOverflowClipBoxInline == StyleOverflowClipBox::ContentBox) {
3622 WritingMode wm = container->GetWritingMode();
3623 bool cbH = (wm.IsVertical() ? disp->mOverflowClipBoxBlock
3624 : disp->mOverflowClipBoxInline) ==
3625 StyleOverflowClipBox::ContentBox;
3626 bool cbV = (wm.IsVertical() ? disp->mOverflowClipBoxInline
3627 : disp->mOverflowClipBoxBlock) ==
3628 StyleOverflowClipBox::ContentBox;
3629 nsMargin padding = container->GetUsedPadding();
3630 if (!cbH) {
3631 padding.left = padding.right = nscoord(0);
3633 if (!cbV) {
3634 padding.top = padding.bottom = nscoord(0);
3636 targetRect.Inflate(padding);
3639 targetRect -= sf->GetScrolledFrame()->GetPosition();
3642 AutoWeakFrame wf(container);
3643 ScrollToShowRect(sf, targetRect, aMargin, aVertical, aHorizontal,
3644 aScrollFlags);
3645 if (!wf.IsAlive()) {
3646 return didScroll;
3650 nsPoint newPosition = sf->LastScrollDestination();
3651 // If the scroll position increased, that means our content moved up,
3652 // so our rect's offset should decrease
3653 rect += oldPosition - newPosition;
3655 if (oldPosition != newPosition) {
3656 didScroll = true;
3659 // only scroll one container when this flag is set
3660 if (aScrollFlags & ScrollFlags::ScrollFirstAncestorOnly) {
3661 break;
3664 nsIFrame* parent;
3665 if (container->IsTransformed()) {
3666 container->GetTransformMatrix(ViewportType::Layout, RelativeTo{nullptr},
3667 &parent);
3668 rect =
3669 nsLayoutUtils::TransformFrameRectToAncestor(container, rect, parent);
3670 } else {
3671 rect += container->GetPosition();
3672 parent = container->GetParent();
3674 if (!parent && !(aScrollFlags & ScrollFlags::ScrollNoParentFrames)) {
3675 nsPoint extraOffset(0, 0);
3676 int32_t APD = container->PresContext()->AppUnitsPerDevPixel();
3677 parent = nsLayoutUtils::GetCrossDocParentFrameInProcess(container,
3678 &extraOffset);
3679 if (parent) {
3680 int32_t parentAPD = parent->PresContext()->AppUnitsPerDevPixel();
3681 rect = rect.ScaleToOtherAppUnitsRoundOut(APD, parentAPD);
3682 rect += extraOffset;
3683 } else {
3684 nsCOMPtr<nsIDocShell> docShell =
3685 container->PresContext()->GetDocShell();
3686 if (BrowserChild* browserChild = BrowserChild::GetFrom(docShell)) {
3687 // Defer to the parent document if this is an out-of-process iframe.
3688 Unused << browserChild->SendScrollRectIntoView(
3689 rect, aVertical, aHorizontal, aScrollFlags, APD);
3693 container = parent;
3694 } while (container);
3696 return didScroll;
3699 void PresShell::ScheduleViewManagerFlush(PaintType aType) {
3700 if (MOZ_UNLIKELY(mIsDestroying)) {
3701 return;
3704 if (aType == PaintType::DelayedCompress) {
3705 // Delay paint for 1 second.
3706 static const uint32_t kPaintDelayPeriod = 1000;
3707 if (!mDelayedPaintTimer) {
3708 nsTimerCallbackFunc PaintTimerCallBack = [](nsITimer* aTimer,
3709 void* aClosure) {
3710 // The passed-in PresShell is always alive here. Because if PresShell
3711 // died, mDelayedPaintTimer->Cancel() would be called during the
3712 // destruction and this callback would never be invoked.
3713 auto self = static_cast<PresShell*>(aClosure);
3714 self->SetNextPaintCompressed();
3715 self->ScheduleViewManagerFlush();
3718 NS_NewTimerWithFuncCallback(
3719 getter_AddRefs(mDelayedPaintTimer), PaintTimerCallBack, this,
3720 kPaintDelayPeriod, nsITimer::TYPE_ONE_SHOT, "PaintTimerCallBack",
3721 mDocument->EventTargetFor(TaskCategory::Other));
3723 return;
3726 nsPresContext* presContext = GetPresContext();
3727 if (presContext) {
3728 presContext->RefreshDriver()->ScheduleViewManagerFlush();
3730 SetNeedLayoutFlush();
3733 void PresShell::DispatchSynthMouseMove(WidgetGUIEvent* aEvent) {
3734 AUTO_PROFILER_TRACING_MARKER_DOCSHELL("Paint", "DispatchSynthMouseMove",
3735 GRAPHICS, mPresContext->GetDocShell());
3736 nsEventStatus status = nsEventStatus_eIgnore;
3737 nsView* targetView = nsView::GetViewFor(aEvent->mWidget);
3738 if (!targetView) return;
3739 RefPtr<nsViewManager> viewManager = targetView->GetViewManager();
3740 viewManager->DispatchEvent(aEvent, targetView, &status);
3743 void PresShell::ClearMouseCaptureOnView(nsView* aView) {
3744 if (nsIContent* capturingContent = GetCapturingContent()) {
3745 if (aView) {
3746 // if a view was specified, ensure that the captured content is within
3747 // this view.
3748 nsIFrame* frame = capturingContent->GetPrimaryFrame();
3749 if (frame) {
3750 nsView* view = frame->GetClosestView();
3751 // if there is no view, capturing won't be handled any more, so
3752 // just release the capture.
3753 if (view) {
3754 do {
3755 if (view == aView) {
3756 ReleaseCapturingContent();
3757 // the view containing the captured content likely disappeared so
3758 // disable capture for now.
3759 AllowMouseCapture(false);
3760 break;
3763 view = view->GetParent();
3764 } while (view);
3765 // return if the view wasn't found
3766 return;
3771 ReleaseCapturingContent();
3774 // disable mouse capture until the next mousedown as a dialog has opened
3775 // or a drag has started. Otherwise, someone could start capture during
3776 // the modal dialog or drag.
3777 AllowMouseCapture(false);
3780 void PresShell::ClearMouseCapture() {
3781 nsIContent* capturingContent = GetCapturingContent();
3782 if (!capturingContent) {
3783 AllowMouseCapture(false);
3784 return;
3787 ReleaseCapturingContent();
3788 AllowMouseCapture(false);
3791 void PresShell::ClearMouseCapture(nsIFrame* aFrame) {
3792 MOZ_ASSERT(
3793 aFrame && aFrame->GetParent() &&
3794 aFrame->GetParent()->Type() == LayoutFrameType::Deck,
3795 "This function should only be called with a child frame of <deck>");
3797 nsIContent* capturingContent = GetCapturingContent();
3798 if (!capturingContent) {
3799 AllowMouseCapture(false);
3800 return;
3803 nsIFrame* capturingFrame = capturingContent->GetPrimaryFrame();
3804 if (!capturingFrame) {
3805 ReleaseCapturingContent();
3806 AllowMouseCapture(false);
3807 return;
3810 if (nsLayoutUtils::IsAncestorFrameCrossDocInProcess(aFrame, capturingFrame)) {
3811 ReleaseCapturingContent();
3812 AllowMouseCapture(false);
3816 nsresult PresShell::CaptureHistoryState(nsILayoutHistoryState** aState) {
3817 MOZ_ASSERT(nullptr != aState, "null state pointer");
3819 // We actually have to mess with the docshell here, since we want to
3820 // store the state back in it.
3821 // XXXbz this isn't really right, since this is being called in the
3822 // content viewer's Hide() method... by that point the docshell's
3823 // state could be wrong. We should sort out a better ownership
3824 // model for the layout history state.
3825 nsCOMPtr<nsIDocShell> docShell(mPresContext->GetDocShell());
3826 if (!docShell) return NS_ERROR_FAILURE;
3828 nsCOMPtr<nsILayoutHistoryState> historyState;
3829 docShell->GetLayoutHistoryState(getter_AddRefs(historyState));
3830 if (!historyState) {
3831 // Create the document state object
3832 historyState = NS_NewLayoutHistoryState();
3833 docShell->SetLayoutHistoryState(historyState);
3836 *aState = historyState;
3837 NS_IF_ADDREF(*aState);
3839 // Capture frame state for the entire frame hierarchy
3840 nsIFrame* rootFrame = mFrameConstructor->GetRootFrame();
3841 if (!rootFrame) return NS_OK;
3843 mFrameConstructor->CaptureFrameState(rootFrame, historyState);
3845 return NS_OK;
3848 void PresShell::ScheduleBeforeFirstPaint() {
3849 if (!mDocument->IsResourceDoc()) {
3850 // Notify observers that a new page is about to be drawn. Execute this
3851 // as soon as it is safe to run JS, which is guaranteed to be before we
3852 // go back to the event loop and actually draw the page.
3853 MOZ_LOG(gLog, LogLevel::Debug,
3854 ("PresShell::ScheduleBeforeFirstPaint this=%p", this));
3856 nsContentUtils::AddScriptRunner(
3857 new nsBeforeFirstPaintDispatcher(mDocument));
3861 void PresShell::UnsuppressAndInvalidate() {
3862 // Note: We ignore the EnsureVisible check for resource documents, because
3863 // they won't have a docshell, so they'll always fail EnsureVisible.
3864 if ((!mDocument->IsResourceDoc() && !mPresContext->EnsureVisible()) ||
3865 mHaveShutDown) {
3866 // No point; we're about to be torn down anyway.
3867 return;
3870 ScheduleBeforeFirstPaint();
3872 mPaintingSuppressed = false;
3873 nsIFrame* rootFrame = mFrameConstructor->GetRootFrame();
3874 if (rootFrame) {
3875 // let's assume that outline on a root frame is not supported
3876 rootFrame->InvalidateFrame();
3879 // now that painting is unsuppressed, focus may be set on the document
3880 if (nsPIDOMWindowOuter* win = mDocument->GetWindow()) {
3881 win->SetReadyForFocus();
3884 if (!mHaveShutDown) {
3885 SynthesizeMouseMove(false);
3886 ScheduleApproximateFrameVisibilityUpdateNow();
3890 void PresShell::CancelPaintSuppressionTimer() {
3891 if (mPaintSuppressionTimer) {
3892 mPaintSuppressionTimer->Cancel();
3893 mPaintSuppressionTimer = nullptr;
3897 void PresShell::UnsuppressPainting() {
3898 CancelPaintSuppressionTimer();
3900 if (mIsDocumentGone || !mPaintingSuppressed) {
3901 return;
3904 // If we have reflows pending, just wait until we process
3905 // the reflows and get all the frames where we want them
3906 // before actually unlocking the painting. Otherwise
3907 // go ahead and unlock now.
3908 if (!mDirtyRoots.IsEmpty())
3909 mShouldUnsuppressPainting = true;
3910 else
3911 UnsuppressAndInvalidate();
3914 // Post a request to handle an arbitrary callback after reflow has finished.
3915 nsresult PresShell::PostReflowCallback(nsIReflowCallback* aCallback) {
3916 void* result = AllocateByObjectID(eArenaObjectID_nsCallbackEventRequest,
3917 sizeof(nsCallbackEventRequest));
3918 nsCallbackEventRequest* request = (nsCallbackEventRequest*)result;
3920 request->callback = aCallback;
3921 request->next = nullptr;
3923 if (mLastCallbackEventRequest) {
3924 mLastCallbackEventRequest = mLastCallbackEventRequest->next = request;
3925 } else {
3926 mFirstCallbackEventRequest = request;
3927 mLastCallbackEventRequest = request;
3930 return NS_OK;
3933 void PresShell::CancelReflowCallback(nsIReflowCallback* aCallback) {
3934 nsCallbackEventRequest* before = nullptr;
3935 nsCallbackEventRequest* node = mFirstCallbackEventRequest;
3936 while (node) {
3937 nsIReflowCallback* callback = node->callback;
3939 if (callback == aCallback) {
3940 nsCallbackEventRequest* toFree = node;
3941 if (node == mFirstCallbackEventRequest) {
3942 node = node->next;
3943 mFirstCallbackEventRequest = node;
3944 NS_ASSERTION(before == nullptr, "impossible");
3945 } else {
3946 node = node->next;
3947 before->next = node;
3950 if (toFree == mLastCallbackEventRequest) {
3951 mLastCallbackEventRequest = before;
3954 FreeByObjectID(eArenaObjectID_nsCallbackEventRequest, toFree);
3955 } else {
3956 before = node;
3957 node = node->next;
3962 void PresShell::CancelPostedReflowCallbacks() {
3963 while (mFirstCallbackEventRequest) {
3964 nsCallbackEventRequest* node = mFirstCallbackEventRequest;
3965 mFirstCallbackEventRequest = node->next;
3966 if (!mFirstCallbackEventRequest) {
3967 mLastCallbackEventRequest = nullptr;
3969 nsIReflowCallback* callback = node->callback;
3970 FreeByObjectID(eArenaObjectID_nsCallbackEventRequest, node);
3971 if (callback) {
3972 callback->ReflowCallbackCanceled();
3977 void PresShell::HandlePostedReflowCallbacks(bool aInterruptible) {
3978 bool shouldFlush = false;
3980 while (mFirstCallbackEventRequest) {
3981 nsCallbackEventRequest* node = mFirstCallbackEventRequest;
3982 mFirstCallbackEventRequest = node->next;
3983 if (!mFirstCallbackEventRequest) {
3984 mLastCallbackEventRequest = nullptr;
3986 nsIReflowCallback* callback = node->callback;
3987 FreeByObjectID(eArenaObjectID_nsCallbackEventRequest, node);
3988 if (callback) {
3989 if (callback->ReflowFinished()) {
3990 shouldFlush = true;
3995 FlushType flushType =
3996 aInterruptible ? FlushType::InterruptibleLayout : FlushType::Layout;
3997 if (shouldFlush && !mIsDestroying) {
3998 FlushPendingNotifications(flushType);
4002 bool PresShell::IsSafeToFlush() const {
4003 // Not safe if we are getting torn down, reflowing, or in the middle of frame
4004 // construction.
4005 if (mIsReflowing || mChangeNestCount || mIsDestroying) {
4006 return false;
4009 // Not safe if we are painting
4010 if (nsViewManager* viewManager = GetViewManager()) {
4011 bool isPainting = false;
4012 viewManager->IsPainting(isPainting);
4013 if (isPainting) {
4014 return false;
4018 return true;
4021 void PresShell::NotifyFontFaceSetOnRefresh() {
4022 if (FontFaceSet* set = mDocument->GetFonts()) {
4023 set->DidRefresh();
4027 void PresShell::DoFlushPendingNotifications(FlushType aType) {
4028 // by default, flush animations if aType >= FlushType::Style
4029 mozilla::ChangesToFlush flush(aType, aType >= FlushType::Style);
4030 FlushPendingNotifications(flush);
4033 #ifdef DEBUG
4034 static void AssertFrameSubtreeIsSane(const nsIFrame& aRoot) {
4035 if (const nsIContent* content = aRoot.GetContent()) {
4036 MOZ_ASSERT(content->GetFlattenedTreeParentNodeForStyle(),
4037 "Node not in the flattened tree still has a frame?");
4040 for (const auto& childList : aRoot.ChildLists()) {
4041 for (const nsIFrame* child : childList.mList) {
4042 AssertFrameSubtreeIsSane(*child);
4046 #endif
4048 static inline void AssertFrameTreeIsSane(const PresShell& aPresShell) {
4049 #ifdef DEBUG
4050 if (const nsIFrame* root = aPresShell.GetRootFrame()) {
4051 AssertFrameSubtreeIsSane(*root);
4053 #endif
4056 void PresShell::DoFlushPendingNotifications(mozilla::ChangesToFlush aFlush) {
4057 // FIXME(emilio, bug 1530177): Turn into a release assert when bug 1530188 and
4058 // bug 1530190 are fixed.
4059 MOZ_DIAGNOSTIC_ASSERT(!mForbiddenToFlush, "This is bad!");
4061 // Per our API contract, hold a strong ref to ourselves until we return.
4062 RefPtr<PresShell> kungFuDeathGrip = this;
4065 * VERY IMPORTANT: If you add some sort of new flushing to this
4066 * method, make sure to add the relevant SetNeedLayoutFlush or
4067 * SetNeedStyleFlush calls on the shell.
4069 FlushType flushType = aFlush.mFlushType;
4071 MOZ_ASSERT(NeedFlush(flushType), "Why did we get called?");
4073 AUTO_PROFILER_MARKER_TEXT(
4074 "DoFlushPendingNotifications", LAYOUT,
4075 MarkerOptions(MarkerStack::Capture(), MarkerInnerWindowIdFromDocShell(
4076 mPresContext->GetDocShell())),
4077 nsDependentCString(kFlushTypeNames[flushType]));
4078 AUTO_PROFILER_LABEL_DYNAMIC_CSTR_NONSENSITIVE(
4079 "PresShell::DoFlushPendingNotifications", LAYOUT,
4080 kFlushTypeNames[flushType]);
4082 #ifdef ACCESSIBILITY
4083 # ifdef DEBUG
4084 if (nsAccessibilityService* accService = GetAccService()) {
4085 NS_ASSERTION(!accService->IsProcessingRefreshDriverNotification(),
4086 "Flush during accessible tree update!");
4088 # endif
4089 #endif
4091 NS_ASSERTION(flushType >= FlushType::Style, "Why did we get called?");
4093 mNeedStyleFlush = false;
4094 mNeedThrottledAnimationFlush =
4095 mNeedThrottledAnimationFlush && !aFlush.mFlushAnimations;
4096 mNeedLayoutFlush =
4097 mNeedLayoutFlush && (flushType < FlushType::InterruptibleLayout);
4099 bool isSafeToFlush = IsSafeToFlush();
4101 // If layout could possibly trigger scripts, then it's only safe to flush if
4102 // it's safe to run script.
4103 bool hasHadScriptObject;
4104 if (mDocument->GetScriptHandlingObject(hasHadScriptObject) ||
4105 hasHadScriptObject) {
4106 isSafeToFlush = isSafeToFlush && nsContentUtils::IsSafeToRunScript();
4109 // Don't flush if the doc is already in the bfcache.
4110 if (MOZ_UNLIKELY(mDocument->GetPresShell() != this)) {
4111 MOZ_DIAGNOSTIC_ASSERT(!mDocument->GetPresShell(),
4112 "Where did this shell come from?");
4113 isSafeToFlush = false;
4116 MOZ_DIAGNOSTIC_ASSERT(!mIsDestroying || !isSafeToFlush);
4117 MOZ_DIAGNOSTIC_ASSERT(mIsDestroying || mViewManager);
4118 MOZ_DIAGNOSTIC_ASSERT(mIsDestroying || mDocument->HasShellOrBFCacheEntry());
4120 // Make sure the view manager stays alive.
4121 RefPtr<nsViewManager> viewManager = mViewManager;
4122 bool didStyleFlush = false;
4123 bool didLayoutFlush = false;
4124 if (isSafeToFlush) {
4125 // Record that we are in a flush, so that our optimization in
4126 // Document::FlushPendingNotifications doesn't skip any re-entrant
4127 // calls to us. Otherwise, we might miss some needed flushes, since
4128 // we clear mNeedStyleFlush / mNeedLayoutFlush here at the top of
4129 // the function but we might not have done the work yet.
4130 AutoRestore<bool> guard(mInFlush);
4131 mInFlush = true;
4133 // We need to make sure external resource documents are flushed too (for
4134 // example, svg filters that reference a filter in an external document
4135 // need the frames in the external document to be constructed for the
4136 // filter to work). We only need external resources to be flushed when the
4137 // main document is flushing >= FlushType::Frames, so we flush external
4138 // resources here instead of Document::FlushPendingNotifications.
4139 mDocument->FlushExternalResources(flushType);
4141 // Force flushing of any pending content notifications that might have
4142 // queued up while our event was pending. That will ensure that we don't
4143 // construct frames for content right now that's still waiting to be
4144 // notified on,
4145 mDocument->FlushPendingNotifications(FlushType::ContentAndNotify);
4147 mDocument->UpdateSVGUseElementShadowTrees();
4149 // Process pending restyles, since any flush of the presshell wants
4150 // up-to-date style data.
4151 if (MOZ_LIKELY(!mIsDestroying)) {
4152 viewManager->FlushDelayedResize(false);
4153 mPresContext->FlushPendingMediaFeatureValuesChanged();
4156 if (MOZ_LIKELY(!mIsDestroying)) {
4157 // Now that we have flushed media queries, update the rules before looking
4158 // up @font-face / @counter-style / @font-feature-values rules.
4159 StyleSet()->UpdateStylistIfNeeded();
4161 // Flush any pending update of the user font set, since that could
4162 // cause style changes (for updating ex/ch units, and to cause a
4163 // reflow).
4164 mDocument->FlushUserFontSet();
4166 mPresContext->FlushCounterStyles();
4168 mPresContext->FlushFontFeatureValues();
4170 // Flush any requested SMIL samples.
4171 if (mDocument->HasAnimationController()) {
4172 mDocument->GetAnimationController()->FlushResampleRequests();
4175 if (aFlush.mFlushAnimations && mPresContext->EffectCompositor()) {
4176 mPresContext->EffectCompositor()->PostRestyleForThrottledAnimations();
4180 // The FlushResampleRequests() above flushed style changes.
4181 if (MOZ_LIKELY(!mIsDestroying)) {
4182 nsAutoScriptBlocker scriptBlocker;
4183 Maybe<uint64_t> innerWindowID;
4184 if (auto* window = mDocument->GetInnerWindow()) {
4185 innerWindowID = Some(window->WindowID());
4187 AutoProfilerStyleMarker tracingStyleFlush(std::move(mStyleCause),
4188 innerWindowID);
4189 PerfStats::AutoMetricRecording<PerfStats::Metric::Styling> autoRecording;
4190 LAYOUT_TELEMETRY_RECORD_BASE(Restyle);
4192 mPresContext->RestyleManager()->ProcessPendingRestyles();
4195 // Now those constructors or events might have posted restyle
4196 // events. At the same time, we still need up-to-date style data.
4197 // In particular, reflow depends on style being completely up to
4198 // date. If it's not, then style reparenting, which can
4199 // happen during reflow, might suddenly pick up the new rules and
4200 // we'll end up with frames whose style doesn't match the frame
4201 // type.
4202 if (MOZ_LIKELY(!mIsDestroying)) {
4203 nsAutoScriptBlocker scriptBlocker;
4204 Maybe<uint64_t> innerWindowID;
4205 if (auto* window = mDocument->GetInnerWindow()) {
4206 innerWindowID = Some(window->WindowID());
4208 AutoProfilerStyleMarker tracingStyleFlush(std::move(mStyleCause),
4209 innerWindowID);
4210 PerfStats::AutoMetricRecording<PerfStats::Metric::Styling> autoRecording;
4211 LAYOUT_TELEMETRY_RECORD_BASE(Restyle);
4213 mPresContext->RestyleManager()->ProcessPendingRestyles();
4214 // Clear mNeedStyleFlush here agagin to make this flag work properly for
4215 // optimization since the flag might have set in ProcessPendingRestyles().
4216 mNeedStyleFlush = false;
4219 AssertFrameTreeIsSane(*this);
4221 didStyleFlush = true;
4223 // There might be more pending constructors now, but we're not going to
4224 // worry about them. They can't be triggered during reflow, so we should
4225 // be good.
4227 if (flushType >= (SuppressInterruptibleReflows()
4228 ? FlushType::Layout
4229 : FlushType::InterruptibleLayout) &&
4230 !mIsDestroying) {
4231 didLayoutFlush = true;
4232 mFrameConstructor->RecalcQuotesAndCounters();
4233 if (ProcessReflowCommands(flushType < FlushType::Layout)) {
4234 if (mContentToScrollTo) {
4235 DoScrollContentIntoView();
4236 if (mContentToScrollTo) {
4237 mContentToScrollTo->RemoveProperty(nsGkAtoms::scrolling);
4238 mContentToScrollTo = nullptr;
4244 if (flushType >= FlushType::Layout) {
4245 if (!mIsDestroying) {
4246 viewManager->UpdateWidgetGeometry();
4251 if (!didStyleFlush && flushType >= FlushType::Style && !mIsDestroying) {
4252 SetNeedStyleFlush();
4253 if (aFlush.mFlushAnimations) {
4254 SetNeedThrottledAnimationFlush();
4258 if (!didLayoutFlush && flushType >= FlushType::InterruptibleLayout &&
4259 !mIsDestroying) {
4260 // We suppressed this flush either due to it not being safe to flush,
4261 // or due to SuppressInterruptibleReflows(). Either way, the
4262 // mNeedLayoutFlush flag needs to be re-set.
4263 SetNeedLayoutFlush();
4266 // Update flush counters
4267 if (didStyleFlush) {
4268 mLayoutTelemetry.IncReqsPerFlush(FlushType::Style);
4271 if (didLayoutFlush) {
4272 mLayoutTelemetry.IncReqsPerFlush(FlushType::Layout);
4275 // Record telemetry for the number of requests per each flush type.
4277 // Flushes happen as style or style+layout. This depends upon the `flushType`
4278 // where flushType >= InterruptibleLayout means flush layout and flushType >=
4279 // Style means flush style. We only report if didLayoutFlush or didStyleFlush
4280 // is true because we care if a flush really did take place. (Flush is guarded
4281 // by `isSafeToFlush == true`.)
4282 if (flushType >= FlushType::InterruptibleLayout && didLayoutFlush) {
4283 MOZ_ASSERT(didLayoutFlush == didStyleFlush);
4284 mLayoutTelemetry.PingReqsPerFlushTelemetry(FlushType::Layout);
4285 } else if (flushType >= FlushType::Style && didStyleFlush) {
4286 MOZ_ASSERT(!didLayoutFlush);
4287 mLayoutTelemetry.PingReqsPerFlushTelemetry(FlushType::Style);
4291 MOZ_CAN_RUN_SCRIPT_BOUNDARY void PresShell::CharacterDataChanged(
4292 nsIContent* aContent, const CharacterDataChangeInfo& aInfo) {
4293 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript());
4294 MOZ_ASSERT(!mIsDocumentGone, "Unexpected CharacterDataChanged");
4295 MOZ_ASSERT(aContent->OwnerDoc() == mDocument, "Unexpected document");
4297 nsAutoCauseReflowNotifier crNotifier(this);
4299 mPresContext->RestyleManager()->CharacterDataChanged(aContent, aInfo);
4300 mFrameConstructor->CharacterDataChanged(aContent, aInfo);
4303 MOZ_CAN_RUN_SCRIPT_BOUNDARY void PresShell::ContentStateChanged(
4304 Document* aDocument, nsIContent* aContent, EventStates aStateMask) {
4305 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript());
4306 MOZ_ASSERT(!mIsDocumentGone, "Unexpected ContentStateChanged");
4307 MOZ_ASSERT(aDocument == mDocument, "Unexpected aDocument");
4309 if (mDidInitialize) {
4310 nsAutoCauseReflowNotifier crNotifier(this);
4311 mPresContext->RestyleManager()->ContentStateChanged(aContent, aStateMask);
4315 void PresShell::DocumentStatesChanged(EventStates aStateMask) {
4316 MOZ_ASSERT(!mIsDocumentGone, "Unexpected DocumentStatesChanged");
4317 MOZ_ASSERT(mDocument);
4318 MOZ_ASSERT(!aStateMask.IsEmpty());
4320 if (mDidInitialize) {
4321 StyleSet()->InvalidateStyleForDocumentStateChanges(aStateMask);
4324 if (aStateMask.HasState(NS_DOCUMENT_STATE_WINDOW_INACTIVE)) {
4325 if (nsIFrame* root = mFrameConstructor->GetRootFrame()) {
4326 root->SchedulePaint();
4331 MOZ_CAN_RUN_SCRIPT_BOUNDARY void PresShell::AttributeWillChange(
4332 Element* aElement, int32_t aNameSpaceID, nsAtom* aAttribute,
4333 int32_t aModType) {
4334 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript());
4335 MOZ_ASSERT(!mIsDocumentGone, "Unexpected AttributeWillChange");
4336 MOZ_ASSERT(aElement->OwnerDoc() == mDocument, "Unexpected document");
4338 // XXXwaterson it might be more elegant to wait until after the
4339 // initial reflow to begin observing the document. That would
4340 // squelch any other inappropriate notifications as well.
4341 if (mDidInitialize) {
4342 nsAutoCauseReflowNotifier crNotifier(this);
4343 mPresContext->RestyleManager()->AttributeWillChange(aElement, aNameSpaceID,
4344 aAttribute, aModType);
4348 MOZ_CAN_RUN_SCRIPT_BOUNDARY void PresShell::AttributeChanged(
4349 Element* aElement, int32_t aNameSpaceID, nsAtom* aAttribute,
4350 int32_t aModType, const nsAttrValue* aOldValue) {
4351 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript());
4352 MOZ_ASSERT(!mIsDocumentGone, "Unexpected AttributeChanged");
4353 MOZ_ASSERT(aElement->OwnerDoc() == mDocument, "Unexpected document");
4355 // XXXwaterson it might be more elegant to wait until after the
4356 // initial reflow to begin observing the document. That would
4357 // squelch any other inappropriate notifications as well.
4358 if (mDidInitialize) {
4359 nsAutoCauseReflowNotifier crNotifier(this);
4360 mPresContext->RestyleManager()->AttributeChanged(
4361 aElement, aNameSpaceID, aAttribute, aModType, aOldValue);
4365 MOZ_CAN_RUN_SCRIPT_BOUNDARY void PresShell::ContentAppended(
4366 nsIContent* aFirstNewContent) {
4367 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript());
4368 MOZ_ASSERT(!mIsDocumentGone, "Unexpected ContentAppended");
4369 MOZ_ASSERT(aFirstNewContent->OwnerDoc() == mDocument, "Unexpected document");
4371 // We never call ContentAppended with a document as the container, so we can
4372 // assert that we have an nsIContent parent.
4373 MOZ_ASSERT(aFirstNewContent->GetParent());
4374 MOZ_ASSERT(aFirstNewContent->GetParent()->IsElement() ||
4375 aFirstNewContent->GetParent()->IsShadowRoot());
4377 if (!mDidInitialize) {
4378 return;
4381 nsAutoCauseReflowNotifier crNotifier(this);
4383 // Call this here so it only happens for real content mutations and
4384 // not cases when the frame constructor calls its own methods to force
4385 // frame reconstruction.
4386 mPresContext->RestyleManager()->ContentAppended(aFirstNewContent);
4388 mFrameConstructor->ContentAppended(
4389 aFirstNewContent, nsCSSFrameConstructor::InsertionKind::Async);
4392 MOZ_CAN_RUN_SCRIPT_BOUNDARY void PresShell::ContentInserted(
4393 nsIContent* aChild) {
4394 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript());
4395 MOZ_ASSERT(!mIsDocumentGone, "Unexpected ContentInserted");
4396 MOZ_ASSERT(aChild->OwnerDoc() == mDocument, "Unexpected document");
4398 if (!mDidInitialize) {
4399 return;
4402 nsAutoCauseReflowNotifier crNotifier(this);
4404 // Call this here so it only happens for real content mutations and
4405 // not cases when the frame constructor calls its own methods to force
4406 // frame reconstruction.
4407 mPresContext->RestyleManager()->ContentInserted(aChild);
4409 mFrameConstructor->ContentInserted(
4410 aChild, nsCSSFrameConstructor::InsertionKind::Async);
4413 MOZ_CAN_RUN_SCRIPT_BOUNDARY void PresShell::ContentRemoved(
4414 nsIContent* aChild, nsIContent* aPreviousSibling) {
4415 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript());
4416 MOZ_ASSERT(!mIsDocumentGone, "Unexpected ContentRemoved");
4417 MOZ_ASSERT(aChild->OwnerDoc() == mDocument, "Unexpected document");
4418 nsINode* container = aChild->GetParentNode();
4420 // Notify the ESM that the content has been removed, so that
4421 // it can clean up any state related to the content.
4423 mPresContext->EventStateManager()->ContentRemoved(mDocument, aChild);
4425 nsAutoCauseReflowNotifier crNotifier(this);
4427 // Call this here so it only happens for real content mutations and
4428 // not cases when the frame constructor calls its own methods to force
4429 // frame reconstruction.
4430 nsIContent* oldNextSibling = nullptr;
4432 // Editor calls into here with NAC via HTMLEditor::DeleteRefToAnonymousNode.
4433 // This could be asserted if that caller is fixed.
4434 if (MOZ_LIKELY(!aChild->IsRootOfNativeAnonymousSubtree())) {
4435 oldNextSibling = aPreviousSibling ? aPreviousSibling->GetNextSibling()
4436 : container->GetFirstChild();
4439 // After removing aChild from tree we should save information about live
4440 // ancestor
4441 if (mPointerEventTarget &&
4442 mPointerEventTarget->IsInclusiveDescendantOf(aChild)) {
4443 mPointerEventTarget = aChild->GetParent();
4446 mFrameConstructor->ContentRemoved(aChild, oldNextSibling,
4447 nsCSSFrameConstructor::REMOVE_CONTENT);
4449 // NOTE(emilio): It's important that this goes after the frame constructor
4450 // stuff, otherwise the frame constructor can't see elements which are
4451 // display: contents / display: none, because we'd have cleared all the style
4452 // data from there.
4453 mPresContext->RestyleManager()->ContentRemoved(aChild, oldNextSibling);
4456 void PresShell::NotifyCounterStylesAreDirty() {
4457 // TODO: Looks like that nsFrameConstructor::NotifyCounterStylesAreDirty()
4458 // does not run script. If so, we don't need to block script with
4459 // nsAutoCauseReflowNotifier here. Instead, there should be methods
4460 // and stack only class which manages only mChangeNestCount for
4461 // avoiding unnecessary `MOZ_CAN_RUN_SCRIPT` marking.
4462 nsAutoCauseReflowNotifier reflowNotifier(this);
4463 mFrameConstructor->NotifyCounterStylesAreDirty();
4466 bool PresShell::FrameIsAncestorOfDirtyRoot(nsIFrame* aFrame) const {
4467 return mDirtyRoots.FrameIsAncestorOfDirtyRoot(aFrame);
4470 void PresShell::ReconstructFrames() {
4471 MOZ_ASSERT(!mFrameConstructor->GetRootFrame() || mDidInitialize,
4472 "Must not have root frame before initial reflow");
4473 if (!mDidInitialize || mIsDestroying) {
4474 // Nothing to do here
4475 return;
4478 // Have to make sure that the content notifications are flushed before we
4479 // start messing with the frame model; otherwise we can get content doubling.
4481 // Also make sure that styles are flushed before calling into the frame
4482 // constructor, since that's what it expects.
4483 mDocument->FlushPendingNotifications(FlushType::Style);
4485 if (mIsDestroying) {
4486 return;
4489 nsAutoCauseReflowNotifier crNotifier(this);
4490 mFrameConstructor->ReconstructDocElementHierarchy(
4491 nsCSSFrameConstructor::InsertionKind::Sync);
4494 nsresult PresShell::RenderDocument(const nsRect& aRect,
4495 RenderDocumentFlags aFlags,
4496 nscolor aBackgroundColor,
4497 gfxContext* aThebesContext) {
4498 NS_ENSURE_TRUE(!(aFlags & RenderDocumentFlags::IsUntrusted),
4499 NS_ERROR_NOT_IMPLEMENTED);
4501 nsRootPresContext* rootPresContext = mPresContext->GetRootPresContext();
4502 if (rootPresContext) {
4503 rootPresContext->FlushWillPaintObservers();
4504 if (mIsDestroying) return NS_OK;
4507 nsAutoScriptBlocker blockScripts;
4509 // Set up the rectangle as the path in aThebesContext
4510 gfxRect r(0, 0, nsPresContext::AppUnitsToFloatCSSPixels(aRect.width),
4511 nsPresContext::AppUnitsToFloatCSSPixels(aRect.height));
4512 aThebesContext->NewPath();
4513 #ifdef MOZ_GFX_OPTIMIZE_MOBILE
4514 aThebesContext->SnappedRectangle(r);
4515 #else
4516 aThebesContext->Rectangle(r);
4517 #endif
4519 nsIFrame* rootFrame = mFrameConstructor->GetRootFrame();
4520 if (!rootFrame) {
4521 // Nothing to paint, just fill the rect
4522 aThebesContext->SetColor(sRGBColor::FromABGR(aBackgroundColor));
4523 aThebesContext->Fill();
4524 return NS_OK;
4527 gfxContextAutoSaveRestore save(aThebesContext);
4529 MOZ_ASSERT(aThebesContext->CurrentOp() == CompositionOp::OP_OVER);
4531 aThebesContext->Clip();
4533 nsDeviceContext* devCtx = mPresContext->DeviceContext();
4535 gfxPoint offset(-nsPresContext::AppUnitsToFloatCSSPixels(aRect.x),
4536 -nsPresContext::AppUnitsToFloatCSSPixels(aRect.y));
4537 gfxFloat scale =
4538 gfxFloat(devCtx->AppUnitsPerDevPixel()) / AppUnitsPerCSSPixel();
4540 // Since canvas APIs use floats to set up their matrices, we may have some
4541 // slight rounding errors here. We use NudgeToIntegers() here to adjust
4542 // matrix components that are integers up to the accuracy of floats to be
4543 // those integers.
4544 gfxMatrix newTM = aThebesContext->CurrentMatrixDouble()
4545 .PreTranslate(offset)
4546 .PreScale(scale, scale)
4547 .NudgeToIntegers();
4548 aThebesContext->SetMatrixDouble(newTM);
4550 AutoSaveRestoreRenderingState _(this);
4552 bool wouldFlushRetainedLayers = false;
4553 PaintFrameFlags flags = PaintFrameFlags::IgnoreSuppression;
4554 if (aThebesContext->CurrentMatrix().HasNonIntegerTranslation()) {
4555 flags |= PaintFrameFlags::InTransform;
4557 if (!(aFlags & RenderDocumentFlags::AsyncDecodeImages)) {
4558 flags |= PaintFrameFlags::SyncDecodeImages;
4560 if (aFlags & RenderDocumentFlags::UseHighQualityScaling) {
4561 flags |= PaintFrameFlags::UseHighQualityScaling;
4563 if (aFlags & RenderDocumentFlags::UseWidgetLayers) {
4564 // We only support using widget layers on display root's with widgets.
4565 nsView* view = rootFrame->GetView();
4566 if (view && view->GetWidget() &&
4567 nsLayoutUtils::GetDisplayRootFrame(rootFrame) == rootFrame) {
4568 LayerManager* layerManager = view->GetWidget()->GetLayerManager();
4569 // ClientLayerManagers or WebRenderLayerManagers in content processes
4570 // don't support taking snapshots.
4571 if (layerManager &&
4572 (!layerManager->AsKnowsCompositor() || XRE_IsParentProcess())) {
4573 flags |= PaintFrameFlags::WidgetLayers;
4577 if (!(aFlags & RenderDocumentFlags::DrawCaret)) {
4578 wouldFlushRetainedLayers = true;
4579 flags |= PaintFrameFlags::HideCaret;
4581 if (aFlags & RenderDocumentFlags::IgnoreViewportScrolling) {
4582 wouldFlushRetainedLayers = !IgnoringViewportScrolling();
4583 mRenderingStateFlags |= RenderingStateFlags::IgnoringViewportScrolling;
4585 if (aFlags & RenderDocumentFlags::ResetViewportScrolling) {
4586 wouldFlushRetainedLayers = true;
4587 flags |= PaintFrameFlags::ResetViewportScrolling;
4589 if (aFlags & RenderDocumentFlags::DrawWindowNotFlushing) {
4590 mRenderingStateFlags |= RenderingStateFlags::DrawWindowNotFlushing;
4592 if (aFlags & RenderDocumentFlags::DocumentRelative) {
4593 // XXX be smarter about this ... drawWindow might want a rect
4594 // that's "pretty close" to what our retained layer tree covers.
4595 // In that case, it wouldn't disturb normal rendering too much,
4596 // and we should allow it.
4597 wouldFlushRetainedLayers = true;
4598 flags |= PaintFrameFlags::DocumentRelative;
4601 // Don't let drawWindow blow away our retained layer tree
4602 if ((flags & PaintFrameFlags::WidgetLayers) && wouldFlushRetainedLayers) {
4603 flags &= ~PaintFrameFlags::WidgetLayers;
4606 nsLayoutUtils::PaintFrame(aThebesContext, rootFrame, nsRegion(aRect),
4607 aBackgroundColor,
4608 nsDisplayListBuilderMode::Painting, flags);
4610 return NS_OK;
4614 * Clip the display list aList to a range. Returns the clipped
4615 * rectangle surrounding the range.
4617 nsRect PresShell::ClipListToRange(nsDisplayListBuilder* aBuilder,
4618 nsDisplayList* aList, nsRange* aRange) {
4619 // iterate though the display items and add up the bounding boxes of each.
4620 // This will allow the total area of the frames within the range to be
4621 // determined. To do this, remove an item from the bottom of the list, check
4622 // whether it should be part of the range, and if so, append it to the top
4623 // of the temporary list tmpList. If the item is a text frame at the end of
4624 // the selection range, clip it to the portion of the text frame that is
4625 // part of the selection. Then, append the wrapper to the top of the list.
4626 // Otherwise, just delete the item and don't append it.
4627 nsRect surfaceRect;
4628 nsDisplayList tmpList;
4630 nsDisplayItem* i;
4631 while ((i = aList->RemoveBottom())) {
4632 if (i->GetType() == DisplayItemType::TYPE_CONTAINER) {
4633 tmpList.AppendToTop(i);
4634 surfaceRect.UnionRect(
4635 surfaceRect, ClipListToRange(aBuilder, i->GetChildren(), aRange));
4636 continue;
4639 // itemToInsert indiciates the item that should be inserted into the
4640 // temporary list. If null, no item should be inserted.
4641 nsDisplayItem* itemToInsert = nullptr;
4642 nsIFrame* frame = i->Frame();
4643 nsIContent* content = frame->GetContent();
4644 if (content) {
4645 bool atStart = (content == aRange->GetStartContainer());
4646 bool atEnd = (content == aRange->GetEndContainer());
4647 if ((atStart || atEnd) && frame->IsTextFrame()) {
4648 auto [frameStartOffset, frameEndOffset] = frame->GetOffsets();
4650 int32_t hilightStart =
4651 atStart ? std::max(static_cast<int32_t>(aRange->StartOffset()),
4652 frameStartOffset)
4653 : frameStartOffset;
4654 int32_t hilightEnd =
4655 atEnd ? std::min(static_cast<int32_t>(aRange->EndOffset()),
4656 frameEndOffset)
4657 : frameEndOffset;
4658 if (hilightStart < hilightEnd) {
4659 // determine the location of the start and end edges of the range.
4660 nsPoint startPoint, endPoint;
4661 frame->GetPointFromOffset(hilightStart, &startPoint);
4662 frame->GetPointFromOffset(hilightEnd, &endPoint);
4664 // The clip rectangle is determined by taking the the start and
4665 // end points of the range, offset from the reference frame.
4666 // Because of rtl, the end point may be to the left of (or above,
4667 // in vertical mode) the start point, so x (or y) is set to the
4668 // lower of the values.
4669 nsRect textRect(aBuilder->ToReferenceFrame(frame), frame->GetSize());
4670 if (frame->GetWritingMode().IsVertical()) {
4671 nscoord y = std::min(startPoint.y, endPoint.y);
4672 textRect.y += y;
4673 textRect.height = std::max(startPoint.y, endPoint.y) - y;
4674 } else {
4675 nscoord x = std::min(startPoint.x, endPoint.x);
4676 textRect.x += x;
4677 textRect.width = std::max(startPoint.x, endPoint.x) - x;
4679 surfaceRect.UnionRect(surfaceRect, textRect);
4681 const ActiveScrolledRoot* asr = i->GetActiveScrolledRoot();
4683 DisplayItemClip newClip;
4684 newClip.SetTo(textRect);
4686 const DisplayItemClipChain* newClipChain =
4687 aBuilder->AllocateDisplayItemClipChain(newClip, asr, nullptr);
4689 i->IntersectClip(aBuilder, newClipChain, true);
4690 itemToInsert = i;
4693 // Don't try to descend into subdocuments.
4694 // If this ever changes we'd need to add handling for subdocuments with
4695 // different zoom levels.
4696 else if (content->GetUncomposedDoc() ==
4697 aRange->GetStartContainer()->GetUncomposedDoc()) {
4698 // if the node is within the range, append it to the temporary list
4699 bool before, after;
4700 nsresult rv =
4701 RangeUtils::CompareNodeToRange(content, aRange, &before, &after);
4702 if (NS_SUCCEEDED(rv) && !before && !after) {
4703 itemToInsert = i;
4704 bool snap;
4705 surfaceRect.UnionRect(surfaceRect, i->GetBounds(aBuilder, &snap));
4710 // insert the item into the list if necessary. If the item has a child
4711 // list, insert that as well
4712 nsDisplayList* sublist = i->GetSameCoordinateSystemChildren();
4713 if (itemToInsert || sublist) {
4714 tmpList.AppendToTop(itemToInsert ? itemToInsert : i);
4715 // if the item is a list, iterate over it as well
4716 if (sublist)
4717 surfaceRect.UnionRect(surfaceRect,
4718 ClipListToRange(aBuilder, sublist, aRange));
4719 } else {
4720 // otherwise, just delete the item and don't readd it to the list
4721 i->Destroy(aBuilder);
4725 // now add all the items back onto the original list again
4726 aList->AppendToTop(&tmpList);
4728 return surfaceRect;
4731 #ifdef DEBUG
4732 # include <stdio.h>
4734 static bool gDumpRangePaintList = false;
4735 #endif
4737 UniquePtr<RangePaintInfo> PresShell::CreateRangePaintInfo(
4738 nsRange* aRange, nsRect& aSurfaceRect, bool aForPrimarySelection) {
4739 nsIFrame* ancestorFrame = nullptr;
4740 nsIFrame* rootFrame = GetRootFrame();
4742 // If the start or end of the range is the document, just use the root
4743 // frame, otherwise get the common ancestor of the two endpoints of the
4744 // range.
4745 nsINode* startContainer = aRange->GetStartContainer();
4746 nsINode* endContainer = aRange->GetEndContainer();
4747 Document* doc = startContainer->GetComposedDoc();
4748 if (startContainer == doc || endContainer == doc) {
4749 ancestorFrame = rootFrame;
4750 } else {
4751 nsINode* ancestor = nsContentUtils::GetClosestCommonInclusiveAncestor(
4752 startContainer, endContainer);
4753 NS_ASSERTION(!ancestor || ancestor->IsContent(),
4754 "common ancestor is not content");
4756 while (ancestor && ancestor->IsContent()) {
4757 ancestorFrame = ancestor->AsContent()->GetPrimaryFrame();
4758 if (ancestorFrame) {
4759 break;
4762 ancestor = ancestor->GetParentOrShadowHostNode();
4765 // use the nearest ancestor frame that includes all continuations as the
4766 // root for building the display list
4767 while (ancestorFrame &&
4768 nsLayoutUtils::GetNextContinuationOrIBSplitSibling(ancestorFrame))
4769 ancestorFrame = ancestorFrame->GetParent();
4772 if (!ancestorFrame) {
4773 return nullptr;
4776 // get a display list containing the range
4777 auto info = MakeUnique<RangePaintInfo>(aRange, ancestorFrame);
4778 info->mBuilder.SetIncludeAllOutOfFlows();
4779 if (aForPrimarySelection) {
4780 info->mBuilder.SetSelectedFramesOnly();
4782 info->mBuilder.EnterPresShell(ancestorFrame);
4784 ContentSubtreeIterator subtreeIter;
4785 nsresult rv = subtreeIter.Init(aRange);
4786 if (NS_FAILED(rv)) {
4787 return nullptr;
4790 auto BuildDisplayListForNode = [&](nsINode* aNode) {
4791 if (MOZ_UNLIKELY(!aNode->IsContent())) {
4792 return;
4794 nsIFrame* frame = aNode->AsContent()->GetPrimaryFrame();
4795 // XXX deal with frame being null due to display:contents
4796 for (; frame;
4797 frame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(frame)) {
4798 info->mBuilder.SetVisibleRect(frame->InkOverflowRect());
4799 info->mBuilder.SetDirtyRect(frame->InkOverflowRect());
4800 frame->BuildDisplayListForStackingContext(&info->mBuilder, &info->mList);
4803 if (startContainer->NodeType() == nsINode::TEXT_NODE) {
4804 BuildDisplayListForNode(startContainer);
4806 for (; !subtreeIter.IsDone(); subtreeIter.Next()) {
4807 nsCOMPtr<nsINode> node = subtreeIter.GetCurrentNode();
4808 BuildDisplayListForNode(node);
4810 if (endContainer != startContainer &&
4811 endContainer->NodeType() == nsINode::TEXT_NODE) {
4812 BuildDisplayListForNode(endContainer);
4815 // If one of the ancestor presShells (including this one) has a resolution
4816 // set, we may have some APZ zoom applied. That means we may want to rasterize
4817 // the nodes at that zoom level. Populate `info` with the relevant information
4818 // so that the caller can decide what to do. Also wrap the display list in
4819 // appropriate nsDisplayAsyncZoom display items. This code handles the general
4820 // case with nested async zooms (even though that never actually happens),
4821 // because it fell out of the implementation for free.
4823 // TODO: Do we need to do the same for ancestor transforms?
4824 for (nsPresContext* ctx = GetPresContext(); ctx;
4825 ctx = ctx->GetParentPresContext()) {
4826 PresShell* shell = ctx->PresShell();
4827 float resolution = shell->GetResolution();
4829 // If we are at the root document in the process, try to see if documents
4830 // in enclosing processes have a resolution and include that as well.
4831 if (!ctx->GetParentPresContext()) {
4832 // xScale is an arbitrary choice. Outside of edge cases involving CSS
4833 // transforms, xScale == yScale so it doesn't matter.
4834 resolution *= ViewportUtils::TryInferEnclosingResolution(shell).xScale;
4837 if (resolution == 1.0) {
4838 continue;
4841 info->mResolution *= resolution;
4842 nsIFrame* rootScrollFrame = shell->GetRootScrollFrame();
4843 ViewID zoomedId =
4844 nsLayoutUtils::FindOrCreateIDFor(rootScrollFrame->GetContent());
4846 nsDisplayList wrapped;
4847 wrapped.AppendNewToTop<nsDisplayAsyncZoom>(&info->mBuilder, rootScrollFrame,
4848 &info->mList, nullptr, zoomedId);
4849 info->mList.AppendToTop(&wrapped);
4852 #ifdef DEBUG
4853 if (gDumpRangePaintList) {
4854 fprintf(stderr, "CreateRangePaintInfo --- before ClipListToRange:\n");
4855 nsIFrame::PrintDisplayList(&(info->mBuilder), info->mList);
4857 #endif
4859 nsRect rangeRect = ClipListToRange(&info->mBuilder, &info->mList, aRange);
4861 info->mBuilder.LeavePresShell(ancestorFrame, &info->mList);
4863 #ifdef DEBUG
4864 if (gDumpRangePaintList) {
4865 fprintf(stderr, "CreateRangePaintInfo --- after ClipListToRange:\n");
4866 nsIFrame::PrintDisplayList(&(info->mBuilder), info->mList);
4868 #endif
4870 // determine the offset of the reference frame for the display list
4871 // to the root frame. This will allow the coordinates used when painting
4872 // to all be offset from the same point
4873 info->mRootOffset = ancestorFrame->GetBoundingClientRect().TopLeft();
4874 rangeRect.MoveBy(info->mRootOffset);
4875 aSurfaceRect.UnionRect(aSurfaceRect, rangeRect);
4877 return info;
4880 already_AddRefed<SourceSurface> PresShell::PaintRangePaintInfo(
4881 const nsTArray<UniquePtr<RangePaintInfo>>& aItems, Selection* aSelection,
4882 const Maybe<CSSIntRegion>& aRegion, nsRect aArea,
4883 const LayoutDeviceIntPoint aPoint, LayoutDeviceIntRect* aScreenRect,
4884 RenderImageFlags aFlags) {
4885 nsPresContext* pc = GetPresContext();
4886 if (!pc || aArea.width == 0 || aArea.height == 0) return nullptr;
4888 // use the rectangle to create the surface
4889 LayoutDeviceIntRect pixelArea = LayoutDeviceIntRect::FromAppUnitsToOutside(
4890 aArea, pc->AppUnitsPerDevPixel());
4892 // if the image should not be resized, scale must be 1
4893 float scale = 1.0;
4895 nsRect maxSize;
4896 pc->DeviceContext()->GetClientRect(maxSize);
4898 // check if the image should be resized
4899 bool resize = !!(aFlags & RenderImageFlags::AutoScale);
4901 if (resize) {
4902 // check if image-resizing-algorithm should be used
4903 if (aFlags & RenderImageFlags::IsImage) {
4904 // get max screensize
4905 int32_t maxWidth = pc->AppUnitsToDevPixels(maxSize.width);
4906 int32_t maxHeight = pc->AppUnitsToDevPixels(maxSize.height);
4907 // resize image relative to the screensize
4908 // get best height/width relative to screensize
4909 float bestHeight = float(maxHeight) * RELATIVE_SCALEFACTOR;
4910 float bestWidth = float(maxWidth) * RELATIVE_SCALEFACTOR;
4911 // calculate scale for bestWidth
4912 float adjustedScale = bestWidth / float(pixelArea.width);
4913 // get the worst height (height when width is perfect)
4914 float worstHeight = float(pixelArea.height) * adjustedScale;
4915 // get the difference of best and worst height
4916 float difference = bestHeight - worstHeight;
4917 // halve the difference and add it to worstHeight to get
4918 // the best compromise between bestHeight and bestWidth,
4919 // then calculate the corresponding scale factor
4920 adjustedScale = (worstHeight + difference / 2) / float(pixelArea.height);
4921 // prevent upscaling
4922 scale = std::min(scale, adjustedScale);
4923 } else {
4924 // get half of max screensize
4925 int32_t maxWidth = pc->AppUnitsToDevPixels(maxSize.width >> 1);
4926 int32_t maxHeight = pc->AppUnitsToDevPixels(maxSize.height >> 1);
4927 if (pixelArea.width > maxWidth || pixelArea.height > maxHeight) {
4928 // divide the maximum size by the image size in both directions.
4929 // Whichever direction produces the smallest result determines how much
4930 // should be scaled.
4931 if (pixelArea.width > maxWidth)
4932 scale = std::min(scale, float(maxWidth) / pixelArea.width);
4933 if (pixelArea.height > maxHeight)
4934 scale = std::min(scale, float(maxHeight) / pixelArea.height);
4938 // Pick a resolution scale factor that is the highest we need for any of
4939 // the items. This means some items may get rendered at a higher-than-needed
4940 // resolution but at least nothing will be avoidably blurry.
4941 float resolutionScale = 1.0;
4942 for (const UniquePtr<RangePaintInfo>& rangeInfo : aItems) {
4943 resolutionScale = std::max(resolutionScale, rangeInfo->mResolution);
4945 float unclampedResolution = resolutionScale;
4946 // Clamp the resolution scale so that `pixelArea` when scaled by `scale` and
4947 // `resolutionScale` isn't bigger than `maxSize`. This prevents creating
4948 // giant/unbounded images.
4949 resolutionScale =
4950 std::min(resolutionScale, maxSize.width / (scale * pixelArea.width));
4951 resolutionScale =
4952 std::min(resolutionScale, maxSize.height / (scale * pixelArea.height));
4953 // The following assert should only get hit if pixelArea scaled by `scale`
4954 // alone would already have been bigger than `maxSize`, which should never
4955 // be the case. For release builds we handle gracefully by reverting
4956 // resolutionScale to 1.0 to avoid unexpected consequences.
4957 MOZ_ASSERT(resolutionScale >= 1.0);
4958 resolutionScale = std::max(1.0f, resolutionScale);
4960 scale *= resolutionScale;
4962 // Now we need adjust the output screen position of the surface based on the
4963 // scaling factor and any APZ zoom that may be in effect. The goal is here
4964 // to set `aScreenRect`'s top-left corner (in screen-relative LD pixels)
4965 // such that the scaling effect on the surface appears anchored at `aPoint`
4966 // ("anchor" here is like "transform-origin"). When this code is e.g. used
4967 // to generate a drag image for dragging operations, `aPoint` refers to the
4968 // position of the mouse cursor (also in screen-relative LD pixels), and the
4969 // user-visible effect of doing this is that the point at which the user
4970 // clicked to start the drag remains under the mouse during the drag.
4972 // In order to do this we first compute the top-left corner of the
4973 // pixelArea is screen-relative LD pixels.
4974 LayoutDevicePoint visualPoint = ViewportUtils::ToScreenRelativeVisual(
4975 LayoutDevicePoint(pixelArea.TopLeft()), pc);
4976 // And then adjust the output screen position based on that, which we can do
4977 // since everything here is screen-relative LD pixels. Note that the scale
4978 // factor we use here is the effective "transform" scale applied to the
4979 // content we're painting, relative to the scale at which it would normally
4980 // get painted at as part of page rendering (`unclampedResolution`).
4981 float scaleRelativeToNormalContent = scale / unclampedResolution;
4982 aScreenRect->x = NSToIntFloor(aPoint.x - float(aPoint.x - visualPoint.x) *
4983 scaleRelativeToNormalContent);
4984 aScreenRect->y = NSToIntFloor(aPoint.y - float(aPoint.y - visualPoint.y) *
4985 scaleRelativeToNormalContent);
4987 pixelArea.width = NSToIntFloor(float(pixelArea.width) * scale);
4988 pixelArea.height = NSToIntFloor(float(pixelArea.height) * scale);
4989 if (!pixelArea.width || !pixelArea.height) {
4990 return nullptr;
4992 } else {
4993 // move aScreenRect to the position of the surface in screen coordinates
4994 LayoutDevicePoint visualPoint = ViewportUtils::ToScreenRelativeVisual(
4995 LayoutDevicePoint(pixelArea.TopLeft()), pc);
4996 aScreenRect->MoveTo(RoundedToInt(visualPoint));
4998 aScreenRect->width = pixelArea.width;
4999 aScreenRect->height = pixelArea.height;
5001 RefPtr<DrawTarget> dt =
5002 gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
5003 IntSize(pixelArea.width, pixelArea.height), SurfaceFormat::B8G8R8A8);
5004 if (!dt || !dt->IsValid()) {
5005 return nullptr;
5008 RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(dt);
5009 MOZ_ASSERT(ctx); // already checked the draw target above
5011 if (aRegion) {
5012 RefPtr<PathBuilder> builder = dt->CreatePathBuilder(FillRule::FILL_WINDING);
5014 // Convert aRegion from CSS pixels to dev pixels
5015 nsIntRegion region = aRegion->ToAppUnits(AppUnitsPerCSSPixel())
5016 .ToOutsidePixels(pc->AppUnitsPerDevPixel());
5017 for (auto iter = region.RectIter(); !iter.Done(); iter.Next()) {
5018 const IntRect& rect = iter.Get();
5020 builder->MoveTo(rect.TopLeft());
5021 builder->LineTo(rect.TopRight());
5022 builder->LineTo(rect.BottomRight());
5023 builder->LineTo(rect.BottomLeft());
5024 builder->LineTo(rect.TopLeft());
5027 RefPtr<Path> path = builder->Finish();
5028 ctx->Clip(path);
5031 gfxMatrix initialTM = ctx->CurrentMatrixDouble();
5033 if (resize) {
5034 initialTM.PreScale(scale, scale);
5037 // translate so that points are relative to the surface area
5038 gfxPoint surfaceOffset = nsLayoutUtils::PointToGfxPoint(
5039 -aArea.TopLeft(), pc->AppUnitsPerDevPixel());
5040 initialTM.PreTranslate(surfaceOffset);
5042 // temporarily hide the selection so that text is drawn normally. If a
5043 // selection is being rendered, use that, otherwise use the presshell's
5044 // selection.
5045 RefPtr<nsFrameSelection> frameSelection;
5046 if (aSelection) {
5047 frameSelection = aSelection->GetFrameSelection();
5048 } else {
5049 frameSelection = FrameSelection();
5051 int16_t oldDisplaySelection = frameSelection->GetDisplaySelection();
5052 frameSelection->SetDisplaySelection(nsISelectionController::SELECTION_HIDDEN);
5054 // next, paint each range in the selection
5055 for (const UniquePtr<RangePaintInfo>& rangeInfo : aItems) {
5056 // the display lists paint relative to the offset from the reference
5057 // frame, so account for that translation too:
5058 gfxPoint rootOffset = nsLayoutUtils::PointToGfxPoint(
5059 rangeInfo->mRootOffset, pc->AppUnitsPerDevPixel());
5060 ctx->SetMatrixDouble(initialTM.PreTranslate(rootOffset));
5061 aArea.MoveBy(-rangeInfo->mRootOffset.x, -rangeInfo->mRootOffset.y);
5062 nsRegion visible(aArea);
5063 RefPtr<LayerManager> layerManager = rangeInfo->mList.PaintRoot(
5064 &rangeInfo->mBuilder, ctx, nsDisplayList::PAINT_DEFAULT, Nothing());
5065 aArea.MoveBy(rangeInfo->mRootOffset.x, rangeInfo->mRootOffset.y);
5068 // restore the old selection display state
5069 frameSelection->SetDisplaySelection(oldDisplaySelection);
5071 return dt->Snapshot();
5074 already_AddRefed<SourceSurface> PresShell::RenderNode(
5075 nsINode* aNode, const Maybe<CSSIntRegion>& aRegion,
5076 const LayoutDeviceIntPoint aPoint, LayoutDeviceIntRect* aScreenRect,
5077 RenderImageFlags aFlags) {
5078 // area will hold the size of the surface needed to draw the node, measured
5079 // from the root frame.
5080 nsRect area;
5081 nsTArray<UniquePtr<RangePaintInfo>> rangeItems;
5083 // nothing to draw if the node isn't in a document
5084 if (!aNode->IsInComposedDoc()) {
5085 return nullptr;
5088 RefPtr<nsRange> range = nsRange::Create(aNode);
5089 IgnoredErrorResult rv;
5090 range->SelectNode(*aNode, rv);
5091 if (rv.Failed()) {
5092 return nullptr;
5095 UniquePtr<RangePaintInfo> info = CreateRangePaintInfo(range, area, false);
5096 if (info) {
5097 // XXX(Bug 1631371) Check if this should use a fallible operation as it
5098 // pretended earlier, or change the return type to void.
5099 rangeItems.AppendElement(std::move(info));
5102 Maybe<CSSIntRegion> region = aRegion;
5103 if (region) {
5104 // combine the area with the supplied region
5105 CSSIntRect rrectPixels = region->GetBounds();
5107 nsRect rrect = ToAppUnits(rrectPixels, AppUnitsPerCSSPixel());
5108 area.IntersectRect(area, rrect);
5110 nsPresContext* pc = GetPresContext();
5111 if (!pc) return nullptr;
5113 // move the region so that it is offset from the topleft corner of the
5114 // surface
5115 region->MoveBy(-nsPresContext::AppUnitsToIntCSSPixels(area.x),
5116 -nsPresContext::AppUnitsToIntCSSPixels(area.y));
5119 return PaintRangePaintInfo(rangeItems, nullptr, region, area, aPoint,
5120 aScreenRect, aFlags);
5123 already_AddRefed<SourceSurface> PresShell::RenderSelection(
5124 Selection* aSelection, const LayoutDeviceIntPoint aPoint,
5125 LayoutDeviceIntRect* aScreenRect, RenderImageFlags aFlags) {
5126 // area will hold the size of the surface needed to draw the selection,
5127 // measured from the root frame.
5128 nsRect area;
5129 nsTArray<UniquePtr<RangePaintInfo>> rangeItems;
5131 // iterate over each range and collect them into the rangeItems array.
5132 // This is done so that the size of selection can be determined so as
5133 // to allocate a surface area
5134 uint32_t numRanges = aSelection->RangeCount();
5135 NS_ASSERTION(numRanges > 0, "RenderSelection called with no selection");
5137 for (uint32_t r = 0; r < numRanges; r++) {
5138 RefPtr<nsRange> range = aSelection->GetRangeAt(r);
5140 UniquePtr<RangePaintInfo> info = CreateRangePaintInfo(range, area, true);
5141 if (info) {
5142 // XXX(Bug 1631371) Check if this should use a fallible operation as it
5143 // pretended earlier.
5144 rangeItems.AppendElement(std::move(info));
5148 return PaintRangePaintInfo(rangeItems, aSelection, Nothing(), area, aPoint,
5149 aScreenRect, aFlags);
5152 void PresShell::AddPrintPreviewBackgroundItem(nsDisplayListBuilder* aBuilder,
5153 nsDisplayList* aList,
5154 nsIFrame* aFrame,
5155 const nsRect& aBounds) {
5156 aList->AppendNewToBottom<nsDisplaySolidColor>(aBuilder, aFrame, aBounds,
5157 NS_RGB(115, 115, 115));
5160 static bool AddCanvasBackgroundColor(const nsDisplayList* aList,
5161 nsIFrame* aCanvasFrame, nscolor aColor,
5162 bool aCSSBackgroundColor) {
5163 for (nsDisplayItem* i : *aList) {
5164 const DisplayItemType type = i->GetType();
5166 if (i->Frame() == aCanvasFrame &&
5167 type == DisplayItemType::TYPE_CANVAS_BACKGROUND_COLOR) {
5168 auto* bg = static_cast<nsDisplayCanvasBackgroundColor*>(i);
5169 bg->SetExtraBackgroundColor(aColor);
5170 return true;
5173 const bool isBlendContainer =
5174 type == DisplayItemType::TYPE_BLEND_CONTAINER ||
5175 type == DisplayItemType::TYPE_TABLE_BLEND_CONTAINER;
5177 nsDisplayList* sublist = i->GetSameCoordinateSystemChildren();
5178 if (sublist && !(isBlendContainer && !aCSSBackgroundColor) &&
5179 AddCanvasBackgroundColor(sublist, aCanvasFrame, aColor,
5180 aCSSBackgroundColor))
5181 return true;
5183 return false;
5186 void PresShell::AddCanvasBackgroundColorItem(
5187 nsDisplayListBuilder* aBuilder, nsDisplayList* aList, nsIFrame* aFrame,
5188 const nsRect& aBounds, nscolor aBackstopColor,
5189 AddCanvasBackgroundColorFlags aFlags) {
5190 if (aBounds.IsEmpty()) {
5191 return;
5193 // We don't want to add an item for the canvas background color if the frame
5194 // (sub)tree we are painting doesn't include any canvas frames. There isn't
5195 // an easy way to check this directly, but if we check if the root of the
5196 // (sub)tree we are painting is a canvas frame that should cover us in all
5197 // cases (it will usually be a viewport frame when we have a canvas frame in
5198 // the (sub)tree).
5199 if (!(aFlags & AddCanvasBackgroundColorFlags::ForceDraw) &&
5200 !nsCSSRendering::IsCanvasFrame(aFrame)) {
5201 return;
5204 nscolor bgcolor = NS_ComposeColors(aBackstopColor, mCanvasBackgroundColor);
5205 if (NS_GET_A(bgcolor) == 0) return;
5207 // To make layers work better, we want to avoid having a big non-scrolled
5208 // color background behind a scrolled transparent background. Instead,
5209 // we'll try to move the color background into the scrolled content
5210 // by making nsDisplayCanvasBackground paint it.
5211 // If we're only adding an unscrolled item, then pretend that we've
5212 // already done it.
5213 bool addedScrollingBackgroundColor =
5214 !!(aFlags & AddCanvasBackgroundColorFlags::AppendUnscrolledOnly);
5215 if (!aFrame->GetParent() && !addedScrollingBackgroundColor) {
5216 nsIScrollableFrame* sf =
5217 aFrame->PresShell()->GetRootScrollFrameAsScrollable();
5218 if (sf) {
5219 nsCanvasFrame* canvasFrame = do_QueryFrame(sf->GetScrolledFrame());
5220 if (canvasFrame && canvasFrame->IsVisibleForPainting()) {
5221 addedScrollingBackgroundColor = AddCanvasBackgroundColor(
5222 aList, canvasFrame, bgcolor, mHasCSSBackgroundColor);
5227 // With async scrolling, we'd like to have two instances of the background
5228 // color: one that scrolls with the content (for the reasons stated above),
5229 // and one underneath which does not scroll with the content, but which can
5230 // be shown during checkerboarding and overscroll.
5231 // We can only do that if the color is opaque.
5232 bool forceUnscrolledItem =
5233 nsLayoutUtils::UsesAsyncScrolling(aFrame) && NS_GET_A(bgcolor) == 255;
5235 if (!addedScrollingBackgroundColor || forceUnscrolledItem) {
5236 aList->AppendNewToBottom<nsDisplaySolidColor>(aBuilder, aFrame, aBounds,
5237 bgcolor);
5241 static bool IsTransparentContainerElement(nsPresContext* aPresContext) {
5242 nsCOMPtr<nsIDocShell> docShell = aPresContext->GetDocShell();
5243 if (!docShell) {
5244 return false;
5247 nsCOMPtr<nsPIDOMWindowOuter> pwin = docShell->GetWindow();
5248 if (!pwin) return false;
5249 nsCOMPtr<Element> containerElement = pwin->GetFrameElementInternal();
5251 BrowserChild* tab = BrowserChild::GetFrom(docShell);
5252 if (tab) {
5253 // Check if presShell is the top PresShell. Only the top can
5254 // influence the canvas background color.
5255 if (aPresContext->GetPresShell() != tab->GetTopLevelPresShell()) {
5256 tab = nullptr;
5260 return (containerElement && containerElement->HasAttr(
5261 kNameSpaceID_None, nsGkAtoms::transparent)) ||
5262 (tab && tab->IsTransparent());
5265 nscolor PresShell::GetDefaultBackgroundColorToDraw() {
5266 if (!mPresContext || !mPresContext->GetBackgroundColorDraw()) {
5267 return NS_RGB(255, 255, 255);
5270 nscolor backgroundColor = mPresContext->DefaultBackgroundColor();
5271 if (backgroundColor != NS_RGB(255, 255, 255)) {
5272 // Return non-default color.
5273 return backgroundColor;
5276 // Use a dark background for top-level about:blank that is inaccessible to
5277 // content JS.
5278 Document* doc = GetDocument();
5279 BrowsingContext* bc = doc->GetBrowsingContext();
5280 if (bc && bc->IsTop() && !bc->HasOpener() && doc->GetDocumentURI() &&
5281 NS_IsAboutBlank(doc->GetDocumentURI()) &&
5282 doc->PrefersColorScheme(Document::IgnoreRFP::Yes) ==
5283 StylePrefersColorScheme::Dark) {
5284 // Use --in-content-page-background for prefers-color-scheme: dark.
5285 return StaticPrefs::browser_proton_enabled() ? NS_RGB(0x1C, 0x1B, 0x22)
5286 : NS_RGB(0x2A, 0x2A, 0x2E);
5289 return backgroundColor;
5292 void PresShell::UpdateCanvasBackground() {
5293 // If we have a frame tree and it has style information that
5294 // specifies the background color of the canvas, update our local
5295 // cache of that color.
5296 nsIFrame* rootStyleFrame = FrameConstructor()->GetRootElementStyleFrame();
5297 if (rootStyleFrame) {
5298 ComputedStyle* bgStyle =
5299 nsCSSRendering::FindRootFrameBackground(rootStyleFrame);
5300 // XXX We should really be passing the canvasframe, not the root element
5301 // style frame but we don't have access to the canvasframe here. It isn't
5302 // a problem because only a few frames can return something other than true
5303 // and none of them would be a canvas frame or root element style frame.
5304 bool drawBackgroundImage = false;
5305 bool drawBackgroundColor = false;
5306 const nsStyleDisplay* disp = rootStyleFrame->StyleDisplay();
5307 StyleAppearance appearance = disp->EffectiveAppearance();
5308 if (rootStyleFrame->IsThemed(disp) &&
5309 appearance != StyleAppearance::MozWinGlass &&
5310 appearance != StyleAppearance::MozWinBorderlessGlass) {
5311 // Ignore the CSS background-color if -moz-appearance is used and it is
5312 // not one of the glass values. (Windows 7 Glass has traditionally not
5313 // overridden background colors, so we preserve that behavior for now.)
5314 mCanvasBackgroundColor = NS_RGBA(0, 0, 0, 0);
5315 } else {
5316 mCanvasBackgroundColor = nsCSSRendering::DetermineBackgroundColor(
5317 mPresContext, bgStyle, rootStyleFrame, drawBackgroundImage,
5318 drawBackgroundColor);
5320 mHasCSSBackgroundColor = drawBackgroundColor;
5321 if (mPresContext->IsRootContentDocumentCrossProcess() &&
5322 !IsTransparentContainerElement(mPresContext)) {
5323 mCanvasBackgroundColor = NS_ComposeColors(
5324 GetDefaultBackgroundColorToDraw(), mCanvasBackgroundColor);
5328 // If the root element of the document (ie html) has style 'display: none'
5329 // then the document's background color does not get drawn; cache the
5330 // color we actually draw.
5331 if (!FrameConstructor()->GetRootElementFrame()) {
5332 mCanvasBackgroundColor = GetDefaultBackgroundColorToDraw();
5336 nscolor PresShell::ComputeBackstopColor(nsView* aDisplayRoot) {
5337 nsIWidget* widget = aDisplayRoot->GetWidget();
5338 if (widget && (widget->GetTransparencyMode() != eTransparencyOpaque ||
5339 widget->WidgetPaintsBackground())) {
5340 // Within a transparent widget, so the backstop color must be
5341 // totally transparent.
5342 return NS_RGBA(0, 0, 0, 0);
5344 // Within an opaque widget (or no widget at all), so the backstop
5345 // color must be totally opaque. The user's default background
5346 // as reported by the prescontext is guaranteed to be opaque.
5347 return GetDefaultBackgroundColorToDraw();
5350 struct PaintParams {
5351 nscolor mBackgroundColor;
5354 LayerManager* PresShell::GetLayerManager() {
5355 NS_ASSERTION(mViewManager, "Should have view manager");
5357 nsView* rootView = mViewManager->GetRootView();
5358 if (rootView) {
5359 if (nsIWidget* widget = rootView->GetWidget()) {
5360 return widget->GetLayerManager();
5363 return nullptr;
5366 bool PresShell::AsyncPanZoomEnabled() {
5367 NS_ASSERTION(mViewManager, "Should have view manager");
5368 nsView* rootView = mViewManager->GetRootView();
5369 if (rootView) {
5370 if (nsIWidget* widget = rootView->GetWidget()) {
5371 return widget->AsyncPanZoomEnabled();
5374 return gfxPlatform::AsyncPanZoomEnabled();
5377 nsresult PresShell::SetResolutionAndScaleTo(float aResolution,
5378 ResolutionChangeOrigin aOrigin) {
5379 if (!(aResolution > 0.0)) {
5380 return NS_ERROR_ILLEGAL_VALUE;
5382 if (aResolution == mResolution.valueOr(0.0)) {
5383 MOZ_ASSERT(mResolution.isSome());
5384 return NS_OK;
5387 // GetResolution handles mResolution being nothing by returning 1 so this
5388 // is checking that the resolution is actually changing.
5389 bool resolutionUpdated = (aResolution != GetResolution());
5391 mLastResolutionChangeOrigin = aOrigin;
5393 RenderingState state(this);
5394 state.mResolution = Some(aResolution);
5395 SetRenderingState(state);
5396 if (mMobileViewportManager) {
5397 mMobileViewportManager->ResolutionUpdated(aOrigin);
5399 if (aOrigin == ResolutionChangeOrigin::Apz) {
5400 mResolutionUpdatedByApz = true;
5401 } else if (resolutionUpdated) {
5402 mResolutionUpdated = true;
5405 if (auto* window = nsGlobalWindowInner::Cast(mDocument->GetInnerWindow())) {
5406 window->VisualViewport()->PostResizeEvent();
5409 return NS_OK;
5412 float PresShell::GetCumulativeResolution() const {
5413 float resolution = GetResolution();
5414 nsPresContext* parentCtx = GetPresContext()->GetParentPresContext();
5415 if (parentCtx) {
5416 resolution *= parentCtx->PresShell()->GetCumulativeResolution();
5418 return resolution;
5421 void PresShell::SetRestoreResolution(float aResolution,
5422 LayoutDeviceIntSize aDisplaySize) {
5423 if (mMobileViewportManager) {
5424 mMobileViewportManager->SetRestoreResolution(aResolution, aDisplaySize);
5428 void PresShell::SetRenderingState(const RenderingState& aState) {
5429 if (mRenderingStateFlags != aState.mRenderingStateFlags) {
5430 // Rendering state changed in a way that forces us to flush any
5431 // retained layers we might already have.
5432 LayerManager* manager = GetLayerManager();
5433 if (manager) {
5434 FrameLayerBuilder::InvalidateAllLayers(manager);
5438 if (GetResolution() != aState.mResolution.valueOr(1.f)) {
5439 if (nsIFrame* frame = GetRootFrame()) {
5440 frame->SchedulePaint();
5444 mRenderingStateFlags = aState.mRenderingStateFlags;
5445 mResolution = aState.mResolution;
5448 void PresShell::SynthesizeMouseMove(bool aFromScroll) {
5449 if (!StaticPrefs::layout_reflow_synthMouseMove()) return;
5451 if (mPaintingSuppressed || !mIsActive || !mPresContext) {
5452 return;
5455 if (!mPresContext->IsRoot()) {
5456 if (PresShell* rootPresShell = GetRootPresShell()) {
5457 rootPresShell->SynthesizeMouseMove(aFromScroll);
5459 return;
5462 if (mMouseLocation == nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE))
5463 return;
5465 if (!mSynthMouseMoveEvent.IsPending()) {
5466 RefPtr<nsSynthMouseMoveEvent> ev =
5467 new nsSynthMouseMoveEvent(this, aFromScroll);
5469 GetPresContext()->RefreshDriver()->AddRefreshObserver(
5470 ev, FlushType::Display, "Synthetic mouse move event");
5471 mSynthMouseMoveEvent = std::move(ev);
5476 * Find the first floating view with a frame and a widget in a postorder
5477 * traversal of the view tree that contains the point. Thus more deeply nested
5478 * floating views are preferred over their ancestors, and floating views earlier
5479 * in the view hierarchy (i.e., added later) are preferred over their siblings.
5480 * This is adequate for finding the "topmost" floating view under a point, given
5481 * that floating views don't supporting having a specific z-index.
5483 * We cannot exit early when aPt is outside the view bounds, because floating
5484 * views aren't necessarily included in their parent's bounds, so this could
5485 * traverse the entire view hierarchy --- use carefully.
5487 * aPt is relative aRelativeToView with the viewport type
5488 * aRelativeToViewportType. aRelativeToView will always have a frame. If aView
5489 * has a frame then aRelativeToView will be aView. (The reason aRelativeToView
5490 * and aView are separate is because we need to traverse into views without
5491 * frames (ie the inner view of a subdocument frame) but we can only easily
5492 * transform between views using TransformPoint which takes frames.)
5494 static nsView* FindFloatingViewContaining(nsView* aRelativeToView,
5495 ViewportType aRelativeToViewportType,
5496 nsView* aView, nsPoint aPt) {
5497 MOZ_ASSERT(aRelativeToView->GetFrame());
5499 if (aView->GetVisibility() == nsViewVisibility_kHide) {
5500 // No need to look into descendants.
5501 return nullptr;
5504 bool crossingZoomBoundary = false;
5506 nsIFrame* frame = aView->GetFrame();
5507 if (frame) {
5508 if (!frame->IsVisibleConsideringAncestors(
5509 nsIFrame::VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY) ||
5510 !frame->PresShell()->IsActive()) {
5511 return nullptr;
5514 // We start out in visual coords and then if we cross the zoom boundary we
5515 // become in layout coords. The zoom boundary always occurs in a document
5516 // with IsRootContentDocumentCrossProcess. The root view of such a document
5517 // is outside the zoom boundary and any child view must be inside the zoom
5518 // boundary because we only create views for certain kinds of frames and
5519 // none of them can be between the root frame and the zoom boundary.
5520 if (aRelativeToViewportType == ViewportType::Visual) {
5521 if (!aRelativeToView->GetParent() ||
5522 aRelativeToView->GetViewManager() !=
5523 aRelativeToView->GetParent()->GetViewManager()) {
5524 if (aRelativeToView->GetFrame()
5525 ->PresContext()
5526 ->IsRootContentDocumentCrossProcess()) {
5527 crossingZoomBoundary = true;
5532 ViewportType nextRelativeToViewportType = aRelativeToViewportType;
5533 if (crossingZoomBoundary) {
5534 nextRelativeToViewportType = ViewportType::Layout;
5537 nsLayoutUtils::TransformResult result = nsLayoutUtils::TransformPoint(
5538 RelativeTo{aRelativeToView->GetFrame(), aRelativeToViewportType},
5539 RelativeTo{frame, nextRelativeToViewportType}, aPt);
5540 if (result != nsLayoutUtils::TRANSFORM_SUCCEEDED) {
5541 return nullptr;
5544 aRelativeToView = aView;
5545 aRelativeToViewportType = nextRelativeToViewportType;
5548 for (nsView* v = aView->GetFirstChild(); v; v = v->GetNextSibling()) {
5549 nsView* r = FindFloatingViewContaining(aRelativeToView,
5550 aRelativeToViewportType, v, aPt);
5551 if (r) return r;
5554 if (!frame || !aView->GetFloating() || !aView->HasWidget()) {
5555 return nullptr;
5558 // Even though aPt is in visual coordinates until we cross the zoom boundary
5559 // it is valid to compare it to view coords (which are in layout coords)
5560 // because visual coords are the same as layout coords for every view outside
5561 // of the zoom boundary except for the root view of the root content document.
5562 // For the root view of the root content document, its bounds don't actually
5563 // correspond to what is visible when we have a MobileViewportManager. So we
5564 // skip the hit test. This is okay because the point has already been hit
5565 // test: 1) if we are the root view in the process then the point comes from a
5566 // real mouse event so it must have been over our widget, or 2) if we are the
5567 // root of a subdocument then hittesting against the view of the subdocument
5568 // frame that contains us already happened and succeeded before getting here.
5569 if (!crossingZoomBoundary) {
5570 if (aView->GetDimensions().Contains(aPt)) {
5571 return aView;
5575 return nullptr;
5579 * This finds the first view with a frame that contains the given point in a
5580 * postorder traversal of the view tree, assuming that the point is not in a
5581 * floating view. It assumes that only floating views extend outside the bounds
5582 * of their parents.
5584 * This methods should only be called if FindFloatingViewContaining returns
5585 * null.
5587 * aPt is relative aRelativeToView with the viewport type
5588 * aRelativeToViewportType. aRelativeToView will always have a frame. If aView
5589 * has a frame then aRelativeToView will be aView. (The reason aRelativeToView
5590 * and aView are separate is because we need to traverse into views without
5591 * frames (ie the inner view of a subdocument frame) but we can only easily
5592 * transform between views using TransformPoint which takes frames.)
5594 static nsView* FindViewContaining(nsView* aRelativeToView,
5595 ViewportType aRelativeToViewportType,
5596 nsView* aView, nsPoint aPt) {
5597 MOZ_ASSERT(aRelativeToView->GetFrame());
5599 if (aView->GetVisibility() == nsViewVisibility_kHide) {
5600 return nullptr;
5603 nsIFrame* frame = aView->GetFrame();
5604 if (frame) {
5605 if (!frame->IsVisibleConsideringAncestors(
5606 nsIFrame::VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY) ||
5607 !frame->PresShell()->IsActive()) {
5608 return nullptr;
5611 // We start out in visual coords and then if we cross the zoom boundary we
5612 // become in layout coords. The zoom boundary always occurs in a document
5613 // with IsRootContentDocumentCrossProcess. The root view of such a document
5614 // is outside the zoom boundary and any child view must be inside the zoom
5615 // boundary because we only create views for certain kinds of frames and
5616 // none of them can be between the root frame and the zoom boundary.
5617 bool crossingZoomBoundary = false;
5618 if (aRelativeToViewportType == ViewportType::Visual) {
5619 if (!aRelativeToView->GetParent() ||
5620 aRelativeToView->GetViewManager() !=
5621 aRelativeToView->GetParent()->GetViewManager()) {
5622 if (aRelativeToView->GetFrame()
5623 ->PresContext()
5624 ->IsRootContentDocumentCrossProcess()) {
5625 crossingZoomBoundary = true;
5630 ViewportType nextRelativeToViewportType = aRelativeToViewportType;
5631 if (crossingZoomBoundary) {
5632 nextRelativeToViewportType = ViewportType::Layout;
5635 nsLayoutUtils::TransformResult result = nsLayoutUtils::TransformPoint(
5636 RelativeTo{aRelativeToView->GetFrame(), aRelativeToViewportType},
5637 RelativeTo{frame, nextRelativeToViewportType}, aPt);
5638 if (result != nsLayoutUtils::TRANSFORM_SUCCEEDED) {
5639 return nullptr;
5642 // Even though aPt is in visual coordinates until we cross the zoom boundary
5643 // it is valid to compare it to view coords (which are in layout coords)
5644 // because visual coords are the same as layout coords for every view
5645 // outside of the zoom boundary except for the root view of the root content
5646 // document.
5647 // For the root view of the root content document, its bounds don't
5648 // actually correspond to what is visible when we have a
5649 // MobileViewportManager. So we skip the hit test. This is okay because the
5650 // point has already been hit test: 1) if we are the root view in the
5651 // process then the point comes from a real mouse event so it must have been
5652 // over our widget, or 2) if we are the root of a subdocument then
5653 // hittesting against the view of the subdocument frame that contains us
5654 // already happened and succeeded before getting here.
5655 if (!crossingZoomBoundary) {
5656 if (!aView->GetDimensions().Contains(aPt)) {
5657 return nullptr;
5661 aRelativeToView = aView;
5662 aRelativeToViewportType = nextRelativeToViewportType;
5665 for (nsView* v = aView->GetFirstChild(); v; v = v->GetNextSibling()) {
5666 nsView* r =
5667 FindViewContaining(aRelativeToView, aRelativeToViewportType, v, aPt);
5668 if (r) return r;
5671 return frame ? aView : nullptr;
5674 static BrowserBridgeChild* GetChildBrowser(nsView* aView) {
5675 if (!aView) {
5676 return nullptr;
5678 nsIFrame* frame = aView->GetFrame();
5679 if (!frame && aView->GetParent()) {
5680 // If frame is null then view is an anonymous inner view, and we want
5681 // the frame from the corresponding outer view.
5682 frame = aView->GetParent()->GetFrame();
5684 if (!frame || !frame->GetContent()) {
5685 return nullptr;
5687 return BrowserBridgeChild::GetFrom(frame->GetContent());
5690 void PresShell::ProcessSynthMouseMoveEvent(bool aFromScroll) {
5691 // If drag session has started, we shouldn't synthesize mousemove event.
5692 nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
5693 if (dragSession) {
5694 mSynthMouseMoveEvent.Forget();
5695 return;
5698 // allow new event to be posted while handling this one only if the
5699 // source of the event is a scroll (to prevent infinite reflow loops)
5700 if (aFromScroll) {
5701 mSynthMouseMoveEvent.Forget();
5704 nsView* rootView = mViewManager ? mViewManager->GetRootView() : nullptr;
5705 if (mMouseLocation == nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE) ||
5706 !rootView || !rootView->HasWidget() || !mPresContext) {
5707 mSynthMouseMoveEvent.Forget();
5708 return;
5711 NS_ASSERTION(mPresContext->IsRoot(), "Only a root pres shell should be here");
5713 // Hold a ref to ourselves so DispatchEvent won't destroy us (since
5714 // we need to access members after we call DispatchEvent).
5715 RefPtr<PresShell> kungFuDeathGrip(this);
5717 #ifdef DEBUG_MOUSE_LOCATION
5718 printf("[ps=%p]synthesizing mouse move to (%d,%d)\n", this, mMouseLocation.x,
5719 mMouseLocation.y);
5720 #endif
5722 int32_t APD = mPresContext->AppUnitsPerDevPixel();
5724 // We need a widget to put in the event we are going to dispatch so we look
5725 // for a view that has a widget and the mouse location is over. We first look
5726 // for floating views, if there isn't one we use the root view. |view| holds
5727 // that view.
5728 nsView* view = nullptr;
5730 // The appunits per devpixel ratio of |view|.
5731 int32_t viewAPD;
5733 // mRefPoint will be mMouseLocation relative to the widget of |view|, the
5734 // widget we will put in the event we dispatch, in viewAPD appunits
5735 nsPoint refpoint(0, 0);
5737 // We always dispatch the event to the pres shell that contains the view that
5738 // the mouse is over. pointVM is the VM of that pres shell.
5739 nsViewManager* pointVM = nullptr;
5741 // This could be a bit slow (traverses entire view hierarchy)
5742 // but it's OK to do it once per synthetic mouse event
5743 if (rootView->GetFrame()) {
5744 view = FindFloatingViewContaining(rootView, ViewportType::Visual, rootView,
5745 mMouseLocation);
5747 nsView* pointView = view;
5748 if (!view) {
5749 view = rootView;
5750 if (rootView->GetFrame()) {
5751 pointView = FindViewContaining(rootView, ViewportType::Visual, rootView,
5752 mMouseLocation);
5753 } else {
5754 pointView = rootView;
5756 // pointView can be null in situations related to mouse capture
5757 pointVM = (pointView ? pointView : view)->GetViewManager();
5758 refpoint = mMouseLocation + rootView->ViewToWidgetOffset();
5759 viewAPD = APD;
5760 } else {
5761 pointVM = view->GetViewManager();
5762 nsIFrame* frame = view->GetFrame();
5763 NS_ASSERTION(frame, "floating views can't be anonymous");
5764 viewAPD = frame->PresContext()->AppUnitsPerDevPixel();
5765 refpoint = mMouseLocation;
5766 DebugOnly<nsLayoutUtils::TransformResult> result =
5767 nsLayoutUtils::TransformPoint(
5768 RelativeTo{rootView->GetFrame(), ViewportType::Visual},
5769 RelativeTo{frame, ViewportType::Layout}, refpoint);
5770 MOZ_ASSERT(result == nsLayoutUtils::TRANSFORM_SUCCEEDED);
5771 refpoint += view->ViewToWidgetOffset();
5773 NS_ASSERTION(view->GetWidget(), "view should have a widget here");
5774 WidgetMouseEvent event(true, eMouseMove, view->GetWidget(),
5775 WidgetMouseEvent::eSynthesized);
5776 event.mRefPoint =
5777 LayoutDeviceIntPoint::FromAppUnitsToNearest(refpoint, viewAPD);
5778 event.mTime = PR_IntervalNow();
5779 // XXX set event.mModifiers ?
5780 // XXX mnakano I think that we should get the latest information from widget.
5782 if (BrowserBridgeChild* bbc = GetChildBrowser(pointView)) {
5783 // If we have a BrowserBridgeChild, we're going to be dispatching this
5784 // mouse event into an OOP iframe of the current document.
5785 event.mLayersId = bbc->GetLayersId();
5786 bbc->SendDispatchSynthesizedMouseEvent(event);
5787 } else if (RefPtr<PresShell> presShell = pointVM->GetPresShell()) {
5788 // Since this gets run in a refresh tick there isn't an InputAPZContext on
5789 // the stack from the nsBaseWidget. We need to simulate one with at least
5790 // the correct target guid, so that the correct callback transform gets
5791 // applied if this event goes to a child process. The input block id is set
5792 // to 0 because this is a synthetic event which doesn't really belong to any
5793 // input block. Same for the APZ response field.
5794 InputAPZContext apzContext(mMouseEventTargetGuid, 0, nsEventStatus_eIgnore);
5795 presShell->DispatchSynthMouseMove(&event);
5798 if (!aFromScroll) {
5799 mSynthMouseMoveEvent.Forget();
5803 /* static */
5804 void PresShell::MarkFramesInListApproximatelyVisible(
5805 const nsDisplayList& aList) {
5806 for (nsDisplayItem* item : aList) {
5807 nsDisplayList* sublist = item->GetChildren();
5808 if (sublist) {
5809 MarkFramesInListApproximatelyVisible(*sublist);
5810 continue;
5813 nsIFrame* frame = item->Frame();
5814 MOZ_ASSERT(frame);
5816 if (!frame->TrackingVisibility()) {
5817 continue;
5820 // Use the presshell containing the frame.
5821 PresShell* presShell = frame->PresShell();
5822 MOZ_ASSERT(!presShell->AssumeAllFramesVisible());
5823 if (presShell->mApproximatelyVisibleFrames.EnsureInserted(frame)) {
5824 // The frame was added to mApproximatelyVisibleFrames, so increment its
5825 // visible count.
5826 frame->IncApproximateVisibleCount();
5831 /* static */
5832 void PresShell::DecApproximateVisibleCount(
5833 VisibleFrames& aFrames, const Maybe<OnNonvisible>& aNonvisibleAction
5834 /* = Nothing() */) {
5835 for (nsIFrame* frame : aFrames) {
5836 // Decrement the frame's visible count if we're still tracking its
5837 // visibility. (We may not be, if the frame disabled visibility tracking
5838 // after we added it to the visible frames list.)
5839 if (frame->TrackingVisibility()) {
5840 frame->DecApproximateVisibleCount(aNonvisibleAction);
5845 void PresShell::RebuildApproximateFrameVisibilityDisplayList(
5846 const nsDisplayList& aList) {
5847 MOZ_ASSERT(!mApproximateFrameVisibilityVisited, "already visited?");
5848 mApproximateFrameVisibilityVisited = true;
5850 // Remove the entries of the mApproximatelyVisibleFrames hashtable and put
5851 // them in oldApproxVisibleFrames.
5852 VisibleFrames oldApproximatelyVisibleFrames =
5853 std::move(mApproximatelyVisibleFrames);
5855 MarkFramesInListApproximatelyVisible(aList);
5857 DecApproximateVisibleCount(oldApproximatelyVisibleFrames);
5860 /* static */
5861 void PresShell::ClearApproximateFrameVisibilityVisited(nsView* aView,
5862 bool aClear) {
5863 nsViewManager* vm = aView->GetViewManager();
5864 if (aClear) {
5865 PresShell* presShell = vm->GetPresShell();
5866 if (!presShell->mApproximateFrameVisibilityVisited) {
5867 presShell->ClearApproximatelyVisibleFramesList();
5869 presShell->mApproximateFrameVisibilityVisited = false;
5871 for (nsView* v = aView->GetFirstChild(); v; v = v->GetNextSibling()) {
5872 ClearApproximateFrameVisibilityVisited(v, v->GetViewManager() != vm);
5876 void PresShell::ClearApproximatelyVisibleFramesList(
5877 const Maybe<OnNonvisible>& aNonvisibleAction
5878 /* = Nothing() */) {
5879 DecApproximateVisibleCount(mApproximatelyVisibleFrames, aNonvisibleAction);
5880 mApproximatelyVisibleFrames.Clear();
5883 void PresShell::MarkFramesInSubtreeApproximatelyVisible(
5884 nsIFrame* aFrame, const nsRect& aRect, bool aRemoveOnly /* = false */) {
5885 MOZ_ASSERT(aFrame->PresShell() == this, "wrong presshell");
5887 if (aFrame->TrackingVisibility() && aFrame->StyleVisibility()->IsVisible() &&
5888 (!aRemoveOnly ||
5889 aFrame->GetVisibility() == Visibility::ApproximatelyVisible)) {
5890 MOZ_ASSERT(!AssumeAllFramesVisible());
5891 if (mApproximatelyVisibleFrames.EnsureInserted(aFrame)) {
5892 // The frame was added to mApproximatelyVisibleFrames, so increment its
5893 // visible count.
5894 aFrame->IncApproximateVisibleCount();
5898 nsSubDocumentFrame* subdocFrame = do_QueryFrame(aFrame);
5899 if (subdocFrame) {
5900 PresShell* presShell = subdocFrame->GetSubdocumentPresShellForPainting(
5901 nsSubDocumentFrame::IGNORE_PAINT_SUPPRESSION);
5902 if (presShell && !presShell->AssumeAllFramesVisible()) {
5903 nsRect rect = aRect;
5904 nsIFrame* root = presShell->GetRootFrame();
5905 if (root) {
5906 rect.MoveBy(aFrame->GetOffsetToCrossDoc(root));
5907 } else {
5908 rect.MoveBy(-aFrame->GetContentRectRelativeToSelf().TopLeft());
5910 rect = rect.ScaleToOtherAppUnitsRoundOut(
5911 aFrame->PresContext()->AppUnitsPerDevPixel(),
5912 presShell->GetPresContext()->AppUnitsPerDevPixel());
5914 presShell->RebuildApproximateFrameVisibility(&rect);
5916 return;
5919 nsRect rect = aRect;
5921 nsIScrollableFrame* scrollFrame = do_QueryFrame(aFrame);
5922 if (scrollFrame) {
5923 bool ignoreDisplayPort = false;
5924 if (DisplayPortUtils::IsMissingDisplayPortBaseRect(aFrame->GetContent())) {
5925 // We can properly set the base rect for root scroll frames on top level
5926 // and root content documents. Otherwise the base rect we compute might
5927 // be way too big without the limiting that
5928 // ScrollFrameHelper::DecideScrollableLayer does, so we just ignore the
5929 // displayport in that case.
5930 nsPresContext* pc = aFrame->PresContext();
5931 if (scrollFrame->IsRootScrollFrameOfDocument() &&
5932 (pc->IsRootContentDocumentCrossProcess() ||
5933 (pc->IsChrome() && !pc->GetParentPresContext()))) {
5934 nsRect baseRect(
5935 nsPoint(), nsLayoutUtils::CalculateCompositionSizeForFrame(aFrame));
5936 DisplayPortUtils::SetDisplayPortBase(aFrame->GetContent(), baseRect);
5937 } else {
5938 ignoreDisplayPort = true;
5942 nsRect displayPort;
5943 bool usingDisplayport =
5944 !ignoreDisplayPort &&
5945 DisplayPortUtils::GetDisplayPortForVisibilityTesting(
5946 aFrame->GetContent(), &displayPort);
5948 scrollFrame->NotifyApproximateFrameVisibilityUpdate(!usingDisplayport);
5950 if (usingDisplayport) {
5951 rect = displayPort;
5952 } else {
5953 rect = rect.Intersect(scrollFrame->GetScrollPortRect());
5955 rect = scrollFrame->ExpandRectToNearlyVisible(rect);
5958 bool preserves3DChildren = aFrame->Extend3DContext();
5960 // We assume all frames in popups are visible, so we skip them here.
5961 const nsIFrame::ChildListIDs skip = {nsIFrame::kPopupList,
5962 nsIFrame::kSelectPopupList};
5963 for (const auto& [list, listID] : aFrame->ChildLists()) {
5964 if (skip.contains(listID)) {
5965 continue;
5968 for (nsIFrame* child : list) {
5969 nsRect r = rect - child->GetPosition();
5970 if (!r.IntersectRect(r, child->InkOverflowRect())) {
5971 continue;
5973 if (child->IsTransformed()) {
5974 // for children of a preserve3d element we just pass down the same dirty
5975 // rect
5976 if (!preserves3DChildren ||
5977 !child->Combines3DTransformWithAncestors()) {
5978 const nsRect overflow = child->InkOverflowRectRelativeToSelf();
5979 nsRect out;
5980 if (nsDisplayTransform::UntransformRect(r, overflow, child, &out)) {
5981 r = out;
5982 } else {
5983 r.SetEmpty();
5987 MarkFramesInSubtreeApproximatelyVisible(child, r);
5992 void PresShell::RebuildApproximateFrameVisibility(
5993 nsRect* aRect, bool aRemoveOnly /* = false */) {
5994 MOZ_ASSERT(!mApproximateFrameVisibilityVisited, "already visited?");
5995 mApproximateFrameVisibilityVisited = true;
5997 nsIFrame* rootFrame = GetRootFrame();
5998 if (!rootFrame) {
5999 return;
6002 // Remove the entries of the mApproximatelyVisibleFrames hashtable and put
6003 // them in oldApproximatelyVisibleFrames.
6004 VisibleFrames oldApproximatelyVisibleFrames =
6005 std::move(mApproximatelyVisibleFrames);
6007 nsRect vis(nsPoint(0, 0), rootFrame->GetSize());
6008 if (aRect) {
6009 vis = *aRect;
6012 MarkFramesInSubtreeApproximatelyVisible(rootFrame, vis, aRemoveOnly);
6014 DecApproximateVisibleCount(oldApproximatelyVisibleFrames);
6017 void PresShell::UpdateApproximateFrameVisibility() {
6018 DoUpdateApproximateFrameVisibility(/* aRemoveOnly = */ false);
6021 void PresShell::DoUpdateApproximateFrameVisibility(bool aRemoveOnly) {
6022 MOZ_ASSERT(
6023 !mPresContext || mPresContext->IsRootContentDocumentInProcess(),
6024 "Updating approximate frame visibility on a non-root content document?");
6026 mUpdateApproximateFrameVisibilityEvent.Revoke();
6028 if (mHaveShutDown || mIsDestroying) {
6029 return;
6032 // call update on that frame
6033 nsIFrame* rootFrame = GetRootFrame();
6034 if (!rootFrame) {
6035 ClearApproximatelyVisibleFramesList(Some(OnNonvisible::DiscardImages));
6036 return;
6039 RebuildApproximateFrameVisibility(/* aRect = */ nullptr, aRemoveOnly);
6040 ClearApproximateFrameVisibilityVisited(rootFrame->GetView(), true);
6042 #ifdef DEBUG_FRAME_VISIBILITY_DISPLAY_LIST
6043 // This can be used to debug the frame walker by comparing beforeFrameList
6044 // and mApproximatelyVisibleFrames in RebuildFrameVisibilityDisplayList to see
6045 // if they produce the same results (mApproximatelyVisibleFrames holds the
6046 // frames the display list thinks are visible, beforeFrameList holds the
6047 // frames the frame walker thinks are visible).
6048 nsDisplayListBuilder builder(
6049 rootFrame, nsDisplayListBuilderMode::FRAME_VISIBILITY, false);
6050 nsRect updateRect(nsPoint(0, 0), rootFrame->GetSize());
6051 nsIFrame* rootScroll = GetRootScrollFrame();
6052 if (rootScroll) {
6053 nsIContent* content = rootScroll->GetContent();
6054 if (content) {
6055 Unused << nsLayoutUtils::GetDisplayPortForVisibilityTesting(
6056 content, &updateRect, RelativeTo::ScrollFrame);
6059 if (IgnoringViewportScrolling()) {
6060 builder.SetIgnoreScrollFrame(rootScroll);
6063 builder.IgnorePaintSuppression();
6064 builder.EnterPresShell(rootFrame);
6065 nsDisplayList list;
6066 rootFrame->BuildDisplayListForStackingContext(&builder, updateRect, &list);
6067 builder.LeavePresShell(rootFrame, &list);
6069 RebuildApproximateFrameVisibilityDisplayList(list);
6071 ClearApproximateFrameVisibilityVisited(rootFrame->GetView(), true);
6073 list.DeleteAll(&builder);
6074 #endif
6077 bool PresShell::AssumeAllFramesVisible() {
6078 if (!StaticPrefs::layout_framevisibility_enabled() || !mPresContext ||
6079 !mDocument) {
6080 return true;
6083 // We assume all frames are visible in print, print preview, chrome, and
6084 // resource docs and don't keep track of them.
6085 if (mPresContext->Type() == nsPresContext::eContext_PrintPreview ||
6086 mPresContext->Type() == nsPresContext::eContext_Print ||
6087 mPresContext->IsChrome() || mDocument->IsResourceDoc()) {
6088 return true;
6091 // If we're assuming all frames are visible in the top level content
6092 // document, we need to in subdocuments as well. Otherwise we can get in a
6093 // situation where things like animations won't work in subdocuments because
6094 // their frames appear not to be visible, since we won't schedule an image
6095 // visibility update if the top level content document is assuming all
6096 // frames are visible.
6098 // Note that it's not safe to call IsRootContentDocument() if we're
6099 // currently being destroyed, so we have to check that first.
6100 if (!mHaveShutDown && !mIsDestroying &&
6101 !mPresContext->IsRootContentDocumentInProcess()) {
6102 nsPresContext* presContext =
6103 mPresContext->GetInProcessRootContentDocumentPresContext();
6104 if (presContext && presContext->PresShell()->AssumeAllFramesVisible()) {
6105 return true;
6109 return false;
6112 void PresShell::ScheduleApproximateFrameVisibilityUpdateSoon() {
6113 if (AssumeAllFramesVisible()) {
6114 return;
6117 if (!mPresContext) {
6118 return;
6121 nsRefreshDriver* refreshDriver = mPresContext->RefreshDriver();
6122 if (!refreshDriver) {
6123 return;
6126 // Ask the refresh driver to update frame visibility soon.
6127 refreshDriver->ScheduleFrameVisibilityUpdate();
6130 void PresShell::ScheduleApproximateFrameVisibilityUpdateNow() {
6131 if (AssumeAllFramesVisible()) {
6132 return;
6135 if (!mPresContext->IsRootContentDocumentInProcess()) {
6136 nsPresContext* presContext =
6137 mPresContext->GetInProcessRootContentDocumentPresContext();
6138 if (!presContext) return;
6139 MOZ_ASSERT(presContext->IsRootContentDocumentInProcess(),
6140 "Didn't get a root prescontext from "
6141 "GetInProcessRootContentDocumentPresContext?");
6142 presContext->PresShell()->ScheduleApproximateFrameVisibilityUpdateNow();
6143 return;
6146 if (mHaveShutDown || mIsDestroying) {
6147 return;
6150 if (mUpdateApproximateFrameVisibilityEvent.IsPending()) {
6151 return;
6154 RefPtr<nsRunnableMethod<PresShell>> event =
6155 NewRunnableMethod("PresShell::UpdateApproximateFrameVisibility", this,
6156 &PresShell::UpdateApproximateFrameVisibility);
6157 nsresult rv = mDocument->Dispatch(TaskCategory::Other, do_AddRef(event));
6159 if (NS_SUCCEEDED(rv)) {
6160 mUpdateApproximateFrameVisibilityEvent = std::move(event);
6164 void PresShell::EnsureFrameInApproximatelyVisibleList(nsIFrame* aFrame) {
6165 if (!aFrame->TrackingVisibility()) {
6166 return;
6169 if (AssumeAllFramesVisible()) {
6170 aFrame->IncApproximateVisibleCount();
6171 return;
6174 #ifdef DEBUG
6175 // Make sure it's in this pres shell.
6176 nsCOMPtr<nsIContent> content = aFrame->GetContent();
6177 if (content) {
6178 PresShell* presShell = content->OwnerDoc()->GetPresShell();
6179 MOZ_ASSERT(!presShell || presShell == this, "wrong shell");
6181 #endif
6183 if (mApproximatelyVisibleFrames.EnsureInserted(aFrame)) {
6184 // We inserted a new entry.
6185 aFrame->IncApproximateVisibleCount();
6189 void PresShell::RemoveFrameFromApproximatelyVisibleList(nsIFrame* aFrame) {
6190 #ifdef DEBUG
6191 // Make sure it's in this pres shell.
6192 nsCOMPtr<nsIContent> content = aFrame->GetContent();
6193 if (content) {
6194 PresShell* presShell = content->OwnerDoc()->GetPresShell();
6195 MOZ_ASSERT(!presShell || presShell == this, "wrong shell");
6197 #endif
6199 if (AssumeAllFramesVisible()) {
6200 MOZ_ASSERT(mApproximatelyVisibleFrames.Count() == 0,
6201 "Shouldn't have any frames in the table");
6202 return;
6205 if (mApproximatelyVisibleFrames.EnsureRemoved(aFrame) &&
6206 aFrame->TrackingVisibility()) {
6207 // aFrame was in the hashtable, and we're still tracking its visibility,
6208 // so we need to decrement its visible count.
6209 aFrame->DecApproximateVisibleCount();
6213 class nsAutoNotifyDidPaint {
6214 public:
6215 nsAutoNotifyDidPaint(PresShell* aShell, PaintFlags aFlags)
6216 : mShell(aShell), mFlags(aFlags) {}
6217 ~nsAutoNotifyDidPaint() {
6218 if (!!(mFlags & PaintFlags::PaintComposite)) {
6219 mShell->GetPresContext()->NotifyDidPaintForSubtree();
6223 private:
6224 PresShell* mShell;
6225 PaintFlags mFlags;
6228 void PresShell::Paint(nsView* aViewToPaint, const nsRegion& aDirtyRegion,
6229 PaintFlags aFlags) {
6230 nsCString url;
6231 nsIURI* uri = mDocument->GetDocumentURI();
6232 Document* contentRoot = GetPrimaryContentDocument();
6233 if (contentRoot) {
6234 uri = contentRoot->GetDocumentURI();
6236 url = uri ? uri->GetSpecOrDefault() : "N/A"_ns;
6237 AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING("PresShell::Paint", GRAPHICS, url);
6239 Maybe<js::AutoAssertNoContentJS> nojs;
6241 // On Android, Flash can call into content JS during painting, so we can't
6242 // assert there. However, we don't rely on this assertion on Android because
6243 // we don't paint while JS is running.
6244 #if !defined(MOZ_WIDGET_ANDROID)
6245 if (!(aFlags & PaintFlags::PaintComposite)) {
6246 // We need to allow content JS when the flag is set since we may trigger
6247 // MozAfterPaint events in content in those cases.
6248 nojs.emplace(dom::danger::GetJSContext());
6250 #endif
6252 NS_ASSERTION(!mIsDestroying, "painting a destroyed PresShell");
6253 NS_ASSERTION(aViewToPaint, "null view");
6255 MOZ_ASSERT(!mApproximateFrameVisibilityVisited, "Should have been cleared");
6257 if (!mIsActive) {
6258 return;
6261 if (StaticPrefs::apz_keyboard_enabled_AtStartup()) {
6262 // Update the focus target for async keyboard scrolling. This will be
6263 // forwarded to APZ by nsDisplayList::PaintRoot. We need to to do this
6264 // before we enter the paint phase because dispatching eVoid events can
6265 // cause layout to happen.
6266 mAPZFocusTarget = FocusTarget(this, mAPZFocusSequenceNumber);
6269 nsPresContext* presContext = GetPresContext();
6270 AUTO_LAYOUT_PHASE_ENTRY_POINT(presContext, Paint);
6272 nsIFrame* frame = aViewToPaint->GetFrame();
6274 LayerManager* layerManager = aViewToPaint->GetWidget()->GetLayerManager();
6275 NS_ASSERTION(layerManager, "Must be in paint event");
6276 bool shouldInvalidate = layerManager->NeedsWidgetInvalidation();
6278 nsAutoNotifyDidPaint notifyDidPaint(this, aFlags);
6280 // Whether or not we should set first paint when painting is suppressed
6281 // is debatable. For now we'll do it because B2G relied on first paint
6282 // to configure the viewport and we only want to do that when we have
6283 // real content to paint. See Bug 798245
6284 if (mIsFirstPaint && !mPaintingSuppressed) {
6285 MOZ_LOG(gLog, LogLevel::Debug,
6286 ("PresShell::Paint, first paint, this=%p", this));
6288 layerManager->SetIsFirstPaint();
6289 mIsFirstPaint = false;
6292 if (!layerManager->BeginTransaction(url)) {
6293 return;
6296 // Send an updated focus target with this transaction. Be sure to do this
6297 // before we paint in the case this is an empty transaction.
6298 layerManager->SetFocusTarget(mAPZFocusTarget);
6300 if (frame) {
6301 // Try to do an empty transaction, if the frame tree does not
6302 // need to be updated. Do not try to do an empty transaction on
6303 // a non-retained layer manager (like the BasicLayerManager that
6304 // draws the window title bar on Mac), because a) it won't work
6305 // and b) below we don't want to clear NS_FRAME_UPDATE_LAYER_TREE,
6306 // that will cause us to forget to update the real layer manager!
6308 if (!(aFlags & PaintFlags::PaintLayers)) {
6309 if (layerManager->EndEmptyTransaction()) {
6310 return;
6312 NS_WARNING("Must complete empty transaction when compositing!");
6315 if (!(aFlags & PaintFlags::PaintSyncDecodeImages) &&
6316 !frame->HasAnyStateBits(NS_FRAME_UPDATE_LAYER_TREE) &&
6317 !mNextPaintCompressed) {
6318 NotifySubDocInvalidationFunc computeInvalidFunc =
6319 presContext->MayHavePaintEventListenerInSubDocument()
6320 ? nsPresContext::NotifySubDocInvalidation
6321 : 0;
6322 bool computeInvalidRect =
6323 computeInvalidFunc ||
6324 (layerManager->GetBackendType() == LayersBackend::LAYERS_BASIC);
6326 UniquePtr<LayerProperties> props;
6327 // For WR, the layermanager has no root layer. We want to avoid
6328 // calling ComputeDifferences in that case because it assumes non-null
6329 // and crashes.
6330 if (computeInvalidRect && layerManager->GetRoot()) {
6331 props = LayerProperties::CloneFrom(layerManager->GetRoot());
6334 MaybeSetupTransactionIdAllocator(layerManager, presContext);
6336 if (layerManager->EndEmptyTransaction(
6337 (aFlags & PaintFlags::PaintComposite)
6338 ? LayerManager::END_DEFAULT
6339 : LayerManager::END_NO_COMPOSITE)) {
6340 nsIntRegion invalid;
6341 bool areaOverflowed = false;
6342 if (props) {
6343 if (!props->ComputeDifferences(layerManager->GetRoot(), invalid,
6344 computeInvalidFunc)) {
6345 areaOverflowed = true;
6347 } else {
6348 LayerProperties::ClearInvalidations(layerManager->GetRoot());
6350 if (props && !areaOverflowed) {
6351 if (!invalid.IsEmpty()) {
6352 nsIntRect bounds = invalid.GetBounds();
6353 nsRect rect(presContext->DevPixelsToAppUnits(bounds.x),
6354 presContext->DevPixelsToAppUnits(bounds.y),
6355 presContext->DevPixelsToAppUnits(bounds.width),
6356 presContext->DevPixelsToAppUnits(bounds.height));
6357 if (shouldInvalidate) {
6358 aViewToPaint->GetViewManager()->InvalidateViewNoSuppression(
6359 aViewToPaint, rect);
6361 presContext->NotifyInvalidation(
6362 layerManager->GetLastTransactionId(), bounds);
6364 } else if (shouldInvalidate) {
6365 aViewToPaint->GetViewManager()->InvalidateView(aViewToPaint);
6368 frame->UpdatePaintCountForPaintedPresShells();
6369 return;
6372 frame->RemoveStateBits(NS_FRAME_UPDATE_LAYER_TREE);
6374 if (frame) {
6375 frame->ClearPresShellsFromLastPaint();
6378 nscolor bgcolor = ComputeBackstopColor(aViewToPaint);
6379 PaintFrameFlags flags =
6380 PaintFrameFlags::WidgetLayers | PaintFrameFlags::ExistingTransaction;
6381 if (!(aFlags & PaintFlags::PaintComposite)) {
6382 flags |= PaintFrameFlags::NoComposite;
6384 // We force sync-decode for printing / print-preview (printing already does
6385 // this from nsPageSequenceFrame::PrintNextSheet).
6386 if (aFlags & PaintFlags::PaintSyncDecodeImages ||
6387 mDocument->IsStaticDocument()) {
6388 flags |= PaintFrameFlags::SyncDecodeImages;
6390 if (mNextPaintCompressed) {
6391 flags |= PaintFrameFlags::Compressed;
6392 mNextPaintCompressed = false;
6394 if (layerManager->GetBackendType() == layers::LayersBackend::LAYERS_WR) {
6395 flags |= PaintFrameFlags::ForWebRender;
6398 if (frame) {
6399 // We can paint directly into the widget using its layer manager.
6400 nsLayoutUtils::PaintFrame(nullptr, frame, aDirtyRegion, bgcolor,
6401 nsDisplayListBuilderMode::Painting, flags);
6402 return;
6405 if (layerManager->GetBackendType() == layers::LayersBackend::LAYERS_WR) {
6406 nsPresContext* pc = GetPresContext();
6407 LayoutDeviceRect bounds = LayoutDeviceRect::FromAppUnits(
6408 pc->GetVisibleArea(), pc->AppUnitsPerDevPixel());
6409 bgcolor = NS_ComposeColors(bgcolor, mCanvasBackgroundColor);
6410 WebRenderBackgroundData data(wr::ToLayoutRect(bounds),
6411 wr::ToColorF(ToDeviceColor(bgcolor)));
6412 WrFiltersHolder wrFilters;
6414 MaybeSetupTransactionIdAllocator(layerManager, presContext);
6415 layerManager->AsWebRenderLayerManager()->EndTransactionWithoutLayer(
6416 nullptr, nullptr, std::move(wrFilters), &data, 0);
6417 return;
6420 RefPtr<ColorLayer> root = layerManager->CreateColorLayer();
6421 if (root) {
6422 nsPresContext* pc = GetPresContext();
6423 nsIntRect bounds =
6424 pc->GetVisibleArea().ToOutsidePixels(pc->AppUnitsPerDevPixel());
6425 bgcolor = NS_ComposeColors(bgcolor, mCanvasBackgroundColor);
6426 root->SetColor(ToDeviceColor(bgcolor));
6427 root->SetVisibleRegion(LayerIntRegion::FromUnknownRegion(bounds));
6428 layerManager->SetRoot(root);
6430 MaybeSetupTransactionIdAllocator(layerManager, presContext);
6431 layerManager->EndTransaction(nullptr, nullptr,
6432 (aFlags & PaintFlags::PaintComposite)
6433 ? LayerManager::END_DEFAULT
6434 : LayerManager::END_NO_COMPOSITE);
6437 // static
6438 void PresShell::SetCapturingContent(nsIContent* aContent, CaptureFlags aFlags,
6439 WidgetEvent* aEvent) {
6440 // If capture was set for pointer lock, don't unlock unless we are coming
6441 // out of pointer lock explicitly.
6442 if (!aContent && sCapturingContentInfo.mPointerLock &&
6443 !(aFlags & CaptureFlags::PointerLock)) {
6444 return;
6447 sCapturingContentInfo.mContent = nullptr;
6448 sCapturingContentInfo.mRemoteTarget = nullptr;
6450 // only set capturing content if allowed or the
6451 // CaptureFlags::IgnoreAllowedState or CaptureFlags::PointerLock are used.
6452 if ((aFlags & CaptureFlags::IgnoreAllowedState) ||
6453 sCapturingContentInfo.mAllowed || (aFlags & CaptureFlags::PointerLock)) {
6454 if (aContent) {
6455 sCapturingContentInfo.mContent = aContent;
6457 if (aEvent) {
6458 MOZ_ASSERT(XRE_IsParentProcess());
6459 MOZ_ASSERT(aEvent->mMessage == eMouseDown);
6460 MOZ_ASSERT(aEvent->HasBeenPostedToRemoteProcess());
6461 sCapturingContentInfo.mRemoteTarget =
6462 BrowserParent::GetLastMouseRemoteTarget();
6463 MOZ_ASSERT(sCapturingContentInfo.mRemoteTarget);
6465 // CaptureFlags::PointerLock is the same as
6466 // CaptureFlags::RetargetToElement & CaptureFlags::IgnoreAllowedState.
6467 sCapturingContentInfo.mRetargetToElement =
6468 !!(aFlags & CaptureFlags::RetargetToElement) ||
6469 !!(aFlags & CaptureFlags::PointerLock);
6470 sCapturingContentInfo.mPreventDrag =
6471 !!(aFlags & CaptureFlags::PreventDragStart);
6472 sCapturingContentInfo.mPointerLock = !!(aFlags & CaptureFlags::PointerLock);
6476 nsIContent* PresShell::GetCurrentEventContent() {
6477 if (mCurrentEventContent &&
6478 mCurrentEventContent->GetComposedDoc() != mDocument) {
6479 mCurrentEventContent = nullptr;
6480 mCurrentEventFrame = nullptr;
6482 return mCurrentEventContent;
6485 nsIFrame* PresShell::GetCurrentEventFrame() {
6486 if (MOZ_UNLIKELY(mIsDestroying)) {
6487 return nullptr;
6490 // GetCurrentEventContent() makes sure the content is still in the
6491 // same document that this pres shell belongs to. If not, then the
6492 // frame shouldn't get an event, nor should we even assume its safe
6493 // to try and find the frame.
6494 nsIContent* content = GetCurrentEventContent();
6495 if (!mCurrentEventFrame && content) {
6496 mCurrentEventFrame = content->GetPrimaryFrame();
6497 MOZ_ASSERT(!mCurrentEventFrame ||
6498 mCurrentEventFrame->PresContext()->GetPresShell() == this);
6500 return mCurrentEventFrame;
6503 already_AddRefed<nsIContent> PresShell::GetEventTargetContent(
6504 WidgetEvent* aEvent) {
6505 nsCOMPtr<nsIContent> content = GetCurrentEventContent();
6506 if (!content) {
6507 nsIFrame* currentEventFrame = GetCurrentEventFrame();
6508 if (currentEventFrame) {
6509 currentEventFrame->GetContentForEvent(aEvent, getter_AddRefs(content));
6510 NS_ASSERTION(!content || content->GetComposedDoc() == mDocument,
6511 "handing out content from a different doc");
6514 return content.forget();
6517 void PresShell::PushCurrentEventInfo(nsIFrame* aFrame, nsIContent* aContent) {
6518 if (mCurrentEventFrame || mCurrentEventContent) {
6519 mCurrentEventFrameStack.InsertElementAt(0, mCurrentEventFrame);
6520 mCurrentEventContentStack.InsertObjectAt(mCurrentEventContent, 0);
6522 mCurrentEventFrame = aFrame;
6523 mCurrentEventContent = aContent;
6526 void PresShell::PopCurrentEventInfo() {
6527 mCurrentEventFrame = nullptr;
6528 mCurrentEventContent = nullptr;
6530 if (0 != mCurrentEventFrameStack.Length()) {
6531 mCurrentEventFrame = mCurrentEventFrameStack.ElementAt(0);
6532 mCurrentEventFrameStack.RemoveElementAt(0);
6533 mCurrentEventContent = mCurrentEventContentStack.ObjectAt(0);
6534 mCurrentEventContentStack.RemoveObjectAt(0);
6536 // Don't use it if it has moved to a different document.
6537 if (mCurrentEventContent &&
6538 mCurrentEventContent->GetComposedDoc() != mDocument) {
6539 mCurrentEventContent = nullptr;
6540 mCurrentEventFrame = nullptr;
6545 // static
6546 bool PresShell::EventHandler::InZombieDocument(nsIContent* aContent) {
6547 // If a content node points to a null document, or the document is not
6548 // attached to a window, then it is possibly in a zombie document,
6549 // about to be replaced by a newly loading document.
6550 // Such documents cannot handle DOM events.
6551 // It might actually be in a node not attached to any document,
6552 // in which case there is not parent presshell to retarget it to.
6553 Document* doc = aContent->GetComposedDoc();
6554 return !doc || !doc->GetWindow();
6557 already_AddRefed<nsPIDOMWindowOuter> PresShell::GetRootWindow() {
6558 nsCOMPtr<nsPIDOMWindowOuter> window = mDocument->GetWindow();
6559 if (window) {
6560 nsCOMPtr<nsPIDOMWindowOuter> rootWindow = window->GetPrivateRoot();
6561 NS_ASSERTION(rootWindow, "nsPIDOMWindow::GetPrivateRoot() returns NULL");
6562 return rootWindow.forget();
6565 // If we don't have DOM window, we're zombie, we should find the root window
6566 // with our parent shell.
6567 RefPtr<PresShell> parentPresShell = GetParentPresShellForEventHandling();
6568 NS_ENSURE_TRUE(parentPresShell, nullptr);
6569 return parentPresShell->GetRootWindow();
6572 already_AddRefed<nsPIDOMWindowOuter>
6573 PresShell::GetFocusedDOMWindowInOurWindow() {
6574 nsCOMPtr<nsPIDOMWindowOuter> rootWindow = GetRootWindow();
6575 NS_ENSURE_TRUE(rootWindow, nullptr);
6576 nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
6577 nsFocusManager::GetFocusedDescendant(rootWindow,
6578 nsFocusManager::eIncludeAllDescendants,
6579 getter_AddRefs(focusedWindow));
6580 return focusedWindow.forget();
6583 already_AddRefed<nsIContent> PresShell::GetFocusedContentInOurWindow() const {
6584 nsFocusManager* fm = nsFocusManager::GetFocusManager();
6585 if (fm && mDocument) {
6586 RefPtr<Element> focusedElement;
6587 fm->GetFocusedElementForWindow(mDocument->GetWindow(), false, nullptr,
6588 getter_AddRefs(focusedElement));
6589 return focusedElement.forget();
6591 return nullptr;
6594 already_AddRefed<PresShell> PresShell::GetParentPresShellForEventHandling() {
6595 if (!mPresContext) {
6596 return nullptr;
6599 // Now, find the parent pres shell and send the event there
6600 RefPtr<nsDocShell> docShell = mPresContext->GetDocShell();
6601 if (!docShell) {
6602 docShell = mForwardingContainer.get();
6605 // Might have gone away, or never been around to start with
6606 if (!docShell) {
6607 return nullptr;
6610 BrowsingContext* bc = docShell->GetBrowsingContext();
6611 if (!bc) {
6612 return nullptr;
6615 RefPtr<BrowsingContext> parentBC;
6616 if (XRE_IsParentProcess()) {
6617 parentBC = bc->Canonical()->GetParentCrossChromeBoundary();
6618 } else {
6619 parentBC = bc->GetParent();
6622 RefPtr<nsIDocShell> parentDocShell =
6623 parentBC ? parentBC->GetDocShell() : nullptr;
6624 if (!parentDocShell) {
6625 return nullptr;
6628 RefPtr<PresShell> parentPresShell = parentDocShell->GetPresShell();
6629 return parentPresShell.forget();
6632 nsresult PresShell::EventHandler::RetargetEventToParent(
6633 WidgetGUIEvent* aGUIEvent, nsEventStatus* aEventStatus) {
6634 // Send this events straight up to the parent pres shell.
6635 // We do this for keystroke events in zombie documents or if either a frame
6636 // or a root content is not present.
6637 // That way at least the UI key bindings can work.
6639 RefPtr<PresShell> parentPresShell = GetParentPresShellForEventHandling();
6640 NS_ENSURE_TRUE(parentPresShell, NS_ERROR_FAILURE);
6642 // Fake the event as though it's from the parent pres shell's root frame.
6643 return parentPresShell->HandleEvent(parentPresShell->GetRootFrame(),
6644 aGUIEvent, true, aEventStatus);
6647 void PresShell::DisableNonTestMouseEvents(bool aDisable) {
6648 sDisableNonTestMouseEvents = aDisable;
6651 bool PresShell::MouseLocationWasSetBySynthesizedMouseEventForTests() const {
6652 if (!mPresContext) {
6653 return false;
6655 if (mPresContext->IsRoot()) {
6656 return mMouseLocationWasSetBySynthesizedMouseEventForTests;
6658 PresShell* rootPresShell = GetRootPresShell();
6659 return rootPresShell &&
6660 rootPresShell->mMouseLocationWasSetBySynthesizedMouseEventForTests;
6663 void PresShell::RecordMouseLocation(WidgetGUIEvent* aEvent) {
6664 if (!mPresContext) return;
6666 if (!mPresContext->IsRoot()) {
6667 PresShell* rootPresShell = GetRootPresShell();
6668 if (rootPresShell) {
6669 rootPresShell->RecordMouseLocation(aEvent);
6671 return;
6674 if ((aEvent->mMessage == eMouseMove &&
6675 aEvent->AsMouseEvent()->mReason == WidgetMouseEvent::eReal) ||
6676 aEvent->mMessage == eMouseEnterIntoWidget ||
6677 aEvent->mMessage == eMouseDown || aEvent->mMessage == eMouseUp) {
6678 nsIFrame* rootFrame = GetRootFrame();
6679 if (!rootFrame) {
6680 nsView* rootView = mViewManager->GetRootView();
6681 mMouseLocation = nsLayoutUtils::TranslateWidgetToView(
6682 mPresContext, aEvent->mWidget, aEvent->mRefPoint, rootView);
6683 mMouseEventTargetGuid = InputAPZContext::GetTargetLayerGuid();
6684 } else {
6685 RelativeTo relativeTo{rootFrame};
6686 if (rootFrame->PresContext()->IsRootContentDocumentCrossProcess()) {
6687 relativeTo.mViewportType = ViewportType::Visual;
6689 mMouseLocation =
6690 nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, relativeTo);
6691 mMouseEventTargetGuid = InputAPZContext::GetTargetLayerGuid();
6693 mMouseLocationWasSetBySynthesizedMouseEventForTests =
6694 aEvent->mFlags.mIsSynthesizedForTests;
6695 #ifdef DEBUG_MOUSE_LOCATION
6696 if (aEvent->mMessage == eMouseEnterIntoWidget) {
6697 printf("[ps=%p]got mouse enter for %p\n", this, aEvent->mWidget);
6699 printf("[ps=%p]setting mouse location to (%d,%d)\n", this, mMouseLocation.x,
6700 mMouseLocation.y);
6701 #endif
6702 if (aEvent->mMessage == eMouseEnterIntoWidget) {
6703 SynthesizeMouseMove(false);
6705 } else if (aEvent->mMessage == eMouseExitFromWidget) {
6706 // Although we only care about the mouse moving into an area for which this
6707 // pres shell doesn't receive mouse move events, we don't check which widget
6708 // the mouse exit was for since this seems to vary by platform. Hopefully
6709 // this won't matter at all since we'll get the mouse move or enter after
6710 // the mouse exit when the mouse moves from one of our widgets into another.
6711 mMouseLocation = nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
6712 mMouseEventTargetGuid = InputAPZContext::GetTargetLayerGuid();
6713 mMouseLocationWasSetBySynthesizedMouseEventForTests =
6714 aEvent->mFlags.mIsSynthesizedForTests;
6715 #ifdef DEBUG_MOUSE_LOCATION
6716 printf("[ps=%p]got mouse exit for %p\n", this, aEvent->mWidget);
6717 printf("[ps=%p]clearing mouse location\n", this);
6718 #endif
6722 void PresShell::nsSynthMouseMoveEvent::Revoke() {
6723 if (mPresShell) {
6724 mPresShell->GetPresContext()->RefreshDriver()->RemoveRefreshObserver(
6725 this, FlushType::Display);
6726 mPresShell = nullptr;
6730 // static
6731 nsIFrame* PresShell::EventHandler::GetNearestFrameContainingPresShell(
6732 PresShell* aPresShell) {
6733 nsView* view = aPresShell->GetViewManager()->GetRootView();
6734 while (view && !view->GetFrame()) {
6735 view = view->GetParent();
6738 nsIFrame* frame = nullptr;
6739 if (view) {
6740 frame = view->GetFrame();
6743 return frame;
6746 static CallState FlushThrottledStyles(Document& aDocument) {
6747 PresShell* presShell = aDocument.GetPresShell();
6748 if (presShell && presShell->IsVisible()) {
6749 if (nsPresContext* presContext = presShell->GetPresContext()) {
6750 presContext->RestyleManager()->UpdateOnlyAnimationStyles();
6754 aDocument.EnumerateSubDocuments(FlushThrottledStyles);
6755 return CallState::Continue;
6758 bool PresShell::CanDispatchEvent(const WidgetGUIEvent* aEvent) const {
6759 bool rv =
6760 mPresContext && !mHaveShutDown && nsContentUtils::IsSafeToRunScript();
6761 if (aEvent) {
6762 rv &= (aEvent && aEvent->mWidget && !aEvent->mWidget->Destroyed());
6764 return rv;
6767 /* static */
6768 PresShell* PresShell::GetShellForEventTarget(nsIFrame* aFrame,
6769 nsIContent* aContent) {
6770 if (aFrame) {
6771 return aFrame->PresShell();
6773 if (aContent) {
6774 Document* doc = aContent->GetComposedDoc();
6775 if (!doc) {
6776 return nullptr;
6778 return doc->GetPresShell();
6780 return nullptr;
6783 /* static */
6784 PresShell* PresShell::GetShellForTouchEvent(WidgetGUIEvent* aEvent) {
6785 switch (aEvent->mMessage) {
6786 case eTouchMove:
6787 case eTouchCancel:
6788 case eTouchEnd: {
6789 // get the correct shell to dispatch to
6790 WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
6791 for (dom::Touch* touch : touchEvent->mTouches) {
6792 if (!touch) {
6793 return nullptr;
6796 RefPtr<dom::Touch> oldTouch =
6797 TouchManager::GetCapturedTouch(touch->Identifier());
6798 if (!oldTouch) {
6799 return nullptr;
6802 nsCOMPtr<nsIContent> content = do_QueryInterface(oldTouch->GetTarget());
6803 if (!content) {
6804 return nullptr;
6807 nsIFrame* contentFrame = content->GetPrimaryFrame();
6808 if (!contentFrame) {
6809 return nullptr;
6812 PresShell* presShell = contentFrame->PresContext()->GetPresShell();
6813 if (presShell) {
6814 return presShell;
6817 return nullptr;
6819 default:
6820 return nullptr;
6824 nsresult PresShell::HandleEvent(nsIFrame* aFrameForPresShell,
6825 WidgetGUIEvent* aGUIEvent,
6826 bool aDontRetargetEvents,
6827 nsEventStatus* aEventStatus) {
6828 MOZ_ASSERT(aGUIEvent);
6829 // If it's synthesized in the parent process and our mouse location was set
6830 // by a mouse event which was synthesized for tests because the test does not
6831 // want to change `:hover` state with the synthesized mouse event for native
6832 // mouse cursor position.
6833 if (aGUIEvent->mMessage == eMouseMove &&
6834 aGUIEvent->CameFromAnotherProcess() && XRE_IsContentProcess() &&
6835 !aGUIEvent->mFlags.mIsSynthesizedForTests &&
6836 MouseLocationWasSetBySynthesizedMouseEventForTests() &&
6837 aGUIEvent->AsMouseEvent()->mReason == WidgetMouseEvent::eSynthesized) {
6838 return NS_OK;
6840 EventHandler eventHandler(*this);
6841 return eventHandler.HandleEvent(aFrameForPresShell, aGUIEvent,
6842 aDontRetargetEvents, aEventStatus);
6845 nsresult PresShell::EventHandler::HandleEvent(nsIFrame* aFrameForPresShell,
6846 WidgetGUIEvent* aGUIEvent,
6847 bool aDontRetargetEvents,
6848 nsEventStatus* aEventStatus) {
6849 MOZ_ASSERT(aGUIEvent);
6850 MOZ_DIAGNOSTIC_ASSERT(aGUIEvent->IsTrusted());
6851 MOZ_ASSERT(aEventStatus);
6853 NS_ASSERTION(aFrameForPresShell, "aFrameForPresShell should be not null");
6855 // Update the latest focus sequence number with this new sequence number;
6856 // the next transasction that gets sent to the compositor will carry this over
6857 if (mPresShell->mAPZFocusSequenceNumber < aGUIEvent->mFocusSequenceNumber) {
6858 mPresShell->mAPZFocusSequenceNumber = aGUIEvent->mFocusSequenceNumber;
6861 if (mPresShell->IsDestroying() ||
6862 (PresShell::sDisableNonTestMouseEvents &&
6863 !aGUIEvent->mFlags.mIsSynthesizedForTests &&
6864 aGUIEvent->HasMouseEventMessage())) {
6865 return NS_OK;
6868 mPresShell->RecordMouseLocation(aGUIEvent);
6870 if (MaybeHandleEventWithAccessibleCaret(aFrameForPresShell, aGUIEvent,
6871 aEventStatus)) {
6872 // Handled by AccessibleCaretEventHub.
6873 return NS_OK;
6876 if (MaybeDiscardEvent(aGUIEvent)) {
6877 // Cannot handle the event for now.
6878 return NS_OK;
6881 if (!aDontRetargetEvents) {
6882 // If aGUIEvent should be handled in another PresShell, we should call its
6883 // HandleEvent() and do nothing here.
6884 nsresult rv = NS_OK;
6885 if (MaybeHandleEventWithAnotherPresShell(aFrameForPresShell, aGUIEvent,
6886 aEventStatus, &rv)) {
6887 // Handled by another PresShell or nobody can handle the event.
6888 return rv;
6892 if (MaybeDiscardOrDelayKeyboardEvent(aGUIEvent)) {
6893 // The event is discarded or put into the delayed event queue.
6894 return NS_OK;
6897 if (aGUIEvent->IsUsingCoordinates()) {
6898 return HandleEventUsingCoordinates(aFrameForPresShell, aGUIEvent,
6899 aEventStatus, aDontRetargetEvents);
6902 // Activation events need to be dispatched even if no frame was found, since
6903 // we don't want the focus to be out of sync.
6904 if (!aFrameForPresShell) {
6905 if (!NS_EVENT_NEEDS_FRAME(aGUIEvent)) {
6906 // Push nullptr for both current event target content and frame since
6907 // there is no frame but the event does not require a frame.
6908 AutoCurrentEventInfoSetter eventInfoSetter(*this);
6909 return HandleEventWithCurrentEventInfo(aGUIEvent, aEventStatus, true,
6910 nullptr);
6913 if (aGUIEvent->HasKeyEventMessage()) {
6914 // Keypress events in new blank tabs should not be completely thrown away.
6915 // Retarget them -- the parent chrome shell might make use of them.
6916 return RetargetEventToParent(aGUIEvent, aEventStatus);
6919 return NS_OK;
6922 if (aGUIEvent->IsTargetedAtFocusedContent()) {
6923 return HandleEventAtFocusedContent(aGUIEvent, aEventStatus);
6926 return HandleEventWithFrameForPresShell(aFrameForPresShell, aGUIEvent,
6927 aEventStatus);
6930 nsresult PresShell::EventHandler::HandleEventUsingCoordinates(
6931 nsIFrame* aFrameForPresShell, WidgetGUIEvent* aGUIEvent,
6932 nsEventStatus* aEventStatus, bool aDontRetargetEvents) {
6933 MOZ_ASSERT(aGUIEvent);
6934 MOZ_ASSERT(aGUIEvent->IsUsingCoordinates());
6935 MOZ_ASSERT(aEventStatus);
6937 // Flush pending notifications to handle the event with the latest layout.
6938 // But if it causes destroying the frame for mPresShell, stop handling the
6939 // event. (why?)
6940 AutoWeakFrame weakFrame(aFrameForPresShell);
6941 MaybeFlushPendingNotifications(aGUIEvent);
6942 if (!weakFrame.IsAlive()) {
6943 *aEventStatus = nsEventStatus_eIgnore;
6944 return NS_OK;
6947 // XXX Retrieving capturing content here. However, some of the following
6948 // methods allow to run script. So, isn't it possible the capturing
6949 // content outdated?
6950 nsCOMPtr<nsIContent> capturingContent =
6951 EventHandler::GetCapturingContentFor(aGUIEvent);
6953 if (GetDocument() && aGUIEvent->mClass == eTouchEventClass) {
6954 PointerLockManager::Unlock();
6957 nsIFrame* frameForPresShell = MaybeFlushThrottledStyles(aFrameForPresShell);
6958 if (NS_WARN_IF(!frameForPresShell)) {
6959 return NS_OK;
6962 bool isCapturingContentIgnored = false;
6963 bool isCaptureRetargeted = false;
6964 nsIFrame* rootFrameToHandleEvent = ComputeRootFrameToHandleEvent(
6965 frameForPresShell, aGUIEvent, capturingContent,
6966 &isCapturingContentIgnored, &isCaptureRetargeted);
6967 if (isCapturingContentIgnored) {
6968 capturingContent = nullptr;
6971 // The order to generate pointer event is
6972 // 1. check pending pointer capture.
6973 // 2. check if there is a capturing content.
6974 // 3. hit test
6975 // 4. dispatch pointer events
6976 // 5. check whether the targets of all Touch instances are in the same
6977 // document and suppress invalid instances.
6978 // 6. dispatch mouse or touch events.
6980 // Try to keep frame for following check, because frame can be damaged
6981 // during MaybeProcessPointerCapture.
6983 AutoWeakFrame frameKeeper(rootFrameToHandleEvent);
6984 PointerEventHandler::MaybeProcessPointerCapture(aGUIEvent);
6985 // Prevent application crashes, in case damaged frame.
6986 if (!frameKeeper.IsAlive()) {
6987 NS_WARNING("Nothing to handle this event!");
6988 return NS_OK;
6992 // Only capture mouse events and pointer events.
6993 RefPtr<Element> pointerCapturingElement =
6994 PointerEventHandler::GetPointerCapturingElement(aGUIEvent);
6996 if (pointerCapturingElement) {
6997 rootFrameToHandleEvent = pointerCapturingElement->GetPrimaryFrame();
6998 if (!rootFrameToHandleEvent) {
6999 return HandleEventWithPointerCapturingContentWithoutItsFrame(
7000 aFrameForPresShell, aGUIEvent, pointerCapturingElement, aEventStatus);
7004 WidgetMouseEvent* mouseEvent = aGUIEvent->AsMouseEvent();
7005 bool isWindowLevelMouseExit =
7006 (aGUIEvent->mMessage == eMouseExitFromWidget) &&
7007 (mouseEvent &&
7008 (mouseEvent->mExitFrom.value() == WidgetMouseEvent::ePlatformTopLevel ||
7009 mouseEvent->mExitFrom.value() == WidgetMouseEvent::ePuppet));
7011 // Get the frame at the event point. However, don't do this if we're
7012 // capturing and retargeting the event because the captured frame will
7013 // be used instead below. Also keep using the root frame if we're dealing
7014 // with a window-level mouse exit event since we want to start sending
7015 // mouse out events at the root EventStateManager.
7016 EventTargetData eventTargetData(rootFrameToHandleEvent);
7017 if (!isCaptureRetargeted && !isWindowLevelMouseExit &&
7018 !pointerCapturingElement) {
7019 if (!ComputeEventTargetFrameAndPresShellAtEventPoint(
7020 rootFrameToHandleEvent, aGUIEvent, &eventTargetData)) {
7021 *aEventStatus = nsEventStatus_eIgnore;
7022 return NS_OK;
7026 // if a node is capturing the mouse, check if the event needs to be
7027 // retargeted at the capturing content instead. This will be the case when
7028 // capture retargeting is being used, no frame was found or the frame's
7029 // content is not a descendant of the capturing content.
7030 if (capturingContent && !pointerCapturingElement &&
7031 (PresShell::sCapturingContentInfo.mRetargetToElement ||
7032 !eventTargetData.mFrame->GetContent() ||
7033 !nsContentUtils::ContentIsCrossDocDescendantOf(
7034 eventTargetData.mFrame->GetContent(), capturingContent))) {
7035 // A check was already done above to ensure that capturingContent is
7036 // in this presshell.
7037 NS_ASSERTION(capturingContent->OwnerDoc() == GetDocument(),
7038 "Unexpected document");
7039 nsIFrame* capturingFrame = capturingContent->GetPrimaryFrame();
7040 if (capturingFrame) {
7041 eventTargetData.SetFrameAndComputePresShell(capturingFrame);
7045 if (NS_WARN_IF(!eventTargetData.mFrame)) {
7046 return NS_OK;
7049 // Suppress mouse event if it's being targeted at an element inside
7050 // a document which needs events suppressed
7051 if (MaybeDiscardOrDelayMouseEvent(eventTargetData.mFrame, aGUIEvent)) {
7052 return NS_OK;
7055 // Check if we have an active EventStateManager which isn't the
7056 // EventStateManager of the current PresContext. If that is the case, and
7057 // mouse is over some ancestor document, forward event handling to the
7058 // active document. This way content can get mouse events even when mouse
7059 // is over the chrome or outside the window.
7060 if (eventTargetData.MaybeRetargetToActiveDocument(aGUIEvent) &&
7061 NS_WARN_IF(!eventTargetData.mFrame)) {
7062 return NS_OK;
7065 if (!eventTargetData.ComputeElementFromFrame(aGUIEvent)) {
7066 return NS_OK;
7068 // Note that even if ComputeElementFromFrame() returns true,
7069 // eventTargetData.mContent can be nullptr here.
7071 // Dispatch a pointer event if Pointer Events is enabled. Note that if
7072 // pointer event listeners change the layout, eventTargetData is
7073 // automatically updated.
7074 if (!DispatchPrecedingPointerEvent(
7075 aFrameForPresShell, aGUIEvent, pointerCapturingElement,
7076 aDontRetargetEvents, &eventTargetData, aEventStatus)) {
7077 return NS_OK;
7080 // frame could be null after dispatching pointer events.
7081 // XXX Despite of this comment, we update the event target data outside
7082 // DispatchPrecedingPointerEvent(). Can we make it call
7083 // UpdateTouchEventTarget()?
7084 eventTargetData.UpdateTouchEventTarget(aGUIEvent);
7086 // Handle the event in the correct shell.
7087 // We pass the subshell's root frame as the frame to start from. This is
7088 // the only correct alternative; if the event was captured then it
7089 // must have been captured by us or some ancestor shell and we
7090 // now ask the subshell to dispatch it normally.
7091 EventHandler eventHandler(*eventTargetData.mPresShell);
7092 AutoCurrentEventInfoSetter eventInfoSetter(eventHandler, eventTargetData);
7093 // eventTargetData is on the stack and is guaranteed to keep its
7094 // mOverrideClickTarget alive, so we can just use MOZ_KnownLive here.
7095 nsresult rv = eventHandler.HandleEventWithCurrentEventInfo(
7096 aGUIEvent, aEventStatus, true,
7097 MOZ_KnownLive(eventTargetData.mOverrideClickTarget));
7098 #ifdef DEBUG
7099 eventTargetData.mPresShell->ShowEventTargetDebug();
7100 #endif
7101 return rv;
7104 bool PresShell::EventHandler::MaybeFlushPendingNotifications(
7105 WidgetGUIEvent* aGUIEvent) {
7106 MOZ_ASSERT(aGUIEvent);
7108 switch (aGUIEvent->mMessage) {
7109 case eMouseDown:
7110 case eMouseUp: {
7111 RefPtr<nsPresContext> presContext = mPresShell->GetPresContext();
7112 if (NS_WARN_IF(!presContext)) {
7113 return false;
7115 uint64_t framesConstructedCount = presContext->FramesConstructedCount();
7116 uint64_t framesReflowedCount = presContext->FramesReflowedCount();
7118 MOZ_KnownLive(mPresShell)->FlushPendingNotifications(FlushType::Layout);
7119 return framesConstructedCount != presContext->FramesConstructedCount() ||
7120 framesReflowedCount != presContext->FramesReflowedCount();
7122 default:
7123 return false;
7127 // The type of coordinates to use for hit-testing input events
7128 // that are relative to the RCD's viewport frame.
7129 // On most platforms, use visual coordinates so that scrollbars
7130 // can be targeted.
7131 // On mobile, use layout coordinates because hit-testing in
7132 // visual coordinates clashes with mobile viewport sizing, where
7133 // the ViewportFrame is sized to the initial containing block
7134 // (ICB) size, which is in layout coordinates. This is fine
7135 // because we don't need to be able to target scrollbars on mobile
7136 // (scrollbar dragging isn't supported).
7137 static ViewportType ViewportTypeForInputEventsRelativeToRoot() {
7138 #ifdef MOZ_WIDGET_ANDROID
7139 return ViewportType::Layout;
7140 #else
7141 return ViewportType::Visual;
7142 #endif
7145 nsIFrame* PresShell::EventHandler::GetFrameToHandleNonTouchEvent(
7146 nsIFrame* aRootFrameToHandleEvent, WidgetGUIEvent* aGUIEvent) {
7147 MOZ_ASSERT(aGUIEvent);
7148 MOZ_ASSERT(aGUIEvent->mClass != eTouchEventClass);
7150 ViewportType viewportType = ViewportType::Layout;
7151 if (aRootFrameToHandleEvent->Type() == LayoutFrameType::Viewport) {
7152 nsPresContext* pc = aRootFrameToHandleEvent->PresContext();
7153 if (pc->IsChrome()) {
7154 viewportType = ViewportType::Visual;
7155 } else if (pc->IsRootContentDocument()) {
7156 viewportType = ViewportTypeForInputEventsRelativeToRoot();
7159 RelativeTo relativeTo{aRootFrameToHandleEvent, viewportType};
7160 nsPoint eventPoint =
7161 nsLayoutUtils::GetEventCoordinatesRelativeTo(aGUIEvent, relativeTo);
7163 uint32_t flags = 0;
7164 if (aGUIEvent->mClass == eMouseEventClass) {
7165 WidgetMouseEvent* mouseEvent = aGUIEvent->AsMouseEvent();
7166 if (mouseEvent && mouseEvent->mIgnoreRootScrollFrame) {
7167 flags |= INPUT_IGNORE_ROOT_SCROLL_FRAME;
7171 nsIFrame* targetFrame =
7172 FindFrameTargetedByInputEvent(aGUIEvent, relativeTo, eventPoint, flags);
7173 if (!targetFrame) {
7174 return aRootFrameToHandleEvent;
7177 if (targetFrame->PresShell() == mPresShell) {
7178 // If found target is in mPresShell, we've already found it in the latest
7179 // layout so that we can use it.
7180 return targetFrame;
7183 // If target is in a child document, we've not flushed its layout yet.
7184 PresShell* childPresShell = targetFrame->PresShell();
7185 EventHandler childEventHandler(*childPresShell);
7186 AutoWeakFrame weakFrame(aRootFrameToHandleEvent);
7187 bool layoutChanged =
7188 childEventHandler.MaybeFlushPendingNotifications(aGUIEvent);
7189 if (!weakFrame.IsAlive()) {
7190 // Stop handling the event if the root frame to handle event is destroyed
7191 // by the reflow. (but why?)
7192 return nullptr;
7194 if (!layoutChanged) {
7195 // If the layout in the child PresShell hasn't been changed, we don't
7196 // need to recompute the target.
7197 return targetFrame;
7200 // Finally, we need to recompute the target with the latest layout.
7201 targetFrame =
7202 FindFrameTargetedByInputEvent(aGUIEvent, relativeTo, eventPoint, flags);
7204 return targetFrame ? targetFrame : aRootFrameToHandleEvent;
7207 bool PresShell::EventHandler::ComputeEventTargetFrameAndPresShellAtEventPoint(
7208 nsIFrame* aRootFrameToHandleEvent, WidgetGUIEvent* aGUIEvent,
7209 EventTargetData* aEventTargetData) {
7210 MOZ_ASSERT(aRootFrameToHandleEvent);
7211 MOZ_ASSERT(aGUIEvent);
7212 MOZ_ASSERT(aEventTargetData);
7214 if (aGUIEvent->mClass == eTouchEventClass) {
7215 nsIFrame* targetFrameAtTouchEvent = TouchManager::SetupTarget(
7216 aGUIEvent->AsTouchEvent(), aRootFrameToHandleEvent);
7217 aEventTargetData->SetFrameAndComputePresShell(targetFrameAtTouchEvent);
7218 return true;
7221 nsIFrame* targetFrame =
7222 GetFrameToHandleNonTouchEvent(aRootFrameToHandleEvent, aGUIEvent);
7223 aEventTargetData->SetFrameAndComputePresShell(targetFrame);
7224 return !!aEventTargetData->mFrame;
7227 bool PresShell::EventHandler::DispatchPrecedingPointerEvent(
7228 nsIFrame* aFrameForPresShell, WidgetGUIEvent* aGUIEvent,
7229 nsIContent* aPointerCapturingContent, bool aDontRetargetEvents,
7230 EventTargetData* aEventTargetData, nsEventStatus* aEventStatus) {
7231 MOZ_ASSERT(aFrameForPresShell);
7232 MOZ_ASSERT(aGUIEvent);
7233 MOZ_ASSERT(aEventTargetData);
7234 MOZ_ASSERT(aEventStatus);
7236 // Dispatch pointer events from the mouse or touch events. Regarding
7237 // pointer events from mouse, we should dispatch those pointer events to
7238 // the same target as the source mouse events. We pass the frame found
7239 // in hit test to PointerEventHandler and dispatch pointer events to it.
7241 // Regarding pointer events from touch, the behavior is different. Touch
7242 // events are dispatched to the same target as the target of touchstart.
7243 // Multiple touch points must be dispatched to the same document. Pointer
7244 // events from touch can be dispatched to different documents. We Pass the
7245 // original frame to PointerEventHandler, reentry PresShell::HandleEvent,
7246 // and do hit test for each point.
7247 nsIFrame* targetFrame = aGUIEvent->mClass == eTouchEventClass
7248 ? aFrameForPresShell
7249 : aEventTargetData->mFrame;
7251 if (aPointerCapturingContent) {
7252 aEventTargetData->mOverrideClickTarget =
7253 GetOverrideClickTarget(aGUIEvent, aFrameForPresShell);
7254 aEventTargetData->mPresShell =
7255 PresShell::GetShellForEventTarget(nullptr, aPointerCapturingContent);
7256 if (!aEventTargetData->mPresShell) {
7257 // If we can't process event for the capturing content, release
7258 // the capture.
7259 PointerEventHandler::ReleaseIfCaptureByDescendant(
7260 aPointerCapturingContent);
7261 return false;
7264 targetFrame = aPointerCapturingContent->GetPrimaryFrame();
7265 aEventTargetData->mFrame = targetFrame;
7268 AutoWeakFrame weakTargetFrame(targetFrame);
7269 AutoWeakFrame weakFrame(aEventTargetData->mFrame);
7270 nsCOMPtr<nsIContent> content(aEventTargetData->mContent);
7271 RefPtr<PresShell> presShell(aEventTargetData->mPresShell);
7272 nsCOMPtr<nsIContent> targetContent;
7273 PointerEventHandler::DispatchPointerFromMouseOrTouch(
7274 presShell, aEventTargetData->mFrame, content, aGUIEvent,
7275 aDontRetargetEvents, aEventStatus, getter_AddRefs(targetContent));
7277 // If the target frame is alive, the caller should keep handling the event
7278 // unless event target frame is destroyed.
7279 if (weakTargetFrame.IsAlive()) {
7280 return weakFrame.IsAlive();
7283 // If the event is not a mouse event, the caller should keep handling the
7284 // event unless event target frame is destroyed. Note that this case is
7285 // not defined by the spec.
7286 if (aGUIEvent->mClass != eMouseEventClass) {
7287 return weakFrame.IsAlive();
7290 // Spec defines that mouse events must be dispatched to the same target as
7291 // the pointer event. If the target is no longer participating in its
7292 // ownerDocument's tree, fire the event at the original target's nearest
7293 // ancestor node
7294 if (!targetContent) {
7295 return false;
7298 // XXX Why don't we reset aEventTargetData->mContent here?
7299 aEventTargetData->mFrame = targetContent->GetPrimaryFrame();
7300 aEventTargetData->mPresShell = PresShell::GetShellForEventTarget(
7301 aEventTargetData->mFrame, targetContent);
7303 // If new target PresShel is not found, we cannot keep handling the event.
7304 return !!aEventTargetData->mPresShell;
7307 bool PresShell::EventHandler::MaybeHandleEventWithAccessibleCaret(
7308 nsIFrame* aFrameForPresShell, WidgetGUIEvent* aGUIEvent,
7309 nsEventStatus* aEventStatus) {
7310 MOZ_ASSERT(aGUIEvent);
7311 MOZ_ASSERT(aEventStatus);
7313 // Don't dispatch event to AccessibleCaretEventHub when the event status
7314 // is nsEventStatus_eConsumeNoDefault. This might be happened when content
7315 // preventDefault on the pointer events. In such case, we also call
7316 // preventDefault on mouse events to stop default behaviors.
7317 if (*aEventStatus == nsEventStatus_eConsumeNoDefault) {
7318 return false;
7321 if (!AccessibleCaretEnabled(GetDocument()->GetDocShell())) {
7322 return false;
7325 // AccessibleCaretEventHub handles only mouse, touch, and keyboard events.
7326 if (aGUIEvent->mClass != eMouseEventClass &&
7327 aGUIEvent->mClass != eTouchEventClass &&
7328 aGUIEvent->mClass != eKeyboardEventClass) {
7329 return false;
7332 // First, try the event hub at the event point to handle a long press to
7333 // select a word in an unfocused window.
7334 do {
7335 EventTargetData eventTargetData(nullptr);
7336 if (!ComputeEventTargetFrameAndPresShellAtEventPoint(
7337 aFrameForPresShell, aGUIEvent, &eventTargetData)) {
7338 break;
7341 if (!eventTargetData.mPresShell) {
7342 break;
7345 RefPtr<AccessibleCaretEventHub> eventHub =
7346 eventTargetData.mPresShell->GetAccessibleCaretEventHub();
7347 if (!eventHub) {
7348 break;
7351 *aEventStatus = eventHub->HandleEvent(aGUIEvent);
7352 if (*aEventStatus != nsEventStatus_eConsumeNoDefault) {
7353 break;
7356 // If the event is consumed, cancel APZC panning by setting
7357 // mMultipleActionsPrevented.
7358 aGUIEvent->mFlags.mMultipleActionsPrevented = true;
7359 return true;
7360 } while (false);
7362 // Then, we target the event to the event hub at the focused window.
7363 nsCOMPtr<nsPIDOMWindowOuter> window = GetFocusedDOMWindowInOurWindow();
7364 if (!window) {
7365 return false;
7367 RefPtr<Document> retargetEventDoc = window->GetExtantDoc();
7368 if (!retargetEventDoc) {
7369 return false;
7371 RefPtr<PresShell> presShell = retargetEventDoc->GetPresShell();
7372 if (!presShell) {
7373 return false;
7376 RefPtr<AccessibleCaretEventHub> eventHub =
7377 presShell->GetAccessibleCaretEventHub();
7378 if (!eventHub) {
7379 return false;
7381 *aEventStatus = eventHub->HandleEvent(aGUIEvent);
7382 if (*aEventStatus != nsEventStatus_eConsumeNoDefault) {
7383 return false;
7385 // If the event is consumed, cancel APZC panning by setting
7386 // mMultipleActionsPrevented.
7387 aGUIEvent->mFlags.mMultipleActionsPrevented = true;
7388 return true;
7391 bool PresShell::EventHandler::MaybeDiscardEvent(WidgetGUIEvent* aGUIEvent) {
7392 MOZ_ASSERT(aGUIEvent);
7394 // If it is safe to dispatch events now, don't discard the event.
7395 if (nsContentUtils::IsSafeToRunScript()) {
7396 return false;
7399 // If the event does not cause dispatching DOM event (i.e., internal event),
7400 // we can keep handling it even when it's not safe to run script.
7401 if (!aGUIEvent->IsAllowedToDispatchDOMEvent()) {
7402 return false;
7405 // If the event is a composition event, we need to let IMEStateManager know
7406 // it's discarded because it needs to listen all composition events to manage
7407 // TextComposition instance.
7408 if (aGUIEvent->mClass == eCompositionEventClass) {
7409 IMEStateManager::OnCompositionEventDiscarded(
7410 aGUIEvent->AsCompositionEvent());
7413 #ifdef DEBUG
7414 if (aGUIEvent->IsIMERelatedEvent()) {
7415 nsPrintfCString warning("%s event is discarded",
7416 ToChar(aGUIEvent->mMessage));
7417 NS_WARNING(warning.get());
7419 #endif // #ifdef DEBUG
7421 nsContentUtils::WarnScriptWasIgnored(GetDocument());
7422 return true;
7425 // static
7426 nsIContent* PresShell::EventHandler::GetCapturingContentFor(
7427 WidgetGUIEvent* aGUIEvent) {
7428 return (aGUIEvent->mClass == ePointerEventClass ||
7429 aGUIEvent->mClass == eWheelEventClass ||
7430 aGUIEvent->HasMouseEventMessage())
7431 ? PresShell::GetCapturingContent()
7432 : nullptr;
7435 bool PresShell::EventHandler::GetRetargetEventDocument(
7436 WidgetGUIEvent* aGUIEvent, Document** aRetargetEventDocument) {
7437 MOZ_ASSERT(aGUIEvent);
7438 MOZ_ASSERT(aRetargetEventDocument);
7440 *aRetargetEventDocument = nullptr;
7442 // key and IME related events should not cross top level window boundary.
7443 // Basically, such input events should be fired only on focused widget.
7444 // However, some IMEs might need to clean up composition after focused
7445 // window is deactivated. And also some tests on MozMill want to test key
7446 // handling on deactivated window because MozMill window can be activated
7447 // during tests. So, there is no merit the events should be redirected to
7448 // active window. So, the events should be handled on the last focused
7449 // content in the last focused DOM window in same top level window.
7450 // Note, if no DOM window has been focused yet, we can discard the events.
7451 if (aGUIEvent->IsTargetedAtFocusedWindow()) {
7452 nsCOMPtr<nsPIDOMWindowOuter> window = GetFocusedDOMWindowInOurWindow();
7453 // No DOM window in same top level window has not been focused yet,
7454 // discard the events.
7455 if (!window) {
7456 return false;
7459 RefPtr<Document> retargetEventDoc = window->GetExtantDoc();
7460 if (!retargetEventDoc) {
7461 return false;
7463 retargetEventDoc.forget(aRetargetEventDocument);
7464 return true;
7467 nsIContent* capturingContent =
7468 EventHandler::GetCapturingContentFor(aGUIEvent);
7469 if (capturingContent) {
7470 // if the mouse is being captured then retarget the mouse event at the
7471 // document that is being captured.
7472 RefPtr<Document> retargetEventDoc = capturingContent->GetComposedDoc();
7473 retargetEventDoc.forget(aRetargetEventDocument);
7474 return true;
7477 #ifdef ANDROID
7478 if (aGUIEvent->mClass == eTouchEventClass ||
7479 aGUIEvent->mClass == eMouseEventClass ||
7480 aGUIEvent->mClass == eWheelEventClass) {
7481 RefPtr<Document> retargetEventDoc = mPresShell->GetPrimaryContentDocument();
7482 retargetEventDoc.forget(aRetargetEventDocument);
7483 return true;
7485 #endif // #ifdef ANDROID
7487 // When we don't find another document to handle the event, we need to keep
7488 // handling the event by ourselves.
7489 return true;
7492 nsIFrame* PresShell::EventHandler::GetFrameForHandlingEventWith(
7493 WidgetGUIEvent* aGUIEvent, Document* aRetargetDocument,
7494 nsIFrame* aFrameForPresShell) {
7495 MOZ_ASSERT(aGUIEvent);
7496 MOZ_ASSERT(aRetargetDocument);
7498 RefPtr<PresShell> retargetPresShell = aRetargetDocument->GetPresShell();
7499 // Even if the document doesn't have PresShell, i.e., it's invisible, we
7500 // need to dispatch only KeyboardEvent in its nearest visible document
7501 // because key focus shouldn't be caught by invisible document.
7502 if (!retargetPresShell) {
7503 if (!aGUIEvent->HasKeyEventMessage()) {
7504 return nullptr;
7506 Document* retargetEventDoc = aRetargetDocument;
7507 while (!retargetPresShell) {
7508 retargetEventDoc = retargetEventDoc->GetInProcessParentDocument();
7509 if (!retargetEventDoc) {
7510 return nullptr;
7512 retargetPresShell = retargetEventDoc->GetPresShell();
7516 // If the found PresShell is this instance, caller needs to keep handling
7517 // aGUIEvent by itself. Therefore, return the given frame which was set
7518 // to aFrame of HandleEvent().
7519 if (retargetPresShell == mPresShell) {
7520 return aFrameForPresShell;
7523 // Use root frame of the new PresShell if there is.
7524 nsIFrame* rootFrame = retargetPresShell->GetRootFrame();
7525 if (rootFrame) {
7526 return rootFrame;
7529 // Otherwise, and if aGUIEvent requires content of PresShell, caller should
7530 // stop handling the event.
7531 if (aGUIEvent->mMessage == eQueryTextContent ||
7532 aGUIEvent->IsContentCommandEvent()) {
7533 return nullptr;
7536 // Otherwise, use nearest ancestor frame which includes the PresShell.
7537 return GetNearestFrameContainingPresShell(retargetPresShell);
7540 bool PresShell::EventHandler::MaybeHandleEventWithAnotherPresShell(
7541 nsIFrame* aFrameForPresShell, WidgetGUIEvent* aGUIEvent,
7542 nsEventStatus* aEventStatus, nsresult* aRv) {
7543 MOZ_ASSERT(aGUIEvent);
7544 MOZ_ASSERT(aEventStatus);
7545 MOZ_ASSERT(aRv);
7547 *aRv = NS_OK;
7549 RefPtr<Document> retargetEventDoc;
7550 if (!GetRetargetEventDocument(aGUIEvent, getter_AddRefs(retargetEventDoc))) {
7551 // Nobody can handle this event. So, treat as handled by somebody to make
7552 // caller do nothing anymore.
7553 return true;
7556 // If there is no proper retarget document, the caller should handle the
7557 // event by itself.
7558 if (!retargetEventDoc) {
7559 return false;
7562 nsIFrame* frame = GetFrameForHandlingEventWith(aGUIEvent, retargetEventDoc,
7563 aFrameForPresShell);
7564 if (!frame) {
7565 // Nobody can handle this event. So, treat as handled by somebody to make
7566 // caller do nothing anymore.
7567 return true;
7570 // If we reached same frame as set to HandleEvent(), the caller should handle
7571 // the event by itself.
7572 if (frame == aFrameForPresShell) {
7573 return false;
7576 // We need to handle aGUIEvent with another PresShell.
7577 RefPtr<PresShell> presShell = frame->PresContext()->PresShell();
7578 *aRv = presShell->HandleEvent(frame, aGUIEvent, true, aEventStatus);
7579 return true;
7582 bool PresShell::EventHandler::MaybeDiscardOrDelayKeyboardEvent(
7583 WidgetGUIEvent* aGUIEvent) {
7584 MOZ_ASSERT(aGUIEvent);
7586 if (aGUIEvent->mClass != eKeyboardEventClass) {
7587 return false;
7590 Document* document = GetDocument();
7591 if (!document || !document->EventHandlingSuppressed()) {
7592 return false;
7595 MOZ_ASSERT_IF(InputTaskManager::CanSuspendInputEvent(),
7596 !InputTaskManager::Get()->IsSuspended());
7598 if (aGUIEvent->mMessage == eKeyDown) {
7599 mPresShell->mNoDelayedKeyEvents = true;
7600 } else if (!mPresShell->mNoDelayedKeyEvents) {
7601 UniquePtr<DelayedKeyEvent> delayedKeyEvent =
7602 MakeUnique<DelayedKeyEvent>(aGUIEvent->AsKeyboardEvent());
7603 PushDelayedEventIntoQueue(std::move(delayedKeyEvent));
7605 aGUIEvent->mFlags.mIsSuppressedOrDelayed = true;
7606 return true;
7609 bool PresShell::EventHandler::MaybeDiscardOrDelayMouseEvent(
7610 nsIFrame* aFrameToHandleEvent, WidgetGUIEvent* aGUIEvent) {
7611 MOZ_ASSERT(aFrameToHandleEvent);
7612 MOZ_ASSERT(aGUIEvent);
7614 if (aGUIEvent->mClass != eMouseEventClass) {
7615 return false;
7618 if (!aFrameToHandleEvent->PresContext()
7619 ->Document()
7620 ->EventHandlingSuppressed()) {
7621 return false;
7624 MOZ_ASSERT_IF(InputTaskManager::CanSuspendInputEvent() &&
7625 aGUIEvent->mMessage != eMouseMove,
7626 !InputTaskManager::Get()->IsSuspended());
7628 if (aGUIEvent->mMessage == eMouseDown) {
7629 mPresShell->mNoDelayedMouseEvents = true;
7630 } else if (!mPresShell->mNoDelayedMouseEvents &&
7631 (aGUIEvent->mMessage == eMouseUp ||
7632 // contextmenu is triggered after right mouseup on Windows and
7633 // right mousedown on other platforms.
7634 aGUIEvent->mMessage == eContextMenu ||
7635 aGUIEvent->mMessage == eMouseExitFromWidget)) {
7636 UniquePtr<DelayedMouseEvent> delayedMouseEvent =
7637 MakeUnique<DelayedMouseEvent>(aGUIEvent->AsMouseEvent());
7638 PushDelayedEventIntoQueue(std::move(delayedMouseEvent));
7641 // If there is a suppressed event listener associated with the document,
7642 // notify it about the suppressed mouse event. This allows devtools
7643 // features to continue receiving mouse events even when the devtools
7644 // debugger has paused execution in a page.
7645 RefPtr<EventListener> suppressedListener = aFrameToHandleEvent->PresContext()
7646 ->Document()
7647 ->GetSuppressedEventListener();
7648 if (!suppressedListener ||
7649 aGUIEvent->AsMouseEvent()->mReason == WidgetMouseEvent::eSynthesized) {
7650 return true;
7653 nsCOMPtr<nsIContent> targetContent;
7654 aFrameToHandleEvent->GetContentForEvent(aGUIEvent,
7655 getter_AddRefs(targetContent));
7656 if (targetContent) {
7657 aGUIEvent->mTarget = targetContent;
7660 nsCOMPtr<EventTarget> eventTarget = aGUIEvent->mTarget;
7661 RefPtr<Event> event = EventDispatcher::CreateEvent(
7662 eventTarget, aFrameToHandleEvent->PresContext(), aGUIEvent, u""_ns);
7664 suppressedListener->HandleEvent(*event);
7665 return true;
7668 nsIFrame* PresShell::EventHandler::MaybeFlushThrottledStyles(
7669 nsIFrame* aFrameForPresShell) {
7670 if (!GetDocument()) {
7671 // XXX Only when mPresShell has document, we'll try to look for a frame
7672 // containing mPresShell even if given frame is nullptr. Does this
7673 // make sense?
7674 return aFrameForPresShell;
7677 PresShell* rootPresShell = mPresShell->GetRootPresShell();
7678 if (NS_WARN_IF(!rootPresShell)) {
7679 return nullptr;
7681 Document* rootDocument = rootPresShell->GetDocument();
7682 if (NS_WARN_IF(!rootDocument)) {
7683 return nullptr;
7686 AutoWeakFrame weakFrameForPresShell(aFrameForPresShell);
7687 { // scope for scriptBlocker.
7688 nsAutoScriptBlocker scriptBlocker;
7689 FlushThrottledStyles(*rootDocument);
7692 if (weakFrameForPresShell.IsAlive()) {
7693 return aFrameForPresShell;
7696 return GetNearestFrameContainingPresShell(mPresShell);
7699 nsIFrame* PresShell::EventHandler::ComputeRootFrameToHandleEvent(
7700 nsIFrame* aFrameForPresShell, WidgetGUIEvent* aGUIEvent,
7701 nsIContent* aCapturingContent, bool* aIsCapturingContentIgnored,
7702 bool* aIsCaptureRetargeted) {
7703 MOZ_ASSERT(aFrameForPresShell);
7704 MOZ_ASSERT(aGUIEvent);
7705 MOZ_ASSERT(aIsCapturingContentIgnored);
7706 MOZ_ASSERT(aIsCaptureRetargeted);
7708 nsIFrame* rootFrameToHandleEvent = ComputeRootFrameToHandleEventWithPopup(
7709 aFrameForPresShell, aGUIEvent, aCapturingContent,
7710 aIsCapturingContentIgnored);
7711 if (*aIsCapturingContentIgnored) {
7712 // If the capturing content is ignored, we don't need to respect it.
7713 return rootFrameToHandleEvent;
7716 if (!aCapturingContent) {
7717 return rootFrameToHandleEvent;
7720 // If we have capturing content, let's compute root frame with it again.
7721 return ComputeRootFrameToHandleEventWithCapturingContent(
7722 rootFrameToHandleEvent, aCapturingContent, aIsCapturingContentIgnored,
7723 aIsCaptureRetargeted);
7726 nsIFrame* PresShell::EventHandler::ComputeRootFrameToHandleEventWithPopup(
7727 nsIFrame* aRootFrameToHandleEvent, WidgetGUIEvent* aGUIEvent,
7728 nsIContent* aCapturingContent, bool* aIsCapturingContentIgnored) {
7729 MOZ_ASSERT(aRootFrameToHandleEvent);
7730 MOZ_ASSERT(aGUIEvent);
7731 MOZ_ASSERT(aIsCapturingContentIgnored);
7733 *aIsCapturingContentIgnored = false;
7735 nsPresContext* framePresContext = aRootFrameToHandleEvent->PresContext();
7736 nsPresContext* rootPresContext = framePresContext->GetRootPresContext();
7737 NS_ASSERTION(rootPresContext == GetPresContext()->GetRootPresContext(),
7738 "How did we end up outside the connected "
7739 "prescontext/viewmanager hierarchy?");
7740 nsIFrame* popupFrame = nsLayoutUtils::GetPopupFrameForEventCoordinates(
7741 rootPresContext, aGUIEvent);
7742 if (!popupFrame) {
7743 return aRootFrameToHandleEvent;
7746 // If a remote browser is currently capturing input break out if we
7747 // detect a chrome generated popup.
7748 // XXXedgar, do we need to check fission OOP iframe?
7749 if (aCapturingContent &&
7750 EventStateManager::IsTopLevelRemoteTarget(aCapturingContent)) {
7751 *aIsCapturingContentIgnored = true;
7754 // If the popupFrame is an ancestor of the 'frame', the frame should
7755 // handle the event, otherwise, the popup should handle it.
7756 if (nsContentUtils::ContentIsCrossDocDescendantOf(
7757 framePresContext->GetPresShell()->GetDocument(),
7758 popupFrame->GetContent())) {
7759 return aRootFrameToHandleEvent;
7762 // If we aren't starting our event dispatch from the root frame of the
7763 // root prescontext, then someone must be capturing the mouse. In that
7764 // case we only want to use the popup list if the capture is
7765 // inside the popup.
7766 if (framePresContext == rootPresContext &&
7767 aRootFrameToHandleEvent == FrameConstructor()->GetRootFrame()) {
7768 return popupFrame;
7771 if (aCapturingContent && !*aIsCapturingContentIgnored &&
7772 aCapturingContent->IsInclusiveDescendantOf(popupFrame->GetContent())) {
7773 return popupFrame;
7776 return aRootFrameToHandleEvent;
7779 nsIFrame*
7780 PresShell::EventHandler::ComputeRootFrameToHandleEventWithCapturingContent(
7781 nsIFrame* aRootFrameToHandleEvent, nsIContent* aCapturingContent,
7782 bool* aIsCapturingContentIgnored, bool* aIsCaptureRetargeted) {
7783 MOZ_ASSERT(aRootFrameToHandleEvent);
7784 MOZ_ASSERT(aCapturingContent);
7785 MOZ_ASSERT(aIsCapturingContentIgnored);
7786 MOZ_ASSERT(aIsCaptureRetargeted);
7788 *aIsCapturingContentIgnored = false;
7789 *aIsCaptureRetargeted = false;
7791 // If a capture is active, determine if the BrowsingContext is active. If
7792 // not, clear the capture and target the mouse event normally instead. This
7793 // would occur if the mouse button is held down while a tab change occurs.
7794 // If the BrowsingContext is active, look for a scrolling container.
7795 BrowsingContext* bc = GetPresContext()->Document()->GetBrowsingContext();
7796 if (!bc || !bc->IsActive()) {
7797 ClearMouseCapture();
7798 *aIsCapturingContentIgnored = true;
7799 return aRootFrameToHandleEvent;
7802 if (PresShell::sCapturingContentInfo.mRetargetToElement) {
7803 *aIsCaptureRetargeted = true;
7804 return aRootFrameToHandleEvent;
7807 // A check was already done above to ensure that aCapturingContent is
7808 // in this presshell.
7809 NS_ASSERTION(aCapturingContent->OwnerDoc() == GetDocument(),
7810 "Unexpected document");
7811 nsIFrame* captureFrame = aCapturingContent->GetPrimaryFrame();
7812 if (!captureFrame) {
7813 return aRootFrameToHandleEvent;
7816 if (aCapturingContent->IsHTMLElement(nsGkAtoms::select)) {
7817 // a dropdown <select> has a child in its selectPopupList and we should
7818 // capture on that instead.
7819 nsIFrame* childFrame =
7820 captureFrame->GetChildList(nsIFrame::kSelectPopupList).FirstChild();
7821 if (childFrame) {
7822 captureFrame = childFrame;
7826 // scrollable frames should use the scrolling container as the root instead
7827 // of the document
7828 nsIScrollableFrame* scrollFrame = do_QueryFrame(captureFrame);
7829 return scrollFrame ? scrollFrame->GetScrolledFrame()
7830 : aRootFrameToHandleEvent;
7833 nsresult
7834 PresShell::EventHandler::HandleEventWithPointerCapturingContentWithoutItsFrame(
7835 nsIFrame* aFrameForPresShell, WidgetGUIEvent* aGUIEvent,
7836 nsIContent* aPointerCapturingContent, nsEventStatus* aEventStatus) {
7837 MOZ_ASSERT(aGUIEvent);
7838 MOZ_ASSERT(aPointerCapturingContent);
7839 MOZ_ASSERT(!aPointerCapturingContent->GetPrimaryFrame(),
7840 "Handle the event with frame rather than only with the content");
7841 MOZ_ASSERT(aEventStatus);
7843 RefPtr<PresShell> presShellForCapturingContent =
7844 PresShell::GetShellForEventTarget(nullptr, aPointerCapturingContent);
7845 if (!presShellForCapturingContent) {
7846 // If we can't process event for the capturing content, release
7847 // the capture.
7848 PointerEventHandler::ReleaseIfCaptureByDescendant(aPointerCapturingContent);
7849 return NS_OK;
7852 nsCOMPtr<nsIContent> overrideClickTarget =
7853 GetOverrideClickTarget(aGUIEvent, aFrameForPresShell);
7855 // Dispatch events to the capturing content even it's frame is
7856 // destroyed.
7857 PointerEventHandler::DispatchPointerFromMouseOrTouch(
7858 presShellForCapturingContent, nullptr, aPointerCapturingContent,
7859 aGUIEvent, false, aEventStatus, nullptr);
7861 if (presShellForCapturingContent == mPresShell) {
7862 return HandleEventWithTarget(aGUIEvent, nullptr, aPointerCapturingContent,
7863 aEventStatus, true, nullptr,
7864 overrideClickTarget);
7867 EventHandler eventHandlerForCapturingContent(
7868 std::move(presShellForCapturingContent));
7869 return eventHandlerForCapturingContent.HandleEventWithTarget(
7870 aGUIEvent, nullptr, aPointerCapturingContent, aEventStatus, true, nullptr,
7871 overrideClickTarget);
7874 nsresult PresShell::EventHandler::HandleEventAtFocusedContent(
7875 WidgetGUIEvent* aGUIEvent, nsEventStatus* aEventStatus) {
7876 MOZ_ASSERT(aGUIEvent);
7877 MOZ_ASSERT(aGUIEvent->IsTargetedAtFocusedContent());
7878 MOZ_ASSERT(aEventStatus);
7880 AutoCurrentEventInfoSetter eventInfoSetter(*this);
7882 RefPtr<Element> eventTargetElement =
7883 ComputeFocusedEventTargetElement(aGUIEvent);
7885 mPresShell->mCurrentEventFrame = nullptr;
7886 if (eventTargetElement) {
7887 nsresult rv = NS_OK;
7888 if (MaybeHandleEventWithAnotherPresShell(eventTargetElement, aGUIEvent,
7889 aEventStatus, &rv)) {
7890 return rv;
7894 // If we cannot handle the event with mPresShell, let's try to handle it
7895 // with parent PresShell.
7896 mPresShell->mCurrentEventContent = eventTargetElement;
7897 if (!mPresShell->GetCurrentEventContent() ||
7898 !mPresShell->GetCurrentEventFrame() ||
7899 InZombieDocument(mPresShell->mCurrentEventContent)) {
7900 return RetargetEventToParent(aGUIEvent, aEventStatus);
7903 nsresult rv =
7904 HandleEventWithCurrentEventInfo(aGUIEvent, aEventStatus, true, nullptr);
7906 #ifdef DEBUG
7907 mPresShell->ShowEventTargetDebug();
7908 #endif
7910 return rv;
7913 Element* PresShell::EventHandler::ComputeFocusedEventTargetElement(
7914 WidgetGUIEvent* aGUIEvent) {
7915 MOZ_ASSERT(aGUIEvent);
7916 MOZ_ASSERT(aGUIEvent->IsTargetedAtFocusedContent());
7918 // key and IME related events go to the focused frame in this DOM window.
7919 nsPIDOMWindowOuter* window = GetDocument()->GetWindow();
7920 nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
7921 Element* eventTargetElement = nsFocusManager::GetFocusedDescendant(
7922 window, nsFocusManager::eOnlyCurrentWindow,
7923 getter_AddRefs(focusedWindow));
7925 // otherwise, if there is no focused content or the focused content has
7926 // no frame, just use the root content. This ensures that key events
7927 // still get sent to the window properly if nothing is focused or if a
7928 // frame goes away while it is focused.
7929 if (!eventTargetElement || !eventTargetElement->GetPrimaryFrame()) {
7930 eventTargetElement = GetDocument()->GetUnfocusedKeyEventTarget();
7933 switch (aGUIEvent->mMessage) {
7934 case eKeyDown:
7935 sLastKeyDownEventTargetElement = eventTargetElement;
7936 return eventTargetElement;
7937 case eKeyPress:
7938 case eKeyUp:
7939 if (!sLastKeyDownEventTargetElement) {
7940 return eventTargetElement;
7942 // If a different element is now focused for the keypress/keyup event
7943 // than what was focused during the keydown event, check if the new
7944 // focused element is not in a chrome document any more, and if so,
7945 // retarget the event back at the keydown target. This prevents a
7946 // content area from grabbing the focus from chrome in-between key
7947 // events.
7948 if (eventTargetElement) {
7949 bool keyDownIsChrome = nsContentUtils::IsChromeDoc(
7950 sLastKeyDownEventTargetElement->GetComposedDoc());
7951 if (keyDownIsChrome != nsContentUtils::IsChromeDoc(
7952 eventTargetElement->GetComposedDoc()) ||
7953 (keyDownIsChrome && BrowserParent::GetFrom(eventTargetElement))) {
7954 eventTargetElement = sLastKeyDownEventTargetElement;
7958 if (aGUIEvent->mMessage == eKeyUp) {
7959 sLastKeyDownEventTargetElement = nullptr;
7961 [[fallthrough]];
7962 default:
7963 return eventTargetElement;
7967 bool PresShell::EventHandler::MaybeHandleEventWithAnotherPresShell(
7968 Element* aEventTargetElement, WidgetGUIEvent* aGUIEvent,
7969 nsEventStatus* aEventStatus, nsresult* aRv) {
7970 MOZ_ASSERT(aEventTargetElement);
7971 MOZ_ASSERT(aGUIEvent);
7972 MOZ_ASSERT(!aGUIEvent->IsUsingCoordinates());
7973 MOZ_ASSERT(aEventStatus);
7974 MOZ_ASSERT(aRv);
7976 Document* eventTargetDocument = aEventTargetElement->OwnerDoc();
7977 if (!eventTargetDocument || eventTargetDocument == GetDocument()) {
7978 *aRv = NS_OK;
7979 return false;
7982 RefPtr<PresShell> eventTargetPresShell = eventTargetDocument->GetPresShell();
7983 if (!eventTargetPresShell) {
7984 *aRv = NS_OK;
7985 return true; // No PresShell can handle the event.
7988 EventHandler eventHandler(std::move(eventTargetPresShell));
7989 *aRv = eventHandler.HandleRetargetedEvent(aGUIEvent, aEventStatus,
7990 aEventTargetElement);
7991 return true;
7994 nsresult PresShell::EventHandler::HandleEventWithFrameForPresShell(
7995 nsIFrame* aFrameForPresShell, WidgetGUIEvent* aGUIEvent,
7996 nsEventStatus* aEventStatus) {
7997 MOZ_ASSERT(aGUIEvent);
7998 MOZ_ASSERT(!aGUIEvent->IsUsingCoordinates());
7999 MOZ_ASSERT(!aGUIEvent->IsTargetedAtFocusedContent());
8000 MOZ_ASSERT(aEventStatus);
8002 AutoCurrentEventInfoSetter eventInfoSetter(*this, aFrameForPresShell,
8003 nullptr);
8005 nsresult rv = NS_OK;
8006 if (mPresShell->GetCurrentEventFrame()) {
8007 rv =
8008 HandleEventWithCurrentEventInfo(aGUIEvent, aEventStatus, true, nullptr);
8011 #ifdef DEBUG
8012 mPresShell->ShowEventTargetDebug();
8013 #endif
8015 return rv;
8018 Document* PresShell::GetPrimaryContentDocument() {
8019 nsPresContext* context = GetPresContext();
8020 if (!context || !context->IsRoot()) {
8021 return nullptr;
8024 nsCOMPtr<nsIDocShellTreeItem> shellAsTreeItem = context->GetDocShell();
8025 if (!shellAsTreeItem) {
8026 return nullptr;
8029 nsCOMPtr<nsIDocShellTreeOwner> owner;
8030 shellAsTreeItem->GetTreeOwner(getter_AddRefs(owner));
8031 if (!owner) {
8032 return nullptr;
8035 // now get the primary content shell (active tab)
8036 nsCOMPtr<nsIDocShellTreeItem> item;
8037 owner->GetPrimaryContentShell(getter_AddRefs(item));
8038 nsCOMPtr<nsIDocShell> childDocShell = do_QueryInterface(item);
8039 if (!childDocShell) {
8040 return nullptr;
8043 return childDocShell->GetExtantDocument();
8046 #ifdef DEBUG
8047 void PresShell::ShowEventTargetDebug() {
8048 if (nsIFrame::GetShowEventTargetFrameBorder() && GetCurrentEventFrame()) {
8049 if (mDrawEventTargetFrame) {
8050 mDrawEventTargetFrame->InvalidateFrame();
8053 mDrawEventTargetFrame = mCurrentEventFrame;
8054 mDrawEventTargetFrame->InvalidateFrame();
8057 #endif
8059 nsresult PresShell::EventHandler::HandleEventWithTarget(
8060 WidgetEvent* aEvent, nsIFrame* aNewEventFrame, nsIContent* aNewEventContent,
8061 nsEventStatus* aEventStatus, bool aIsHandlingNativeEvent,
8062 nsIContent** aTargetContent, nsIContent* aOverrideClickTarget) {
8063 MOZ_ASSERT(aEvent);
8064 MOZ_DIAGNOSTIC_ASSERT(aEvent->IsTrusted());
8066 #if DEBUG
8067 MOZ_ASSERT(!aNewEventFrame ||
8068 aNewEventFrame->PresContext()->GetPresShell() == mPresShell,
8069 "wrong shell");
8070 if (aNewEventContent) {
8071 Document* doc = aNewEventContent->GetComposedDoc();
8072 NS_ASSERTION(doc, "event for content that isn't in a document");
8073 // NOTE: We don't require that the document still have a PresShell.
8074 // See bug 1375940.
8076 #endif
8077 NS_ENSURE_STATE(!aNewEventContent ||
8078 aNewEventContent->GetComposedDoc() == GetDocument());
8079 AutoPointerEventTargetUpdater updater(mPresShell, aEvent, aNewEventFrame,
8080 aTargetContent);
8081 AutoCurrentEventInfoSetter eventInfoSetter(*this, aNewEventFrame,
8082 aNewEventContent);
8083 nsresult rv = HandleEventWithCurrentEventInfo(aEvent, aEventStatus, false,
8084 aOverrideClickTarget);
8085 return rv;
8088 namespace {
8090 class MOZ_RAII AutoEventHandler final {
8091 public:
8092 AutoEventHandler(WidgetEvent* aEvent, Document* aDocument) : mEvent(aEvent) {
8093 MOZ_ASSERT(mEvent);
8094 MOZ_ASSERT(mEvent->IsTrusted());
8096 if (mEvent->mMessage == eMouseDown) {
8097 PresShell::ReleaseCapturingContent();
8098 PresShell::AllowMouseCapture(true);
8100 if (NeedsToUpdateCurrentMouseBtnState()) {
8101 WidgetMouseEvent* mouseEvent = mEvent->AsMouseEvent();
8102 if (mouseEvent) {
8103 EventStateManager::sCurrentMouseBtn = mouseEvent->mButton;
8108 ~AutoEventHandler() {
8109 if (mEvent->mMessage == eMouseDown) {
8110 PresShell::AllowMouseCapture(false);
8112 if (NeedsToUpdateCurrentMouseBtnState()) {
8113 EventStateManager::sCurrentMouseBtn = MouseButton::eNotPressed;
8117 protected:
8118 bool NeedsToUpdateCurrentMouseBtnState() const {
8119 return mEvent->mMessage == eMouseDown || mEvent->mMessage == eMouseUp ||
8120 mEvent->mMessage == ePointerDown || mEvent->mMessage == ePointerUp;
8123 WidgetEvent* mEvent;
8126 } // anonymous namespace
8128 nsresult PresShell::EventHandler::HandleEventWithCurrentEventInfo(
8129 WidgetEvent* aEvent, nsEventStatus* aEventStatus,
8130 bool aIsHandlingNativeEvent, nsIContent* aOverrideClickTarget) {
8131 MOZ_ASSERT(aEvent);
8132 MOZ_ASSERT(aEventStatus);
8134 RefPtr<EventStateManager> manager = GetPresContext()->EventStateManager();
8136 // If we cannot handle the event with mPresShell because of no target,
8137 // just record the response time.
8138 // XXX Is this intentional? In such case, the score is really good because
8139 // of nothing to do. So, it may make average and median better.
8140 if (NS_EVENT_NEEDS_FRAME(aEvent) && !mPresShell->GetCurrentEventFrame() &&
8141 !mPresShell->GetCurrentEventContent()) {
8142 RecordEventHandlingResponsePerformance(aEvent);
8143 return NS_OK;
8146 if (mPresShell->mCurrentEventContent && aEvent->IsTargetedAtFocusedWindow()) {
8147 nsFocusManager* fm = nsFocusManager::GetFocusManager();
8148 if (fm) {
8149 // This may run script now. So, mPresShell might be destroyed after here.
8150 fm->FlushBeforeEventHandlingIfNeeded(mPresShell->mCurrentEventContent);
8154 bool touchIsNew = false;
8155 if (!PrepareToDispatchEvent(aEvent, aEventStatus, &touchIsNew)) {
8156 return NS_OK;
8159 // We finished preparing to dispatch the event. So, let's record the
8160 // performance.
8161 RecordEventPreparationPerformance(aEvent);
8163 AutoHandlingUserInputStatePusher userInpStatePusher(
8164 UserActivation::IsUserInteractionEvent(aEvent), aEvent);
8165 AutoEventHandler eventHandler(aEvent, GetDocument());
8166 AutoPopupStatePusher popupStatePusher(
8167 PopupBlocker::GetEventPopupControlState(aEvent));
8169 // FIXME. If the event was reused, we need to clear the old target,
8170 // bug 329430
8171 aEvent->mTarget = nullptr;
8173 HandlingTimeAccumulator handlingTimeAccumulator(*this, aEvent);
8175 nsresult rv = DispatchEvent(manager, aEvent, touchIsNew, aEventStatus,
8176 aOverrideClickTarget);
8178 if (!mPresShell->IsDestroying() && aIsHandlingNativeEvent) {
8179 // Ensure that notifications to IME should be sent before getting next
8180 // native event from the event queue.
8181 // XXX Should we check the event message or event class instead of
8182 // using aIsHandlingNativeEvent?
8183 manager->TryToFlushPendingNotificationsToIME();
8186 FinalizeHandlingEvent(aEvent);
8188 RecordEventHandlingResponsePerformance(aEvent);
8190 return rv; // Result of DispatchEvent()
8193 nsresult PresShell::EventHandler::DispatchEvent(
8194 EventStateManager* aEventStateManager, WidgetEvent* aEvent,
8195 bool aTouchIsNew, nsEventStatus* aEventStatus,
8196 nsIContent* aOverrideClickTarget) {
8197 MOZ_ASSERT(aEventStateManager);
8198 MOZ_ASSERT(aEvent);
8199 MOZ_ASSERT(aEventStatus);
8201 // 1. Give event to event manager for pre event state changes and
8202 // generation of synthetic events.
8203 { // Scope for presContext
8204 RefPtr<nsPresContext> presContext = GetPresContext();
8205 nsCOMPtr<nsIContent> eventContent = mPresShell->mCurrentEventContent;
8206 nsresult rv = aEventStateManager->PreHandleEvent(
8207 presContext, aEvent, mPresShell->mCurrentEventFrame, eventContent,
8208 aEventStatus, aOverrideClickTarget);
8209 if (NS_FAILED(rv)) {
8210 return rv;
8214 // 2. Give event to the DOM for third party and JS use.
8215 bool wasHandlingKeyBoardEvent = nsContentUtils::IsHandlingKeyBoardEvent();
8216 if (aEvent->mClass == eKeyboardEventClass) {
8217 nsContentUtils::SetIsHandlingKeyBoardEvent(true);
8219 // If EventStateManager or something wants reply from remote process and
8220 // needs to win any other event listeners in chrome, the event is both
8221 // stopped its propagation and marked as "waiting reply from remote
8222 // process". In this case, PresShell shouldn't dispatch the event into
8223 // the DOM tree because they don't have a chance to stop propagation in
8224 // the system event group. On the other hand, if its propagation is not
8225 // stopped, that means that the event may be reserved by chrome. If it's
8226 // reserved by chrome, the event shouldn't be sent to any remote
8227 // processes. In this case, PresShell needs to dispatch the event to
8228 // the DOM tree for checking if it's reserved.
8229 if (aEvent->IsAllowedToDispatchDOMEvent() &&
8230 !(aEvent->PropagationStopped() &&
8231 aEvent->IsWaitingReplyFromRemoteProcess())) {
8232 MOZ_ASSERT(nsContentUtils::IsSafeToRunScript(),
8233 "Somebody changed aEvent to cause a DOM event!");
8234 nsPresShellEventCB eventCB(mPresShell);
8235 if (nsIFrame* target = mPresShell->GetCurrentEventFrame()) {
8236 if (target->OnlySystemGroupDispatch(aEvent->mMessage)) {
8237 aEvent->StopPropagation();
8240 if (aEvent->mClass == eTouchEventClass) {
8241 DispatchTouchEventToDOM(aEvent, aEventStatus, &eventCB, aTouchIsNew);
8242 } else {
8243 DispatchEventToDOM(aEvent, aEventStatus, &eventCB);
8247 nsContentUtils::SetIsHandlingKeyBoardEvent(wasHandlingKeyBoardEvent);
8249 if (mPresShell->IsDestroying()) {
8250 return NS_OK;
8253 // 3. Give event to event manager for post event state changes and
8254 // generation of synthetic events.
8255 // Refetch the prescontext, in case it changed.
8256 RefPtr<nsPresContext> presContext = GetPresContext();
8257 return aEventStateManager->PostHandleEvent(
8258 presContext, aEvent, mPresShell->GetCurrentEventFrame(), aEventStatus,
8259 aOverrideClickTarget);
8262 bool PresShell::EventHandler::PrepareToDispatchEvent(
8263 WidgetEvent* aEvent, nsEventStatus* aEventStatus, bool* aTouchIsNew) {
8264 MOZ_ASSERT(aEvent->IsTrusted());
8265 MOZ_ASSERT(aEventStatus);
8266 MOZ_ASSERT(aTouchIsNew);
8268 *aTouchIsNew = false;
8269 if (aEvent->IsUserAction()) {
8270 mPresShell->mHasHandledUserInput = true;
8273 switch (aEvent->mMessage) {
8274 case eKeyPress:
8275 case eKeyDown:
8276 case eKeyUp: {
8277 WidgetKeyboardEvent* keyboardEvent = aEvent->AsKeyboardEvent();
8278 MaybeHandleKeyboardEventBeforeDispatch(keyboardEvent);
8279 return true;
8281 case eMouseMove: {
8282 bool allowCapture = EventStateManager::GetActiveEventStateManager() &&
8283 GetPresContext() &&
8284 GetPresContext()->EventStateManager() ==
8285 EventStateManager::GetActiveEventStateManager();
8286 PresShell::AllowMouseCapture(allowCapture);
8287 return true;
8289 case eDrop: {
8290 nsCOMPtr<nsIDragSession> session = nsContentUtils::GetDragSession();
8291 if (session) {
8292 bool onlyChromeDrop = false;
8293 session->GetOnlyChromeDrop(&onlyChromeDrop);
8294 if (onlyChromeDrop) {
8295 aEvent->mFlags.mOnlyChromeDispatch = true;
8298 return true;
8300 case eContextMenu: {
8301 // If we cannot open context menu even though eContextMenu is fired, we
8302 // should stop dispatching it into the DOM.
8303 WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
8304 if (mouseEvent->IsContextMenuKeyEvent() &&
8305 !AdjustContextMenuKeyEvent(mouseEvent)) {
8306 return false;
8309 // If "Shift" state is active, context menu should be forcibly opened even
8310 // if web apps want to prevent it since we respect our users' intention.
8311 // In this case, we don't fire "contextmenu" event on web content because
8312 // of not cancelable.
8313 if (mouseEvent->IsShift()) {
8314 aEvent->mFlags.mOnlyChromeDispatch = true;
8315 aEvent->mFlags.mRetargetToNonNativeAnonymous = true;
8317 return true;
8319 case eTouchStart:
8320 case eTouchMove:
8321 case eTouchEnd:
8322 case eTouchCancel:
8323 case eTouchPointerCancel:
8324 return mPresShell->mTouchManager.PreHandleEvent(
8325 aEvent, aEventStatus, *aTouchIsNew, mPresShell->mCurrentEventContent);
8326 default:
8327 return true;
8331 void PresShell::EventHandler::FinalizeHandlingEvent(WidgetEvent* aEvent) {
8332 switch (aEvent->mMessage) {
8333 case eKeyPress:
8334 case eKeyDown:
8335 case eKeyUp: {
8336 if (aEvent->AsKeyboardEvent()->mKeyCode == NS_VK_ESCAPE) {
8337 if (aEvent->mMessage == eKeyUp) {
8338 // Reset this flag after key up is handled.
8339 mPresShell->mIsLastChromeOnlyEscapeKeyConsumed = false;
8340 } else {
8341 if (aEvent->mFlags.mOnlyChromeDispatch &&
8342 aEvent->mFlags.mDefaultPreventedByChrome) {
8343 mPresShell->mIsLastChromeOnlyEscapeKeyConsumed = true;
8345 if (aEvent->mMessage == eKeyDown &&
8346 !aEvent->mFlags.mDefaultPrevented) {
8347 if (Document* doc = GetDocument()) {
8348 doc->TryCancelDialog();
8353 if (aEvent->mMessage == eKeyDown) {
8354 mPresShell->mIsLastKeyDownCanceled = aEvent->mFlags.mDefaultPrevented;
8356 return;
8358 case eMouseUp:
8359 // reset the capturing content now that the mouse button is up
8360 PresShell::ReleaseCapturingContent();
8361 return;
8362 case eMouseMove:
8363 PresShell::AllowMouseCapture(false);
8364 return;
8365 case eDrag:
8366 case eDragEnd:
8367 case eDragEnter:
8368 case eDragExit:
8369 case eDragLeave:
8370 case eDragOver:
8371 case eDrop: {
8372 // After any drag event other than dragstart (which is handled
8373 // separately, as we need to collect the data first), the DataTransfer
8374 // needs to be made protected, and then disconnected.
8375 DataTransfer* dataTransfer = aEvent->AsDragEvent()->mDataTransfer;
8376 if (dataTransfer) {
8377 dataTransfer->Disconnect();
8379 return;
8381 default:
8382 return;
8386 void PresShell::EventHandler::MaybeHandleKeyboardEventBeforeDispatch(
8387 WidgetKeyboardEvent* aKeyboardEvent) {
8388 MOZ_ASSERT(aKeyboardEvent);
8390 if (aKeyboardEvent->mKeyCode != NS_VK_ESCAPE) {
8391 return;
8394 // If we're in fullscreen mode, exit from it forcibly when Escape key is
8395 // pressed.
8396 Document* doc = mPresShell->GetCurrentEventContent()
8397 ? mPresShell->mCurrentEventContent->OwnerDoc()
8398 : nullptr;
8399 Document* root = nsContentUtils::GetInProcessSubtreeRootDocument(doc);
8400 if (root && root->GetFullscreenElement()) {
8401 // Prevent default action on ESC key press when exiting
8402 // DOM fullscreen mode. This prevents the browser ESC key
8403 // handler from stopping all loads in the document, which
8404 // would cause <video> loads to stop.
8405 // XXX We need to claim the Escape key event which will be
8406 // dispatched only into chrome is already consumed by
8407 // content because we need to prevent its default here
8408 // for some reasons (not sure) but we need to detect
8409 // if a chrome event handler will call PreventDefault()
8410 // again and check it later.
8411 aKeyboardEvent->PreventDefaultBeforeDispatch(CrossProcessForwarding::eStop);
8412 aKeyboardEvent->mFlags.mOnlyChromeDispatch = true;
8414 // The event listeners in chrome can prevent this ESC behavior by
8415 // calling prevent default on the preceding keydown/press events.
8416 if (!mPresShell->mIsLastChromeOnlyEscapeKeyConsumed &&
8417 aKeyboardEvent->mMessage == eKeyUp) {
8418 // ESC key released while in DOM fullscreen mode.
8419 // Fully exit all browser windows and documents from
8420 // fullscreen mode.
8421 Document::AsyncExitFullscreen(nullptr);
8425 nsCOMPtr<Document> pointerLockedDoc = PointerLockManager::GetLockedDocument();
8426 if (!mPresShell->mIsLastChromeOnlyEscapeKeyConsumed && pointerLockedDoc) {
8427 // XXX See above comment to understand the reason why this needs
8428 // to claim that the Escape key event is consumed by content
8429 // even though it will be dispatched only into chrome.
8430 aKeyboardEvent->PreventDefaultBeforeDispatch(CrossProcessForwarding::eStop);
8431 aKeyboardEvent->mFlags.mOnlyChromeDispatch = true;
8432 if (aKeyboardEvent->mMessage == eKeyUp) {
8433 PointerLockManager::Unlock();
8438 void PresShell::EventHandler::RecordEventPreparationPerformance(
8439 const WidgetEvent* aEvent) {
8440 MOZ_ASSERT(aEvent);
8442 switch (aEvent->mMessage) {
8443 case eKeyPress:
8444 case eKeyDown:
8445 case eKeyUp:
8446 if (aEvent->AsKeyboardEvent()->ShouldInteractionTimeRecorded()) {
8447 GetPresContext()->RecordInteractionTime(
8448 nsPresContext::InteractionType::KeyInteraction, aEvent->mTimeStamp);
8450 Telemetry::AccumulateTimeDelta(Telemetry::INPUT_EVENT_QUEUED_KEYBOARD_MS,
8451 aEvent->mTimeStamp);
8452 return;
8454 case eMouseDown:
8455 case eMouseUp:
8456 Telemetry::AccumulateTimeDelta(Telemetry::INPUT_EVENT_QUEUED_CLICK_MS,
8457 aEvent->mTimeStamp);
8458 [[fallthrough]];
8459 case ePointerDown:
8460 case ePointerUp:
8461 GetPresContext()->RecordInteractionTime(
8462 nsPresContext::InteractionType::ClickInteraction, aEvent->mTimeStamp);
8463 return;
8465 case eMouseMove:
8466 if (aEvent->mFlags.mHandledByAPZ) {
8467 Telemetry::AccumulateTimeDelta(
8468 Telemetry::INPUT_EVENT_QUEUED_APZ_MOUSE_MOVE_MS,
8469 aEvent->mTimeStamp);
8471 GetPresContext()->RecordInteractionTime(
8472 nsPresContext::InteractionType::MouseMoveInteraction,
8473 aEvent->mTimeStamp);
8474 return;
8476 case eWheel:
8477 if (aEvent->mFlags.mHandledByAPZ) {
8478 Telemetry::AccumulateTimeDelta(
8479 Telemetry::INPUT_EVENT_QUEUED_APZ_WHEEL_MS, aEvent->mTimeStamp);
8481 return;
8483 case eTouchMove:
8484 if (aEvent->mFlags.mHandledByAPZ) {
8485 Telemetry::AccumulateTimeDelta(
8486 Telemetry::INPUT_EVENT_QUEUED_APZ_TOUCH_MOVE_MS,
8487 aEvent->mTimeStamp);
8489 return;
8491 default:
8492 return;
8496 void PresShell::EventHandler::RecordEventHandlingResponsePerformance(
8497 const WidgetEvent* aEvent) {
8498 if (!Telemetry::CanRecordBase() || aEvent->mTimeStamp.IsNull() ||
8499 aEvent->mTimeStamp <= mPresShell->mLastOSWake ||
8500 !aEvent->AsInputEvent()) {
8501 return;
8504 TimeStamp now = TimeStamp::Now();
8505 double millis = (now - aEvent->mTimeStamp).ToMilliseconds();
8506 Telemetry::Accumulate(Telemetry::INPUT_EVENT_RESPONSE_MS, millis);
8507 if (GetDocument() &&
8508 GetDocument()->GetReadyStateEnum() != Document::READYSTATE_COMPLETE) {
8509 Telemetry::Accumulate(Telemetry::LOAD_INPUT_EVENT_RESPONSE_MS, millis);
8512 if (!sLastInputProcessed || sLastInputProcessed < aEvent->mTimeStamp) {
8513 if (sLastInputProcessed) {
8514 // This input event was created after we handled the last one.
8515 // Accumulate the previous events' coalesced duration.
8516 double lastMillis =
8517 (sLastInputProcessed - sLastInputCreated).ToMilliseconds();
8518 Telemetry::Accumulate(Telemetry::INPUT_EVENT_RESPONSE_COALESCED_MS,
8519 lastMillis);
8521 if (MOZ_UNLIKELY(!PresShell::sProcessInteractable)) {
8522 // For content process, we use the ready state of
8523 // top-level-content-document to know if the process has finished the
8524 // start-up.
8525 // For parent process, see the topic
8526 // 'sessionstore-one-or-no-tab-restored' in PresShell::Observe.
8527 if (XRE_IsContentProcess() && GetDocument() &&
8528 GetDocument()->IsTopLevelContentDocument()) {
8529 switch (GetDocument()->GetReadyStateEnum()) {
8530 case Document::READYSTATE_INTERACTIVE:
8531 case Document::READYSTATE_COMPLETE:
8532 PresShell::sProcessInteractable = true;
8533 break;
8534 default:
8535 break;
8539 if (MOZ_LIKELY(PresShell::sProcessInteractable)) {
8540 Telemetry::Accumulate(Telemetry::INPUT_EVENT_RESPONSE_POST_STARTUP_MS,
8541 lastMillis);
8542 } else {
8543 Telemetry::Accumulate(Telemetry::INPUT_EVENT_RESPONSE_STARTUP_MS,
8544 lastMillis);
8547 sLastInputCreated = aEvent->mTimeStamp;
8548 } else if (aEvent->mTimeStamp < sLastInputCreated) {
8549 // This event was created before the last input. May be processing out
8550 // of order, so coalesce backwards, too.
8551 sLastInputCreated = aEvent->mTimeStamp;
8553 sLastInputProcessed = now;
8556 // static
8557 nsIPrincipal*
8558 PresShell::EventHandler::GetDocumentPrincipalToCompareWithBlacklist(
8559 PresShell& aPresShell) {
8560 nsPresContext* presContext = aPresShell.GetPresContext();
8561 if (NS_WARN_IF(!presContext)) {
8562 return nullptr;
8564 return presContext->Document()->GetPrincipalForPrefBasedHacks();
8567 nsresult PresShell::EventHandler::DispatchEventToDOM(
8568 WidgetEvent* aEvent, nsEventStatus* aEventStatus,
8569 nsPresShellEventCB* aEventCB) {
8570 nsresult rv = NS_OK;
8571 nsCOMPtr<nsINode> eventTarget = mPresShell->mCurrentEventContent;
8572 nsPresShellEventCB* eventCBPtr = aEventCB;
8573 if (!eventTarget) {
8574 nsCOMPtr<nsIContent> targetContent;
8575 if (mPresShell->mCurrentEventFrame) {
8576 rv = mPresShell->mCurrentEventFrame->GetContentForEvent(
8577 aEvent, getter_AddRefs(targetContent));
8579 if (NS_SUCCEEDED(rv) && targetContent) {
8580 eventTarget = targetContent;
8581 } else if (GetDocument()) {
8582 eventTarget = GetDocument();
8583 // If we don't have any content, the callback wouldn't probably
8584 // do nothing.
8585 eventCBPtr = nullptr;
8588 if (eventTarget) {
8589 if (aEvent->IsBlockedForFingerprintingResistance()) {
8590 aEvent->mFlags.mOnlySystemGroupDispatchInContent = true;
8591 } else if (aEvent->mMessage == eKeyPress) {
8592 // If eKeyPress event is marked as not dispatched in the default event
8593 // group in web content, it's caused by non-printable key or key
8594 // combination. In this case, UI Events declares that browsers
8595 // shouldn't dispatch keypress event. However, some web apps may be
8596 // broken with this strict behavior due to historical issue.
8597 // Therefore, we need to keep dispatching keypress event for such keys
8598 // even with breaking the standard.
8599 // Similarly, the other browsers sets non-zero value of keyCode or
8600 // charCode of keypress event to the other. Therefore, we should
8601 // behave so, however, some web apps may be broken. On such web apps,
8602 // we should keep using legacy our behavior.
8603 if (!mPresShell->mInitializedWithKeyPressEventDispatchingBlacklist) {
8604 mPresShell->mInitializedWithKeyPressEventDispatchingBlacklist = true;
8605 nsCOMPtr<nsIPrincipal> principal =
8606 GetDocumentPrincipalToCompareWithBlacklist(*mPresShell);
8607 if (principal) {
8608 mPresShell->mForceDispatchKeyPressEventsForNonPrintableKeys =
8609 principal->IsURIInPrefList(
8610 "dom.keyboardevent.keypress.hack.dispatch_non_printable_"
8611 "keys") ||
8612 principal->IsURIInPrefList(
8613 "dom.keyboardevent.keypress.hack."
8614 "dispatch_non_printable_keys.addl");
8616 mPresShell->mForceUseLegacyKeyCodeAndCharCodeValues |=
8617 principal->IsURIInPrefList(
8618 "dom.keyboardevent.keypress.hack."
8619 "use_legacy_keycode_and_charcode") ||
8620 principal->IsURIInPrefList(
8621 "dom.keyboardevent.keypress.hack."
8622 "use_legacy_keycode_and_charcode.addl");
8625 if (mPresShell->mForceDispatchKeyPressEventsForNonPrintableKeys) {
8626 aEvent->mFlags.mOnlySystemGroupDispatchInContent = false;
8628 if (mPresShell->mForceUseLegacyKeyCodeAndCharCodeValues) {
8629 aEvent->AsKeyboardEvent()->mUseLegacyKeyCodeAndCharCodeValues = true;
8631 } else if (aEvent->mMessage == eMouseUp) {
8632 // Historically Firefox has dispatched click events for non-primary
8633 // buttons, but only on window and document (and inside input/textarea),
8634 // not on elements in general. The UI events spec forbids click (and
8635 // dblclick) for non-primary mouse buttons, and specifies auxclick
8636 // instead. In case of some websites that rely on non-primary click to
8637 // prevent new tab etc. and don't have auxclick code to do the same, we
8638 // need to revert to the historial non-standard behaviour
8639 if (!mPresShell->mInitializedWithClickEventDispatchingBlacklist) {
8640 mPresShell->mInitializedWithClickEventDispatchingBlacklist = true;
8642 nsCOMPtr<nsIPrincipal> principal =
8643 GetDocumentPrincipalToCompareWithBlacklist(*mPresShell);
8645 if (principal) {
8646 mPresShell->mForceUseLegacyNonPrimaryDispatch =
8647 principal->IsURIInPrefList(
8648 "dom.mouseevent.click.hack.use_legacy_non-primary_dispatch");
8651 if (mPresShell->mForceUseLegacyNonPrimaryDispatch) {
8652 aEvent->AsMouseEvent()->mUseLegacyNonPrimaryDispatch = true;
8656 if (aEvent->mClass == eCompositionEventClass) {
8657 RefPtr<nsPresContext> presContext = GetPresContext();
8658 RefPtr<BrowserParent> browserParent =
8659 IMEStateManager::GetActiveBrowserParent();
8660 IMEStateManager::DispatchCompositionEvent(
8661 eventTarget, presContext, browserParent, aEvent->AsCompositionEvent(),
8662 aEventStatus, eventCBPtr);
8663 } else {
8664 RefPtr<nsPresContext> presContext = GetPresContext();
8665 EventDispatcher::Dispatch(eventTarget, presContext, aEvent, nullptr,
8666 aEventStatus, eventCBPtr);
8669 return rv;
8672 void PresShell::EventHandler::DispatchTouchEventToDOM(
8673 WidgetEvent* aEvent, nsEventStatus* aEventStatus,
8674 nsPresShellEventCB* aEventCB, bool aTouchIsNew) {
8675 // calling preventDefault on touchstart or the first touchmove for a
8676 // point prevents mouse events. calling it on the touchend should
8677 // prevent click dispatching.
8678 bool canPrevent = (aEvent->mMessage == eTouchStart) ||
8679 (aEvent->mMessage == eTouchMove && aTouchIsNew) ||
8680 (aEvent->mMessage == eTouchEnd);
8681 bool preventDefault = false;
8682 nsEventStatus tmpStatus = nsEventStatus_eIgnore;
8683 WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
8685 // loop over all touches and dispatch events on any that have changed
8686 for (dom::Touch* touch : touchEvent->mTouches) {
8687 // We should remove all suppressed touch instances in
8688 // TouchManager::PreHandleEvent.
8689 MOZ_ASSERT(!touch->mIsTouchEventSuppressed);
8691 if (!touch || !touch->mChanged) {
8692 continue;
8695 nsCOMPtr<EventTarget> targetPtr = touch->mTarget;
8696 nsCOMPtr<nsIContent> content = do_QueryInterface(targetPtr);
8697 if (!content) {
8698 continue;
8701 Document* doc = content->OwnerDoc();
8702 nsIContent* capturingContent = PresShell::GetCapturingContent();
8703 if (capturingContent) {
8704 if (capturingContent->OwnerDoc() != doc) {
8705 // Wrong document, don't dispatch anything.
8706 continue;
8708 content = capturingContent;
8710 // copy the event
8711 MOZ_ASSERT(touchEvent->IsTrusted());
8712 WidgetTouchEvent newEvent(true, touchEvent->mMessage, touchEvent->mWidget);
8713 newEvent.AssignTouchEventData(*touchEvent, false);
8714 newEvent.mTarget = targetPtr;
8715 newEvent.mFlags.mHandledByAPZ = touchEvent->mFlags.mHandledByAPZ;
8717 RefPtr<PresShell> contentPresShell;
8718 if (doc == GetDocument()) {
8719 contentPresShell = doc->GetPresShell();
8720 if (contentPresShell) {
8721 // XXXsmaug huge hack. Pushing possibly capturing content,
8722 // even though event target is something else.
8723 contentPresShell->PushCurrentEventInfo(content->GetPrimaryFrame(),
8724 content);
8728 nsPresContext* context = doc->GetPresContext();
8729 if (!context) {
8730 if (contentPresShell) {
8731 contentPresShell->PopCurrentEventInfo();
8733 continue;
8736 tmpStatus = nsEventStatus_eIgnore;
8737 EventDispatcher::Dispatch(targetPtr, context, &newEvent, nullptr,
8738 &tmpStatus, aEventCB);
8739 if (nsEventStatus_eConsumeNoDefault == tmpStatus ||
8740 newEvent.mFlags.mMultipleActionsPrevented) {
8741 preventDefault = true;
8744 if (newEvent.mFlags.mMultipleActionsPrevented) {
8745 touchEvent->mFlags.mMultipleActionsPrevented = true;
8748 if (contentPresShell) {
8749 contentPresShell->PopCurrentEventInfo();
8753 if (preventDefault && canPrevent) {
8754 *aEventStatus = nsEventStatus_eConsumeNoDefault;
8755 } else {
8756 *aEventStatus = nsEventStatus_eIgnore;
8760 // Dispatch event to content only (NOT full processing)
8761 // See also HandleEventWithTarget which does full event processing.
8762 nsresult PresShell::HandleDOMEventWithTarget(nsIContent* aTargetContent,
8763 WidgetEvent* aEvent,
8764 nsEventStatus* aStatus) {
8765 nsresult rv = NS_OK;
8767 PushCurrentEventInfo(nullptr, aTargetContent);
8769 // Bug 41013: Check if the event should be dispatched to content.
8770 // It's possible that we are in the middle of destroying the window
8771 // and the js context is out of date. This check detects the case
8772 // that caused a crash in bug 41013, but there may be a better way
8773 // to handle this situation!
8774 nsCOMPtr<nsISupports> container = mPresContext->GetContainerWeak();
8775 if (container) {
8776 // Dispatch event to content
8777 rv = EventDispatcher::Dispatch(aTargetContent, mPresContext, aEvent,
8778 nullptr, aStatus);
8781 PopCurrentEventInfo();
8782 return rv;
8785 // See the method above.
8786 nsresult PresShell::HandleDOMEventWithTarget(nsIContent* aTargetContent,
8787 Event* aEvent,
8788 nsEventStatus* aStatus) {
8789 nsresult rv = NS_OK;
8791 PushCurrentEventInfo(nullptr, aTargetContent);
8792 nsCOMPtr<nsISupports> container = mPresContext->GetContainerWeak();
8793 if (container) {
8794 rv = EventDispatcher::DispatchDOMEvent(aTargetContent, nullptr, aEvent,
8795 mPresContext, aStatus);
8798 PopCurrentEventInfo();
8799 return rv;
8802 bool PresShell::EventHandler::AdjustContextMenuKeyEvent(
8803 WidgetMouseEvent* aMouseEvent) {
8804 #ifdef MOZ_XUL
8805 // if a menu is open, open the context menu relative to the active item on the
8806 // menu.
8807 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
8808 if (pm) {
8809 nsIFrame* popupFrame = pm->GetTopPopup(ePopupTypeMenu);
8810 if (popupFrame) {
8811 nsIFrame* itemFrame =
8812 (static_cast<nsMenuPopupFrame*>(popupFrame))->GetCurrentMenuItem();
8813 if (!itemFrame) itemFrame = popupFrame;
8815 nsCOMPtr<nsIWidget> widget = popupFrame->GetNearestWidget();
8816 aMouseEvent->mWidget = widget;
8817 LayoutDeviceIntPoint widgetPoint = widget->WidgetToScreenOffset();
8818 aMouseEvent->mRefPoint =
8819 LayoutDeviceIntPoint::FromAppUnitsToNearest(
8820 itemFrame->GetScreenRectInAppUnits().BottomLeft(),
8821 itemFrame->PresContext()->AppUnitsPerDevPixel()) -
8822 widgetPoint;
8824 mPresShell->mCurrentEventContent = itemFrame->GetContent();
8825 mPresShell->mCurrentEventFrame = itemFrame;
8827 return true;
8830 #endif
8832 // If we're here because of the key-equiv for showing context menus, we
8833 // have to twiddle with the NS event to make sure the context menu comes
8834 // up in the upper left of the relevant content area before we create
8835 // the DOM event. Since we never call InitMouseEvent() on the event,
8836 // the client X/Y will be 0,0. We can make use of that if the widget is null.
8837 // Use the root view manager's widget since it's most likely to have one,
8838 // and the coordinates returned by GetCurrentItemAndPositionForElement
8839 // are relative to the widget of the root of the root view manager.
8840 nsRootPresContext* rootPC = GetPresContext()->GetRootPresContext();
8841 aMouseEvent->mRefPoint = LayoutDeviceIntPoint(0, 0);
8842 if (rootPC) {
8843 rootPC->PresShell()->GetViewManager()->GetRootWidget(
8844 getter_AddRefs(aMouseEvent->mWidget));
8846 if (aMouseEvent->mWidget) {
8847 // default the refpoint to the topleft of our document
8848 nsPoint offset(0, 0);
8849 nsIFrame* rootFrame = FrameConstructor()->GetRootFrame();
8850 if (rootFrame) {
8851 nsView* view = rootFrame->GetClosestView(&offset);
8852 offset += view->GetOffsetToWidget(aMouseEvent->mWidget);
8853 aMouseEvent->mRefPoint = LayoutDeviceIntPoint::FromAppUnitsToNearest(
8854 offset, GetPresContext()->AppUnitsPerDevPixel());
8857 } else {
8858 aMouseEvent->mWidget = nullptr;
8861 // see if we should use the caret position for the popup
8862 LayoutDeviceIntPoint caretPoint;
8863 // Beware! This may flush notifications via synchronous
8864 // ScrollSelectionIntoView.
8865 if (PrepareToUseCaretPosition(MOZ_KnownLive(aMouseEvent->mWidget),
8866 caretPoint)) {
8867 // caret position is good
8868 int32_t devPixelRatio = GetPresContext()->AppUnitsPerDevPixel();
8869 caretPoint = LayoutDeviceIntPoint::FromAppUnitsToNearest(
8870 ViewportUtils::LayoutToVisual(
8871 LayoutDeviceIntPoint::ToAppUnits(caretPoint, devPixelRatio),
8872 GetPresContext()->PresShell()),
8873 devPixelRatio);
8874 aMouseEvent->mRefPoint = caretPoint;
8875 return true;
8878 // If we're here because of the key-equiv for showing context menus, we
8879 // have to reset the event target to the currently focused element. Get it
8880 // from the focus controller.
8881 RefPtr<Element> currentFocus;
8882 nsFocusManager* fm = nsFocusManager::GetFocusManager();
8883 if (fm) {
8884 currentFocus = fm->GetFocusedElement();
8887 // Reset event coordinates relative to focused frame in view
8888 if (currentFocus) {
8889 nsCOMPtr<nsIContent> currentPointElement;
8890 GetCurrentItemAndPositionForElement(
8891 currentFocus, getter_AddRefs(currentPointElement),
8892 aMouseEvent->mRefPoint, MOZ_KnownLive(aMouseEvent->mWidget));
8893 if (currentPointElement) {
8894 mPresShell->mCurrentEventContent = currentPointElement;
8895 mPresShell->mCurrentEventFrame = nullptr;
8896 mPresShell->GetCurrentEventFrame();
8900 return true;
8903 // PresShell::EventHandler::PrepareToUseCaretPosition
8905 // This checks to see if we should use the caret position for popup context
8906 // menus. Returns true if the caret position should be used, and the
8907 // coordinates of that position is returned in aTargetPt. This function
8908 // will also scroll the window as needed to make the caret visible.
8910 // The event widget should be the widget that generated the event, and
8911 // whose coordinate system the resulting event's mRefPoint should be
8912 // relative to. The returned point is in device pixels realtive to the
8913 // widget passed in.
8914 bool PresShell::EventHandler::PrepareToUseCaretPosition(
8915 nsIWidget* aEventWidget, LayoutDeviceIntPoint& aTargetPt) {
8916 nsresult rv;
8918 // check caret visibility
8919 RefPtr<nsCaret> caret = mPresShell->GetCaret();
8920 NS_ENSURE_TRUE(caret, false);
8922 bool caretVisible = caret->IsVisible();
8923 if (!caretVisible) return false;
8925 // caret selection, this is a temporary weak reference, so no refcounting is
8926 // needed
8927 Selection* domSelection = caret->GetSelection();
8928 NS_ENSURE_TRUE(domSelection, false);
8930 // since the match could be an anonymous textnode inside a
8931 // <textarea> or text <input>, we need to get the outer frame
8932 // note: frames are not refcounted
8933 nsIFrame* frame = nullptr; // may be nullptr
8934 nsINode* node = domSelection->GetFocusNode();
8935 NS_ENSURE_TRUE(node, false);
8936 nsCOMPtr<nsIContent> content = nsIContent::FromNode(node);
8937 if (content) {
8938 nsIContent* nonNative = content->FindFirstNonChromeOnlyAccessContent();
8939 content = nonNative;
8942 if (content) {
8943 // It seems like ScrollSelectionIntoView should be enough, but it's
8944 // not. The problem is that scrolling the selection into view when it is
8945 // below the current viewport will align the top line of the frame exactly
8946 // with the bottom of the window. This is fine, BUT, the popup event causes
8947 // the control to be re-focused which does this exact call to
8948 // ScrollContentIntoView, which has a one-pixel disagreement of whether the
8949 // frame is actually in view. The result is that the frame is aligned with
8950 // the top of the window, but the menu is still at the bottom.
8952 // Doing this call first forces the frame to be in view, eliminating the
8953 // problem. The only difference in the result is that if your cursor is in
8954 // an edit box below the current view, you'll get the edit box aligned with
8955 // the top of the window. This is arguably better behavior anyway.
8956 rv =
8957 MOZ_KnownLive(mPresShell)
8958 ->ScrollContentIntoView(
8959 content, ScrollAxis(kScrollMinimum, WhenToScroll::IfNotVisible),
8960 ScrollAxis(kScrollMinimum, WhenToScroll::IfNotVisible),
8961 ScrollFlags::ScrollOverflowHidden);
8962 NS_ENSURE_SUCCESS(rv, false);
8963 frame = content->GetPrimaryFrame();
8964 NS_WARNING_ASSERTION(frame, "No frame for focused content?");
8967 // Actually scroll the selection (ie caret) into view. Note that this must
8968 // be synchronous since we will be checking the caret position on the screen.
8970 // Be easy about errors, and just don't scroll in those cases. Better to have
8971 // the correct menu at a weird place than the wrong menu.
8972 // After ScrollSelectionIntoView(), the pending notifications might be
8973 // flushed and PresShell/PresContext/Frames may be dead. See bug 418470.
8974 nsCOMPtr<nsISelectionController> selCon;
8975 if (frame)
8976 frame->GetSelectionController(GetPresContext(), getter_AddRefs(selCon));
8977 else
8978 selCon = static_cast<nsISelectionController*>(mPresShell);
8979 if (selCon) {
8980 rv = selCon->ScrollSelectionIntoView(
8981 nsISelectionController::SELECTION_NORMAL,
8982 nsISelectionController::SELECTION_FOCUS_REGION,
8983 nsISelectionController::SCROLL_SYNCHRONOUS);
8984 NS_ENSURE_SUCCESS(rv, false);
8987 nsPresContext* presContext = GetPresContext();
8989 // get caret position relative to the closest view
8990 nsRect caretCoords;
8991 nsIFrame* caretFrame = caret->GetGeometry(&caretCoords);
8992 if (!caretFrame) return false;
8993 nsPoint viewOffset;
8994 nsView* view = caretFrame->GetClosestView(&viewOffset);
8995 if (!view) return false;
8996 // and then get the caret coords relative to the event widget
8997 if (aEventWidget) {
8998 viewOffset += view->GetOffsetToWidget(aEventWidget);
9000 caretCoords.MoveBy(viewOffset);
9002 // caret coordinates are in app units, convert to pixels
9003 aTargetPt.x =
9004 presContext->AppUnitsToDevPixels(caretCoords.x + caretCoords.width);
9005 aTargetPt.y =
9006 presContext->AppUnitsToDevPixels(caretCoords.y + caretCoords.height);
9008 // make sure rounding doesn't return a pixel which is outside the caret
9009 // (e.g. one line lower)
9010 aTargetPt.y -= 1;
9012 return true;
9015 void PresShell::EventHandler::GetCurrentItemAndPositionForElement(
9016 Element* aFocusedElement, nsIContent** aTargetToUse,
9017 LayoutDeviceIntPoint& aTargetPt, nsIWidget* aRootWidget) {
9018 nsCOMPtr<nsIContent> focusedContent = aFocusedElement;
9019 MOZ_KnownLive(mPresShell)
9020 ->ScrollContentIntoView(focusedContent, ScrollAxis(), ScrollAxis(),
9021 ScrollFlags::ScrollOverflowHidden);
9023 nsPresContext* presContext = GetPresContext();
9025 bool istree = false, checkLineHeight = true;
9026 nscoord extraTreeY = 0;
9028 #ifdef MOZ_XUL
9029 // Set the position to just underneath the current item for multi-select
9030 // lists or just underneath the selected item for single-select lists. If
9031 // the element is not a list, or there is no selection, leave the position
9032 // as is.
9033 nsCOMPtr<Element> item;
9034 nsCOMPtr<nsIDOMXULMultiSelectControlElement> multiSelect =
9035 aFocusedElement->AsXULMultiSelectControl();
9036 if (multiSelect) {
9037 checkLineHeight = false;
9039 int32_t currentIndex;
9040 multiSelect->GetCurrentIndex(&currentIndex);
9041 if (currentIndex >= 0) {
9042 RefPtr<XULTreeElement> tree = XULTreeElement::FromNode(focusedContent);
9043 // Tree view special case (tree items have no frames)
9044 // Get the focused row and add its coordinates, which are already in
9045 // pixels
9046 // XXX Boris, should we create a new interface so that this doesn't
9047 // need to know about trees? Something like nsINodelessChildCreator
9048 // which could provide the current focus coordinates?
9049 if (tree) {
9050 tree->EnsureRowIsVisible(currentIndex);
9051 int32_t firstVisibleRow = tree->GetFirstVisibleRow();
9052 int32_t rowHeight = tree->RowHeight();
9054 extraTreeY += nsPresContext::CSSPixelsToAppUnits(
9055 (currentIndex - firstVisibleRow + 1) * rowHeight);
9056 istree = true;
9058 RefPtr<nsTreeColumns> cols = tree->GetColumns();
9059 if (cols) {
9060 nsTreeColumn* col = cols->GetFirstColumn();
9061 if (col) {
9062 RefPtr<Element> colElement = col->Element();
9063 nsIFrame* frame = colElement->GetPrimaryFrame();
9064 if (frame) {
9065 extraTreeY += frame->GetSize().height;
9069 } else {
9070 multiSelect->GetCurrentItem(getter_AddRefs(item));
9073 } else {
9074 // don't check menulists as the selected item will be inside a popup.
9075 nsCOMPtr<nsIDOMXULMenuListElement> menulist =
9076 aFocusedElement->AsXULMenuList();
9077 if (!menulist) {
9078 nsCOMPtr<nsIDOMXULSelectControlElement> select =
9079 aFocusedElement->AsXULSelectControl();
9080 if (select) {
9081 checkLineHeight = false;
9082 select->GetSelectedItem(getter_AddRefs(item));
9087 if (item) {
9088 focusedContent = item;
9090 #endif
9092 nsIFrame* frame = focusedContent->GetPrimaryFrame();
9093 if (frame) {
9094 NS_ASSERTION(
9095 frame->PresContext() == GetPresContext(),
9096 "handling event for focused content that is not in our document?");
9098 nsPoint frameOrigin(0, 0);
9100 // Get the frame's origin within its view
9101 nsView* view = frame->GetClosestView(&frameOrigin);
9102 NS_ASSERTION(view, "No view for frame");
9104 // View's origin relative the widget
9105 if (aRootWidget) {
9106 frameOrigin += view->GetOffsetToWidget(aRootWidget);
9109 // Start context menu down and to the right from top left of frame
9110 // use the lineheight. This is a good distance to move the context
9111 // menu away from the top left corner of the frame. If we always
9112 // used the frame height, the context menu could end up far away,
9113 // for example when we're focused on linked images.
9114 // On the other hand, we want to use the frame height if it's less
9115 // than the current line height, so that the context menu appears
9116 // associated with the correct frame.
9117 nscoord extra = 0;
9118 if (!istree) {
9119 extra = frame->GetSize().height;
9120 if (checkLineHeight) {
9121 nsIScrollableFrame* scrollFrame =
9122 nsLayoutUtils::GetNearestScrollableFrame(
9123 frame, nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN |
9124 nsLayoutUtils::SCROLLABLE_FIXEDPOS_FINDS_ROOT);
9125 if (scrollFrame) {
9126 nsSize scrollAmount = scrollFrame->GetLineScrollAmount();
9127 nsIFrame* f = do_QueryFrame(scrollFrame);
9128 int32_t APD = presContext->AppUnitsPerDevPixel();
9129 int32_t scrollAPD = f->PresContext()->AppUnitsPerDevPixel();
9130 scrollAmount = scrollAmount.ScaleToOtherAppUnits(scrollAPD, APD);
9131 if (extra > scrollAmount.height) {
9132 extra = scrollAmount.height;
9138 aTargetPt.x = presContext->AppUnitsToDevPixels(frameOrigin.x);
9139 aTargetPt.y =
9140 presContext->AppUnitsToDevPixels(frameOrigin.y + extra + extraTreeY);
9143 NS_IF_ADDREF(*aTargetToUse = focusedContent);
9146 bool PresShell::ShouldIgnoreInvalidation() {
9147 return mPaintingSuppressed || !mIsActive || mIsNeverPainting;
9150 void PresShell::WillPaint() {
9151 // Check the simplest things first. In particular, it's important to
9152 // check mIsActive before making any of the more expensive calls such
9153 // as GetRootPresContext, for the case of a browser with a large
9154 // number of tabs.
9155 // Don't bother doing anything if some viewmanager in our tree is painting
9156 // while we still have painting suppressed or we are not active.
9157 if (!mIsActive || mPaintingSuppressed || !IsVisible()) {
9158 return;
9161 nsRootPresContext* rootPresContext = mPresContext->GetRootPresContext();
9162 if (!rootPresContext) {
9163 // In some edge cases, such as when we don't have a root frame yet,
9164 // we can't find the root prescontext. There's nothing to do in that
9165 // case.
9166 return;
9169 rootPresContext->FlushWillPaintObservers();
9170 if (mIsDestroying) return;
9172 // Process reflows, if we have them, to reduce flicker due to invalidates and
9173 // reflow being interspersed. Note that we _do_ allow this to be
9174 // interruptible; if we can't do all the reflows it's better to flicker a bit
9175 // than to freeze up.
9176 FlushPendingNotifications(
9177 ChangesToFlush(FlushType::InterruptibleLayout, false));
9180 void PresShell::DidPaintWindow() {
9181 nsRootPresContext* rootPresContext = mPresContext->GetRootPresContext();
9182 if (rootPresContext != mPresContext) {
9183 // This could be a popup's presshell. No point in notifying XPConnect
9184 // about compositing of popups.
9185 return;
9188 if (!mHasReceivedPaintMessage) {
9189 mHasReceivedPaintMessage = true;
9191 nsCOMPtr<nsIObserverService> obsvc = services::GetObserverService();
9192 if (obsvc && mDocument) {
9193 nsPIDOMWindowOuter* window = mDocument->GetWindow();
9194 nsCOMPtr<nsIDOMChromeWindow> chromeWin(do_QueryInterface(window));
9195 if (chromeWin) {
9196 obsvc->NotifyObservers(chromeWin, "widget-first-paint", nullptr);
9202 bool PresShell::IsVisible() const {
9203 if (!mIsActive || !mViewManager) return false;
9205 nsView* view = mViewManager->GetRootView();
9206 if (!view) return true;
9208 // inner view of subdoc frame
9209 view = view->GetParent();
9210 if (!view) return true;
9212 // subdoc view
9213 view = view->GetParent();
9214 if (!view) return true;
9216 nsIFrame* frame = view->GetFrame();
9217 if (!frame) return true;
9219 return frame->IsVisibleConsideringAncestors(
9220 nsIFrame::VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY);
9223 void PresShell::SuppressDisplayport(bool aEnabled) {
9224 if (aEnabled) {
9225 mActiveSuppressDisplayport++;
9226 } else if (mActiveSuppressDisplayport > 0) {
9227 bool isSuppressed = IsDisplayportSuppressed();
9228 mActiveSuppressDisplayport--;
9229 if (isSuppressed && !IsDisplayportSuppressed()) {
9230 // We unsuppressed the displayport, trigger a paint
9231 if (nsIFrame* rootFrame = mFrameConstructor->GetRootFrame()) {
9232 rootFrame->SchedulePaint();
9238 static bool sDisplayPortSuppressionRespected = true;
9240 void PresShell::RespectDisplayportSuppression(bool aEnabled) {
9241 bool isSuppressed = IsDisplayportSuppressed();
9242 sDisplayPortSuppressionRespected = aEnabled;
9243 if (isSuppressed && !IsDisplayportSuppressed()) {
9244 // We unsuppressed the displayport, trigger a paint
9245 if (nsIFrame* rootFrame = mFrameConstructor->GetRootFrame()) {
9246 rootFrame->SchedulePaint();
9251 bool PresShell::IsDisplayportSuppressed() {
9252 return sDisplayPortSuppressionRespected && mActiveSuppressDisplayport > 0;
9255 static CallState FreezeSubDocument(Document& aDocument) {
9256 if (PresShell* presShell = aDocument.GetPresShell()) {
9257 presShell->Freeze();
9259 return CallState::Continue;
9262 void PresShell::Freeze(bool aIncludeSubDocuments) {
9263 mUpdateApproximateFrameVisibilityEvent.Revoke();
9265 MaybeReleaseCapturingContent();
9267 if (mCaret) {
9268 SetCaretEnabled(false);
9271 mPaintingSuppressed = true;
9273 if (aIncludeSubDocuments && mDocument) {
9274 mDocument->EnumerateSubDocuments(FreezeSubDocument);
9277 nsPresContext* presContext = GetPresContext();
9278 if (presContext) {
9279 presContext->DisableInteractionTimeRecording();
9280 if (presContext->RefreshDriver()->GetPresContext() == presContext) {
9281 presContext->RefreshDriver()->Freeze();
9285 mFrozen = true;
9286 if (mDocument) {
9287 UpdateImageLockingState();
9291 void PresShell::FireOrClearDelayedEvents(bool aFireEvents) {
9292 mNoDelayedMouseEvents = false;
9293 mNoDelayedKeyEvents = false;
9294 if (!aFireEvents) {
9295 mDelayedEvents.Clear();
9296 return;
9299 if (mDocument) {
9300 RefPtr<Document> doc = mDocument;
9301 while (!mIsDestroying && mDelayedEvents.Length() &&
9302 !doc->EventHandlingSuppressed()) {
9303 UniquePtr<DelayedEvent> ev = std::move(mDelayedEvents[0]);
9304 mDelayedEvents.RemoveElementAt(0);
9305 if (ev->IsKeyPressEvent() && mIsLastKeyDownCanceled) {
9306 continue;
9308 ev->Dispatch();
9310 if (!doc->EventHandlingSuppressed()) {
9311 mDelayedEvents.Clear();
9316 void PresShell::Thaw(bool aIncludeSubDocuments) {
9317 nsPresContext* presContext = GetPresContext();
9318 if (presContext &&
9319 presContext->RefreshDriver()->GetPresContext() == presContext) {
9320 presContext->RefreshDriver()->Thaw();
9323 if (aIncludeSubDocuments && mDocument) {
9324 mDocument->EnumerateSubDocuments([](Document& aSubDoc) {
9325 if (PresShell* presShell = aSubDoc.GetPresShell()) {
9326 presShell->Thaw();
9328 return CallState::Continue;
9332 // Get the activeness of our presshell, as this might have changed
9333 // while we were in the bfcache
9334 ActivenessMaybeChanged();
9336 // We're now unfrozen
9337 mFrozen = false;
9338 UpdateImageLockingState();
9340 UnsuppressPainting();
9343 //--------------------------------------------------------
9344 // Start of protected and private methods on the PresShell
9345 //--------------------------------------------------------
9347 void PresShell::MaybeScheduleReflow() {
9348 ASSERT_REFLOW_SCHEDULED_STATE();
9349 if (mObservingLayoutFlushes || mIsDestroying || mIsReflowing ||
9350 mDirtyRoots.IsEmpty())
9351 return;
9353 if (!mPresContext->HasPendingInterrupt() || !ScheduleReflowOffTimer()) {
9354 ScheduleReflow();
9357 ASSERT_REFLOW_SCHEDULED_STATE();
9360 void PresShell::ScheduleReflow() {
9361 ASSERT_REFLOW_SCHEDULED_STATE();
9362 DoObserveLayoutFlushes();
9363 ASSERT_REFLOW_SCHEDULED_STATE();
9366 void PresShell::WillCauseReflow() {
9367 nsContentUtils::AddScriptBlocker();
9368 ++mChangeNestCount;
9371 void PresShell::DidCauseReflow() {
9372 NS_ASSERTION(mChangeNestCount != 0, "Unexpected call to DidCauseReflow()");
9373 --mChangeNestCount;
9374 nsContentUtils::RemoveScriptBlocker();
9377 void PresShell::WillDoReflow() {
9378 mDocument->FlushUserFontSet();
9380 mPresContext->FlushCounterStyles();
9382 mPresContext->FlushFontFeatureValues();
9384 mLastReflowStart = GetPerformanceNowUnclamped();
9387 void PresShell::DidDoReflow(bool aInterruptible) {
9388 HandlePostedReflowCallbacks(aInterruptible);
9389 if (mIsDestroying) {
9390 return;
9393 nsAutoScriptBlocker scriptBlocker;
9394 AutoAssertNoFlush noReentrantFlush(*this);
9395 if (nsCOMPtr<nsIDocShell> docShell = mPresContext->GetDocShell()) {
9396 DOMHighResTimeStamp now = GetPerformanceNowUnclamped();
9397 docShell->NotifyReflowObservers(aInterruptible, mLastReflowStart, now);
9400 if (!mPresContext->HasPendingInterrupt()) {
9401 mDocument->ScheduleResizeObserversNotification();
9404 if (StaticPrefs::layout_reflow_synthMouseMove()) {
9405 SynthesizeMouseMove(false);
9408 mPresContext->NotifyMissingFonts();
9411 DOMHighResTimeStamp PresShell::GetPerformanceNowUnclamped() {
9412 DOMHighResTimeStamp now = 0;
9414 if (nsPIDOMWindowInner* window = mDocument->GetInnerWindow()) {
9415 Performance* perf = window->GetPerformance();
9417 if (perf) {
9418 now = perf->NowUnclamped();
9422 return now;
9425 void PresShell::sReflowContinueCallback(nsITimer* aTimer, void* aPresShell) {
9426 RefPtr<PresShell> self = static_cast<PresShell*>(aPresShell);
9428 MOZ_ASSERT(aTimer == self->mReflowContinueTimer, "Unexpected timer");
9429 self->mReflowContinueTimer = nullptr;
9430 self->ScheduleReflow();
9433 bool PresShell::ScheduleReflowOffTimer() {
9434 MOZ_ASSERT(!mObservingLayoutFlushes, "Shouldn't get here");
9435 ASSERT_REFLOW_SCHEDULED_STATE();
9437 if (!mReflowContinueTimer) {
9438 nsresult rv = NS_NewTimerWithFuncCallback(
9439 getter_AddRefs(mReflowContinueTimer), sReflowContinueCallback, this, 30,
9440 nsITimer::TYPE_ONE_SHOT, "sReflowContinueCallback",
9441 mDocument->EventTargetFor(TaskCategory::Other));
9442 return NS_SUCCEEDED(rv);
9444 return true;
9447 bool PresShell::DoReflow(nsIFrame* target, bool aInterruptible,
9448 OverflowChangedTracker* aOverflowTracker) {
9449 [[maybe_unused]] nsIURI* uri = mDocument->GetDocumentURI();
9450 AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING(
9451 "Reflow", LAYOUT_Reflow, uri ? uri->GetSpecOrDefault() : "N/A"_ns);
9453 LAYOUT_TELEMETRY_RECORD_BASE(Reflow);
9455 PerfStats::AutoMetricRecording<PerfStats::Metric::Reflowing> autoRecording;
9457 gfxTextPerfMetrics* tp = mPresContext->GetTextPerfMetrics();
9458 TimeStamp timeStart;
9459 if (tp) {
9460 tp->Accumulate();
9461 tp->reflowCount++;
9462 timeStart = TimeStamp::Now();
9465 if (mMobileViewportManager) {
9466 mMobileViewportManager->UpdateSizesBeforeReflow();
9469 // Schedule a paint, but don't actually mark this frame as changed for
9470 // retained DL building purposes. If any child frames get moved, then
9471 // they will schedule paint again. We could probaby skip this, and just
9472 // schedule a similar paint when a frame is deleted.
9473 target->SchedulePaint(nsIFrame::PAINT_DEFAULT, false);
9475 nsDocShell* docShell =
9476 static_cast<nsDocShell*>(GetPresContext()->GetDocShell());
9477 RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
9478 bool isTimelineRecording = timelines && timelines->HasConsumer(docShell);
9480 if (isTimelineRecording) {
9481 timelines->AddMarkerForDocShell(docShell, "Reflow",
9482 MarkerTracingType::START);
9485 Maybe<uint64_t> innerWindowID;
9486 if (auto* window = mDocument->GetInnerWindow()) {
9487 innerWindowID = Some(window->WindowID());
9489 AutoProfilerTracing tracingLayoutFlush(
9490 "Paint", "Reflow", geckoprofiler::category::LAYOUT,
9491 std::move(mReflowCause), innerWindowID);
9492 mReflowCause = nullptr;
9494 FlushPendingScrollAnchorSelections();
9496 if (mReflowContinueTimer) {
9497 mReflowContinueTimer->Cancel();
9498 mReflowContinueTimer = nullptr;
9501 const bool isRoot = target == mFrameConstructor->GetRootFrame();
9503 MOZ_ASSERT(isRoot || aOverflowTracker,
9504 "caller must provide overflow tracker when reflowing "
9505 "non-root frames");
9507 // CreateReferenceRenderingContext can return nullptr
9508 RefPtr<gfxContext> rcx(CreateReferenceRenderingContext());
9510 #ifdef DEBUG
9511 mCurrentReflowRoot = target;
9512 #endif
9514 // If the target frame is the root of the frame hierarchy, then
9515 // use all the available space. If it's simply a `reflow root',
9516 // then use the target frame's size as the available space.
9517 WritingMode wm = target->GetWritingMode();
9518 LogicalSize size(wm);
9519 if (isRoot) {
9520 size = LogicalSize(wm, mPresContext->GetVisibleArea().Size());
9521 } else {
9522 size = target->GetLogicalSize();
9525 OverflowAreas oldOverflow; // initialized and used only when !isRoot
9526 if (!isRoot) {
9527 oldOverflow = target->GetOverflowAreas();
9530 NS_ASSERTION(!target->GetNextInFlow() && !target->GetPrevInFlow(),
9531 "reflow roots should never split");
9533 // Don't pass size directly to the reflow input, since a
9534 // constrained height implies page/column breaking.
9535 LogicalSize reflowSize(wm, size.ISize(wm), NS_UNCONSTRAINEDSIZE);
9536 ReflowInput reflowInput(mPresContext, target, rcx, reflowSize,
9537 ReflowInput::InitFlag::CallerWillInit);
9538 reflowInput.mOrthogonalLimit = size.BSize(wm);
9540 if (isRoot) {
9541 reflowInput.Init(mPresContext);
9543 // When the root frame is being reflowed with unconstrained block-size
9544 // (which happens when we're called from
9545 // nsDocumentViewer::SizeToContent), we're effectively doing a
9546 // resize in the block direction, since it changes the meaning of
9547 // percentage block-sizes even if no block-sizes actually changed.
9548 // The same applies when we reflow again after that computation. This is
9549 // an unusual case, and isn't caught by ReflowInput::InitResizeFlags.
9550 bool hasUnconstrainedBSize = size.BSize(wm) == NS_UNCONSTRAINEDSIZE;
9552 if (hasUnconstrainedBSize || mLastRootReflowHadUnconstrainedBSize) {
9553 reflowInput.SetBResize(true);
9556 mLastRootReflowHadUnconstrainedBSize = hasUnconstrainedBSize;
9557 } else {
9558 // Initialize reflow input with current used border and padding,
9559 // in case this was set specially by the parent frame when the reflow root
9560 // was reflowed by its parent.
9561 reflowInput.Init(mPresContext, Nothing(),
9562 Some(target->GetLogicalUsedBorder(wm)),
9563 Some(target->GetLogicalUsedPadding(wm)));
9566 // fix the computed height
9567 NS_ASSERTION(reflowInput.ComputedPhysicalMargin() == nsMargin(0, 0, 0, 0),
9568 "reflow input should not set margin for reflow roots");
9569 if (size.BSize(wm) != NS_UNCONSTRAINEDSIZE) {
9570 nscoord computedBSize =
9571 size.BSize(wm) -
9572 reflowInput.ComputedLogicalBorderPadding(wm).BStartEnd(wm);
9573 computedBSize = std::max(computedBSize, 0);
9574 reflowInput.SetComputedBSize(computedBSize);
9576 NS_ASSERTION(
9577 reflowInput.ComputedISize() ==
9578 size.ISize(wm) -
9579 reflowInput.ComputedLogicalBorderPadding(wm).IStartEnd(wm),
9580 "reflow input computed incorrect inline size");
9582 mPresContext->ReflowStarted(aInterruptible);
9583 mIsReflowing = true;
9585 nsReflowStatus status;
9586 ReflowOutput desiredSize(reflowInput);
9587 target->Reflow(mPresContext, desiredSize, reflowInput, status);
9589 // If an incremental reflow is initiated at a frame other than the
9590 // root frame, then its desired size had better not change! If it's
9591 // initiated at the root, then the size better not change unless its
9592 // height was unconstrained to start with.
9593 nsRect boundsRelativeToTarget =
9594 nsRect(0, 0, desiredSize.Width(), desiredSize.Height());
9595 NS_ASSERTION((isRoot && size.BSize(wm) == NS_UNCONSTRAINEDSIZE) ||
9596 (desiredSize.ISize(wm) == size.ISize(wm) &&
9597 desiredSize.BSize(wm) == size.BSize(wm)),
9598 "non-root frame's desired size changed during an "
9599 "incremental reflow");
9600 NS_ASSERTION(status.IsEmpty(), "reflow roots should never split");
9602 target->SetSize(boundsRelativeToTarget.Size());
9604 // Always use boundsRelativeToTarget here, not
9605 // desiredSize.InkOverflowRect(), because for root frames (where they
9606 // could be different, since root frames are allowed to have overflow) the
9607 // root view bounds need to match the viewport bounds; the view manager
9608 // "window dimensions" code depends on it.
9609 nsContainerFrame::SyncFrameViewAfterReflow(
9610 mPresContext, target, target->GetView(), boundsRelativeToTarget);
9611 nsContainerFrame::SyncWindowProperties(mPresContext, target,
9612 target->GetView(), rcx,
9613 nsContainerFrame::SET_ASYNC);
9615 target->DidReflow(mPresContext, nullptr);
9616 if (target->IsInScrollAnchorChain()) {
9617 ScrollAnchorContainer* container = ScrollAnchorContainer::FindFor(target);
9618 PostPendingScrollAnchorAdjustment(container);
9620 if (isRoot && size.BSize(wm) == NS_UNCONSTRAINEDSIZE) {
9621 mPresContext->SetVisibleArea(boundsRelativeToTarget);
9624 #ifdef DEBUG
9625 mCurrentReflowRoot = nullptr;
9626 #endif
9628 if (!isRoot && oldOverflow != target->GetOverflowAreas()) {
9629 // The overflow area changed. Propagate this change to ancestors.
9630 aOverflowTracker->AddFrame(target->GetParent(),
9631 OverflowChangedTracker::CHILDREN_CHANGED);
9634 NS_ASSERTION(
9635 mPresContext->HasPendingInterrupt() || mFramesToDirty.Count() == 0,
9636 "Why do we need to dirty anything if not interrupted?");
9638 mIsReflowing = false;
9639 bool interrupted = mPresContext->HasPendingInterrupt();
9640 if (interrupted) {
9641 // Make sure target gets reflowed again.
9642 for (const auto& key : mFramesToDirty) {
9643 // Mark frames dirty until target frame.
9644 for (nsIFrame* f = key; f && !f->IsSubtreeDirty(); f = f->GetParent()) {
9645 f->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
9646 if (f->IsFlexItem()) {
9647 nsFlexContainerFrame::MarkCachedFlexMeasurementsDirty(f);
9650 if (f == target) {
9651 break;
9656 NS_ASSERTION(target->IsSubtreeDirty(), "Why is the target not dirty?");
9657 mDirtyRoots.Add(target);
9658 SetNeedLayoutFlush();
9660 // Clear mFramesToDirty after we've done the target->IsSubtreeDirty()
9661 // assertion so that if it fails it's easier to see what's going on.
9662 #ifdef NOISY_INTERRUPTIBLE_REFLOW
9663 printf("mFramesToDirty.Count() == %u\n", mFramesToDirty.Count());
9664 #endif /* NOISY_INTERRUPTIBLE_REFLOW */
9665 mFramesToDirty.Clear();
9667 // Any FlushPendingNotifications with interruptible reflows
9668 // should be suppressed now. We don't want to do extra reflow work
9669 // before our reflow event happens.
9670 mWasLastReflowInterrupted = true;
9671 MaybeScheduleReflow();
9674 // dump text perf metrics for reflows with significant text processing
9675 if (tp) {
9676 if (tp->current.numChars > 100) {
9677 TimeDuration reflowTime = TimeStamp::Now() - timeStart;
9678 LogTextPerfStats(tp, this, tp->current, reflowTime.ToMilliseconds(),
9679 eLog_reflow, nullptr);
9681 tp->Accumulate();
9684 if (isTimelineRecording) {
9685 timelines->AddMarkerForDocShell(docShell, "Reflow", MarkerTracingType::END);
9688 return !interrupted;
9691 #ifdef DEBUG
9692 void PresShell::DoVerifyReflow() {
9693 if (GetVerifyReflowEnable()) {
9694 // First synchronously render what we have so far so that we can
9695 // see it.
9696 nsView* rootView = mViewManager->GetRootView();
9697 mViewManager->InvalidateView(rootView);
9699 FlushPendingNotifications(FlushType::Layout);
9700 mInVerifyReflow = true;
9701 bool ok = VerifyIncrementalReflow();
9702 mInVerifyReflow = false;
9703 if (VerifyReflowFlags::All & gVerifyReflowFlags) {
9704 printf("ProcessReflowCommands: finished (%s)\n", ok ? "ok" : "failed");
9707 if (!mDirtyRoots.IsEmpty()) {
9708 printf("XXX yikes! reflow commands queued during verify-reflow\n");
9712 #endif
9714 // used with Telemetry metrics
9715 #define NS_LONG_REFLOW_TIME_MS 5000
9717 bool PresShell::ProcessReflowCommands(bool aInterruptible) {
9718 if (mDirtyRoots.IsEmpty() && !mShouldUnsuppressPainting) {
9719 // Nothing to do; bail out
9720 return true;
9723 mozilla::TimeStamp timerStart = mozilla::TimeStamp::Now();
9724 bool interrupted = false;
9725 if (!mDirtyRoots.IsEmpty()) {
9726 #ifdef DEBUG
9727 if (VerifyReflowFlags::DumpCommands & gVerifyReflowFlags) {
9728 printf("ProcessReflowCommands: begin incremental reflow\n");
9730 #endif
9732 // If reflow is interruptible, then make a note of our deadline.
9733 const PRIntervalTime deadline =
9734 aInterruptible
9735 ? PR_IntervalNow() + PR_MicrosecondsToInterval(gMaxRCProcessingTime)
9736 : (PRIntervalTime)0;
9738 // Scope for the reflow entry point
9740 nsAutoScriptBlocker scriptBlocker;
9741 WillDoReflow();
9742 AUTO_LAYOUT_PHASE_ENTRY_POINT(GetPresContext(), Reflow);
9743 nsViewManager::AutoDisableRefresh refreshBlocker(mViewManager);
9745 OverflowChangedTracker overflowTracker;
9747 do {
9748 // Send an incremental reflow notification to the target frame.
9749 nsIFrame* target = mDirtyRoots.PopShallowestRoot();
9751 if (!target->IsSubtreeDirty()) {
9752 // It's not dirty anymore, which probably means the notification
9753 // was posted in the middle of a reflow (perhaps with a reflow
9754 // root in the middle). Don't do anything.
9755 continue;
9758 interrupted = !DoReflow(target, aInterruptible, &overflowTracker);
9760 // Keep going until we're out of reflow commands, or we've run
9761 // past our deadline, or we're interrupted.
9762 } while (!interrupted && !mDirtyRoots.IsEmpty() &&
9763 (!aInterruptible || PR_IntervalNow() < deadline));
9765 interrupted = !mDirtyRoots.IsEmpty();
9767 overflowTracker.Flush();
9769 if (!interrupted) {
9770 // We didn't get interrupted. Go ahead and perform scroll anchor
9771 // adjustments.
9772 FlushPendingScrollAnchorAdjustments();
9776 // Exiting the scriptblocker might have killed us
9777 if (!mIsDestroying) {
9778 DidDoReflow(aInterruptible);
9781 // DidDoReflow might have killed us
9782 if (!mIsDestroying) {
9783 #ifdef DEBUG
9784 if (VerifyReflowFlags::DumpCommands & gVerifyReflowFlags) {
9785 printf("\nPresShell::ProcessReflowCommands() finished: this=%p\n",
9786 (void*)this);
9788 DoVerifyReflow();
9789 #endif
9791 // If any new reflow commands were enqueued during the reflow, schedule
9792 // another reflow event to process them. Note that we want to do this
9793 // after DidDoReflow(), since that method can change whether there are
9794 // dirty roots around by flushing, and there's no point in posting a
9795 // reflow event just to have the flush revoke it.
9796 if (!mDirtyRoots.IsEmpty()) {
9797 MaybeScheduleReflow();
9798 // And record that we might need flushing
9799 SetNeedLayoutFlush();
9804 if (!mIsDestroying && mShouldUnsuppressPainting && mDirtyRoots.IsEmpty()) {
9805 // We only unlock if we're out of reflows. It's pointless
9806 // to unlock if reflows are still pending, since reflows
9807 // are just going to thrash the frames around some more. By
9808 // waiting we avoid an overeager "jitter" effect.
9809 mShouldUnsuppressPainting = false;
9810 UnsuppressAndInvalidate();
9813 if (mDocument->GetRootElement()) {
9814 TimeDuration elapsed = TimeStamp::Now() - timerStart;
9815 int32_t intElapsed = int32_t(elapsed.ToMilliseconds());
9817 if (intElapsed > NS_LONG_REFLOW_TIME_MS) {
9818 Telemetry::Accumulate(Telemetry::LONG_REFLOW_INTERRUPTIBLE,
9819 aInterruptible ? 1 : 0);
9823 return !interrupted;
9826 void PresShell::WindowSizeMoveDone() {
9827 if (mPresContext) {
9828 EventStateManager::ClearGlobalActiveContent(nullptr);
9829 ClearMouseCapture();
9833 NS_IMETHODIMP
9834 PresShell::Observe(nsISupports* aSubject, const char* aTopic,
9835 const char16_t* aData) {
9836 if (mIsDestroying) {
9837 NS_WARNING("our observers should have been unregistered by now");
9838 return NS_OK;
9841 if (!nsCRT::strcmp(aTopic, "memory-pressure")) {
9842 if (!AssumeAllFramesVisible() && mPresContext->IsRootContentDocument()) {
9843 DoUpdateApproximateFrameVisibility(/* aRemoveOnly = */ true);
9845 return NS_OK;
9848 if (!nsCRT::strcmp(aTopic, NS_WIDGET_WAKE_OBSERVER_TOPIC)) {
9849 mLastOSWake = TimeStamp::Now();
9850 return NS_OK;
9853 // For parent process, user may expect the UI is interactable after a
9854 // tab (previously opened page or home page) has restored.
9855 if (!nsCRT::strcmp(aTopic, "sessionstore-one-or-no-tab-restored")) {
9856 MOZ_ASSERT(XRE_IsParentProcess());
9857 sProcessInteractable = true;
9859 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
9860 if (os) {
9861 os->RemoveObserver(this, "sessionstore-one-or-no-tab-restored");
9863 return NS_OK;
9866 if (!nsCRT::strcmp(aTopic, "font-info-updated")) {
9867 mPresContext->ForceReflowForFontInfoUpdate();
9868 return NS_OK;
9871 if (!nsCRT::strcmp(aTopic, "look-and-feel-changed")) {
9872 // See how LookAndFeel::NotifyChangedAllWindows encodes this.
9873 auto kind = widget::ThemeChangeKind(aData[0]);
9874 ThemeChanged(kind);
9875 return NS_OK;
9878 NS_WARNING("unrecognized topic in PresShell::Observe");
9879 return NS_ERROR_FAILURE;
9882 bool PresShell::AddRefreshObserver(nsARefreshObserver* aObserver,
9883 FlushType aFlushType,
9884 const char* aObserverDescription) {
9885 nsPresContext* presContext = GetPresContext();
9886 if (MOZ_UNLIKELY(!presContext)) {
9887 return false;
9889 presContext->RefreshDriver()->AddRefreshObserver(aObserver, aFlushType,
9890 aObserverDescription);
9891 return true;
9894 bool PresShell::RemoveRefreshObserver(nsARefreshObserver* aObserver,
9895 FlushType aFlushType) {
9896 nsPresContext* presContext = GetPresContext();
9897 return presContext && presContext->RefreshDriver()->RemoveRefreshObserver(
9898 aObserver, aFlushType);
9901 bool PresShell::AddPostRefreshObserver(nsAPostRefreshObserver* aObserver) {
9902 nsPresContext* presContext = GetPresContext();
9903 if (!presContext) {
9904 return false;
9906 presContext->RefreshDriver()->AddPostRefreshObserver(aObserver);
9907 return true;
9910 bool PresShell::RemovePostRefreshObserver(nsAPostRefreshObserver* aObserver) {
9911 nsPresContext* presContext = GetPresContext();
9912 if (!presContext) {
9913 return false;
9915 presContext->RefreshDriver()->RemovePostRefreshObserver(aObserver);
9916 return true;
9919 void PresShell::DoObserveStyleFlushes() {
9920 MOZ_ASSERT(!ObservingStyleFlushes());
9921 mObservingStyleFlushes = true;
9923 if (MOZ_LIKELY(!mDocument->GetBFCacheEntry())) {
9924 mPresContext->RefreshDriver()->AddStyleFlushObserver(this);
9928 void PresShell::DoObserveLayoutFlushes() {
9929 MOZ_ASSERT(!ObservingLayoutFlushes());
9930 mObservingLayoutFlushes = true;
9932 if (MOZ_LIKELY(!mDocument->GetBFCacheEntry())) {
9933 mPresContext->RefreshDriver()->AddLayoutFlushObserver(this);
9937 //------------------------------------------------------
9938 // End of protected and private methods on the PresShell
9939 //------------------------------------------------------
9941 //------------------------------------------------------------------
9942 //-- Delayed event Classes Impls
9943 //------------------------------------------------------------------
9945 PresShell::DelayedInputEvent::DelayedInputEvent()
9946 : DelayedEvent(), mEvent(nullptr) {}
9948 PresShell::DelayedInputEvent::~DelayedInputEvent() { delete mEvent; }
9950 void PresShell::DelayedInputEvent::Dispatch() {
9951 if (!mEvent || !mEvent->mWidget) {
9952 return;
9954 nsCOMPtr<nsIWidget> widget = mEvent->mWidget;
9955 nsEventStatus status;
9956 widget->DispatchEvent(mEvent, status);
9959 PresShell::DelayedMouseEvent::DelayedMouseEvent(WidgetMouseEvent* aEvent)
9960 : DelayedInputEvent() {
9961 MOZ_DIAGNOSTIC_ASSERT(aEvent->IsTrusted());
9962 WidgetMouseEvent* mouseEvent =
9963 new WidgetMouseEvent(true, aEvent->mMessage, aEvent->mWidget,
9964 aEvent->mReason, aEvent->mContextMenuTrigger);
9965 mouseEvent->AssignMouseEventData(*aEvent, false);
9966 mEvent = mouseEvent;
9969 PresShell::DelayedKeyEvent::DelayedKeyEvent(WidgetKeyboardEvent* aEvent)
9970 : DelayedInputEvent() {
9971 MOZ_DIAGNOSTIC_ASSERT(aEvent->IsTrusted());
9972 WidgetKeyboardEvent* keyEvent =
9973 new WidgetKeyboardEvent(true, aEvent->mMessage, aEvent->mWidget);
9974 keyEvent->AssignKeyEventData(*aEvent, false);
9975 keyEvent->mFlags.mIsSynthesizedForTests =
9976 aEvent->mFlags.mIsSynthesizedForTests;
9977 keyEvent->mFlags.mIsSuppressedOrDelayed = true;
9978 mEvent = keyEvent;
9981 bool PresShell::DelayedKeyEvent::IsKeyPressEvent() {
9982 return mEvent->mMessage == eKeyPress;
9985 // Start of DEBUG only code
9987 #ifdef DEBUG
9989 static void LogVerifyMessage(nsIFrame* k1, nsIFrame* k2, const char* aMsg) {
9990 nsAutoString n1, n2;
9991 if (k1) {
9992 k1->GetFrameName(n1);
9993 } else {
9994 n1.AssignLiteral(u"(null)");
9997 if (k2) {
9998 k2->GetFrameName(n2);
9999 } else {
10000 n2.AssignLiteral(u"(null)");
10003 printf("verifyreflow: %s %p != %s %p %s\n",
10004 NS_LossyConvertUTF16toASCII(n1).get(), (void*)k1,
10005 NS_LossyConvertUTF16toASCII(n2).get(), (void*)k2, aMsg);
10008 static void LogVerifyMessage(nsIFrame* k1, nsIFrame* k2, const char* aMsg,
10009 const nsRect& r1, const nsRect& r2) {
10010 printf("VerifyReflow Error:\n");
10011 nsAutoString name;
10013 if (k1) {
10014 k1->GetFrameName(name);
10015 printf(" %s %p ", NS_LossyConvertUTF16toASCII(name).get(), (void*)k1);
10017 printf("{%d, %d, %d, %d} != \n", r1.x, r1.y, r1.width, r1.height);
10019 if (k2) {
10020 k2->GetFrameName(name);
10021 printf(" %s %p ", NS_LossyConvertUTF16toASCII(name).get(), (void*)k2);
10023 printf("{%d, %d, %d, %d}\n %s\n", r2.x, r2.y, r2.width, r2.height, aMsg);
10026 static void LogVerifyMessage(nsIFrame* k1, nsIFrame* k2, const char* aMsg,
10027 const nsIntRect& r1, const nsIntRect& r2) {
10028 printf("VerifyReflow Error:\n");
10029 nsAutoString name;
10031 if (k1) {
10032 k1->GetFrameName(name);
10033 printf(" %s %p ", NS_LossyConvertUTF16toASCII(name).get(), (void*)k1);
10035 printf("{%d, %d, %d, %d} != \n", r1.x, r1.y, r1.width, r1.height);
10037 if (k2) {
10038 k2->GetFrameName(name);
10039 printf(" %s %p ", NS_LossyConvertUTF16toASCII(name).get(), (void*)k2);
10041 printf("{%d, %d, %d, %d}\n %s\n", r2.x, r2.y, r2.width, r2.height, aMsg);
10044 static bool CompareTrees(nsPresContext* aFirstPresContext,
10045 nsIFrame* aFirstFrame,
10046 nsPresContext* aSecondPresContext,
10047 nsIFrame* aSecondFrame) {
10048 if (!aFirstPresContext || !aFirstFrame || !aSecondPresContext ||
10049 !aSecondFrame)
10050 return true;
10051 // XXX Evil hack to reduce false positives; I can't seem to figure
10052 // out how to flush scrollbar changes correctly
10053 // if (aFirstFrame->IsScrollbarFrame())
10054 // return true;
10055 bool ok = true;
10056 const auto& childLists1 = aFirstFrame->ChildLists();
10057 const auto& childLists2 = aSecondFrame->ChildLists();
10058 auto iterLists1 = childLists1.begin();
10059 auto iterLists2 = childLists2.begin();
10060 do {
10061 const nsFrameList& kids1 =
10062 iterLists1 != childLists1.end() ? iterLists1->mList : nsFrameList();
10063 const nsFrameList& kids2 =
10064 iterLists2 != childLists2.end() ? iterLists2->mList : nsFrameList();
10065 int32_t l1 = kids1.GetLength();
10066 int32_t l2 = kids2.GetLength();
10067 if (l1 != l2) {
10068 ok = false;
10069 LogVerifyMessage(kids1.FirstChild(), kids2.FirstChild(),
10070 "child counts don't match: ");
10071 printf("%d != %d\n", l1, l2);
10072 if (!(VerifyReflowFlags::All & gVerifyReflowFlags)) {
10073 break;
10077 LayoutDeviceIntRect r1, r2;
10078 nsView* v1;
10079 nsView* v2;
10080 for (nsFrameList::Enumerator e1(kids1), e2(kids2);; e1.Next(), e2.Next()) {
10081 nsIFrame* k1 = e1.get();
10082 nsIFrame* k2 = e2.get();
10083 if (((nullptr == k1) && (nullptr != k2)) ||
10084 ((nullptr != k1) && (nullptr == k2))) {
10085 ok = false;
10086 LogVerifyMessage(k1, k2, "child lists are different\n");
10087 break;
10088 } else if (nullptr != k1) {
10089 // Verify that the frames are the same size
10090 if (!k1->GetRect().IsEqualInterior(k2->GetRect())) {
10091 ok = false;
10092 LogVerifyMessage(k1, k2, "(frame rects)", k1->GetRect(),
10093 k2->GetRect());
10096 // Make sure either both have views or neither have views; if they
10097 // do have views, make sure the views are the same size. If the
10098 // views have widgets, make sure they both do or neither does. If
10099 // they do, make sure the widgets are the same size.
10100 v1 = k1->GetView();
10101 v2 = k2->GetView();
10102 if (((nullptr == v1) && (nullptr != v2)) ||
10103 ((nullptr != v1) && (nullptr == v2))) {
10104 ok = false;
10105 LogVerifyMessage(k1, k2, "child views are not matched\n");
10106 } else if (nullptr != v1) {
10107 if (!v1->GetBounds().IsEqualInterior(v2->GetBounds())) {
10108 LogVerifyMessage(k1, k2, "(view rects)", v1->GetBounds(),
10109 v2->GetBounds());
10112 nsIWidget* w1 = v1->GetWidget();
10113 nsIWidget* w2 = v2->GetWidget();
10114 if (((nullptr == w1) && (nullptr != w2)) ||
10115 ((nullptr != w1) && (nullptr == w2))) {
10116 ok = false;
10117 LogVerifyMessage(k1, k2, "child widgets are not matched\n");
10118 } else if (nullptr != w1) {
10119 r1 = w1->GetBounds();
10120 r2 = w2->GetBounds();
10121 if (!r1.IsEqualEdges(r2)) {
10122 LogVerifyMessage(k1, k2, "(widget rects)", r1.ToUnknownRect(),
10123 r2.ToUnknownRect());
10127 if (!ok && !(VerifyReflowFlags::All & gVerifyReflowFlags)) {
10128 break;
10131 // XXX Should perhaps compare their float managers.
10133 // Compare the sub-trees too
10134 if (!CompareTrees(aFirstPresContext, k1, aSecondPresContext, k2)) {
10135 ok = false;
10136 if (!(VerifyReflowFlags::All & gVerifyReflowFlags)) {
10137 break;
10140 } else {
10141 break;
10144 if (!ok && (!(VerifyReflowFlags::All & gVerifyReflowFlags))) {
10145 break;
10148 ++iterLists1;
10149 ++iterLists2;
10150 const bool lists1Done = iterLists1 == childLists1.end();
10151 const bool lists2Done = iterLists2 == childLists2.end();
10152 if (lists1Done != lists2Done ||
10153 (!lists1Done && iterLists1->mID != iterLists2->mID)) {
10154 if (!(VerifyReflowFlags::All & gVerifyReflowFlags)) {
10155 ok = false;
10157 LogVerifyMessage(kids1.FirstChild(), kids2.FirstChild(),
10158 "child list names are not matched: ");
10159 fprintf(stdout, "%s != %s\n",
10160 !lists1Done ? ChildListName(iterLists1->mID) : "(null)",
10161 !lists2Done ? ChildListName(iterLists2->mID) : "(null)");
10162 break;
10164 } while (ok && iterLists1 != childLists1.end());
10166 return ok;
10168 #endif
10170 #if 0
10171 static nsIFrame*
10172 FindTopFrame(nsIFrame* aRoot)
10174 if (aRoot) {
10175 nsIContent* content = aRoot->GetContent();
10176 if (content) {
10177 nsAtom* tag;
10178 content->GetTag(tag);
10179 if (nullptr != tag) {
10180 NS_RELEASE(tag);
10181 return aRoot;
10185 // Try one of the children
10186 for (nsIFrame* kid : aRoot->PrincipalChildList()) {
10187 nsIFrame* result = FindTopFrame(kid);
10188 if (nullptr != result) {
10189 return result;
10193 return nullptr;
10195 #endif
10197 #ifdef DEBUG
10199 // After an incremental reflow, we verify the correctness by doing a
10200 // full reflow into a fresh frame tree.
10201 bool PresShell::VerifyIncrementalReflow() {
10202 if (VerifyReflowFlags::Noisy & gVerifyReflowFlags) {
10203 printf("Building Verification Tree...\n");
10206 // Create a presentation context to view the new frame tree
10207 RefPtr<nsPresContext> cx = new nsRootPresContext(
10208 mDocument, mPresContext->IsPaginated()
10209 ? nsPresContext::eContext_PrintPreview
10210 : nsPresContext::eContext_Galley);
10211 NS_ENSURE_TRUE(cx, false);
10213 nsDeviceContext* dc = mPresContext->DeviceContext();
10214 nsresult rv = cx->Init(dc);
10215 NS_ENSURE_SUCCESS(rv, false);
10217 // Get our scrolling preference
10218 nsView* rootView = mViewManager->GetRootView();
10219 NS_ENSURE_TRUE(rootView->HasWidget(), false);
10220 nsIWidget* parentWidget = rootView->GetWidget();
10222 // Create a new view manager.
10223 RefPtr<nsViewManager> vm = new nsViewManager();
10224 NS_ENSURE_TRUE(vm, false);
10225 rv = vm->Init(dc);
10226 NS_ENSURE_SUCCESS(rv, false);
10228 // Create a child window of the parent that is our "root view/window"
10229 // Create a view
10230 nsRect tbounds = mPresContext->GetVisibleArea();
10231 nsView* view = vm->CreateView(tbounds, nullptr);
10232 NS_ENSURE_TRUE(view, false);
10234 // now create the widget for the view
10235 rv = view->CreateWidgetForParent(parentWidget, nullptr, true);
10236 NS_ENSURE_SUCCESS(rv, false);
10238 // Setup hierarchical relationship in view manager
10239 vm->SetRootView(view);
10241 // Make the new presentation context the same size as our
10242 // presentation context.
10243 cx->SetVisibleArea(mPresContext->GetVisibleArea());
10245 RefPtr<PresShell> presShell = mDocument->CreatePresShell(cx, vm);
10246 NS_ENSURE_TRUE(presShell, false);
10248 // Note that after we create the shell, we must make sure to destroy it
10249 presShell->SetVerifyReflowEnable(
10250 false); // turn off verify reflow while we're
10251 // reflowing the test frame tree
10252 vm->SetPresShell(presShell);
10254 nsAutoCauseReflowNotifier crNotifier(this);
10255 presShell->Initialize();
10257 presShell->FlushPendingNotifications(FlushType::Layout);
10258 presShell->SetVerifyReflowEnable(
10259 true); // turn on verify reflow again now that
10260 // we're done reflowing the test frame tree
10261 // Force the non-primary presshell to unsuppress; it doesn't want to normally
10262 // because it thinks it's hidden
10263 presShell->mPaintingSuppressed = false;
10264 if (VerifyReflowFlags::Noisy & gVerifyReflowFlags) {
10265 printf("Verification Tree built, comparing...\n");
10268 // Now that the document has been reflowed, use its frame tree to
10269 // compare against our frame tree.
10270 nsIFrame* root1 = mFrameConstructor->GetRootFrame();
10271 nsIFrame* root2 = presShell->GetRootFrame();
10272 bool ok = CompareTrees(mPresContext, root1, cx, root2);
10273 if (!ok && (VerifyReflowFlags::Noisy & gVerifyReflowFlags)) {
10274 printf("Verify reflow failed, primary tree:\n");
10275 root1->List(stdout);
10276 printf("Verification tree:\n");
10277 root2->List(stdout);
10280 # if 0
10281 // Sample code for dumping page to png
10282 // XXX Needs to be made more flexible
10283 if (!ok) {
10284 nsString stra;
10285 static int num = 0;
10286 stra.AppendLiteral("C:\\mozilla\\mozilla\\debug\\filea");
10287 stra.AppendInt(num);
10288 stra.AppendLiteral(".png");
10289 gfxUtils::WriteAsPNG(presShell, stra);
10290 nsString strb;
10291 strb.AppendLiteral("C:\\mozilla\\mozilla\\debug\\fileb");
10292 strb.AppendInt(num);
10293 strb.AppendLiteral(".png");
10294 gfxUtils::WriteAsPNG(presShell, strb);
10295 ++num;
10297 # endif
10299 presShell->EndObservingDocument();
10300 presShell->Destroy();
10301 if (VerifyReflowFlags::Noisy & gVerifyReflowFlags) {
10302 printf("Finished Verifying Reflow...\n");
10305 return ok;
10308 // Layout debugging hooks
10309 void PresShell::ListComputedStyles(FILE* out, int32_t aIndent) {
10310 nsIFrame* rootFrame = GetRootFrame();
10311 if (rootFrame) {
10312 rootFrame->Style()->List(out, aIndent);
10315 // The root element's frame's ComputedStyle is the root of a separate tree.
10316 Element* rootElement = mDocument->GetRootElement();
10317 if (rootElement) {
10318 nsIFrame* rootElementFrame = rootElement->GetPrimaryFrame();
10319 if (rootElementFrame) {
10320 rootElementFrame->Style()->List(out, aIndent);
10324 #endif
10326 #if defined(DEBUG) || defined(MOZ_LAYOUT_DEBUGGER)
10327 void PresShell::ListStyleSheets(FILE* out, int32_t aIndent) {
10328 auto ListStyleSheetsAtOrigin = [this, out, aIndent](StyleOrigin origin) {
10329 int32_t sheetCount = StyleSet()->SheetCount(origin);
10330 for (int32_t i = 0; i < sheetCount; ++i) {
10331 StyleSet()->SheetAt(origin, i)->List(out, aIndent);
10335 ListStyleSheetsAtOrigin(StyleOrigin::UserAgent);
10336 ListStyleSheetsAtOrigin(StyleOrigin::User);
10337 ListStyleSheetsAtOrigin(StyleOrigin::Author);
10339 #endif
10341 //=============================================================
10342 //=============================================================
10343 //-- Debug Reflow Counts
10344 //=============================================================
10345 //=============================================================
10346 #ifdef MOZ_REFLOW_PERF
10347 //-------------------------------------------------------------
10348 void PresShell::DumpReflows() {
10349 if (mReflowCountMgr) {
10350 nsAutoCString uriStr;
10351 if (mDocument) {
10352 nsIURI* uri = mDocument->GetDocumentURI();
10353 if (uri) {
10354 uri->GetPathQueryRef(uriStr);
10357 mReflowCountMgr->DisplayTotals(uriStr.get());
10358 mReflowCountMgr->DisplayHTMLTotals(uriStr.get());
10359 mReflowCountMgr->DisplayDiffsInTotals();
10363 //-------------------------------------------------------------
10364 void PresShell::CountReflows(const char* aName, nsIFrame* aFrame) {
10365 if (mReflowCountMgr) {
10366 mReflowCountMgr->Add(aName, aFrame);
10370 //-------------------------------------------------------------
10371 void PresShell::PaintCount(const char* aName, gfxContext* aRenderingContext,
10372 nsPresContext* aPresContext, nsIFrame* aFrame,
10373 const nsPoint& aOffset, uint32_t aColor) {
10374 if (mReflowCountMgr) {
10375 mReflowCountMgr->PaintCount(aName, aRenderingContext, aPresContext, aFrame,
10376 aOffset, aColor);
10380 //-------------------------------------------------------------
10381 void PresShell::SetPaintFrameCount(bool aPaintFrameCounts) {
10382 if (mReflowCountMgr) {
10383 mReflowCountMgr->SetPaintFrameCounts(aPaintFrameCounts);
10387 bool PresShell::IsPaintingFrameCounts() {
10388 if (mReflowCountMgr) return mReflowCountMgr->IsPaintingFrameCounts();
10389 return false;
10392 //------------------------------------------------------------------
10393 //-- Reflow Counter Classes Impls
10394 //------------------------------------------------------------------
10396 //------------------------------------------------------------------
10397 ReflowCounter::ReflowCounter(ReflowCountMgr* aMgr) : mMgr(aMgr) {
10398 ClearTotals();
10399 SetTotalsCache();
10402 //------------------------------------------------------------------
10403 ReflowCounter::~ReflowCounter() = default;
10405 //------------------------------------------------------------------
10406 void ReflowCounter::ClearTotals() { mTotal = 0; }
10408 //------------------------------------------------------------------
10409 void ReflowCounter::SetTotalsCache() { mCacheTotal = mTotal; }
10411 //------------------------------------------------------------------
10412 void ReflowCounter::CalcDiffInTotals() { mCacheTotal = mTotal - mCacheTotal; }
10414 //------------------------------------------------------------------
10415 void ReflowCounter::DisplayTotals(const char* aStr) {
10416 DisplayTotals(mTotal, aStr ? aStr : "Totals");
10419 //------------------------------------------------------------------
10420 void ReflowCounter::DisplayDiffTotals(const char* aStr) {
10421 DisplayTotals(mCacheTotal, aStr ? aStr : "Diff Totals");
10424 //------------------------------------------------------------------
10425 void ReflowCounter::DisplayHTMLTotals(const char* aStr) {
10426 DisplayHTMLTotals(mTotal, aStr ? aStr : "Totals");
10429 //------------------------------------------------------------------
10430 void ReflowCounter::DisplayTotals(uint32_t aTotal, const char* aTitle) {
10431 // figure total
10432 if (aTotal == 0) {
10433 return;
10435 ReflowCounter* gTots = (ReflowCounter*)mMgr->LookUp(kGrandTotalsStr);
10437 printf("%25s\t", aTitle);
10438 printf("%d\t", aTotal);
10439 if (gTots != this && aTotal > 0) {
10440 gTots->Add(aTotal);
10444 //------------------------------------------------------------------
10445 void ReflowCounter::DisplayHTMLTotals(uint32_t aTotal, const char* aTitle) {
10446 if (aTotal == 0) {
10447 return;
10450 ReflowCounter* gTots = (ReflowCounter*)mMgr->LookUp(kGrandTotalsStr);
10451 FILE* fd = mMgr->GetOutFile();
10452 if (!fd) {
10453 return;
10456 fprintf(fd, "<tr><td><center>%s</center></td>", aTitle);
10457 fprintf(fd, "<td><center>%d</center></td></tr>\n", aTotal);
10459 if (gTots != this && aTotal > 0) {
10460 gTots->Add(aTotal);
10464 //------------------------------------------------------------------
10465 //-- ReflowCountMgr
10466 //------------------------------------------------------------------
10468 # define KEY_BUF_SIZE_FOR_PTR \
10469 24 // adequate char[] buffer to sprintf a pointer
10471 ReflowCountMgr::ReflowCountMgr() : mCounts(10), mIndiFrameCounts(10) {
10472 mCycledOnce = false;
10473 mDumpFrameCounts = false;
10474 mDumpFrameByFrameCounts = false;
10475 mPaintFrameByFrameCounts = false;
10478 //------------------------------------------------------------------
10479 ReflowCountMgr::~ReflowCountMgr() = default;
10481 //------------------------------------------------------------------
10482 ReflowCounter* ReflowCountMgr::LookUp(const char* aName) {
10483 return mCounts.Get(aName);
10486 //------------------------------------------------------------------
10487 void ReflowCountMgr::Add(const char* aName, nsIFrame* aFrame) {
10488 NS_ASSERTION(aName != nullptr, "Name shouldn't be null!");
10490 if (mDumpFrameCounts) {
10491 auto* const counter = mCounts.GetOrInsertNew(aName, this);
10492 counter->Add();
10495 if ((mDumpFrameByFrameCounts || mPaintFrameByFrameCounts) &&
10496 aFrame != nullptr) {
10497 char key[KEY_BUF_SIZE_FOR_PTR];
10498 SprintfLiteral(key, "%p", (void*)aFrame);
10499 auto* const counter =
10500 mIndiFrameCounts
10501 .LookupOrInsertWith(key,
10502 [&aName, &aFrame, this]() {
10503 auto counter =
10504 MakeUnique<IndiReflowCounter>(this);
10505 counter->mFrame = aFrame;
10506 counter->mName.AssignASCII(aName);
10507 return counter;
10509 .get();
10510 // this eliminates extra counts from super classes
10511 if (counter && counter->mName.EqualsASCII(aName)) {
10512 counter->mCount++;
10513 counter->mCounter.Add(1);
10518 //------------------------------------------------------------------
10519 void ReflowCountMgr::PaintCount(const char* aName,
10520 gfxContext* aRenderingContext,
10521 nsPresContext* aPresContext, nsIFrame* aFrame,
10522 const nsPoint& aOffset, uint32_t aColor) {
10523 if (mPaintFrameByFrameCounts && aFrame != nullptr) {
10524 char key[KEY_BUF_SIZE_FOR_PTR];
10525 SprintfLiteral(key, "%p", (void*)aFrame);
10526 IndiReflowCounter* counter = mIndiFrameCounts.Get(key);
10527 if (counter != nullptr && counter->mName.EqualsASCII(aName)) {
10528 DrawTarget* drawTarget = aRenderingContext->GetDrawTarget();
10529 int32_t appUnitsPerDevPixel = aPresContext->AppUnitsPerDevPixel();
10531 aRenderingContext->Save();
10532 gfxPoint devPixelOffset =
10533 nsLayoutUtils::PointToGfxPoint(aOffset, appUnitsPerDevPixel);
10534 aRenderingContext->SetMatrixDouble(
10535 aRenderingContext->CurrentMatrixDouble().PreTranslate(
10536 devPixelOffset));
10538 // We don't care about the document language or user fonts here;
10539 // just get a default Latin font.
10540 nsFont font(StyleGenericFontFamily::Serif, Length::FromPixels(11));
10541 nsFontMetrics::Params params;
10542 params.language = nsGkAtoms::x_western;
10543 params.textPerf = aPresContext->GetTextPerfMetrics();
10544 params.fontStats = nullptr; // not interested here
10545 params.featureValueLookup = aPresContext->GetFontFeatureValuesLookup();
10546 RefPtr<nsFontMetrics> fm =
10547 aPresContext->DeviceContext()->GetMetricsFor(font, params);
10549 char buf[16];
10550 int len = SprintfLiteral(buf, "%d", counter->mCount);
10551 nscoord x = 0, y = fm->MaxAscent();
10552 nscoord width, height = fm->MaxHeight();
10553 fm->SetTextRunRTL(false);
10554 width = fm->GetWidth(buf, len, drawTarget);
10556 sRGBColor color;
10557 sRGBColor color2;
10558 if (aColor != 0) {
10559 color = sRGBColor::FromABGR(aColor);
10560 color2 = sRGBColor(0.f, 0.f, 0.f);
10561 } else {
10562 gfx::Float rc = 0.f, gc = 0.f, bc = 0.f;
10563 if (counter->mCount < 5) {
10564 rc = 1.f;
10565 gc = 1.f;
10566 } else if (counter->mCount < 11) {
10567 gc = 1.f;
10568 } else {
10569 rc = 1.f;
10571 color = sRGBColor(rc, gc, bc);
10572 color2 = sRGBColor(rc / 2, gc / 2, bc / 2);
10575 nsRect rect(0, 0, width + 15, height + 15);
10576 Rect devPxRect =
10577 NSRectToSnappedRect(rect, appUnitsPerDevPixel, *drawTarget);
10578 ColorPattern black(ToDeviceColor(sRGBColor::OpaqueBlack()));
10579 drawTarget->FillRect(devPxRect, black);
10581 aRenderingContext->SetColor(color2);
10582 fm->DrawString(buf, len, x + 15, y + 15, aRenderingContext);
10583 aRenderingContext->SetColor(color);
10584 fm->DrawString(buf, len, x, y, aRenderingContext);
10586 aRenderingContext->Restore();
10591 //------------------------------------------------------------------
10592 void ReflowCountMgr::DoGrandTotals() {
10593 mCounts.WithEntryHandle(kGrandTotalsStr, [this](auto&& entry) {
10594 if (!entry) {
10595 entry.Insert(MakeUnique<ReflowCounter>(this));
10596 } else {
10597 entry.Data()->ClearTotals();
10601 printf("\t\t\t\tTotal\n");
10602 for (uint32_t i = 0; i < 78; i++) {
10603 printf("-");
10605 printf("\n");
10606 for (const auto& entry : mCounts) {
10607 entry.GetData()->DisplayTotals(entry.GetKey());
10611 static void RecurseIndiTotals(
10612 nsPresContext* aPresContext,
10613 nsClassHashtable<nsCharPtrHashKey, IndiReflowCounter>& aHT,
10614 nsIFrame* aParentFrame, int32_t aLevel) {
10615 if (aParentFrame == nullptr) {
10616 return;
10619 char key[KEY_BUF_SIZE_FOR_PTR];
10620 SprintfLiteral(key, "%p", (void*)aParentFrame);
10621 IndiReflowCounter* counter = aHT.Get(key);
10622 if (counter) {
10623 counter->mHasBeenOutput = true;
10624 char* name = ToNewCString(counter->mName);
10625 for (int32_t i = 0; i < aLevel; i++) printf(" ");
10626 printf("%s - %p [%d][", name, (void*)aParentFrame, counter->mCount);
10627 printf("%d", counter->mCounter.GetTotal());
10628 printf("]\n");
10629 free(name);
10632 for (nsIFrame* child : aParentFrame->PrincipalChildList()) {
10633 RecurseIndiTotals(aPresContext, aHT, child, aLevel + 1);
10637 //------------------------------------------------------------------
10638 void ReflowCountMgr::DoIndiTotalsTree() {
10639 printf("\n------------------------------------------------\n");
10640 printf("-- Individual Frame Counts\n");
10641 printf("------------------------------------------------\n");
10643 if (mPresShell) {
10644 nsIFrame* rootFrame = mPresShell->GetRootFrame();
10645 RecurseIndiTotals(mPresContext, mIndiFrameCounts, rootFrame, 0);
10646 printf("------------------------------------------------\n");
10647 printf("-- Individual Counts of Frames not in Root Tree\n");
10648 printf("------------------------------------------------\n");
10649 for (const auto& counter : mIndiFrameCounts.Values()) {
10650 if (!counter->mHasBeenOutput) {
10651 char* name = ToNewCString(counter->mName);
10652 printf("%s - %p [%d][", name, (void*)counter->mFrame,
10653 counter->mCount);
10654 printf("%d", counter->mCounter.GetTotal());
10655 printf("]\n");
10656 free(name);
10662 //------------------------------------------------------------------
10663 void ReflowCountMgr::DoGrandHTMLTotals() {
10664 mCounts.WithEntryHandle(kGrandTotalsStr, [this](auto&& entry) {
10665 if (!entry) {
10666 entry.Insert(MakeUnique<ReflowCounter>(this));
10667 } else {
10668 entry.Data()->ClearTotals();
10672 static const char* title[] = {"Class", "Reflows"};
10673 fprintf(mFD, "<tr>");
10674 for (uint32_t i = 0; i < ArrayLength(title); i++) {
10675 fprintf(mFD, "<td><center><b>%s<b></center></td>", title[i]);
10677 fprintf(mFD, "</tr>\n");
10679 for (const auto& entry : mCounts) {
10680 entry.GetData()->DisplayHTMLTotals(entry.GetKey());
10684 //------------------------------------
10685 void ReflowCountMgr::DisplayTotals(const char* aStr) {
10686 # ifdef DEBUG_rods
10687 printf("%s\n", aStr ? aStr : "No name");
10688 # endif
10689 if (mDumpFrameCounts) {
10690 DoGrandTotals();
10692 if (mDumpFrameByFrameCounts) {
10693 DoIndiTotalsTree();
10696 //------------------------------------
10697 void ReflowCountMgr::DisplayHTMLTotals(const char* aStr) {
10698 # ifdef WIN32x // XXX NOT XP!
10699 char name[1024];
10701 char* sptr = strrchr(aStr, '/');
10702 if (sptr) {
10703 sptr++;
10704 strcpy(name, sptr);
10705 char* eptr = strrchr(name, '.');
10706 if (eptr) {
10707 *eptr = 0;
10709 strcat(name, "_stats.html");
10711 mFD = fopen(name, "w");
10712 if (mFD) {
10713 fprintf(mFD, "<html><head><title>Reflow Stats</title></head><body>\n");
10714 const char* title = aStr ? aStr : "No name";
10715 fprintf(mFD,
10716 "<center><b>%s</b><br><table border=1 "
10717 "style=\"background-color:#e0e0e0\">",
10718 title);
10719 DoGrandHTMLTotals();
10720 fprintf(mFD, "</center></table>\n");
10721 fprintf(mFD, "</body></html>\n");
10722 fclose(mFD);
10723 mFD = nullptr;
10725 # endif // not XP!
10728 //------------------------------------------------------------------
10729 void ReflowCountMgr::ClearTotals() {
10730 for (const auto& data : mCounts.Values()) {
10731 data->ClearTotals();
10735 //------------------------------------------------------------------
10736 void ReflowCountMgr::ClearGrandTotals() {
10737 mCounts.WithEntryHandle(kGrandTotalsStr, [&](auto&& entry) {
10738 if (!entry) {
10739 entry.Insert(MakeUnique<ReflowCounter>(this));
10740 } else {
10741 entry.Data()->ClearTotals();
10742 entry.Data()->SetTotalsCache();
10747 //------------------------------------------------------------------
10748 void ReflowCountMgr::DisplayDiffsInTotals() {
10749 if (mCycledOnce) {
10750 printf("Differences\n");
10751 for (int32_t i = 0; i < 78; i++) {
10752 printf("-");
10754 printf("\n");
10755 ClearGrandTotals();
10758 for (const auto& entry : mCounts) {
10759 if (mCycledOnce) {
10760 entry.GetData()->CalcDiffInTotals();
10761 entry.GetData()->DisplayDiffTotals(entry.GetKey());
10763 entry.GetData()->SetTotalsCache();
10766 mCycledOnce = true;
10769 #endif // MOZ_REFLOW_PERF
10771 nsIFrame* PresShell::GetAbsoluteContainingBlock(nsIFrame* aFrame) {
10772 return FrameConstructor()->GetAbsoluteContainingBlock(
10773 aFrame, nsCSSFrameConstructor::ABS_POS);
10776 #ifdef ACCESSIBILITY
10778 // static
10779 bool PresShell::IsAccessibilityActive() { return GetAccService() != nullptr; }
10781 // static
10782 nsAccessibilityService* PresShell::GetAccessibilityService() {
10783 return GetAccService();
10786 #endif // #ifdef ACCESSIBILITY
10788 void PresShell::ActivenessMaybeChanged() {
10789 if (!mDocument) {
10790 return;
10792 SetIsActive(ShouldBeActive());
10795 bool PresShell::ShouldBeActive() const {
10796 MOZ_LOG(gLog, LogLevel::Debug,
10797 ("PresShell::ShouldBeActive(%s, %d)\n",
10798 mDocument->GetDocumentURI()
10799 ? mDocument->GetDocumentURI()->GetSpecOrDefault().get()
10800 : "(no uri)",
10801 mIsActive));
10803 Document* doc = mDocument;
10805 if (doc->IsBeingUsedAsImage()) {
10806 // Documents used as an image can remain active. They do not tick their
10807 // refresh driver if not painted, and they can't run script or such so they
10808 // can't really observe much else.
10809 return true;
10812 if (Document* displayDoc = doc->GetDisplayDocument()) {
10813 // Ok, we're an external resource document -- we need to use our display
10814 // document's docshell to determine "IsActive" status, since we lack
10815 // a browsing context of our own.
10816 MOZ_ASSERT(!doc->GetBrowsingContext(),
10817 "external resource doc shouldn't have its own BC");
10818 doc = displayDoc;
10821 Document* root = nsContentUtils::GetInProcessSubtreeRootDocument(doc);
10822 if (auto* browserChild = BrowserChild::GetFrom(root->GetDocShell())) {
10823 // We might want to activate a tab even though the browsing-context is not
10824 // active if the BrowserChild is considered visible. This serves two
10825 // purposes:
10827 // * For top-level tabs, we use this for tab warming. The browsing-context
10828 // might still be inactive, but we want to activate the pres shell and
10829 // the refresh driver.
10831 // * For oop iframes, we do want to throttle them if they're not visible.
10833 // TODO(emilio): Consider unifying the in-process vs. fission iframe
10834 // throttling code (in-process throttling for non-visible iframes lives
10835 // right now in Document::ShouldThrottleFrameRequests(), but that only
10836 // throttles rAF).
10837 if (!browserChild->IsVisible()) {
10838 MOZ_LOG(gLog, LogLevel::Debug,
10839 (" > BrowserChild %p is not visible", browserChild));
10840 return false;
10843 // If the browser is visible but just due to be preserving layers
10844 // artificially, we do want to fall back to the browsing context activeness
10845 // instead. Otherwise we do want to be active for the use cases above.
10846 if (!browserChild->IsPreservingLayers()) {
10847 MOZ_LOG(gLog, LogLevel::Debug,
10848 (" > BrowserChild %p is visible and not preserving layers",
10849 browserChild));
10850 return true;
10852 MOZ_LOG(
10853 gLog, LogLevel::Debug,
10854 (" > BrowserChild %p is visible and preserving layers", browserChild));
10857 BrowsingContext* bc = doc->GetBrowsingContext();
10858 MOZ_LOG(gLog, LogLevel::Debug,
10859 (" > BrowsingContext %p active: %d", bc, bc && bc->IsActive()));
10860 return bc && bc->IsActive();
10863 void PresShell::SetIsActive(bool aIsActive) {
10864 MOZ_ASSERT(mDocument, "should only be called with a document");
10866 const bool changed = mIsActive != aIsActive;
10868 mIsActive = aIsActive;
10870 nsPresContext* presContext = GetPresContext();
10871 if (presContext &&
10872 presContext->RefreshDriver()->GetPresContext() == presContext) {
10873 presContext->RefreshDriver()->SetThrottled(!mIsActive);
10876 if (changed) {
10877 // Propagate state-change to my resource documents' PresShells and other
10878 // subdocuments.
10880 // Note that it is fine to not propagate to fission iframes. Those will
10881 // become active / inactive as needed as a result of they getting painted /
10882 // not painted eventually.
10883 auto recurse = [aIsActive](Document& aSubDoc) {
10884 if (PresShell* presShell = aSubDoc.GetPresShell()) {
10885 presShell->SetIsActive(aIsActive);
10887 return CallState::Continue;
10889 mDocument->EnumerateExternalResources(recurse);
10890 mDocument->EnumerateSubDocuments(recurse);
10893 UpdateImageLockingState();
10894 #ifdef ACCESSIBILITY
10895 if (aIsActive) {
10896 if (nsAccessibilityService* accService =
10897 PresShell::GetAccessibilityService()) {
10898 accService->PresShellActivated(this);
10901 #endif // #ifdef ACCESSIBILITY
10903 #if defined(MOZ_WIDGET_ANDROID)
10904 if (changed && !aIsActive && presContext &&
10905 presContext->IsRootContentDocumentCrossProcess()) {
10906 if (BrowserChild* browserChild = BrowserChild::GetFrom(this)) {
10907 // Reset the dynamic toolbar offset state.
10908 presContext->UpdateDynamicToolbarOffset(0);
10911 #endif
10913 if (aIsActive) {
10914 if (nsIFrame* rootFrame = GetRootFrame()) {
10915 rootFrame->SchedulePaint();
10920 RefPtr<MobileViewportManager> PresShell::GetMobileViewportManager() const {
10921 return mMobileViewportManager;
10924 Maybe<MobileViewportManager::ManagerType> UseMobileViewportManager(
10925 PresShell* aPresShell, Document* aDocument) {
10926 // If we're not using APZ, we won't be able to zoom, so there is no
10927 // point in having an MVM.
10928 if (nsPresContext* presContext = aPresShell->GetPresContext()) {
10929 if (nsIWidget* widget = presContext->GetNearestWidget()) {
10930 if (!widget->AsyncPanZoomEnabled()) {
10931 return Nothing();
10935 if (nsLayoutUtils::ShouldHandleMetaViewport(aDocument)) {
10936 return Some(MobileViewportManager::ManagerType::VisualAndMetaViewport);
10938 if (StaticPrefs::apz_mvm_force_enabled() ||
10939 nsLayoutUtils::AllowZoomingForDocument(aDocument)) {
10940 return Some(MobileViewportManager::ManagerType::VisualViewportOnly);
10942 return Nothing();
10945 void PresShell::MaybeRecreateMobileViewportManager(bool aAfterInitialization) {
10946 // Determine if we require a MobileViewportManager, and what kind if so. We
10947 // need one any time we allow resolution zooming for a document, and any time
10948 // we want to obey <meta name="viewport"> tags for it.
10949 Maybe<MobileViewportManager::ManagerType> mvmType =
10950 UseMobileViewportManager(this, mDocument);
10952 if (mvmType.isNothing() && !mMobileViewportManager) {
10953 // We don't need one and don't have it. So we're done.
10954 return;
10956 if (mvmType && mMobileViewportManager &&
10957 *mvmType == mMobileViewportManager->GetManagerType()) {
10958 // We need one and we have one of the correct type, so we're done.
10959 return;
10962 if (mMobileViewportManager) {
10963 // We have one, but we need to either destroy it completely to replace it
10964 // with another one of the correct type. So either way, let's destroy the
10965 // one we have.
10966 mMobileViewportManager->Destroy();
10967 mMobileViewportManager = nullptr;
10968 mMVMContext = nullptr;
10970 ResetVisualViewportSize();
10972 // After we clear out the MVM and the MVMContext, also reset the
10973 // resolution to its pre-MVM value.
10974 SetResolutionAndScaleTo(mDocument->GetSavedResolutionBeforeMVM(),
10975 ResolutionChangeOrigin::MainThreadRestore);
10977 if (aAfterInitialization) {
10978 // Force a reflow to our correct size by going back to the docShell
10979 // and asking it to reassert its size. This is necessary because
10980 // everything underneath the docShell, like the ViewManager, has been
10981 // altered by the MobileViewportManager in an irreversible way.
10982 nsDocShell* docShell =
10983 static_cast<nsDocShell*>(GetPresContext()->GetDocShell());
10984 int32_t width, height;
10985 docShell->GetSize(&width, &height);
10986 docShell->SetSize(width, height, false);
10990 if (mvmType) {
10991 // Let's create the MVM of the type that we need. At this point we shouldn't
10992 // have one.
10993 MOZ_ASSERT(!mMobileViewportManager);
10995 if (mPresContext->IsRootContentDocumentCrossProcess()) {
10996 // Store the resolution so we can restore to this resolution when
10997 // the MVM is destroyed.
10998 mDocument->SetSavedResolutionBeforeMVM(mResolution.valueOr(1.0f));
11000 mMVMContext = new GeckoMVMContext(mDocument, this);
11001 mMobileViewportManager = new MobileViewportManager(mMVMContext, *mvmType);
11002 if (MOZ_UNLIKELY(MOZ_LOG_TEST(sApzMvmLog, LogLevel::Debug))) {
11003 nsIURI* uri = mDocument->GetDocumentURI();
11004 MOZ_LOG(sApzMvmLog, LogLevel::Debug,
11005 ("Created MVM %p (type %d) for URI %s",
11006 mMobileViewportManager.get(), (int)*mvmType,
11007 uri ? uri->GetSpecOrDefault().get() : "(null)"));
11010 if (aAfterInitialization) {
11011 // Setting the initial viewport will trigger a reflow.
11012 mMobileViewportManager->SetInitialViewport();
11018 bool PresShell::UsesMobileViewportSizing() const {
11019 return mMobileViewportManager != nullptr &&
11020 nsLayoutUtils::ShouldHandleMetaViewport(mDocument);
11024 * Determines the current image locking state. Called when one of the
11025 * dependent factors changes.
11027 void PresShell::UpdateImageLockingState() {
11028 // We're locked if we're both thawed and active.
11029 bool locked = !mFrozen && mIsActive;
11031 mDocument->ImageTracker()->SetLockingState(locked);
11033 if (locked) {
11034 // Request decodes for visible image frames; we want to start decoding as
11035 // quickly as possible when we get foregrounded to minimize flashing.
11036 for (const auto& key : mApproximatelyVisibleFrames) {
11037 if (nsImageFrame* imageFrame = do_QueryFrame(key)) {
11038 imageFrame->MaybeDecodeForPredictedSize();
11044 PresShell* PresShell::GetRootPresShell() const {
11045 if (mPresContext) {
11046 nsPresContext* rootPresContext = mPresContext->GetRootPresContext();
11047 if (rootPresContext) {
11048 return rootPresContext->PresShell();
11051 return nullptr;
11054 void PresShell::AddSizeOfIncludingThis(nsWindowSizes& aSizes) const {
11055 MallocSizeOf mallocSizeOf = aSizes.mState.mMallocSizeOf;
11056 mFrameArena.AddSizeOfExcludingThis(aSizes, Arena::ArenaKind::PresShell);
11057 aSizes.mLayoutPresShellSize += mallocSizeOf(this);
11058 if (mCaret) {
11059 aSizes.mLayoutPresShellSize += mCaret->SizeOfIncludingThis(mallocSizeOf);
11061 aSizes.mLayoutPresShellSize +=
11062 mApproximatelyVisibleFrames.ShallowSizeOfExcludingThis(mallocSizeOf) +
11063 mFramesToDirty.ShallowSizeOfExcludingThis(mallocSizeOf) +
11064 mPendingScrollAnchorSelection.ShallowSizeOfExcludingThis(mallocSizeOf) +
11065 mPendingScrollAnchorAdjustment.ShallowSizeOfExcludingThis(mallocSizeOf);
11067 aSizes.mLayoutTextRunsSize += SizeOfTextRuns(mallocSizeOf);
11069 aSizes.mLayoutPresContextSize +=
11070 mPresContext->SizeOfIncludingThis(mallocSizeOf);
11072 mFrameConstructor->AddSizeOfIncludingThis(aSizes);
11075 size_t PresShell::SizeOfTextRuns(MallocSizeOf aMallocSizeOf) const {
11076 nsIFrame* rootFrame = mFrameConstructor->GetRootFrame();
11077 if (!rootFrame) {
11078 return 0;
11081 // clear the TEXT_RUN_MEMORY_ACCOUNTED flags
11082 nsLayoutUtils::SizeOfTextRunsForFrames(rootFrame, nullptr,
11083 /* clear = */ true);
11085 // collect the total memory in use for textruns
11086 return nsLayoutUtils::SizeOfTextRunsForFrames(rootFrame, aMallocSizeOf,
11087 /* clear = */ false);
11090 void PresShell::MarkFixedFramesForReflow(IntrinsicDirty aIntrinsicDirty) {
11091 nsIFrame* rootFrame = mFrameConstructor->GetRootFrame();
11092 if (rootFrame) {
11093 const nsFrameList& childList =
11094 rootFrame->GetChildList(nsIFrame::kFixedList);
11095 for (nsIFrame* childFrame : childList) {
11096 FrameNeedsReflow(childFrame, aIntrinsicDirty, NS_FRAME_IS_DIRTY);
11101 static void AppendSubtree(nsIDocShell* aDocShell,
11102 nsTArray<nsCOMPtr<nsIContentViewer>>& aArray) {
11103 if (nsCOMPtr<nsIContentViewer> cv = aDocShell->GetContentViewer()) {
11104 aArray.AppendElement(cv);
11107 int32_t n = aDocShell->GetInProcessChildCount();
11108 for (int32_t i = 0; i < n; i++) {
11109 nsCOMPtr<nsIDocShellTreeItem> childItem;
11110 aDocShell->GetInProcessChildAt(i, getter_AddRefs(childItem));
11111 if (childItem) {
11112 nsCOMPtr<nsIDocShell> child(do_QueryInterface(childItem));
11113 AppendSubtree(child, aArray);
11118 void PresShell::MaybeReflowForInflationScreenSizeChange() {
11119 nsPresContext* pc = GetPresContext();
11120 const bool fontInflationWasEnabled = FontSizeInflationEnabled();
11121 RecomputeFontSizeInflationEnabled();
11122 bool changed = false;
11123 if (FontSizeInflationEnabled() && FontSizeInflationMinTwips() != 0) {
11124 pc->ScreenSizeInchesForFontInflation(&changed);
11127 changed = changed || fontInflationWasEnabled != FontSizeInflationEnabled();
11128 if (!changed) {
11129 return;
11131 if (nsCOMPtr<nsIDocShell> docShell = pc->GetDocShell()) {
11132 nsTArray<nsCOMPtr<nsIContentViewer>> array;
11133 AppendSubtree(docShell, array);
11134 for (uint32_t i = 0, iEnd = array.Length(); i < iEnd; ++i) {
11135 nsCOMPtr<nsIContentViewer> cv = array[i];
11136 if (RefPtr<PresShell> descendantPresShell = cv->GetPresShell()) {
11137 nsIFrame* rootFrame = descendantPresShell->GetRootFrame();
11138 if (rootFrame) {
11139 descendantPresShell->FrameNeedsReflow(
11140 rootFrame, IntrinsicDirty::StyleChange, NS_FRAME_IS_DIRTY);
11147 void PresShell::CompleteChangeToVisualViewportSize() {
11148 // This can get called during reflow, if the caller wants to get the latest
11149 // visual viewport size after scrollbars have been added/removed. In such a
11150 // case, we don't need to mark things as dirty because the things that we
11151 // would mark dirty either just got updated (the root scrollframe's
11152 // scrollbars), or will be laid out later during this reflow cycle (fixed-pos
11153 // items). Callers that update the visual viewport during a reflow are
11154 // responsible for maintaining these invariants.
11155 if (!mIsReflowing) {
11156 if (nsIScrollableFrame* rootScrollFrame =
11157 GetRootScrollFrameAsScrollable()) {
11158 rootScrollFrame->MarkScrollbarsDirtyForReflow();
11160 MarkFixedFramesForReflow(IntrinsicDirty::Resize);
11163 MaybeReflowForInflationScreenSizeChange();
11165 if (auto* window = nsGlobalWindowInner::Cast(mDocument->GetInnerWindow())) {
11166 window->VisualViewport()->PostResizeEvent();
11170 void PresShell::SetVisualViewportSize(nscoord aWidth, nscoord aHeight) {
11171 MOZ_ASSERT(aWidth >= 0.0 && aHeight >= 0.0);
11173 if (!mVisualViewportSizeSet || mVisualViewportSize.width != aWidth ||
11174 mVisualViewportSize.height != aHeight) {
11175 mVisualViewportSizeSet = true;
11176 mVisualViewportSize.width = aWidth;
11177 mVisualViewportSize.height = aHeight;
11179 CompleteChangeToVisualViewportSize();
11183 void PresShell::ResetVisualViewportSize() {
11184 if (mVisualViewportSizeSet) {
11185 mVisualViewportSizeSet = false;
11186 mVisualViewportSize.width = 0;
11187 mVisualViewportSize.height = 0;
11189 CompleteChangeToVisualViewportSize();
11193 bool PresShell::SetVisualViewportOffset(const nsPoint& aScrollOffset,
11194 const nsPoint& aPrevLayoutScrollPos) {
11195 nsPoint newOffset = aScrollOffset;
11196 nsIScrollableFrame* rootScrollFrame = GetRootScrollFrameAsScrollable();
11197 if (rootScrollFrame) {
11198 // See the comment in nsHTMLScrollFrame::Reflow above the call to
11199 // SetVisualViewportOffset for why we need to do this.
11200 nsRect scrollRange = rootScrollFrame->GetScrollRangeForUserInputEvents();
11201 if (!scrollRange.Contains(newOffset)) {
11202 newOffset.x = std::min(newOffset.x, scrollRange.XMost());
11203 newOffset.x = std::max(newOffset.x, scrollRange.x);
11204 newOffset.y = std::min(newOffset.y, scrollRange.YMost());
11205 newOffset.y = std::max(newOffset.y, scrollRange.y);
11209 nsPoint prevOffset = GetVisualViewportOffset();
11210 if (prevOffset == newOffset) {
11211 return false;
11214 mVisualViewportOffset = Some(newOffset);
11216 if (auto* window = nsGlobalWindowInner::Cast(mDocument->GetInnerWindow())) {
11217 window->VisualViewport()->PostScrollEvent(prevOffset, aPrevLayoutScrollPos);
11220 if (IsVisualViewportSizeSet() && rootScrollFrame) {
11221 rootScrollFrame->Anchor()->UserScrolled();
11224 if (gfxPlatform::UseDesktopZoomingScrollbars()) {
11225 if (nsIScrollableFrame* rootScrollFrame =
11226 GetRootScrollFrameAsScrollable()) {
11227 rootScrollFrame->UpdateScrollbarPosition();
11231 return true;
11234 void PresShell::ScrollToVisual(const nsPoint& aVisualViewportOffset,
11235 FrameMetrics::ScrollOffsetUpdateType aUpdateType,
11236 ScrollMode aMode) {
11237 MOZ_ASSERT(aMode == ScrollMode::Instant || aMode == ScrollMode::SmoothMsd);
11239 if (aMode == ScrollMode::SmoothMsd) {
11240 if (nsIScrollableFrame* sf = GetRootScrollFrameAsScrollable()) {
11241 if (sf->SmoothScrollVisual(aVisualViewportOffset, aUpdateType)) {
11242 return;
11247 // If the caller asked for instant scroll, or if we failed
11248 // to do a smooth scroll, do an instant scroll.
11249 SetPendingVisualScrollUpdate(aVisualViewportOffset, aUpdateType);
11252 void PresShell::SetPendingVisualScrollUpdate(
11253 const nsPoint& aVisualViewportOffset,
11254 FrameMetrics::ScrollOffsetUpdateType aUpdateType) {
11255 mPendingVisualScrollUpdate =
11256 Some(VisualScrollUpdate{aVisualViewportOffset, aUpdateType});
11258 // The pending update is picked up during the next paint.
11259 // Schedule a paint to make sure one will happen.
11260 if (nsIFrame* rootFrame = GetRootFrame()) {
11261 rootFrame->SchedulePaint();
11265 void PresShell::ClearPendingVisualScrollUpdate() {
11266 if (mPendingVisualScrollUpdate && mPendingVisualScrollUpdate->mAcknowledged) {
11267 mPendingVisualScrollUpdate = mozilla::Nothing();
11271 void PresShell::AcknowledgePendingVisualScrollUpdate() {
11272 MOZ_ASSERT(mPendingVisualScrollUpdate);
11273 mPendingVisualScrollUpdate->mAcknowledged = true;
11276 nsPoint PresShell::GetVisualViewportOffsetRelativeToLayoutViewport() const {
11277 return GetVisualViewportOffset() - GetLayoutViewportOffset();
11280 nsPoint PresShell::GetLayoutViewportOffset() const {
11281 nsPoint result;
11282 if (nsIScrollableFrame* sf = GetRootScrollFrameAsScrollable()) {
11283 result = sf->GetScrollPosition();
11285 return result;
11288 nsSize PresShell::GetLayoutViewportSize() const {
11289 nsSize result;
11290 if (nsIScrollableFrame* sf = GetRootScrollFrameAsScrollable()) {
11291 result = sf->GetScrollPortRect().Size();
11293 return result;
11296 nsSize PresShell::GetVisualViewportSizeUpdatedByDynamicToolbar() const {
11297 NS_ASSERTION(mVisualViewportSizeSet,
11298 "asking for visual viewport size when its not set?");
11299 if (!mMobileViewportManager) {
11300 return mVisualViewportSize;
11303 MOZ_ASSERT(GetDynamicToolbarState() == DynamicToolbarState::InTransition ||
11304 GetDynamicToolbarState() == DynamicToolbarState::Collapsed);
11306 nsSize sizeUpdatedByDynamicToolbar =
11307 mMobileViewportManager->GetVisualViewportSizeUpdatedByDynamicToolbar();
11308 return sizeUpdatedByDynamicToolbar == nsSize() ? mVisualViewportSize
11309 : sizeUpdatedByDynamicToolbar;
11312 void PresShell::RecomputeFontSizeInflationEnabled() {
11313 mFontSizeInflationEnabled = DetermineFontSizeInflationState();
11315 // Divide by 100 to convert the pref from a percentage to a fraction.
11316 float fontScale = StaticPrefs::font_size_systemFontScale() / 100.0f;
11317 if (fontScale == 0.0f) {
11318 return;
11321 MOZ_ASSERT(mDocument);
11322 MOZ_ASSERT(mPresContext);
11323 if (mFontSizeInflationEnabled || mDocument->IsSyntheticDocument()) {
11324 mPresContext->SetSystemFontScale(1.0f);
11325 } else {
11326 mPresContext->SetSystemFontScale(fontScale);
11330 bool PresShell::DetermineFontSizeInflationState() {
11331 MOZ_ASSERT(mPresContext, "our pres context should not be null");
11332 if (mPresContext->IsChrome()) {
11333 return false;
11336 if (FontSizeInflationEmPerLine() == 0 && FontSizeInflationMinTwips() == 0) {
11337 return false;
11340 // Force-enabling font inflation always trumps the heuristics here.
11341 if (!FontSizeInflationForceEnabled()) {
11342 if (BrowserChild* tab = BrowserChild::GetFrom(this)) {
11343 // We're in a child process. Cancel inflation if we're not
11344 // async-pan zoomed.
11345 if (!tab->AsyncPanZoomEnabled()) {
11346 return false;
11348 } else if (XRE_IsParentProcess()) {
11349 // We're in the master process. Cancel inflation if it's been
11350 // explicitly disabled.
11351 if (FontSizeInflationDisabledInMasterProcess()) {
11352 return false;
11357 // XXXjwir3:
11358 // See bug 706918, comment 23 for more information on this particular section
11359 // of the code. We're using "screen size" in place of the size of the content
11360 // area, because on mobile, these are close or equal. This will work for our
11361 // purposes (bug 706198), but it will need to be changed in the future to be
11362 // more correct when we bring the rest of the viewport code into platform.
11363 // We actually want the size of the content area, in the event that we don't
11364 // have any metadata about the width and/or height. On mobile, the screen size
11365 // and the size of the content area are very close, or the same value.
11366 // In XUL fennec, the content area is the size of the <browser> widget, but
11367 // in native fennec, the content area is the size of the Gecko LayerView
11368 // object.
11370 // TODO:
11371 // Once bug 716575 has been resolved, this code should be changed so that it
11372 // does the right thing on all platforms.
11373 nsresult rv;
11374 nsCOMPtr<nsIScreenManager> screenMgr =
11375 do_GetService("@mozilla.org/gfx/screenmanager;1", &rv);
11376 if (!NS_SUCCEEDED(rv)) {
11377 return false;
11380 nsCOMPtr<nsIScreen> screen;
11381 screenMgr->GetPrimaryScreen(getter_AddRefs(screen));
11382 if (screen) {
11383 int32_t screenLeft, screenTop, screenWidth, screenHeight;
11384 screen->GetRect(&screenLeft, &screenTop, &screenWidth, &screenHeight);
11386 nsViewportInfo vInf = GetDocument()->GetViewportInfo(
11387 ScreenIntSize(screenWidth, screenHeight));
11389 if (vInf.GetDefaultZoom() >= CSSToScreenScale(1.0f) ||
11390 vInf.IsAutoSizeEnabled()) {
11391 return false;
11395 return true;
11398 void PresShell::SyncWindowProperties(nsView* aView) {
11399 nsIFrame* frame = aView->GetFrame();
11400 if (frame && mPresContext) {
11401 // CreateReferenceRenderingContext can return nullptr
11402 RefPtr<gfxContext> rcx(CreateReferenceRenderingContext());
11403 nsContainerFrame::SyncWindowProperties(mPresContext, frame, aView, rcx, 0);
11407 nsresult PresShell::HasRuleProcessorUsedByMultipleStyleSets(uint32_t aSheetType,
11408 bool* aRetVal) {
11409 *aRetVal = false;
11410 return NS_OK;
11413 void PresShell::NotifyStyleSheetServiceSheetAdded(StyleSheet* aSheet,
11414 uint32_t aSheetType) {
11415 switch (aSheetType) {
11416 case nsIStyleSheetService::AGENT_SHEET:
11417 AddAgentSheet(aSheet);
11418 break;
11419 case nsIStyleSheetService::USER_SHEET:
11420 AddUserSheet(aSheet);
11421 break;
11422 case nsIStyleSheetService::AUTHOR_SHEET:
11423 AddAuthorSheet(aSheet);
11424 break;
11425 default:
11426 MOZ_ASSERT_UNREACHABLE("unexpected aSheetType value");
11427 break;
11431 void PresShell::NotifyStyleSheetServiceSheetRemoved(StyleSheet* aSheet,
11432 uint32_t aSheetType) {
11433 StyleSet()->RemoveStyleSheet(*aSheet);
11434 mDocument->ApplicableStylesChanged();
11437 void PresShell::SetIsUnderHiddenEmbedderElement(
11438 bool aUnderHiddenEmbedderElement) {
11439 if (mUnderHiddenEmbedderElement == aUnderHiddenEmbedderElement) {
11440 return;
11443 mUnderHiddenEmbedderElement = aUnderHiddenEmbedderElement;
11445 if (nsCOMPtr<nsIDocShell> docShell = mPresContext->GetDocShell()) {
11446 BrowsingContext* bc = docShell->GetBrowsingContext();
11448 // Propagate to children.
11449 for (BrowsingContext* child : bc->Children()) {
11450 Element* embedderElement = child->GetEmbedderElement();
11451 if (!embedderElement) {
11452 // TODO: We shouldn't need to null check here since `child` and the
11453 // element returned by `child->GetEmbedderElement()` are in our
11454 // process (the actual browsing context represented by `child` may not
11455 // be, but that doesn't matter). However, there are currently a very
11456 // small number of crashes due to `embedderElement` being null, somehow
11457 // - see bug 1551241. For now we wallpaper the crash.
11458 continue;
11461 bool embedderFrameIsHidden = true;
11462 if (auto embedderFrame = embedderElement->GetPrimaryFrame()) {
11463 embedderFrameIsHidden = !embedderFrame->StyleVisibility()->IsVisible();
11466 if (nsIDocShell* childDocShell = child->GetDocShell()) {
11467 PresShell* presShell = childDocShell->GetPresShell();
11468 if (!presShell) {
11469 continue;
11471 presShell->SetIsUnderHiddenEmbedderElement(
11472 aUnderHiddenEmbedderElement || embedderFrameIsHidden);
11473 } else {
11474 BrowserBridgeChild* bridgeChild =
11475 BrowserBridgeChild::GetFrom(embedderElement);
11476 bridgeChild->SetIsUnderHiddenEmbedderElement(
11477 aUnderHiddenEmbedderElement || embedderFrameIsHidden);
11483 nsIContent* PresShell::EventHandler::GetOverrideClickTarget(
11484 WidgetGUIEvent* aGUIEvent, nsIFrame* aFrame) {
11485 if (aGUIEvent->mMessage != eMouseUp) {
11486 return nullptr;
11489 MOZ_ASSERT(aGUIEvent->mClass == eMouseEventClass);
11490 WidgetMouseEvent* mouseEvent = aGUIEvent->AsMouseEvent();
11492 uint32_t flags = 0;
11493 RelativeTo relativeTo{aFrame};
11494 nsPoint eventPoint =
11495 nsLayoutUtils::GetEventCoordinatesRelativeTo(aGUIEvent, relativeTo);
11496 if (mouseEvent->mIgnoreRootScrollFrame) {
11497 flags |= INPUT_IGNORE_ROOT_SCROLL_FRAME;
11500 nsIFrame* target =
11501 FindFrameTargetedByInputEvent(aGUIEvent, relativeTo, eventPoint, flags);
11502 if (!target) {
11503 return nullptr;
11506 nsIContent* overrideClickTarget = target->GetContent();
11507 while (overrideClickTarget && !overrideClickTarget->IsElement()) {
11508 overrideClickTarget = overrideClickTarget->GetFlattenedTreeParent();
11510 return overrideClickTarget;
11513 /******************************************************************************
11514 * PresShell::EventHandler::EventTargetData
11515 ******************************************************************************/
11517 void PresShell::EventHandler::EventTargetData::SetFrameAndComputePresShell(
11518 nsIFrame* aFrameToHandleEvent) {
11519 if (aFrameToHandleEvent) {
11520 mFrame = aFrameToHandleEvent;
11521 mPresShell = aFrameToHandleEvent->PresShell();
11522 } else {
11523 mFrame = nullptr;
11524 mPresShell = nullptr;
11528 void PresShell::EventHandler::EventTargetData::
11529 SetFrameAndComputePresShellAndContent(nsIFrame* aFrameToHandleEvent,
11530 WidgetGUIEvent* aGUIEvent) {
11531 MOZ_ASSERT(aFrameToHandleEvent);
11532 MOZ_ASSERT(aGUIEvent);
11534 SetFrameAndComputePresShell(aFrameToHandleEvent);
11535 SetContentForEventFromFrame(aGUIEvent);
11538 void PresShell::EventHandler::EventTargetData::SetContentForEventFromFrame(
11539 WidgetGUIEvent* aGUIEvent) {
11540 MOZ_ASSERT(mFrame);
11541 mContent = nullptr;
11542 mFrame->GetContentForEvent(aGUIEvent, getter_AddRefs(mContent));
11545 nsIContent* PresShell::EventHandler::EventTargetData::GetFrameContent() const {
11546 return mFrame ? mFrame->GetContent() : nullptr;
11549 bool PresShell::EventHandler::EventTargetData::MaybeRetargetToActiveDocument(
11550 WidgetGUIEvent* aGUIEvent) {
11551 MOZ_ASSERT(aGUIEvent);
11552 MOZ_ASSERT(mFrame);
11553 MOZ_ASSERT(mPresShell);
11554 MOZ_ASSERT(!mContent, "Doesn't support to retarget the content");
11556 EventStateManager* activeESM =
11557 EventStateManager::GetActiveEventStateManager();
11558 if (!activeESM) {
11559 return false;
11562 if (aGUIEvent->mClass != ePointerEventClass &&
11563 !aGUIEvent->HasMouseEventMessage()) {
11564 return false;
11567 if (activeESM == GetEventStateManager()) {
11568 return false;
11571 nsPresContext* activePresContext = activeESM->GetPresContext();
11572 if (!activePresContext) {
11573 return false;
11576 PresShell* activePresShell = activePresContext->GetPresShell();
11577 if (!activePresShell) {
11578 return false;
11581 // Note, currently for backwards compatibility we don't forward mouse events
11582 // to the active document when mouse is over some subdocument.
11583 if (!nsContentUtils::ContentIsCrossDocDescendantOf(
11584 activePresShell->GetDocument(), GetDocument())) {
11585 return false;
11588 SetFrameAndComputePresShell(activePresShell->GetRootFrame());
11589 return true;
11592 bool PresShell::EventHandler::EventTargetData::ComputeElementFromFrame(
11593 WidgetGUIEvent* aGUIEvent) {
11594 MOZ_ASSERT(aGUIEvent);
11595 MOZ_ASSERT(aGUIEvent->IsUsingCoordinates());
11596 MOZ_ASSERT(mPresShell);
11597 MOZ_ASSERT(mFrame);
11599 SetContentForEventFromFrame(aGUIEvent);
11601 // If there is no content for this frame, target it anyway. Some frames can
11602 // be targeted but do not have content, particularly windows with scrolling
11603 // off.
11604 if (!mContent) {
11605 return true;
11608 // Bug 103055, bug 185889: mouse events apply to *elements*, not all nodes.
11609 // Thus we get the nearest element parent here.
11610 // XXX we leave the frame the same even if we find an element parent, so that
11611 // the text frame will receive the event (selection and friends are the ones
11612 // who care about that anyway)
11614 // We use weak pointers because during this tight loop, the node
11615 // will *not* go away. And this happens on every mousemove.
11616 nsIContent* content = mContent;
11617 while (content && !content->IsElement()) {
11618 content = content->GetFlattenedTreeParent();
11620 mContent = content;
11622 // If we found an element, target it. Otherwise, target *nothing*.
11623 return !!mContent;
11626 void PresShell::EventHandler::EventTargetData::UpdateTouchEventTarget(
11627 WidgetGUIEvent* aGUIEvent) {
11628 MOZ_ASSERT(aGUIEvent);
11630 if (aGUIEvent->mClass != eTouchEventClass) {
11631 return;
11634 if (aGUIEvent->mMessage == eTouchStart) {
11635 WidgetTouchEvent* touchEvent = aGUIEvent->AsTouchEvent();
11636 nsIFrame* newFrame =
11637 TouchManager::SuppressInvalidPointsAndGetTargetedFrame(touchEvent);
11638 if (!newFrame) {
11639 return; // XXX Why don't we stop handling the event in this case?
11641 SetFrameAndComputePresShellAndContent(newFrame, aGUIEvent);
11642 return;
11645 PresShell* newPresShell = PresShell::GetShellForTouchEvent(aGUIEvent);
11646 if (!newPresShell) {
11647 return; // XXX Why don't we stop handling the event in this case?
11650 // Touch events (except touchstart) are dispatching to the captured
11651 // element. Get correct shell from it.
11652 mPresShell = newPresShell;
11655 /******************************************************************************
11656 * PresShell::EventHandler::HandlingTimeAccumulator
11657 ******************************************************************************/
11659 PresShell::EventHandler::HandlingTimeAccumulator::HandlingTimeAccumulator(
11660 const PresShell::EventHandler& aEventHandler, const WidgetEvent* aEvent)
11661 : mEventHandler(aEventHandler),
11662 mEvent(aEvent),
11663 mHandlingStartTime(TimeStamp::Now()) {
11664 MOZ_ASSERT(mEvent);
11665 MOZ_ASSERT(mEvent->IsTrusted());
11668 PresShell::EventHandler::HandlingTimeAccumulator::~HandlingTimeAccumulator() {
11669 if (mEvent->mTimeStamp <= mEventHandler.mPresShell->mLastOSWake) {
11670 return;
11673 switch (mEvent->mMessage) {
11674 case eKeyPress:
11675 case eKeyDown:
11676 case eKeyUp:
11677 Telemetry::AccumulateTimeDelta(Telemetry::INPUT_EVENT_HANDLED_KEYBOARD_MS,
11678 mHandlingStartTime);
11679 return;
11680 case eMouseDown:
11681 Telemetry::AccumulateTimeDelta(
11682 Telemetry::INPUT_EVENT_HANDLED_MOUSE_DOWN_MS, mHandlingStartTime);
11683 return;
11684 case eMouseUp:
11685 Telemetry::AccumulateTimeDelta(Telemetry::INPUT_EVENT_HANDLED_MOUSE_UP_MS,
11686 mHandlingStartTime);
11687 return;
11688 case eMouseMove:
11689 if (mEvent->mFlags.mHandledByAPZ) {
11690 Telemetry::AccumulateTimeDelta(
11691 Telemetry::INPUT_EVENT_HANDLED_APZ_MOUSE_MOVE_MS,
11692 mHandlingStartTime);
11694 return;
11695 case eWheel:
11696 if (mEvent->mFlags.mHandledByAPZ) {
11697 Telemetry::AccumulateTimeDelta(
11698 Telemetry::INPUT_EVENT_HANDLED_APZ_WHEEL_MS, mHandlingStartTime);
11700 return;
11701 case eTouchMove:
11702 if (mEvent->mFlags.mHandledByAPZ) {
11703 Telemetry::AccumulateTimeDelta(
11704 Telemetry::INPUT_EVENT_HANDLED_APZ_TOUCH_MOVE_MS,
11705 mHandlingStartTime);
11707 return;
11708 default:
11709 return;
11713 void PresShell::EndPaint() {
11714 ClearPendingVisualScrollUpdate();
11716 if (mDocument) {
11717 mDocument->EnumerateSubDocuments([](Document& aSubDoc) {
11718 if (PresShell* presShell = aSubDoc.GetPresShell()) {
11719 presShell->EndPaint();
11721 return CallState::Continue;
11726 void PresShell::PingPerTickTelemetry(FlushType aFlushType) {
11727 mLayoutTelemetry.PingPerTickTelemetry(aFlushType);
11730 bool PresShell::GetZoomableByAPZ() const {
11731 return mZoomConstraintsClient && mZoomConstraintsClient->GetAllowZoom();