Bug 1877662 - expose mozconfig as an artifact from build-fat-aar. r=glandium,geckovie...
[gecko.git] / layout / base / PresShell.cpp
blob31c21c337786b76306029149a925293a44c45c28
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/EventForwards.h"
13 #include "mozilla/RefPtr.h"
14 #include "mozilla/dom/AncestorIterator.h"
15 #include "mozilla/dom/FontFaceSet.h"
16 #include "mozilla/dom/ElementBinding.h"
17 #include "mozilla/dom/LargestContentfulPaint.h"
18 #include "mozilla/dom/MouseEventBinding.h"
19 #include "mozilla/dom/PerformanceMainThread.h"
20 #include "mozilla/dom/HTMLAreaElement.h"
21 #include "mozilla/ArrayUtils.h"
22 #include "mozilla/Assertions.h"
23 #include "mozilla/Attributes.h"
24 #include "mozilla/AutoRestore.h"
25 #include "mozilla/CaretAssociationHint.h"
26 #include "mozilla/ContentIterator.h"
27 #include "mozilla/DisplayPortUtils.h"
28 #include "mozilla/EventDispatcher.h"
29 #include "mozilla/EventStateManager.h"
30 #include "mozilla/GeckoMVMContext.h"
31 #include "mozilla/IMEStateManager.h"
32 #include "mozilla/IntegerRange.h"
33 #include "mozilla/MemoryReporting.h"
34 #include "mozilla/dom/BrowserChild.h"
35 #include "mozilla/Likely.h"
36 #include "mozilla/Logging.h"
37 #include "mozilla/MouseEvents.h"
38 #include "mozilla/PerfStats.h"
39 #include "mozilla/PointerLockManager.h"
40 #include "mozilla/PresShellInlines.h"
41 #include "mozilla/RangeUtils.h"
42 #include "mozilla/ScopeExit.h"
43 #include "mozilla/Sprintf.h"
44 #include "mozilla/StaticAnalysisFunctions.h"
45 #include "mozilla/StaticPrefs_apz.h"
46 #include "mozilla/StaticPrefs_dom.h"
47 #include "mozilla/StaticPrefs_font.h"
48 #include "mozilla/StaticPrefs_image.h"
49 #include "mozilla/StaticPrefs_layout.h"
50 #include "mozilla/StaticPrefs_test.h"
51 #include "mozilla/StaticPrefs_toolkit.h"
52 #include "mozilla/Try.h"
53 #include "mozilla/TextEvents.h"
54 #include "mozilla/TimeStamp.h"
55 #include "mozilla/TouchEvents.h"
56 #include "mozilla/UniquePtr.h"
57 #include "mozilla/Unused.h"
58 #include "mozilla/ViewportUtils.h"
59 #include "mozilla/gfx/Types.h"
60 #include <algorithm>
62 #ifdef XP_WIN
63 # include "winuser.h"
64 #endif
66 #include "gfxContext.h"
67 #include "gfxUserFontSet.h"
68 #include "nsContentList.h"
69 #include "nsPresContext.h"
70 #include "nsIContent.h"
71 #include "mozilla/dom/BrowserBridgeChild.h"
72 #include "mozilla/dom/BrowsingContext.h"
73 #include "mozilla/dom/CanonicalBrowsingContext.h"
74 #include "mozilla/dom/ContentChild.h"
75 #include "mozilla/dom/ContentParent.h"
76 #include "mozilla/dom/Element.h"
77 #include "mozilla/dom/PointerEventHandler.h"
78 #include "mozilla/dom/PopupBlocker.h"
79 #include "mozilla/dom/DOMIntersectionObserver.h"
80 #include "mozilla/dom/Document.h"
81 #include "mozilla/dom/DocumentInlines.h"
82 #include "mozilla/dom/UserActivation.h"
83 #include "nsAnimationManager.h"
84 #include "nsNameSpaceManager.h" // for Pref-related rule management (bugs 22963,20760,31816)
85 #include "nsFlexContainerFrame.h"
86 #include "nsIFrame.h"
87 #include "nsViewManager.h"
88 #include "nsView.h"
89 #include "nsCRTGlue.h"
90 #include "prinrval.h"
91 #include "nsTArray.h"
92 #include "nsCOMArray.h"
93 #include "nsContainerFrame.h"
94 #include "mozilla/dom/Selection.h"
95 #include "nsGkAtoms.h"
96 #include "nsRange.h"
97 #include "nsWindowSizes.h"
98 #include "nsCOMPtr.h"
99 #include "nsReadableUtils.h"
100 #include "nsPageSequenceFrame.h"
101 #include "nsCaret.h"
102 #include "mozilla/AccessibleCaretEventHub.h"
103 #include "nsFrameManager.h"
104 #include "nsXPCOM.h"
105 #include "nsILayoutHistoryState.h"
106 #include "nsILineIterator.h" // for ScrollContentIntoView
107 #include "PLDHashTable.h"
108 #include "mozilla/dom/Touch.h"
109 #include "mozilla/dom/TouchEvent.h"
110 #include "mozilla/dom/PointerEventBinding.h"
111 #include "mozilla/dom/ShadowIncludingTreeIterator.h"
112 #include "nsIObserverService.h"
113 #include "nsDocShell.h" // for reflow observation
114 #include "nsIBaseWindow.h"
115 #include "nsError.h"
116 #include "nsLayoutUtils.h"
117 #include "nsViewportInfo.h"
118 #include "nsCSSRendering.h"
119 // for |#ifdef DEBUG| code
120 #include "prenv.h"
121 #include "nsDisplayList.h"
122 #include "nsRegion.h"
123 #include "nsAutoLayoutPhase.h"
124 #include "AutoProfilerStyleMarker.h"
125 #ifdef MOZ_REFLOW_PERF
126 # include "nsFontMetrics.h"
127 #endif
128 #include "MobileViewportManager.h"
129 #include "OverflowChangedTracker.h"
130 #include "PositionedEventTargeting.h"
132 #include "nsIReflowCallback.h"
134 #include "nsPIDOMWindow.h"
135 #include "nsFocusManager.h"
136 #include "nsNetUtil.h"
137 #include "nsThreadUtils.h"
138 #include "nsStyleSheetService.h"
139 #include "gfxUtils.h"
140 #include "mozilla/SMILAnimationController.h"
141 #include "mozilla/dom/SVGAnimationElement.h"
142 #include "mozilla/SVGObserverUtils.h"
143 #include "mozilla/SVGFragmentIdentifier.h"
144 #include "nsFrameSelection.h"
146 #include "mozilla/dom/Performance.h"
147 #include "nsRefreshDriver.h"
148 #include "nsDOMNavigationTiming.h"
150 // Drag & Drop, Clipboard
151 #include "nsIDocShellTreeItem.h"
152 #include "nsIURI.h"
153 #include "nsIScrollableFrame.h"
154 #include "nsITimer.h"
155 #ifdef ACCESSIBILITY
156 # include "mozilla/a11y/DocAccessible.h"
157 # ifdef DEBUG
158 # include "mozilla/a11y/Logging.h"
159 # endif
160 #endif
162 // For style data reconstruction
163 #include "nsStyleChangeList.h"
164 #include "nsCSSFrameConstructor.h"
165 #include "nsTreeBodyFrame.h"
166 #include "XULTreeElement.h"
167 #include "nsMenuPopupFrame.h"
168 #include "nsTreeColumns.h"
169 #include "nsIDOMXULMultSelectCntrlEl.h"
170 #include "nsIDOMXULSelectCntrlItemEl.h"
171 #include "nsIDOMXULMenuListElement.h"
172 #include "nsXULElement.h"
174 #include "mozilla/layers/CompositorBridgeChild.h"
175 #include "gfxPlatform.h"
176 #include "mozilla/css/ImageLoader.h"
177 #include "mozilla/dom/DocumentTimeline.h"
178 #include "mozilla/dom/ScriptSettings.h"
179 #include "mozilla/ErrorResult.h"
180 #include "mozilla/Preferences.h"
181 #include "mozilla/Telemetry.h"
182 #include "nsCanvasFrame.h"
183 #include "nsImageFrame.h"
184 #include "nsIScreen.h"
185 #include "nsIScreenManager.h"
186 #include "nsPlaceholderFrame.h"
187 #include "nsTransitionManager.h"
188 #include "ChildIterator.h"
189 #include "mozilla/RestyleManager.h"
190 #include "nsIDragSession.h"
191 #include "nsIFrameInlines.h"
192 #include "mozilla/gfx/2D.h"
193 #include "nsNetUtil.h"
194 #include "nsSubDocumentFrame.h"
195 #include "nsQueryObject.h"
196 #include "mozilla/GlobalStyleSheetCache.h"
197 #include "mozilla/layers/InputAPZContext.h"
198 #include "mozilla/layers/FocusTarget.h"
199 #include "mozilla/layers/ScrollingInteractionContext.h"
200 #include "mozilla/layers/WebRenderLayerManager.h"
201 #include "mozilla/layers/WebRenderUserData.h"
202 #include "mozilla/layout/ScrollAnchorContainer.h"
203 #include "mozilla/layers/APZPublicUtils.h"
204 #include "mozilla/ProfilerLabels.h"
205 #include "mozilla/ProfilerMarkers.h"
206 #include "mozilla/ScrollTimelineAnimationTracker.h"
207 #include "mozilla/ScrollTypes.h"
208 #include "mozilla/ServoBindings.h"
209 #include "mozilla/ServoStyleSet.h"
210 #include "mozilla/StyleSheet.h"
211 #include "mozilla/StyleSheetInlines.h"
212 #include "mozilla/InputTaskManager.h"
213 #include "mozilla/dom/ImageTracker.h"
214 #include "nsIDocShellTreeOwner.h"
215 #include "nsClassHashtable.h"
216 #include "nsGlobalWindowOuter.h"
217 #include "nsHashKeys.h"
218 #include "ScrollSnap.h"
219 #include "VisualViewport.h"
220 #include "ZoomConstraintsClient.h"
222 // define the scalfactor of drag and drop images
223 // relative to the max screen height/width
224 #define RELATIVE_SCALEFACTOR 0.0925f
226 using namespace mozilla;
227 using namespace mozilla::css;
228 using namespace mozilla::dom;
229 using namespace mozilla::gfx;
230 using namespace mozilla::layers;
231 using namespace mozilla::gfx;
232 using namespace mozilla::layout;
233 using PaintFrameFlags = nsLayoutUtils::PaintFrameFlags;
234 typedef ScrollableLayerGuid::ViewID ViewID;
236 PresShell::CapturingContentInfo PresShell::sCapturingContentInfo;
238 // RangePaintInfo is used to paint ranges to offscreen buffers
239 struct RangePaintInfo {
240 RefPtr<nsRange> mRange;
241 nsDisplayListBuilder mBuilder;
242 nsDisplayList mList;
244 // offset of builder's reference frame to the root frame
245 nsPoint mRootOffset;
247 // Resolution at which the items are normally painted. So if we're painting
248 // these items in a range separately from the "full display list", we may want
249 // to paint them at this resolution.
250 float mResolution = 1.0;
252 RangePaintInfo(nsRange* aRange, nsIFrame* aFrame)
253 : mRange(aRange),
254 mBuilder(aFrame, nsDisplayListBuilderMode::Painting, false),
255 mList(&mBuilder) {
256 MOZ_COUNT_CTOR(RangePaintInfo);
257 mBuilder.BeginFrame();
260 ~RangePaintInfo() {
261 mList.DeleteAll(&mBuilder);
262 mBuilder.EndFrame();
263 MOZ_COUNT_DTOR(RangePaintInfo);
267 #undef NOISY
269 // ----------------------------------------------------------------------
271 #ifdef DEBUG
272 // Set the environment variable GECKO_VERIFY_REFLOW_FLAGS to one or
273 // more of the following flags (comma separated) for handy debug
274 // output.
275 static VerifyReflowFlags gVerifyReflowFlags;
277 struct VerifyReflowFlagData {
278 const char* name;
279 VerifyReflowFlags bit;
282 static const VerifyReflowFlagData gFlags[] = {
283 // clang-format off
284 { "verify", VerifyReflowFlags::On },
285 { "reflow", VerifyReflowFlags::Noisy },
286 { "all", VerifyReflowFlags::All },
287 { "list-commands", VerifyReflowFlags::DumpCommands },
288 { "noisy-commands", VerifyReflowFlags::NoisyCommands },
289 { "really-noisy-commands", VerifyReflowFlags::ReallyNoisyCommands },
290 { "resize", VerifyReflowFlags::DuringResizeReflow },
291 // clang-format on
294 # define NUM_VERIFY_REFLOW_FLAGS (sizeof(gFlags) / sizeof(gFlags[0]))
296 static void ShowVerifyReflowFlags() {
297 printf("Here are the available GECKO_VERIFY_REFLOW_FLAGS:\n");
298 const VerifyReflowFlagData* flag = gFlags;
299 const VerifyReflowFlagData* limit = gFlags + NUM_VERIFY_REFLOW_FLAGS;
300 while (flag < limit) {
301 printf(" %s\n", flag->name);
302 ++flag;
304 printf("Note: GECKO_VERIFY_REFLOW_FLAGS is a comma separated list of flag\n");
305 printf("names (no whitespace)\n");
307 #endif
309 //========================================================================
310 //========================================================================
311 //========================================================================
312 #ifdef MOZ_REFLOW_PERF
313 class ReflowCountMgr;
315 static const char kGrandTotalsStr[] = "Grand Totals";
317 // Counting Class
318 class ReflowCounter {
319 public:
320 explicit ReflowCounter(ReflowCountMgr* aMgr = nullptr);
321 ~ReflowCounter();
323 void ClearTotals();
324 void DisplayTotals(const char* aStr);
325 void DisplayDiffTotals(const char* aStr);
326 void DisplayHTMLTotals(const char* aStr);
328 void Add() { mTotal++; }
329 void Add(uint32_t aTotal) { mTotal += aTotal; }
331 void CalcDiffInTotals();
332 void SetTotalsCache();
334 void SetMgr(ReflowCountMgr* aMgr) { mMgr = aMgr; }
336 uint32_t GetTotal() { return mTotal; }
338 protected:
339 void DisplayTotals(uint32_t aTotal, const char* aTitle);
340 void DisplayHTMLTotals(uint32_t aTotal, const char* aTitle);
342 uint32_t mTotal;
343 uint32_t mCacheTotal;
345 ReflowCountMgr* mMgr; // weak reference (don't delete)
348 // Counting Class
349 class IndiReflowCounter {
350 public:
351 explicit IndiReflowCounter(ReflowCountMgr* aMgr = nullptr)
352 : mFrame(nullptr),
353 mCount(0),
354 mMgr(aMgr),
355 mCounter(aMgr),
356 mHasBeenOutput(false) {}
357 virtual ~IndiReflowCounter() = default;
359 nsAutoString mName;
360 nsIFrame* mFrame; // weak reference (don't delete)
361 int32_t mCount;
363 ReflowCountMgr* mMgr; // weak reference (don't delete)
365 ReflowCounter mCounter;
366 bool mHasBeenOutput;
369 //--------------------
370 // Manager Class
371 //--------------------
372 class ReflowCountMgr {
373 public:
374 ReflowCountMgr();
375 virtual ~ReflowCountMgr();
377 void ClearTotals();
378 void ClearGrandTotals();
379 void DisplayTotals(const char* aStr);
380 void DisplayHTMLTotals(const char* aStr);
381 void DisplayDiffsInTotals();
383 void Add(const char* aName, nsIFrame* aFrame);
384 ReflowCounter* LookUp(const char* aName);
386 void PaintCount(const char* aName, gfxContext* aRenderingContext,
387 nsPresContext* aPresContext, nsIFrame* aFrame,
388 const nsPoint& aOffset, uint32_t aColor);
390 FILE* GetOutFile() { return mFD; }
392 void SetPresContext(nsPresContext* aPresContext) {
393 mPresContext = aPresContext; // weak reference
395 void SetPresShell(PresShell* aPresShell) {
396 mPresShell = aPresShell; // weak reference
399 void SetDumpFrameCounts(bool aVal) { mDumpFrameCounts = aVal; }
400 void SetDumpFrameByFrameCounts(bool aVal) { mDumpFrameByFrameCounts = aVal; }
401 void SetPaintFrameCounts(bool aVal) { mPaintFrameByFrameCounts = aVal; }
403 bool IsPaintingFrameCounts() { return mPaintFrameByFrameCounts; }
405 protected:
406 void DisplayTotals(uint32_t aTotal, uint32_t* aDupArray, char* aTitle);
407 void DisplayHTMLTotals(uint32_t aTotal, uint32_t* aDupArray, char* aTitle);
409 void DoGrandTotals();
410 void DoIndiTotalsTree();
412 // HTML Output Methods
413 void DoGrandHTMLTotals();
415 nsClassHashtable<nsCharPtrHashKey, ReflowCounter> mCounts;
416 nsClassHashtable<nsCharPtrHashKey, IndiReflowCounter> mIndiFrameCounts;
417 FILE* mFD;
419 bool mDumpFrameCounts;
420 bool mDumpFrameByFrameCounts;
421 bool mPaintFrameByFrameCounts;
423 bool mCycledOnce;
425 // Root Frame for Individual Tracking
426 nsPresContext* mPresContext;
427 PresShell* mPresShell;
429 // ReflowCountMgr gReflowCountMgr;
431 #endif
432 //========================================================================
434 // comment out to hide caret
435 #define SHOW_CARET
437 // The upper bound on the amount of time to spend reflowing, in
438 // microseconds. When this bound is exceeded and reflow commands are
439 // still queued up, a reflow event is posted. The idea is for reflow
440 // to not hog the processor beyond the time specifed in
441 // gMaxRCProcessingTime. This data member is initialized from the
442 // layout.reflow.timeslice pref.
443 #define NS_MAX_REFLOW_TIME 1000000
444 static int32_t gMaxRCProcessingTime = -1;
446 struct nsCallbackEventRequest {
447 nsIReflowCallback* callback;
448 nsCallbackEventRequest* next;
451 // ----------------------------------------------------------------------------
453 // NOTE(emilio): It'd be nice for this to assert that our document isn't in the
454 // bfcache, but font pref changes don't care about that, and maybe / probably
455 // shouldn't.
456 #ifdef DEBUG
457 # define ASSERT_REFLOW_SCHEDULED_STATE() \
459 if (ObservingLayoutFlushes()) { \
460 MOZ_ASSERT( \
461 mDocument->GetBFCacheEntry() || \
462 mPresContext->RefreshDriver()->IsLayoutFlushObserver(this), \
463 "Unexpected state"); \
464 } else { \
465 MOZ_ASSERT( \
466 !mPresContext->RefreshDriver()->IsLayoutFlushObserver(this), \
467 "Unexpected state"); \
470 #else
471 # define ASSERT_REFLOW_SCHEDULED_STATE() /* nothing */
472 #endif
474 class nsAutoCauseReflowNotifier {
475 public:
476 MOZ_CAN_RUN_SCRIPT explicit nsAutoCauseReflowNotifier(PresShell* aPresShell)
477 : mPresShell(aPresShell) {
478 mPresShell->WillCauseReflow();
480 MOZ_CAN_RUN_SCRIPT ~nsAutoCauseReflowNotifier() {
481 // This check should not be needed. Currently the only place that seem
482 // to need it is the code that deals with bug 337586.
483 if (!mPresShell->mHaveShutDown) {
484 RefPtr<PresShell> presShell(mPresShell);
485 presShell->DidCauseReflow();
486 } else {
487 nsContentUtils::RemoveScriptBlocker();
491 PresShell* mPresShell;
494 class MOZ_STACK_CLASS nsPresShellEventCB : public EventDispatchingCallback {
495 public:
496 explicit nsPresShellEventCB(PresShell* aPresShell) : mPresShell(aPresShell) {}
498 MOZ_CAN_RUN_SCRIPT
499 virtual void HandleEvent(EventChainPostVisitor& aVisitor) override {
500 if (aVisitor.mPresContext && aVisitor.mEvent->mClass != eBasicEventClass) {
501 if (aVisitor.mEvent->mMessage == eMouseDown ||
502 aVisitor.mEvent->mMessage == eMouseUp) {
503 // Mouse-up and mouse-down events call nsIFrame::HandlePress/Release
504 // which call GetContentOffsetsFromPoint which requires up-to-date
505 // layout. Bring layout up-to-date now so that GetCurrentEventFrame()
506 // below will return a real frame and we don't have to worry about
507 // destroying it by flushing later.
508 MOZ_KnownLive(mPresShell)->FlushPendingNotifications(FlushType::Layout);
509 } else if (aVisitor.mEvent->mMessage == eWheel &&
510 aVisitor.mEventStatus != nsEventStatus_eConsumeNoDefault) {
511 nsIFrame* frame = mPresShell->GetCurrentEventFrame();
512 if (frame) {
513 // chrome (including addons) should be able to know if content
514 // handles both D3E "wheel" event and legacy mouse scroll events.
515 // We should dispatch legacy mouse events before dispatching the
516 // "wheel" event into system group.
517 RefPtr<EventStateManager> esm =
518 aVisitor.mPresContext->EventStateManager();
519 esm->DispatchLegacyMouseScrollEvents(
520 frame, aVisitor.mEvent->AsWheelEvent(), &aVisitor.mEventStatus);
523 nsIFrame* frame = mPresShell->GetCurrentEventFrame();
524 if (!frame && (aVisitor.mEvent->mMessage == eMouseUp ||
525 aVisitor.mEvent->mMessage == eTouchEnd)) {
526 // Redirect BUTTON_UP and TOUCH_END events to the root frame to ensure
527 // that capturing is released.
528 frame = mPresShell->GetRootFrame();
530 if (frame) {
531 frame->HandleEvent(aVisitor.mPresContext, aVisitor.mEvent->AsGUIEvent(),
532 &aVisitor.mEventStatus);
537 RefPtr<PresShell> mPresShell;
540 class nsBeforeFirstPaintDispatcher : public Runnable {
541 public:
542 explicit nsBeforeFirstPaintDispatcher(Document* aDocument)
543 : mozilla::Runnable("nsBeforeFirstPaintDispatcher"),
544 mDocument(aDocument) {}
546 // Fires the "before-first-paint" event so that interested parties (right now,
547 // the mobile browser) are aware of it.
548 NS_IMETHOD Run() override {
549 nsCOMPtr<nsIObserverService> observerService =
550 mozilla::services::GetObserverService();
551 if (observerService) {
552 observerService->NotifyObservers(ToSupports(mDocument),
553 "before-first-paint", nullptr);
555 return NS_OK;
558 private:
559 RefPtr<Document> mDocument;
562 // This is a helper class to track whether the targeted frame is destroyed after
563 // dispatching pointer events. In that case, we need the original targeted
564 // content so that we can dispatch the mouse events to it.
565 class MOZ_STACK_CLASS AutoPointerEventTargetUpdater final {
566 public:
567 AutoPointerEventTargetUpdater(PresShell* aShell, WidgetEvent* aEvent,
568 nsIFrame* aFrame, nsIContent* aTargetContent,
569 nsIContent** aOutTargetContent) {
570 MOZ_ASSERT(aEvent);
571 if (!aOutTargetContent || aEvent->mClass != ePointerEventClass) {
572 // Make the destructor happy.
573 mOutTargetContent = nullptr;
574 return;
576 MOZ_ASSERT(aShell);
577 MOZ_ASSERT_IF(aFrame && aFrame->GetContent(),
578 aShell->GetDocument() == aFrame->GetContent()->OwnerDoc());
580 mShell = aShell;
581 mWeakFrame = aFrame;
582 mOutTargetContent = aOutTargetContent;
583 mFromTouch = aEvent->AsPointerEvent()->mFromTouchEvent;
584 // Touch event target may have no frame, e.g., removed from the DOM
585 MOZ_ASSERT_IF(!mFromTouch, aFrame);
586 mOriginalPointerEventTarget = aShell->mPointerEventTarget =
587 aFrame ? aFrame->GetContent() : aTargetContent;
590 ~AutoPointerEventTargetUpdater() {
591 if (!mOutTargetContent || !mShell || mWeakFrame.IsAlive()) {
592 return;
594 if (mFromTouch) {
595 // If the source event is a touch event, the touch event target should
596 // always be same target as preceding ePointerDown. Therefore, we should
597 // always set it back to the original event target.
598 mOriginalPointerEventTarget.swap(*mOutTargetContent);
599 } else {
600 // If the source event is not a touch event (must be a mouse event in
601 // this case), the event should be fired on the closest inclusive ancestor
602 // of the pointer event target which is still connected. The mutations
603 // are tracked by PresShell::ContentRemoved. Therefore, we should set it.
604 mShell->mPointerEventTarget.swap(*mOutTargetContent);
608 private:
609 RefPtr<PresShell> mShell;
610 nsCOMPtr<nsIContent> mOriginalPointerEventTarget;
611 AutoWeakFrame mWeakFrame;
612 nsIContent** mOutTargetContent;
613 bool mFromTouch = false;
616 bool PresShell::sDisableNonTestMouseEvents = false;
617 int16_t PresShell::sMouseButtons = MouseButtonsFlag::eNoButtons;
619 LazyLogModule PresShell::gLog("PresShell");
621 TimeStamp PresShell::EventHandler::sLastInputCreated;
622 TimeStamp PresShell::EventHandler::sLastInputProcessed;
623 StaticRefPtr<Element> PresShell::EventHandler::sLastKeyDownEventTargetElement;
625 bool PresShell::sProcessInteractable = false;
627 static bool gVerifyReflowEnabled;
629 bool PresShell::GetVerifyReflowEnable() {
630 #ifdef DEBUG
631 static bool firstTime = true;
632 if (firstTime) {
633 firstTime = false;
634 char* flags = PR_GetEnv("GECKO_VERIFY_REFLOW_FLAGS");
635 if (flags) {
636 bool error = false;
638 for (;;) {
639 char* comma = strchr(flags, ',');
640 if (comma) *comma = '\0';
642 bool found = false;
643 const VerifyReflowFlagData* flag = gFlags;
644 const VerifyReflowFlagData* limit = gFlags + NUM_VERIFY_REFLOW_FLAGS;
645 while (flag < limit) {
646 if (nsCRT::strcasecmp(flag->name, flags) == 0) {
647 gVerifyReflowFlags |= flag->bit;
648 found = true;
649 break;
651 ++flag;
654 if (!found) error = true;
656 if (!comma) break;
658 *comma = ',';
659 flags = comma + 1;
662 if (error) ShowVerifyReflowFlags();
665 if (VerifyReflowFlags::On & gVerifyReflowFlags) {
666 gVerifyReflowEnabled = true;
668 printf("Note: verifyreflow is enabled");
669 if (VerifyReflowFlags::Noisy & gVerifyReflowFlags) {
670 printf(" (noisy)");
672 if (VerifyReflowFlags::All & gVerifyReflowFlags) {
673 printf(" (all)");
675 if (VerifyReflowFlags::DumpCommands & gVerifyReflowFlags) {
676 printf(" (show reflow commands)");
678 if (VerifyReflowFlags::NoisyCommands & gVerifyReflowFlags) {
679 printf(" (noisy reflow commands)");
680 if (VerifyReflowFlags::ReallyNoisyCommands & gVerifyReflowFlags) {
681 printf(" (REALLY noisy reflow commands)");
684 printf("\n");
687 #endif
688 return gVerifyReflowEnabled;
691 void PresShell::SetVerifyReflowEnable(bool aEnabled) {
692 gVerifyReflowEnabled = aEnabled;
695 void PresShell::AddAutoWeakFrame(AutoWeakFrame* aWeakFrame) {
696 if (aWeakFrame->GetFrame()) {
697 aWeakFrame->GetFrame()->AddStateBits(NS_FRAME_EXTERNAL_REFERENCE);
699 aWeakFrame->SetPreviousWeakFrame(mAutoWeakFrames);
700 mAutoWeakFrames = aWeakFrame;
703 void PresShell::AddWeakFrame(WeakFrame* aWeakFrame) {
704 if (aWeakFrame->GetFrame()) {
705 aWeakFrame->GetFrame()->AddStateBits(NS_FRAME_EXTERNAL_REFERENCE);
707 MOZ_ASSERT(!mWeakFrames.Contains(aWeakFrame));
708 mWeakFrames.Insert(aWeakFrame);
711 void PresShell::RemoveAutoWeakFrame(AutoWeakFrame* aWeakFrame) {
712 if (mAutoWeakFrames == aWeakFrame) {
713 mAutoWeakFrames = aWeakFrame->GetPreviousWeakFrame();
714 return;
716 AutoWeakFrame* nextWeak = mAutoWeakFrames;
717 while (nextWeak && nextWeak->GetPreviousWeakFrame() != aWeakFrame) {
718 nextWeak = nextWeak->GetPreviousWeakFrame();
720 if (nextWeak) {
721 nextWeak->SetPreviousWeakFrame(aWeakFrame->GetPreviousWeakFrame());
725 void PresShell::RemoveWeakFrame(WeakFrame* aWeakFrame) {
726 MOZ_ASSERT(mWeakFrames.Contains(aWeakFrame));
727 mWeakFrames.Remove(aWeakFrame);
730 already_AddRefed<nsFrameSelection> PresShell::FrameSelection() {
731 RefPtr<nsFrameSelection> ret = mSelection;
732 return ret.forget();
735 //----------------------------------------------------------------------
737 static uint32_t sNextPresShellId = 0;
739 /* static */
740 bool PresShell::AccessibleCaretEnabled(nsIDocShell* aDocShell) {
741 // If the pref forces it on, then enable it.
742 if (StaticPrefs::layout_accessiblecaret_enabled()) {
743 return true;
745 // If the touch pref is on, and touch events are enabled (this depends
746 // on the specific device running), then enable it.
747 if (StaticPrefs::layout_accessiblecaret_enabled_on_touch() &&
748 dom::TouchEvent::PrefEnabled(aDocShell)) {
749 return true;
751 // Otherwise, disabled.
752 return false;
755 PresShell::PresShell(Document* aDocument)
756 : mDocument(aDocument),
757 mViewManager(nullptr),
758 mFrameManager(nullptr),
759 mAutoWeakFrames(nullptr),
760 #ifdef ACCESSIBILITY
761 mDocAccessible(nullptr),
762 #endif // ACCESSIBILITY
763 mCurrentEventFrame(nullptr),
764 mMouseLocation(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE),
765 mLastResolutionChangeOrigin(ResolutionChangeOrigin::Apz),
766 mPaintCount(0),
767 mAPZFocusSequenceNumber(0),
768 mActiveSuppressDisplayport(0),
769 mPresShellId(++sNextPresShellId),
770 mFontSizeInflationEmPerLine(0),
771 mFontSizeInflationMinTwips(0),
772 mFontSizeInflationLineThreshold(0),
773 mSelectionFlags(nsISelectionDisplay::DISPLAY_TEXT |
774 nsISelectionDisplay::DISPLAY_IMAGES),
775 mChangeNestCount(0),
776 mRenderingStateFlags(RenderingStateFlags::None),
777 mInFlush(false),
778 mCaretEnabled(false),
779 mNeedLayoutFlush(true),
780 mNeedStyleFlush(true),
781 mNeedThrottledAnimationFlush(true),
782 mVisualViewportSizeSet(false),
783 mDidInitialize(false),
784 mIsDestroying(false),
785 mIsReflowing(false),
786 mIsObservingDocument(false),
787 mForbiddenToFlush(false),
788 mIsDocumentGone(false),
789 mHaveShutDown(false),
790 mPaintingSuppressed(false),
791 mLastRootReflowHadUnconstrainedBSize(false),
792 mShouldUnsuppressPainting(false),
793 mIgnoreFrameDestruction(false),
794 mIsActive(true),
795 mFrozen(false),
796 mIsFirstPaint(true),
797 mObservesMutationsForPrint(false),
798 mWasLastReflowInterrupted(false),
799 mObservingStyleFlushes(false),
800 mObservingLayoutFlushes(false),
801 mResizeEventPending(false),
802 mFontSizeInflationForceEnabled(false),
803 mFontSizeInflationDisabledInMasterProcess(false),
804 mFontSizeInflationEnabled(false),
805 mIsNeverPainting(false),
806 mResolutionUpdated(false),
807 mResolutionUpdatedByApz(false),
808 mUnderHiddenEmbedderElement(false),
809 mDocumentLoading(false),
810 mNoDelayedMouseEvents(false),
811 mNoDelayedKeyEvents(false),
812 mApproximateFrameVisibilityVisited(false),
813 mIsLastChromeOnlyEscapeKeyConsumed(false),
814 mHasReceivedPaintMessage(false),
815 mIsLastKeyDownCanceled(false),
816 mHasHandledUserInput(false),
817 mForceDispatchKeyPressEventsForNonPrintableKeys(false),
818 mForceUseLegacyKeyCodeAndCharCodeValues(false),
819 mInitializedWithKeyPressEventDispatchingBlacklist(false),
820 mMouseLocationWasSetBySynthesizedMouseEventForTests(false),
821 mHasTriedFastUnsuppress(false),
822 mProcessingReflowCommands(false),
823 mPendingDidDoReflow(false) {
824 MOZ_LOG(gLog, LogLevel::Debug, ("PresShell::PresShell this=%p", this));
825 MOZ_ASSERT(aDocument);
827 #ifdef MOZ_REFLOW_PERF
828 mReflowCountMgr = MakeUnique<ReflowCountMgr>();
829 mReflowCountMgr->SetPresContext(mPresContext);
830 mReflowCountMgr->SetPresShell(this);
831 #endif
832 mLastOSWake = mLoadBegin = TimeStamp::Now();
835 NS_INTERFACE_TABLE_HEAD(PresShell)
836 NS_INTERFACE_TABLE_BEGIN
837 // In most cases, PresShell should be treated as concrete class, but need to
838 // QI for weak reference. Therefore, the case needed by do_QueryReferent()
839 // should be tested first.
840 NS_INTERFACE_TABLE_ENTRY(PresShell, PresShell)
841 NS_INTERFACE_TABLE_ENTRY(PresShell, nsIDocumentObserver)
842 NS_INTERFACE_TABLE_ENTRY(PresShell, nsISelectionController)
843 NS_INTERFACE_TABLE_ENTRY(PresShell, nsISelectionDisplay)
844 NS_INTERFACE_TABLE_ENTRY(PresShell, nsIObserver)
845 NS_INTERFACE_TABLE_ENTRY(PresShell, nsISupportsWeakReference)
846 NS_INTERFACE_TABLE_ENTRY(PresShell, nsIMutationObserver)
847 NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(PresShell, nsISupports, nsIObserver)
848 NS_INTERFACE_TABLE_END
849 NS_INTERFACE_TABLE_TO_MAP_SEGUE
850 NS_INTERFACE_MAP_END
852 NS_IMPL_ADDREF(PresShell)
853 NS_IMPL_RELEASE(PresShell)
855 PresShell::~PresShell() {
856 MOZ_RELEASE_ASSERT(!mForbiddenToFlush,
857 "Flag should only be set temporarily, while doing things "
858 "that shouldn't cause destruction");
859 MOZ_LOG(gLog, LogLevel::Debug, ("PresShell::~PresShell this=%p", this));
861 if (!mHaveShutDown) {
862 MOZ_ASSERT_UNREACHABLE("Someone did not call PresShell::Destroy()");
863 Destroy();
866 NS_ASSERTION(mCurrentEventContentStack.Count() == 0,
867 "Huh, event content left on the stack in pres shell dtor!");
868 NS_ASSERTION(mFirstCallbackEventRequest == nullptr &&
869 mLastCallbackEventRequest == nullptr,
870 "post-reflow queues not empty. This means we're leaking");
872 MOZ_ASSERT(!mAllocatedPointers || mAllocatedPointers->IsEmpty(),
873 "Some pres arena objects were not freed");
875 mFrameManager = nullptr;
876 mFrameConstructor = nullptr;
878 mCurrentEventContent = nullptr;
882 * Initialize the presentation shell. Create view manager and style
883 * manager.
884 * Note this can't be merged into our constructor because caret initialization
885 * calls AddRef() on us.
887 void PresShell::Init(nsPresContext* aPresContext, nsViewManager* aViewManager) {
888 MOZ_ASSERT(mDocument);
889 MOZ_ASSERT(aPresContext);
890 MOZ_ASSERT(aViewManager);
891 MOZ_ASSERT(!mViewManager, "already initialized");
893 mViewManager = aViewManager;
895 // mDocument is now set. It might have a display document whose "need layout/
896 // style" flush flags are not set, but ours will be set. To keep these
897 // consistent, call the flag setting functions to propagate those flags up
898 // to the display document.
899 SetNeedLayoutFlush();
900 SetNeedStyleFlush();
902 // Create our frame constructor.
903 mFrameConstructor = MakeUnique<nsCSSFrameConstructor>(mDocument, this);
905 mFrameManager = mFrameConstructor.get();
907 // The document viewer owns both view manager and pres shell.
908 mViewManager->SetPresShell(this);
910 // Bind the context to the presentation shell.
911 // FYI: We cannot initialize mPresContext in the constructor because we
912 // cannot call AttachPresShell() in it and once we initialize
913 // mPresContext, other objects may refer refresh driver or restyle
914 // manager via mPresContext and that causes hitting MOZ_ASSERT in some
915 // places. Therefore, we should initialize mPresContext here with
916 // const_cast hack since we want to guarantee that mPresContext lives
917 // as long as the PresShell.
918 const_cast<RefPtr<nsPresContext>&>(mPresContext) = aPresContext;
919 mPresContext->AttachPresShell(this);
921 mPresContext->InitFontCache();
923 // FIXME(emilio, bug 1544185): Some Android code somehow depends on the shell
924 // being eagerly registered as a style flush observer. This shouldn't be
925 // needed otherwise.
926 EnsureStyleFlush();
928 const bool accessibleCaretEnabled =
929 AccessibleCaretEnabled(mDocument->GetDocShell());
930 if (accessibleCaretEnabled) {
931 // Need to happen before nsFrameSelection has been set up.
932 mAccessibleCaretEventHub = new AccessibleCaretEventHub(this);
933 mAccessibleCaretEventHub->Init();
936 mSelection = new nsFrameSelection(this, nullptr, accessibleCaretEnabled);
938 // Important: this has to happen after the selection has been set up
939 #ifdef SHOW_CARET
940 // make the caret
941 mCaret = new nsCaret();
942 mCaret->Init(this);
943 mOriginalCaret = mCaret;
945 // SetCaretEnabled(true); // make it show in browser windows
946 #endif
947 // set up selection to be displayed in document
948 // Don't enable selection for print media
949 nsPresContext::nsPresContextType type = mPresContext->Type();
950 if (type != nsPresContext::eContext_PrintPreview &&
951 type != nsPresContext::eContext_Print) {
952 SetDisplaySelection(nsISelectionController::SELECTION_DISABLED);
955 if (gMaxRCProcessingTime == -1) {
956 gMaxRCProcessingTime =
957 Preferences::GetInt("layout.reflow.timeslice", NS_MAX_REFLOW_TIME);
960 if (nsStyleSheetService* ss = nsStyleSheetService::GetInstance()) {
961 ss->RegisterPresShell(this);
965 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
966 if (os) {
967 os->AddObserver(this, "memory-pressure", false);
968 os->AddObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC, false);
969 if (XRE_IsParentProcess() && !sProcessInteractable) {
970 os->AddObserver(this, "sessionstore-one-or-no-tab-restored", false);
972 os->AddObserver(this, "font-info-updated", false);
973 os->AddObserver(this, "internal-look-and-feel-changed", false);
977 #ifdef MOZ_REFLOW_PERF
978 if (mReflowCountMgr) {
979 bool paintFrameCounts =
980 Preferences::GetBool("layout.reflow.showframecounts");
982 bool dumpFrameCounts =
983 Preferences::GetBool("layout.reflow.dumpframecounts");
985 bool dumpFrameByFrameCounts =
986 Preferences::GetBool("layout.reflow.dumpframebyframecounts");
988 mReflowCountMgr->SetDumpFrameCounts(dumpFrameCounts);
989 mReflowCountMgr->SetDumpFrameByFrameCounts(dumpFrameByFrameCounts);
990 mReflowCountMgr->SetPaintFrameCounts(paintFrameCounts);
992 #endif
994 if (mDocument->HasAnimationController()) {
995 SMILAnimationController* animCtrl = mDocument->GetAnimationController();
996 animCtrl->NotifyRefreshDriverCreated(GetPresContext()->RefreshDriver());
999 for (DocumentTimeline* timeline : mDocument->Timelines()) {
1000 timeline->NotifyRefreshDriverCreated(GetPresContext()->RefreshDriver());
1003 // Get our activeness from the docShell.
1004 ActivenessMaybeChanged();
1006 // Setup our font inflation preferences.
1007 mFontSizeInflationEmPerLine = StaticPrefs::font_size_inflation_emPerLine();
1008 mFontSizeInflationMinTwips = StaticPrefs::font_size_inflation_minTwips();
1009 mFontSizeInflationLineThreshold =
1010 StaticPrefs::font_size_inflation_lineThreshold();
1011 mFontSizeInflationForceEnabled =
1012 StaticPrefs::font_size_inflation_forceEnabled();
1013 mFontSizeInflationDisabledInMasterProcess =
1014 StaticPrefs::font_size_inflation_disabledInMasterProcess();
1015 // We'll compute the font size inflation state in Initialize(), when we know
1016 // the document type.
1018 mTouchManager.Init(this, mDocument);
1020 if (mPresContext->IsRootContentDocumentCrossProcess()) {
1021 mZoomConstraintsClient = new ZoomConstraintsClient();
1022 mZoomConstraintsClient->Init(this, mDocument);
1024 // We call this to create mMobileViewportManager, if it is needed.
1025 MaybeRecreateMobileViewportManager(false);
1028 if (nsCOMPtr<nsIDocShell> docShell = mPresContext->GetDocShell()) {
1029 if (BrowsingContext* bc = docShell->GetBrowsingContext()) {
1030 mUnderHiddenEmbedderElement = bc->IsUnderHiddenEmbedderElement();
1035 enum TextPerfLogType { eLog_reflow, eLog_loaddone, eLog_totals };
1037 static void LogTextPerfStats(gfxTextPerfMetrics* aTextPerf,
1038 PresShell* aPresShell,
1039 const gfxTextPerfMetrics::TextCounts& aCounts,
1040 float aTime, TextPerfLogType aLogType,
1041 const char* aURL) {
1042 LogModule* tpLog = gfxPlatform::GetLog(eGfxLog_textperf);
1044 // ignore XUL contexts unless at debug level
1045 mozilla::LogLevel logLevel = LogLevel::Warning;
1046 if (aCounts.numContentTextRuns == 0) {
1047 logLevel = LogLevel::Debug;
1050 if (!MOZ_LOG_TEST(tpLog, logLevel)) {
1051 return;
1054 char prefix[256];
1056 switch (aLogType) {
1057 case eLog_reflow:
1058 SprintfLiteral(prefix, "(textperf-reflow) %p time-ms: %7.0f", aPresShell,
1059 aTime);
1060 break;
1061 case eLog_loaddone:
1062 SprintfLiteral(prefix, "(textperf-loaddone) %p time-ms: %7.0f",
1063 aPresShell, aTime);
1064 break;
1065 default:
1066 MOZ_ASSERT(aLogType == eLog_totals, "unknown textperf log type");
1067 SprintfLiteral(prefix, "(textperf-totals) %p", aPresShell);
1070 double hitRatio = 0.0;
1071 uint32_t lookups = aCounts.wordCacheHit + aCounts.wordCacheMiss;
1072 if (lookups) {
1073 hitRatio = double(aCounts.wordCacheHit) / double(lookups);
1076 if (aLogType == eLog_loaddone) {
1077 MOZ_LOG(
1078 tpLog, logLevel,
1079 ("%s reflow: %d chars: %d "
1080 "[%s] "
1081 "content-textruns: %d chrome-textruns: %d "
1082 "max-textrun-len: %d "
1083 "word-cache-lookups: %d word-cache-hit-ratio: %4.3f "
1084 "word-cache-space: %d word-cache-long: %d "
1085 "pref-fallbacks: %d system-fallbacks: %d "
1086 "textruns-const: %d textruns-destr: %d "
1087 "generic-lookups: %d "
1088 "cumulative-textruns-destr: %d\n",
1089 prefix, aTextPerf->reflowCount, aCounts.numChars, (aURL ? aURL : ""),
1090 aCounts.numContentTextRuns, aCounts.numChromeTextRuns,
1091 aCounts.maxTextRunLen, lookups, hitRatio, aCounts.wordCacheSpaceRules,
1092 aCounts.wordCacheLong, aCounts.fallbackPrefs, aCounts.fallbackSystem,
1093 aCounts.textrunConst, aCounts.textrunDestr, aCounts.genericLookups,
1094 aTextPerf->cumulative.textrunDestr));
1095 } else {
1096 MOZ_LOG(
1097 tpLog, logLevel,
1098 ("%s reflow: %d chars: %d "
1099 "content-textruns: %d chrome-textruns: %d "
1100 "max-textrun-len: %d "
1101 "word-cache-lookups: %d word-cache-hit-ratio: %4.3f "
1102 "word-cache-space: %d word-cache-long: %d "
1103 "pref-fallbacks: %d system-fallbacks: %d "
1104 "textruns-const: %d textruns-destr: %d "
1105 "generic-lookups: %d "
1106 "cumulative-textruns-destr: %d\n",
1107 prefix, aTextPerf->reflowCount, aCounts.numChars,
1108 aCounts.numContentTextRuns, aCounts.numChromeTextRuns,
1109 aCounts.maxTextRunLen, lookups, hitRatio, aCounts.wordCacheSpaceRules,
1110 aCounts.wordCacheLong, aCounts.fallbackPrefs, aCounts.fallbackSystem,
1111 aCounts.textrunConst, aCounts.textrunDestr, aCounts.genericLookups,
1112 aTextPerf->cumulative.textrunDestr));
1116 bool PresShell::InRDMPane() {
1117 if (Document* doc = GetDocument()) {
1118 if (BrowsingContext* bc = doc->GetBrowsingContext()) {
1119 return bc->InRDMPane();
1122 return false;
1125 #if defined(MOZ_WIDGET_ANDROID)
1126 void PresShell::MaybeNotifyShowDynamicToolbar() {
1127 const DynamicToolbarState dynToolbarState = GetDynamicToolbarState();
1128 if ((dynToolbarState == DynamicToolbarState::Collapsed ||
1129 dynToolbarState == DynamicToolbarState::InTransition)) {
1130 MOZ_ASSERT(mPresContext &&
1131 mPresContext->IsRootContentDocumentCrossProcess());
1132 if (BrowserChild* browserChild = BrowserChild::GetFrom(this)) {
1133 browserChild->SendShowDynamicToolbar();
1137 #endif // defined(MOZ_WIDGET_ANDROID)
1139 void PresShell::Destroy() {
1140 // Do not add code before this line please!
1141 if (mHaveShutDown) {
1142 return;
1145 NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
1146 "destroy called on presshell while scripts not blocked");
1148 [[maybe_unused]] nsIURI* uri = mDocument->GetDocumentURI();
1149 AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING_RELEVANT_FOR_JS(
1150 "Layout tree destruction", LAYOUT_Destroy,
1151 uri ? uri->GetSpecOrDefault() : "N/A"_ns);
1153 // Try to determine if the page is the user had a meaningful opportunity to
1154 // zoom this page. This is not 100% accurate but should be "good enough" for
1155 // telemetry purposes.
1156 auto isUserZoomablePage = [&]() -> bool {
1157 if (mIsFirstPaint) {
1158 // Page was never painted, so it wasn't zoomable by the user. We get a
1159 // handful of these "transient" presShells.
1160 return false;
1162 if (!mPresContext->IsRootContentDocumentCrossProcess()) {
1163 // Not a root content document, so APZ doesn't support zooming it.
1164 return false;
1166 if (InRDMPane()) {
1167 // Responsive design mode is a special case that we want to ignore here.
1168 return false;
1170 if (mDocument && mDocument->IsInitialDocument()) {
1171 // Ignore initial about:blank page loads
1172 return false;
1174 if (XRE_IsContentProcess() &&
1175 IsExtensionRemoteType(ContentChild::GetSingleton()->GetRemoteType())) {
1176 // Also omit presShells from the extension process because they sometimes
1177 // can't be zoomed by the user.
1178 return false;
1180 // Otherwise assume the page is user-zoomable.
1181 return true;
1183 if (isUserZoomablePage()) {
1184 Telemetry::Accumulate(Telemetry::APZ_ZOOM_ACTIVITY,
1185 IsResolutionUpdatedByApz());
1188 // dump out cumulative text perf metrics
1189 gfxTextPerfMetrics* tp;
1190 if (mPresContext && (tp = mPresContext->GetTextPerfMetrics())) {
1191 tp->Accumulate();
1192 if (tp->cumulative.numChars > 0) {
1193 LogTextPerfStats(tp, this, tp->cumulative, 0.0, eLog_totals, nullptr);
1196 if (mPresContext) {
1197 if (gfxUserFontSet* fs = mPresContext->GetUserFontSet()) {
1198 uint32_t fontCount;
1199 uint64_t fontSize;
1200 fs->GetLoadStatistics(fontCount, fontSize);
1201 Telemetry::Accumulate(Telemetry::WEBFONT_PER_PAGE, fontCount);
1202 Telemetry::Accumulate(Telemetry::WEBFONT_SIZE_PER_PAGE,
1203 uint32_t(fontSize / 1024));
1204 } else {
1205 Telemetry::Accumulate(Telemetry::WEBFONT_PER_PAGE, 0);
1206 Telemetry::Accumulate(Telemetry::WEBFONT_SIZE_PER_PAGE, 0);
1210 #ifdef MOZ_REFLOW_PERF
1211 DumpReflows();
1212 mReflowCountMgr = nullptr;
1213 #endif
1215 if (mZoomConstraintsClient) {
1216 mZoomConstraintsClient->Destroy();
1217 mZoomConstraintsClient = nullptr;
1219 if (mMobileViewportManager) {
1220 mMobileViewportManager->Destroy();
1221 mMobileViewportManager = nullptr;
1222 mMVMContext = nullptr;
1225 #ifdef ACCESSIBILITY
1226 if (mDocAccessible) {
1227 # ifdef DEBUG
1228 if (a11y::logging::IsEnabled(a11y::logging::eDocDestroy))
1229 a11y::logging::DocDestroy("presshell destroyed", mDocument);
1230 # endif
1232 mDocAccessible->Shutdown();
1233 mDocAccessible = nullptr;
1235 #endif // ACCESSIBILITY
1237 MaybeReleaseCapturingContent();
1239 EventHandler::OnPresShellDestroy(mDocument);
1241 if (mContentToScrollTo) {
1242 mContentToScrollTo->RemoveProperty(nsGkAtoms::scrolling);
1243 mContentToScrollTo = nullptr;
1246 if (mPresContext) {
1247 // We need to notify the destroying the nsPresContext to ESM for
1248 // suppressing to use from ESM.
1249 mPresContext->EventStateManager()->NotifyDestroyPresContext(mPresContext);
1252 if (nsStyleSheetService* ss = nsStyleSheetService::GetInstance()) {
1253 ss->UnregisterPresShell(this);
1257 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
1258 if (os) {
1259 os->RemoveObserver(this, "memory-pressure");
1260 os->RemoveObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC);
1261 if (XRE_IsParentProcess()) {
1262 os->RemoveObserver(this, "sessionstore-one-or-no-tab-restored");
1264 os->RemoveObserver(this, "font-info-updated");
1265 os->RemoveObserver(this, "internal-look-and-feel-changed");
1269 // If our paint suppression timer is still active, kill it.
1270 CancelPaintSuppressionTimer();
1272 // Same for our reflow continuation timer
1273 if (mReflowContinueTimer) {
1274 mReflowContinueTimer->Cancel();
1275 mReflowContinueTimer = nullptr;
1278 mSynthMouseMoveEvent.Revoke();
1280 mUpdateApproximateFrameVisibilityEvent.Revoke();
1282 ClearApproximatelyVisibleFramesList(Some(OnNonvisible::DiscardImages));
1284 if (mCaret) {
1285 mCaret->Terminate();
1286 mCaret = nullptr;
1289 mFocusedFrameSelection = nullptr;
1291 if (mSelection) {
1292 RefPtr<nsFrameSelection> frameSelection = mSelection;
1293 frameSelection->DisconnectFromPresShell();
1296 mIsDestroying = true;
1298 // We can't release all the event content in
1299 // mCurrentEventContentStack here since there might be code on the
1300 // stack that will release the event content too. Double release
1301 // bad!
1303 // The frames will be torn down, so remove them from the current
1304 // event frame stack (since they'd be dangling references if we'd
1305 // leave them in) and null out the mCurrentEventFrame pointer as
1306 // well.
1308 mCurrentEventFrame = nullptr;
1310 int32_t i, count = mCurrentEventFrameStack.Length();
1311 for (i = 0; i < count; i++) {
1312 mCurrentEventFrameStack[i] = nullptr;
1315 mFramesToDirty.Clear();
1316 mPendingScrollAnchorSelection.Clear();
1317 mPendingScrollAnchorAdjustment.Clear();
1318 mPendingScrollResnap.Clear();
1320 if (mViewManager) {
1321 // Clear the view manager's weak pointer back to |this| in case it
1322 // was leaked.
1323 mViewManager->SetPresShell(nullptr);
1324 mViewManager = nullptr;
1327 nsRefreshDriver* rd = GetPresContext()->RefreshDriver();
1329 // This shell must be removed from the document before the frame
1330 // hierarchy is torn down to avoid finding deleted frames through
1331 // this presshell while the frames are being torn down
1332 if (mDocument) {
1333 NS_ASSERTION(mDocument->GetPresShell() == this, "Wrong shell?");
1334 mDocument->ClearServoRestyleRoot();
1335 mDocument->DeletePresShell();
1337 if (mDocument->HasAnimationController()) {
1338 mDocument->GetAnimationController()->NotifyRefreshDriverDestroying(rd);
1340 for (DocumentTimeline* timeline : mDocument->Timelines()) {
1341 timeline->NotifyRefreshDriverDestroying(rd);
1345 if (mPresContext) {
1346 rd->CancelPendingAnimationEvents(mPresContext->AnimationEventDispatcher());
1349 // Revoke any pending events. We need to do this and cancel pending reflows
1350 // before we destroy the frame manager, since apparently frame destruction
1351 // sometimes spins the event queue when plug-ins are involved(!).
1352 // XXXmats is this still needed now that plugins are gone?
1353 StopObservingRefreshDriver();
1355 if (rd->GetPresContext() == GetPresContext()) {
1356 rd->RevokeViewManagerFlush();
1357 rd->ClearHasScheduleFlush();
1360 CancelAllPendingReflows();
1361 CancelPostedReflowCallbacks();
1363 // Destroy the frame manager. This will destroy the frame hierarchy
1364 mFrameConstructor->WillDestroyFrameTree();
1366 NS_WARNING_ASSERTION(!mAutoWeakFrames && mWeakFrames.IsEmpty(),
1367 "Weak frames alive after destroying FrameManager");
1368 while (mAutoWeakFrames) {
1369 mAutoWeakFrames->Clear(this);
1371 const nsTArray<WeakFrame*> weakFrames = ToArray(mWeakFrames);
1372 for (WeakFrame* weakFrame : weakFrames) {
1373 weakFrame->Clear(this);
1376 // Terminate AccessibleCaretEventHub after tearing down the frame tree so that
1377 // we don't need to remove caret element's frame in
1378 // AccessibleCaret::RemoveCaretElement().
1379 if (mAccessibleCaretEventHub) {
1380 mAccessibleCaretEventHub->Terminate();
1381 mAccessibleCaretEventHub = nullptr;
1384 if (mPresContext) {
1385 // We hold a reference to the pres context, and it holds a weak link back
1386 // to us. To avoid the pres context having a dangling reference, set its
1387 // pres shell to nullptr
1388 mPresContext->DetachPresShell();
1391 mHaveShutDown = true;
1393 mTouchManager.Destroy();
1396 void PresShell::StopObservingRefreshDriver() {
1397 nsRefreshDriver* rd = mPresContext->RefreshDriver();
1398 if (mResizeEventPending) {
1399 rd->RemoveResizeEventFlushObserver(this);
1401 if (mObservingLayoutFlushes) {
1402 rd->RemoveLayoutFlushObserver(this);
1404 if (mObservingStyleFlushes) {
1405 rd->RemoveStyleFlushObserver(this);
1409 void PresShell::StartObservingRefreshDriver() {
1410 nsRefreshDriver* rd = mPresContext->RefreshDriver();
1411 if (mResizeEventPending) {
1412 rd->AddResizeEventFlushObserver(this);
1414 if (mObservingLayoutFlushes) {
1415 rd->AddLayoutFlushObserver(this);
1417 if (mObservingStyleFlushes) {
1418 rd->AddStyleFlushObserver(this);
1422 nsRefreshDriver* PresShell::GetRefreshDriver() const {
1423 return mPresContext ? mPresContext->RefreshDriver() : nullptr;
1426 void PresShell::SetAuthorStyleDisabled(bool aStyleDisabled) {
1427 if (aStyleDisabled != StyleSet()->GetAuthorStyleDisabled()) {
1428 StyleSet()->SetAuthorStyleDisabled(aStyleDisabled);
1429 mDocument->ApplicableStylesChanged();
1431 nsCOMPtr<nsIObserverService> observerService =
1432 mozilla::services::GetObserverService();
1433 if (observerService) {
1434 observerService->NotifyObservers(
1435 ToSupports(mDocument), "author-style-disabled-changed", nullptr);
1440 bool PresShell::GetAuthorStyleDisabled() const {
1441 return StyleSet()->GetAuthorStyleDisabled();
1444 void PresShell::AddUserSheet(StyleSheet* aSheet) {
1445 // Make sure this does what nsDocumentViewer::CreateStyleSet does wrt
1446 // ordering. We want this new sheet to come after all the existing stylesheet
1447 // service sheets (which are at the start), but before other user sheets; see
1448 // nsIStyleSheetService.idl for the ordering.
1450 nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance();
1451 nsTArray<RefPtr<StyleSheet>>& userSheets = *sheetService->UserStyleSheets();
1453 // Search for the place to insert the new user sheet. Since all of the
1454 // stylesheet service provided user sheets should be at the start of the style
1455 // set's list, and aSheet should be at the end of userSheets. Given that, we
1456 // can find the right place to insert the new sheet based on the length of
1457 // userSheets.
1458 MOZ_ASSERT(aSheet);
1459 MOZ_ASSERT(userSheets.LastElement() == aSheet);
1461 size_t index = userSheets.Length() - 1;
1463 // Assert that all of userSheets (except for the last, new element) matches up
1464 // with what's in the style set.
1465 for (size_t i = 0; i < index; ++i) {
1466 MOZ_ASSERT(StyleSet()->SheetAt(StyleOrigin::User, i) == userSheets[i]);
1469 if (index == static_cast<size_t>(StyleSet()->SheetCount(StyleOrigin::User))) {
1470 StyleSet()->AppendStyleSheet(*aSheet);
1471 } else {
1472 StyleSheet* ref = StyleSet()->SheetAt(StyleOrigin::User, index);
1473 StyleSet()->InsertStyleSheetBefore(*aSheet, *ref);
1476 mDocument->ApplicableStylesChanged();
1479 void PresShell::AddAgentSheet(StyleSheet* aSheet) {
1480 // Make sure this does what nsDocumentViewer::CreateStyleSet does
1481 // wrt ordering.
1482 StyleSet()->AppendStyleSheet(*aSheet);
1483 mDocument->ApplicableStylesChanged();
1486 void PresShell::AddAuthorSheet(StyleSheet* aSheet) {
1487 // Document specific "additional" Author sheets should be stronger than the
1488 // ones added with the StyleSheetService.
1489 StyleSheet* firstAuthorSheet = mDocument->GetFirstAdditionalAuthorSheet();
1490 if (firstAuthorSheet) {
1491 StyleSet()->InsertStyleSheetBefore(*aSheet, *firstAuthorSheet);
1492 } else {
1493 StyleSet()->AppendStyleSheet(*aSheet);
1496 mDocument->ApplicableStylesChanged();
1499 bool PresShell::FixUpFocus() {
1500 if (NS_WARN_IF(!mDocument)) {
1501 return false;
1504 nsIContent* currentFocus = mDocument->GetUnretargetedFocusedContent(
1505 Document::IncludeChromeOnly::Yes);
1506 if (!currentFocus) {
1507 return false;
1510 // If focus target is an area element with one or more shapes that are
1511 // focusable areas.
1512 if (auto* area = HTMLAreaElement::FromNode(currentFocus)) {
1513 if (nsFocusManager::IsAreaElementFocusable(*area)) {
1514 return false;
1518 nsIFrame* f = currentFocus->GetPrimaryFrame();
1519 if (f && f->IsFocusable()) {
1520 return false;
1523 if (currentFocus == mDocument->GetBody() ||
1524 currentFocus == mDocument->GetRootElement()) {
1525 return false;
1528 RefPtr fm = nsFocusManager::GetFocusManager();
1529 nsCOMPtr<nsPIDOMWindowOuter> window = mDocument->GetWindow();
1530 if (NS_WARN_IF(!window)) {
1531 return false;
1533 fm->ClearFocus(window);
1534 return true;
1537 void PresShell::SelectionWillTakeFocus() {
1538 if (mSelection) {
1539 FrameSelectionWillTakeFocus(*mSelection);
1543 void PresShell::SelectionWillLoseFocus() {
1544 // Do nothing, the main selection is the default focused selection.
1547 // Selection repainting code relies on selection offsets being properly
1548 // adjusted (see bug 1626291), so we need to wait until the DOM is finished
1549 // notifying.
1550 static void RepaintNormalSelectionWhenSafe(nsFrameSelection& aFrameSelection) {
1551 if (nsContentUtils::IsSafeToRunScript()) {
1552 aFrameSelection.RepaintSelection(SelectionType::eNormal);
1553 return;
1556 // Note that importantly we don't defer changing the DisplaySelection. That'd
1557 // be potentially racy with other code that may change it.
1558 nsContentUtils::AddScriptRunner(NS_NewRunnableFunction(
1559 "RepaintNormalSelectionWhenSafe",
1560 [sel = RefPtr<nsFrameSelection>(&aFrameSelection)] {
1561 sel->RepaintSelection(SelectionType::eNormal);
1562 }));
1565 void PresShell::FrameSelectionWillLoseFocus(nsFrameSelection& aFrameSelection) {
1566 if (mFocusedFrameSelection != &aFrameSelection) {
1567 return;
1570 // Do nothing, the main selection is the default focused selection.
1571 if (&aFrameSelection == mSelection) {
1572 return;
1575 RefPtr<nsFrameSelection> old = std::move(mFocusedFrameSelection);
1576 MOZ_ASSERT(!mFocusedFrameSelection);
1578 if (old->GetDisplaySelection() != nsISelectionController::SELECTION_HIDDEN) {
1579 old->SetDisplaySelection(nsISelectionController::SELECTION_HIDDEN);
1580 RepaintNormalSelectionWhenSafe(*old);
1583 if (mSelection) {
1584 FrameSelectionWillTakeFocus(*mSelection);
1588 void PresShell::FrameSelectionWillTakeFocus(nsFrameSelection& aFrameSelection) {
1589 if (mFocusedFrameSelection == &aFrameSelection) {
1590 #ifdef XP_MACOSX
1591 // FIXME: Mac needs to update the global selection cache, even if the
1592 // document's focused selection doesn't change, and this is currently done
1593 // from RepaintSelection. Maybe we should move part of the global selection
1594 // handling here, or something of that sort, unclear.
1595 RepaintNormalSelectionWhenSafe(aFrameSelection);
1596 #endif
1597 return;
1600 RefPtr<nsFrameSelection> old = std::move(mFocusedFrameSelection);
1601 mFocusedFrameSelection = &aFrameSelection;
1603 if (old &&
1604 old->GetDisplaySelection() != nsISelectionController::SELECTION_HIDDEN) {
1605 old->SetDisplaySelection(nsISelectionController::SELECTION_HIDDEN);
1606 RepaintNormalSelectionWhenSafe(*old);
1609 if (aFrameSelection.GetDisplaySelection() !=
1610 nsISelectionController::SELECTION_ON) {
1611 aFrameSelection.SetDisplaySelection(nsISelectionController::SELECTION_ON);
1612 RepaintNormalSelectionWhenSafe(aFrameSelection);
1616 NS_IMETHODIMP
1617 PresShell::SetDisplaySelection(int16_t aToggle) {
1618 RefPtr<nsFrameSelection> frameSelection = mSelection;
1619 frameSelection->SetDisplaySelection(aToggle);
1620 return NS_OK;
1623 NS_IMETHODIMP
1624 PresShell::GetDisplaySelection(int16_t* aToggle) {
1625 RefPtr<nsFrameSelection> frameSelection = mSelection;
1626 *aToggle = frameSelection->GetDisplaySelection();
1627 return NS_OK;
1630 NS_IMETHODIMP
1631 PresShell::GetSelectionFromScript(RawSelectionType aRawSelectionType,
1632 Selection** aSelection) {
1633 if (!aSelection || !mSelection) return NS_ERROR_NULL_POINTER;
1635 RefPtr<nsFrameSelection> frameSelection = mSelection;
1636 RefPtr<Selection> selection =
1637 frameSelection->GetSelection(ToSelectionType(aRawSelectionType));
1639 if (!selection) {
1640 return NS_ERROR_INVALID_ARG;
1643 selection.forget(aSelection);
1644 return NS_OK;
1647 Selection* PresShell::GetSelection(RawSelectionType aRawSelectionType) {
1648 if (!mSelection) {
1649 return nullptr;
1652 RefPtr<nsFrameSelection> frameSelection = mSelection;
1653 return frameSelection->GetSelection(ToSelectionType(aRawSelectionType));
1656 Selection* PresShell::GetCurrentSelection(SelectionType aSelectionType) {
1657 if (!mSelection) {
1658 return nullptr;
1661 RefPtr<nsFrameSelection> frameSelection = mSelection;
1662 return frameSelection->GetSelection(aSelectionType);
1665 nsFrameSelection* PresShell::GetLastFocusedFrameSelection() {
1666 return mFocusedFrameSelection ? mFocusedFrameSelection : mSelection;
1669 NS_IMETHODIMP
1670 PresShell::ScrollSelectionIntoView(RawSelectionType aRawSelectionType,
1671 SelectionRegion aRegion, int16_t aFlags) {
1672 if (!mSelection) return NS_ERROR_NULL_POINTER;
1674 RefPtr<nsFrameSelection> frameSelection = mSelection;
1675 return frameSelection->ScrollSelectionIntoView(
1676 ToSelectionType(aRawSelectionType), aRegion, aFlags);
1679 NS_IMETHODIMP
1680 PresShell::RepaintSelection(RawSelectionType aRawSelectionType) {
1681 if (!mSelection) {
1682 return NS_ERROR_NULL_POINTER;
1685 if (MOZ_UNLIKELY(mIsDestroying)) {
1686 return NS_OK;
1689 RefPtr<nsFrameSelection> frameSelection = mSelection;
1690 return frameSelection->RepaintSelection(ToSelectionType(aRawSelectionType));
1693 // Make shell be a document observer
1694 void PresShell::BeginObservingDocument() {
1695 if (mDocument && !mIsDestroying) {
1696 mIsObservingDocument = true;
1697 if (mIsDocumentGone) {
1698 NS_WARNING(
1699 "Adding a presshell that was disconnected from the document "
1700 "as a document observer? Sounds wrong...");
1701 mIsDocumentGone = false;
1706 // Make shell stop being a document observer
1707 void PresShell::EndObservingDocument() {
1708 // XXXbz do we need to tell the frame constructor that the document
1709 // is gone, perhaps? Except for printing it's NOT gone, sometimes.
1710 mIsDocumentGone = true;
1711 mIsObservingDocument = false;
1714 #ifdef DEBUG_kipp
1715 char* nsPresShell_ReflowStackPointerTop;
1716 #endif
1718 void PresShell::InitPaintSuppressionTimer() {
1719 // Default to PAINTLOCK_EVENT_DELAY if we can't get the pref value.
1720 Document* doc = mDocument->GetDisplayDocument()
1721 ? mDocument->GetDisplayDocument()
1722 : mDocument.get();
1723 const bool inProcess = !doc->GetBrowsingContext() ||
1724 doc->GetBrowsingContext()->Top()->IsInProcess();
1725 int32_t delay = inProcess
1726 ? StaticPrefs::nglayout_initialpaint_delay()
1727 : StaticPrefs::nglayout_initialpaint_delay_in_oopif();
1728 mPaintSuppressionTimer->InitWithNamedFuncCallback(
1729 [](nsITimer* aTimer, void* aPresShell) {
1730 RefPtr<PresShell> self = static_cast<PresShell*>(aPresShell);
1731 self->UnsuppressPainting();
1733 this, delay, nsITimer::TYPE_ONE_SHOT,
1734 "PresShell::sPaintSuppressionCallback");
1737 nsresult PresShell::Initialize() {
1738 if (mIsDestroying) {
1739 return NS_OK;
1742 if (!mDocument) {
1743 // Nothing to do
1744 return NS_OK;
1747 MOZ_LOG(gLog, LogLevel::Debug, ("PresShell::Initialize this=%p", this));
1749 NS_ASSERTION(!mDidInitialize, "Why are we being called?");
1751 RefPtr<PresShell> kungFuDeathGrip(this);
1753 RecomputeFontSizeInflationEnabled();
1754 MOZ_DIAGNOSTIC_ASSERT(!mIsDestroying);
1756 // Ensure the pres context doesn't think it has changed, since we haven't even
1757 // started layout. This avoids spurious restyles / reflows afterwards.
1759 // Note that this is very intentionally before setting mDidInitialize so it
1760 // doesn't notify the document, or run media query change events.
1761 mPresContext->FlushPendingMediaFeatureValuesChanged();
1762 MOZ_DIAGNOSTIC_ASSERT(!mIsDestroying);
1764 mDidInitialize = true;
1766 #ifdef DEBUG
1767 if (VerifyReflowFlags::NoisyCommands & gVerifyReflowFlags) {
1768 if (mDocument) {
1769 nsIURI* uri = mDocument->GetDocumentURI();
1770 if (uri) {
1771 printf("*** PresShell::Initialize (this=%p, url='%s')\n", (void*)this,
1772 uri->GetSpecOrDefault().get());
1776 #endif
1778 // Get the root frame from the frame manager
1779 // XXXbz it would be nice to move this somewhere else... like frame manager
1780 // Init(), say. But we need to make sure our views are all set up by the
1781 // time we do this!
1782 nsIFrame* rootFrame = mFrameConstructor->GetRootFrame();
1783 NS_ASSERTION(!rootFrame, "How did that happen, exactly?");
1785 if (!rootFrame) {
1786 nsAutoScriptBlocker scriptBlocker;
1787 rootFrame = mFrameConstructor->ConstructRootFrame();
1788 mFrameConstructor->SetRootFrame(rootFrame);
1791 NS_ENSURE_STATE(!mHaveShutDown);
1793 if (!rootFrame) {
1794 return NS_ERROR_OUT_OF_MEMORY;
1797 if (Element* root = mDocument->GetRootElement()) {
1799 nsAutoCauseReflowNotifier reflowNotifier(this);
1800 // Have the style sheet processor construct frame for the root
1801 // content object down
1802 mFrameConstructor->ContentInserted(
1803 root, nsCSSFrameConstructor::InsertionKind::Sync);
1805 // Something in mFrameConstructor->ContentInserted may have caused
1806 // Destroy() to get called, bug 337586. Or, nsAutoCauseReflowNotifier
1807 // (which sets up a script blocker) going out of scope may have killed us
1808 // too
1809 NS_ENSURE_STATE(!mHaveShutDown);
1812 if (mDocument->HasAutoFocusCandidates()) {
1813 mDocument->ScheduleFlushAutoFocusCandidates();
1816 NS_ASSERTION(rootFrame, "How did that happen?");
1818 // Note: when the frame was created above it had the NS_FRAME_IS_DIRTY bit
1819 // set, but XBL processing could have caused a reflow which clears it.
1820 if (MOZ_LIKELY(rootFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY))) {
1821 // Unset the DIRTY bits so that FrameNeedsReflow() will work right.
1822 rootFrame->RemoveStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN);
1823 NS_ASSERTION(!mDirtyRoots.Contains(rootFrame),
1824 "Why is the root in mDirtyRoots already?");
1825 FrameNeedsReflow(rootFrame, IntrinsicDirty::None, NS_FRAME_IS_DIRTY);
1826 NS_ASSERTION(mDirtyRoots.Contains(rootFrame),
1827 "Should be in mDirtyRoots now");
1828 NS_ASSERTION(mObservingLayoutFlushes, "Why no reflow scheduled?");
1831 // Restore our root scroll position now if we're getting here after EndLoad
1832 // got called, since this is our one chance to do it. Note that we need not
1833 // have reflowed for this to work; when the scrollframe is finally reflowed
1834 // it'll pick up the position we store in it here.
1835 if (!mDocumentLoading) {
1836 RestoreRootScrollPosition();
1839 // For printing, we just immediately unsuppress.
1840 if (!mPresContext->IsPaginated()) {
1841 // Kick off a one-shot timer based off our pref value. When this timer
1842 // fires, if painting is still locked down, then we will go ahead and
1843 // trigger a full invalidate and allow painting to proceed normally.
1844 mPaintingSuppressed = true;
1845 // Don't suppress painting if the document isn't loading.
1846 Document::ReadyState readyState = mDocument->GetReadyStateEnum();
1847 if (readyState != Document::READYSTATE_COMPLETE) {
1848 mPaintSuppressionTimer = NS_NewTimer();
1850 if (!mPaintSuppressionTimer) {
1851 mPaintingSuppressed = false;
1852 } else {
1853 // Initialize the timer.
1854 mPaintSuppressionTimer->SetTarget(GetMainThreadSerialEventTarget());
1855 InitPaintSuppressionTimer();
1856 if (mHasTriedFastUnsuppress) {
1857 // Someone tried to unsuppress painting before Initialize was called so
1858 // unsuppress painting rather soon.
1859 mHasTriedFastUnsuppress = false;
1860 TryUnsuppressPaintingSoon();
1861 MOZ_ASSERT(mHasTriedFastUnsuppress);
1866 // If we get here and painting is not suppressed, we still want to run the
1867 // unsuppression logic, so set mShouldUnsuppressPainting to true.
1868 if (!mPaintingSuppressed) {
1869 mShouldUnsuppressPainting = true;
1872 return NS_OK; // XXX this needs to be real. MMP
1875 void PresShell::TryUnsuppressPaintingSoon() {
1876 if (mHasTriedFastUnsuppress) {
1877 return;
1879 mHasTriedFastUnsuppress = true;
1881 if (!mDidInitialize || !IsPaintingSuppressed() || !XRE_IsContentProcess()) {
1882 return;
1885 if (!mDocument->IsInitialDocument() &&
1886 mDocument->DidHitCompleteSheetCache() &&
1887 mPresContext->IsRootContentDocumentCrossProcess()) {
1888 // Try to unsuppress faster on a top level page if it uses stylesheet
1889 // cache, since that hints that many resources can be painted sooner than
1890 // in a cold page load case.
1891 NS_DispatchToCurrentThreadQueue(
1892 NS_NewRunnableFunction("PresShell::TryUnsuppressPaintingSoon",
1893 [self = RefPtr{this}]() -> void {
1894 if (self->IsPaintingSuppressed()) {
1895 PROFILER_MARKER_UNTYPED(
1896 "Fast paint unsuppression", GRAPHICS);
1897 self->UnsuppressPainting();
1900 EventQueuePriority::Control);
1904 void PresShell::RefreshZoomConstraintsForScreenSizeChange() {
1905 if (mZoomConstraintsClient) {
1906 mZoomConstraintsClient->ScreenSizeChanged();
1910 void PresShell::ForceResizeReflowWithCurrentDimensions() {
1911 nscoord currentWidth = 0;
1912 nscoord currentHeight = 0;
1913 mViewManager->GetWindowDimensions(&currentWidth, &currentHeight);
1914 ResizeReflow(currentWidth, currentHeight);
1917 void PresShell::ResizeReflow(nscoord aWidth, nscoord aHeight,
1918 ResizeReflowOptions aOptions) {
1919 if (mZoomConstraintsClient) {
1920 // If we have a ZoomConstraintsClient and the available screen area
1921 // changed, then we might need to disable double-tap-to-zoom, so notify
1922 // the ZCC to update itself.
1923 mZoomConstraintsClient->ScreenSizeChanged();
1925 if (UsesMobileViewportSizing()) {
1926 // If we are using mobile viewport sizing, request a reflow from the MVM.
1927 // It can recompute the final CSS viewport and trigger a call to
1928 // ResizeReflowIgnoreOverride if it changed. We don't force adjusting
1929 // of resolution, because that is only necessary when we are destroying
1930 // the MVM.
1931 MOZ_ASSERT(mMobileViewportManager);
1932 mMobileViewportManager->RequestReflow(false);
1933 return;
1935 ResizeReflowIgnoreOverride(aWidth, aHeight, aOptions);
1938 bool PresShell::SimpleResizeReflow(nscoord aWidth, nscoord aHeight) {
1939 MOZ_ASSERT(aWidth != NS_UNCONSTRAINEDSIZE);
1940 MOZ_ASSERT(aHeight != NS_UNCONSTRAINEDSIZE);
1941 nsSize oldSize = mPresContext->GetVisibleArea().Size();
1942 mPresContext->SetVisibleArea(nsRect(0, 0, aWidth, aHeight));
1943 nsIFrame* rootFrame = GetRootFrame();
1944 if (!rootFrame) {
1945 return false;
1947 WritingMode wm = rootFrame->GetWritingMode();
1948 bool isBSizeChanging =
1949 wm.IsVertical() ? oldSize.width != aWidth : oldSize.height != aHeight;
1950 if (isBSizeChanging) {
1951 nsLayoutUtils::MarkIntrinsicISizesDirtyIfDependentOnBSize(rootFrame);
1953 FrameNeedsReflow(rootFrame, IntrinsicDirty::None,
1954 NS_FRAME_HAS_DIRTY_CHILDREN);
1956 if (mMobileViewportManager) {
1957 mMobileViewportManager->UpdateSizesBeforeReflow();
1959 return true;
1962 bool PresShell::CanHandleUserInputEvents(WidgetGUIEvent* aGUIEvent) {
1963 if (XRE_IsParentProcess()) {
1964 return true;
1967 if (aGUIEvent->mFlags.mIsSynthesizedForTests &&
1968 !StaticPrefs::dom_input_events_security_isUserInputHandlingDelayTest()) {
1969 return true;
1972 if (!aGUIEvent->IsUserAction()) {
1973 return true;
1976 if (nsPresContext* rootPresContext = mPresContext->GetRootPresContext()) {
1977 return rootPresContext->UserInputEventsAllowed();
1980 return true;
1983 void PresShell::AddResizeEventFlushObserverIfNeeded() {
1984 if (!mIsDestroying && !mResizeEventPending &&
1985 MOZ_LIKELY(!mDocument->GetBFCacheEntry())) {
1986 mResizeEventPending = true;
1987 mPresContext->RefreshDriver()->AddResizeEventFlushObserver(this);
1991 bool PresShell::ResizeReflowIgnoreOverride(nscoord aWidth, nscoord aHeight,
1992 ResizeReflowOptions aOptions) {
1993 MOZ_ASSERT(!mIsReflowing, "Shouldn't be in reflow here!");
1995 // Historically we never fired resize events if there was no root frame by the
1996 // time this function got called.
1997 const bool initialized = mDidInitialize;
1998 RefPtr<PresShell> kungFuDeathGrip(this);
2000 auto postResizeEventIfNeeded = [this, initialized]() {
2001 if (initialized) {
2002 AddResizeEventFlushObserverIfNeeded();
2006 if (!(aOptions & ResizeReflowOptions::BSizeLimit)) {
2007 nsSize oldSize = mPresContext->GetVisibleArea().Size();
2008 if (oldSize == nsSize(aWidth, aHeight)) {
2009 return false;
2012 bool changed = SimpleResizeReflow(aWidth, aHeight);
2013 postResizeEventIfNeeded();
2014 return changed;
2017 // Make sure that style is flushed before setting the pres context
2018 // VisibleArea.
2020 // Otherwise we may end up with bogus viewport units resolved against the
2021 // unconstrained bsize, or restyling the whole document resolving viewport
2022 // units against targetWidth, which may end up doing wasteful work.
2023 mDocument->FlushPendingNotifications(FlushType::Frames);
2025 nsIFrame* rootFrame = GetRootFrame();
2026 if (mIsDestroying || !rootFrame) {
2027 // If we don't have a root frame yet, that means we haven't had our initial
2028 // reflow... If that's the case, and aWidth or aHeight is unconstrained,
2029 // ignore them altogether.
2030 if (aHeight == NS_UNCONSTRAINEDSIZE || aWidth == NS_UNCONSTRAINEDSIZE) {
2031 // We can't do the work needed for SizeToContent without a root
2032 // frame, and we want to return before setting the visible area.
2033 return false;
2036 mPresContext->SetVisibleArea(nsRect(0, 0, aWidth, aHeight));
2037 // There isn't anything useful we can do if the initial reflow hasn't
2038 // happened.
2039 return true;
2042 WritingMode wm = rootFrame->GetWritingMode();
2043 MOZ_ASSERT((wm.IsVertical() ? aHeight : aWidth) != NS_UNCONSTRAINEDSIZE,
2044 "unconstrained isize not allowed");
2046 nscoord targetWidth = aWidth;
2047 nscoord targetHeight = aHeight;
2048 if (wm.IsVertical()) {
2049 targetWidth = NS_UNCONSTRAINEDSIZE;
2050 } else {
2051 targetHeight = NS_UNCONSTRAINEDSIZE;
2054 mPresContext->SetVisibleArea(nsRect(0, 0, targetWidth, targetHeight));
2055 // XXX Do a full invalidate at the beginning so that invalidates along
2056 // the way don't have region accumulation issues?
2058 // For height:auto BSizes (i.e. layout-controlled), descendant
2059 // intrinsic sizes can't depend on them. So the only other case is
2060 // viewport-controlled BSizes which we handle here.
2061 nsLayoutUtils::MarkIntrinsicISizesDirtyIfDependentOnBSize(rootFrame);
2064 nsAutoCauseReflowNotifier crNotifier(this);
2065 WillDoReflow();
2067 // Kick off a top-down reflow
2068 AUTO_LAYOUT_PHASE_ENTRY_POINT(GetPresContext(), Reflow);
2069 nsViewManager::AutoDisableRefresh refreshBlocker(mViewManager);
2071 mDirtyRoots.Remove(rootFrame);
2072 DoReflow(rootFrame, true, nullptr);
2074 const bool reflowAgain =
2075 wm.IsVertical() ? mPresContext->GetVisibleArea().width > aWidth
2076 : mPresContext->GetVisibleArea().height > aHeight;
2078 if (reflowAgain) {
2079 mPresContext->SetVisibleArea(nsRect(0, 0, aWidth, aHeight));
2080 DoReflow(rootFrame, true, nullptr);
2084 // Now, we may have been destroyed by the destructor of
2085 // `nsAutoCauseReflowNotifier`.
2087 mPendingDidDoReflow = true;
2088 DidDoReflow(true);
2090 // the reflow above should've set our bsize if it was NS_UNCONSTRAINEDSIZE,
2091 // and the isize shouldn't be NS_UNCONSTRAINEDSIZE anyway.
2092 MOZ_DIAGNOSTIC_ASSERT(
2093 mPresContext->GetVisibleArea().width != NS_UNCONSTRAINEDSIZE,
2094 "width should not be NS_UNCONSTRAINEDSIZE after reflow");
2095 MOZ_DIAGNOSTIC_ASSERT(
2096 mPresContext->GetVisibleArea().height != NS_UNCONSTRAINEDSIZE,
2097 "height should not be NS_UNCONSTRAINEDSIZE after reflow");
2099 postResizeEventIfNeeded();
2100 return true;
2103 void PresShell::FireResizeEvent() {
2104 if (mIsDocumentGone) {
2105 return;
2108 // If event handling is suppressed, repost the resize event to the refresh
2109 // driver. The event is marked as delayed so that the refresh driver does not
2110 // continue ticking.
2111 if (mDocument->EventHandlingSuppressed()) {
2112 if (MOZ_LIKELY(!mDocument->GetBFCacheEntry())) {
2113 mDocument->SetHasDelayedRefreshEvent();
2114 mPresContext->RefreshDriver()->AddResizeEventFlushObserver(
2115 this, /* aDelayed = */ true);
2117 return;
2120 mResizeEventPending = false;
2121 FireResizeEventSync();
2124 void PresShell::FireResizeEventSync() {
2125 if (mIsDocumentGone) {
2126 return;
2129 // Send resize event from here.
2130 WidgetEvent event(true, mozilla::eResize);
2131 nsEventStatus status = nsEventStatus_eIgnore;
2133 if (RefPtr<nsPIDOMWindowOuter> window = mDocument->GetWindow()) {
2134 // MOZ_KnownLive due to bug 1506441
2135 EventDispatcher::Dispatch(MOZ_KnownLive(nsGlobalWindowOuter::Cast(window)),
2136 mPresContext, &event, nullptr, &status);
2140 static nsIContent* GetNativeAnonymousSubtreeRoot(nsIContent* aContent) {
2141 if (!aContent) {
2142 return nullptr;
2144 return aContent->GetClosestNativeAnonymousSubtreeRoot();
2147 void PresShell::NativeAnonymousContentRemoved(nsIContent* aAnonContent) {
2148 MOZ_ASSERT(aAnonContent->IsRootOfNativeAnonymousSubtree());
2149 mPresContext->EventStateManager()->NativeAnonymousContentRemoved(
2150 aAnonContent);
2151 #ifdef ACCESSIBILITY
2152 if (nsAccessibilityService* accService = GetAccService()) {
2153 accService->ContentRemoved(this, aAnonContent);
2155 #endif
2156 if (mDocument->DevToolsAnonymousAndShadowEventsEnabled()) {
2157 aAnonContent->QueueDevtoolsAnonymousEvent(/* aIsRemove = */ true);
2159 if (nsIContent* root = GetNativeAnonymousSubtreeRoot(mCurrentEventContent)) {
2160 if (aAnonContent == root) {
2161 mCurrentEventContent = aAnonContent->GetFlattenedTreeParent();
2162 mCurrentEventFrame = nullptr;
2166 for (unsigned int i = 0; i < mCurrentEventContentStack.Length(); i++) {
2167 nsIContent* anon =
2168 GetNativeAnonymousSubtreeRoot(mCurrentEventContentStack.ElementAt(i));
2169 if (aAnonContent == anon) {
2170 mCurrentEventContentStack.ReplaceObjectAt(
2171 aAnonContent->GetFlattenedTreeParent(), i);
2172 mCurrentEventFrameStack[i] = nullptr;
2177 void PresShell::SetIgnoreFrameDestruction(bool aIgnore) {
2178 if (mDocument) {
2179 // We need to tell the ImageLoader to drop all its references to frames
2180 // because they're about to go away and it won't get notifications of that.
2181 mDocument->StyleImageLoader()->ClearFrames(mPresContext);
2183 mIgnoreFrameDestruction = aIgnore;
2186 void PresShell::NotifyDestroyingFrame(nsIFrame* aFrame) {
2187 // We must remove these from FrameLayerBuilder::DisplayItemData::mFrameList
2188 // here, otherwise the DisplayItemData destructor will use the destroyed frame
2189 // when it tries to remove it from the (array) value of this property.
2190 aFrame->RemoveDisplayItemDataForDeletion();
2192 if (!mIgnoreFrameDestruction) {
2193 if (aFrame->HasImageRequest()) {
2194 mDocument->StyleImageLoader()->DropRequestsForFrame(aFrame);
2197 mFrameConstructor->NotifyDestroyingFrame(aFrame);
2199 mDirtyRoots.Remove(aFrame);
2201 // Remove frame properties
2202 aFrame->RemoveAllProperties();
2204 if (aFrame == mCurrentEventFrame) {
2205 mCurrentEventContent = aFrame->GetContent();
2206 mCurrentEventFrame = nullptr;
2209 for (unsigned int i = 0; i < mCurrentEventFrameStack.Length(); i++) {
2210 if (aFrame == mCurrentEventFrameStack.ElementAt(i)) {
2211 // One of our stack frames was deleted. Get its content so that when we
2212 // pop it we can still get its new frame from its content
2213 nsIContent* currentEventContent = aFrame->GetContent();
2214 mCurrentEventContentStack.ReplaceObjectAt(currentEventContent, i);
2215 mCurrentEventFrameStack[i] = nullptr;
2219 mFramesToDirty.Remove(aFrame);
2221 nsIScrollableFrame* scrollableFrame = do_QueryFrame(aFrame);
2222 if (scrollableFrame) {
2223 mPendingScrollAnchorSelection.Remove(scrollableFrame);
2224 mPendingScrollAnchorAdjustment.Remove(scrollableFrame);
2225 mPendingScrollResnap.Remove(scrollableFrame);
2230 already_AddRefed<nsCaret> PresShell::GetCaret() const {
2231 RefPtr<nsCaret> caret = mCaret;
2232 return caret.forget();
2235 already_AddRefed<AccessibleCaretEventHub>
2236 PresShell::GetAccessibleCaretEventHub() const {
2237 RefPtr<AccessibleCaretEventHub> eventHub = mAccessibleCaretEventHub;
2238 return eventHub.forget();
2241 void PresShell::SetCaret(nsCaret* aNewCaret) { mCaret = aNewCaret; }
2243 void PresShell::RestoreCaret() { mCaret = mOriginalCaret; }
2245 NS_IMETHODIMP PresShell::SetCaretEnabled(bool aInEnable) {
2246 bool oldEnabled = mCaretEnabled;
2248 mCaretEnabled = aInEnable;
2250 if (mCaretEnabled != oldEnabled) {
2251 MOZ_ASSERT(mCaret);
2252 if (mCaret) {
2253 mCaret->SetVisible(mCaretEnabled);
2257 return NS_OK;
2260 NS_IMETHODIMP PresShell::SetCaretReadOnly(bool aReadOnly) {
2261 if (mCaret) mCaret->SetCaretReadOnly(aReadOnly);
2262 return NS_OK;
2265 NS_IMETHODIMP PresShell::GetCaretEnabled(bool* aOutEnabled) {
2266 NS_ENSURE_ARG_POINTER(aOutEnabled);
2267 *aOutEnabled = mCaretEnabled;
2268 return NS_OK;
2271 NS_IMETHODIMP PresShell::SetCaretVisibilityDuringSelection(bool aVisibility) {
2272 if (mCaret) mCaret->SetVisibilityDuringSelection(aVisibility);
2273 return NS_OK;
2276 NS_IMETHODIMP PresShell::GetCaretVisible(bool* aOutIsVisible) {
2277 *aOutIsVisible = false;
2278 if (mCaret) {
2279 *aOutIsVisible = mCaret->IsVisible();
2281 return NS_OK;
2284 NS_IMETHODIMP PresShell::SetSelectionFlags(int16_t aFlags) {
2285 mSelectionFlags = aFlags;
2286 return NS_OK;
2289 NS_IMETHODIMP PresShell::GetSelectionFlags(int16_t* aFlags) {
2290 if (!aFlags) {
2291 return NS_ERROR_INVALID_ARG;
2294 *aFlags = mSelectionFlags;
2295 return NS_OK;
2298 // implementation of nsISelectionController
2300 NS_IMETHODIMP
2301 PresShell::PhysicalMove(int16_t aDirection, int16_t aAmount, bool aExtend) {
2302 RefPtr<nsFrameSelection> frameSelection = mSelection;
2303 return frameSelection->PhysicalMove(aDirection, aAmount, aExtend);
2306 NS_IMETHODIMP
2307 PresShell::CharacterMove(bool aForward, bool aExtend) {
2308 RefPtr<nsFrameSelection> frameSelection = mSelection;
2309 return frameSelection->CharacterMove(aForward, aExtend);
2312 NS_IMETHODIMP
2313 PresShell::WordMove(bool aForward, bool aExtend) {
2314 RefPtr<nsFrameSelection> frameSelection = mSelection;
2315 nsresult result = frameSelection->WordMove(aForward, aExtend);
2316 // if we can't go down/up any more we must then move caret completely to
2317 // end/beginning respectively.
2318 if (NS_FAILED(result)) result = CompleteMove(aForward, aExtend);
2319 return result;
2322 NS_IMETHODIMP
2323 PresShell::LineMove(bool aForward, bool aExtend) {
2324 RefPtr<nsFrameSelection> frameSelection = mSelection;
2325 nsresult result = frameSelection->LineMove(aForward, aExtend);
2326 // if we can't go down/up any more we must then move caret completely to
2327 // end/beginning respectively.
2328 if (NS_FAILED(result)) result = CompleteMove(aForward, aExtend);
2329 return result;
2332 NS_IMETHODIMP
2333 PresShell::IntraLineMove(bool aForward, bool aExtend) {
2334 RefPtr<nsFrameSelection> frameSelection = mSelection;
2335 return frameSelection->IntraLineMove(aForward, aExtend);
2338 NS_IMETHODIMP
2339 PresShell::PageMove(bool aForward, bool aExtend) {
2340 nsIFrame* frame = nullptr;
2341 if (!aExtend) {
2342 frame = do_QueryFrame(GetScrollableFrameToScroll(VerticalScrollDirection));
2343 // If there is no scrollable frame, get the frame to move caret instead.
2345 if (!frame || frame->PresContext() != mPresContext) {
2346 frame = mSelection->GetFrameToPageSelect();
2347 if (!frame) {
2348 return NS_OK;
2351 // We may scroll parent scrollable element of current selection limiter.
2352 // In such case, we don't want to scroll selection into view unless
2353 // selection is changed.
2354 RefPtr<nsFrameSelection> frameSelection = mSelection;
2355 return frameSelection->PageMove(
2356 aForward, aExtend, frame, nsFrameSelection::SelectionIntoView::IfChanged);
2359 NS_IMETHODIMP
2360 PresShell::ScrollPage(bool aForward) {
2361 nsIScrollableFrame* scrollFrame =
2362 GetScrollableFrameToScroll(VerticalScrollDirection);
2363 ScrollMode scrollMode = apz::GetScrollModeForOrigin(ScrollOrigin::Pages);
2364 if (scrollFrame) {
2365 scrollFrame->ScrollBy(nsIntPoint(0, aForward ? 1 : -1), ScrollUnit::PAGES,
2366 scrollMode, nullptr,
2367 mozilla::ScrollOrigin::NotSpecified,
2368 nsIScrollableFrame::NOT_MOMENTUM,
2369 ScrollSnapFlags::IntendedDirection |
2370 ScrollSnapFlags::IntendedEndPosition);
2372 return NS_OK;
2375 NS_IMETHODIMP
2376 PresShell::ScrollLine(bool aForward) {
2377 nsIScrollableFrame* scrollFrame =
2378 GetScrollableFrameToScroll(VerticalScrollDirection);
2379 ScrollMode scrollMode = apz::GetScrollModeForOrigin(ScrollOrigin::Lines);
2380 if (scrollFrame) {
2381 nsRect scrollPort = scrollFrame->GetScrollPortRect();
2382 nsSize lineSize = scrollFrame->GetLineScrollAmount();
2383 int32_t lineCount = StaticPrefs::toolkit_scrollbox_verticalScrollDistance();
2384 if (lineCount * lineSize.height > scrollPort.Height()) {
2385 return ScrollPage(aForward);
2387 scrollFrame->ScrollBy(
2388 nsIntPoint(0, aForward ? lineCount : -lineCount), ScrollUnit::LINES,
2389 scrollMode, nullptr, mozilla::ScrollOrigin::NotSpecified,
2390 nsIScrollableFrame::NOT_MOMENTUM, ScrollSnapFlags::IntendedDirection);
2392 return NS_OK;
2395 NS_IMETHODIMP
2396 PresShell::ScrollCharacter(bool aRight) {
2397 nsIScrollableFrame* scrollFrame =
2398 GetScrollableFrameToScroll(HorizontalScrollDirection);
2399 ScrollMode scrollMode = apz::GetScrollModeForOrigin(ScrollOrigin::Lines);
2400 if (scrollFrame) {
2401 int32_t h = StaticPrefs::toolkit_scrollbox_horizontalScrollDistance();
2402 scrollFrame->ScrollBy(
2403 nsIntPoint(aRight ? h : -h, 0), ScrollUnit::LINES, scrollMode, nullptr,
2404 mozilla::ScrollOrigin::NotSpecified, nsIScrollableFrame::NOT_MOMENTUM,
2405 ScrollSnapFlags::IntendedDirection);
2407 return NS_OK;
2410 NS_IMETHODIMP
2411 PresShell::CompleteScroll(bool aForward) {
2412 nsIScrollableFrame* scrollFrame =
2413 GetScrollableFrameToScroll(VerticalScrollDirection);
2414 ScrollMode scrollMode = apz::GetScrollModeForOrigin(ScrollOrigin::Other);
2415 if (scrollFrame) {
2416 scrollFrame->ScrollBy(
2417 nsIntPoint(0, aForward ? 1 : -1), ScrollUnit::WHOLE, scrollMode,
2418 nullptr, mozilla::ScrollOrigin::NotSpecified,
2419 nsIScrollableFrame::NOT_MOMENTUM, ScrollSnapFlags::IntendedEndPosition);
2421 return NS_OK;
2424 NS_IMETHODIMP
2425 PresShell::CompleteMove(bool aForward, bool aExtend) {
2426 // Beware! This may flush notifications via synchronous
2427 // ScrollSelectionIntoView.
2428 RefPtr<nsFrameSelection> frameSelection = mSelection;
2429 nsIContent* limiter = frameSelection->GetAncestorLimiter();
2430 nsIFrame* frame = limiter ? limiter->GetPrimaryFrame()
2431 : FrameConstructor()->GetRootElementFrame();
2432 if (!frame) return NS_ERROR_FAILURE;
2433 nsIFrame::CaretPosition pos = frame->GetExtremeCaretPosition(!aForward);
2435 const nsFrameSelection::FocusMode focusMode =
2436 aExtend ? nsFrameSelection::FocusMode::kExtendSelection
2437 : nsFrameSelection::FocusMode::kCollapseToNewPoint;
2438 frameSelection->HandleClick(
2439 MOZ_KnownLive(pos.mResultContent) /* bug 1636889 */, pos.mContentOffset,
2440 pos.mContentOffset, focusMode,
2441 aForward ? CaretAssociationHint::After : CaretAssociationHint::Before);
2442 if (limiter) {
2443 // HandleClick resets ancestorLimiter, so set it again.
2444 frameSelection->SetAncestorLimiter(limiter);
2447 // After ScrollSelectionIntoView(), the pending notifications might be
2448 // flushed and PresShell/PresContext/Frames may be dead. See bug 418470.
2449 return ScrollSelectionIntoView(
2450 nsISelectionController::SELECTION_NORMAL,
2451 nsISelectionController::SELECTION_FOCUS_REGION,
2452 nsISelectionController::SCROLL_SYNCHRONOUS |
2453 nsISelectionController::SCROLL_FOR_CARET_MOVE);
2456 // end implementations nsISelectionController
2458 nsIFrame* PresShell::GetRootScrollFrame() const {
2459 if (!mFrameConstructor) {
2460 return nullptr;
2462 nsIFrame* rootFrame = mFrameConstructor->GetRootFrame();
2463 // Ensure root frame is a viewport frame
2464 if (!rootFrame || !rootFrame->IsViewportFrame()) {
2465 return nullptr;
2467 nsIFrame* theFrame = rootFrame->PrincipalChildList().FirstChild();
2468 if (!theFrame || !theFrame->IsScrollFrame()) {
2469 return nullptr;
2471 return theFrame;
2474 nsIScrollableFrame* PresShell::GetRootScrollFrameAsScrollable() const {
2475 nsIFrame* frame = GetRootScrollFrame();
2476 if (!frame) {
2477 return nullptr;
2479 nsIScrollableFrame* scrollableFrame = do_QueryFrame(frame);
2480 NS_ASSERTION(scrollableFrame,
2481 "All scroll frames must implement nsIScrollableFrame");
2482 return scrollableFrame;
2485 nsPageSequenceFrame* PresShell::GetPageSequenceFrame() const {
2486 return mFrameConstructor->GetPageSequenceFrame();
2489 nsCanvasFrame* PresShell::GetCanvasFrame() const {
2490 return mFrameConstructor->GetCanvasFrame();
2493 void PresShell::RestoreRootScrollPosition() {
2494 nsIScrollableFrame* scrollableFrame = GetRootScrollFrameAsScrollable();
2495 if (scrollableFrame) {
2496 scrollableFrame->ScrollToRestoredPosition();
2500 void PresShell::MaybeReleaseCapturingContent() {
2501 RefPtr<nsFrameSelection> frameSelection = FrameSelection();
2502 if (frameSelection) {
2503 frameSelection->SetDragState(false);
2505 if (sCapturingContentInfo.mContent &&
2506 sCapturingContentInfo.mContent->OwnerDoc() == mDocument) {
2507 PresShell::ReleaseCapturingContent();
2511 void PresShell::BeginLoad(Document* aDocument) {
2512 mDocumentLoading = true;
2514 gfxTextPerfMetrics* tp = nullptr;
2515 if (mPresContext) {
2516 tp = mPresContext->GetTextPerfMetrics();
2519 bool shouldLog = MOZ_LOG_TEST(gLog, LogLevel::Debug);
2520 if (shouldLog || tp) {
2521 mLoadBegin = TimeStamp::Now();
2524 if (shouldLog) {
2525 nsIURI* uri = mDocument->GetDocumentURI();
2526 MOZ_LOG(gLog, LogLevel::Debug,
2527 ("(presshell) %p load begin [%s]\n", this,
2528 uri ? uri->GetSpecOrDefault().get() : ""));
2532 void PresShell::EndLoad(Document* aDocument) {
2533 MOZ_ASSERT(aDocument == mDocument, "Wrong document");
2535 RestoreRootScrollPosition();
2537 mDocumentLoading = false;
2540 bool PresShell::IsLayoutFlushObserver() {
2541 return GetPresContext()->RefreshDriver()->IsLayoutFlushObserver(this);
2544 void PresShell::LoadComplete() {
2545 gfxTextPerfMetrics* tp = nullptr;
2546 if (mPresContext) {
2547 tp = mPresContext->GetTextPerfMetrics();
2550 // log load
2551 bool shouldLog = MOZ_LOG_TEST(gLog, LogLevel::Debug);
2552 if (shouldLog || tp) {
2553 TimeDuration loadTime = TimeStamp::Now() - mLoadBegin;
2554 nsIURI* uri = mDocument->GetDocumentURI();
2555 nsAutoCString spec;
2556 if (uri) {
2557 spec = uri->GetSpecOrDefault();
2559 if (shouldLog) {
2560 MOZ_LOG(gLog, LogLevel::Debug,
2561 ("(presshell) %p load done time-ms: %9.2f [%s]\n", this,
2562 loadTime.ToMilliseconds(), spec.get()));
2564 if (tp) {
2565 tp->Accumulate();
2566 if (tp->cumulative.numChars > 0) {
2567 LogTextPerfStats(tp, this, tp->cumulative, loadTime.ToMilliseconds(),
2568 eLog_loaddone, spec.get());
2574 #ifdef DEBUG
2575 void PresShell::VerifyHasDirtyRootAncestor(nsIFrame* aFrame) {
2576 // XXXbz due to bug 372769, can't actually assert anything here...
2577 // XXX Since bug 372769 is now fixed, the assertion is being enabled in bug
2578 // 1758104.
2579 # if 0
2580 // XXXbz shouldn't need this part; remove it once FrameNeedsReflow
2581 // handles the root frame correctly.
2582 if (!aFrame->GetParent()) {
2583 return;
2586 // Make sure that there is a reflow root ancestor of |aFrame| that's
2587 // in mDirtyRoots already.
2588 while (aFrame && aFrame->HasAnyStateBits(NS_FRAME_HAS_DIRTY_CHILDREN)) {
2589 if ((aFrame->HasAnyStateBits(NS_FRAME_REFLOW_ROOT |
2590 NS_FRAME_DYNAMIC_REFLOW_ROOT) ||
2591 !aFrame->GetParent()) &&
2592 mDirtyRoots.Contains(aFrame)) {
2593 return;
2596 aFrame = aFrame->GetParent();
2599 MOZ_ASSERT_UNREACHABLE(
2600 "Frame has dirty bits set but isn't scheduled to be "
2601 "reflowed?");
2602 # endif
2604 #endif
2606 void PresShell::PostPendingScrollAnchorSelection(
2607 mozilla::layout::ScrollAnchorContainer* aContainer) {
2608 mPendingScrollAnchorSelection.Insert(aContainer->ScrollableFrame());
2611 void PresShell::FlushPendingScrollAnchorSelections() {
2612 for (nsIScrollableFrame* scroll : mPendingScrollAnchorSelection) {
2613 scroll->Anchor()->SelectAnchor();
2615 mPendingScrollAnchorSelection.Clear();
2618 void PresShell::PostPendingScrollAnchorAdjustment(
2619 ScrollAnchorContainer* aContainer) {
2620 mPendingScrollAnchorAdjustment.Insert(aContainer->ScrollableFrame());
2623 void PresShell::FlushPendingScrollAnchorAdjustments() {
2624 for (nsIScrollableFrame* scroll : mPendingScrollAnchorAdjustment) {
2625 scroll->Anchor()->ApplyAdjustments();
2627 mPendingScrollAnchorAdjustment.Clear();
2630 void PresShell::PostPendingScrollResnap(nsIScrollableFrame* aScrollableFrame) {
2631 mPendingScrollResnap.Insert(aScrollableFrame);
2634 void PresShell::FlushPendingScrollResnap() {
2635 for (nsIScrollableFrame* scrollableFrame : mPendingScrollResnap) {
2636 scrollableFrame->TryResnap();
2638 mPendingScrollResnap.Clear();
2641 void PresShell::FrameNeedsReflow(nsIFrame* aFrame,
2642 IntrinsicDirty aIntrinsicDirty,
2643 nsFrameState aBitToAdd,
2644 ReflowRootHandling aRootHandling) {
2645 MOZ_ASSERT(aBitToAdd == NS_FRAME_IS_DIRTY ||
2646 aBitToAdd == NS_FRAME_HAS_DIRTY_CHILDREN || !aBitToAdd,
2647 "Unexpected bits being added");
2649 // FIXME bug 478135
2650 NS_ASSERTION(
2651 aIntrinsicDirty != IntrinsicDirty::FrameAncestorsAndDescendants ||
2652 aBitToAdd != NS_FRAME_HAS_DIRTY_CHILDREN,
2653 "bits don't correspond to style change reason");
2655 // FIXME bug 457400
2656 NS_ASSERTION(!mIsReflowing, "can't mark frame dirty during reflow");
2658 // If we've not yet done the initial reflow, then don't bother
2659 // enqueuing a reflow command yet.
2660 if (!mDidInitialize) return;
2662 // If we're already destroying, don't bother with this either.
2663 if (mIsDestroying) return;
2665 #ifdef DEBUG
2666 // printf("gShellCounter: %d\n", gShellCounter++);
2667 if (mInVerifyReflow) return;
2669 if (VerifyReflowFlags::NoisyCommands & gVerifyReflowFlags) {
2670 printf("\nPresShell@%p: frame %p needs reflow\n", (void*)this,
2671 (void*)aFrame);
2672 if (VerifyReflowFlags::ReallyNoisyCommands & gVerifyReflowFlags) {
2673 printf("Current content model:\n");
2674 Element* rootElement = mDocument->GetRootElement();
2675 if (rootElement) {
2676 rootElement->List(stdout, 0);
2680 #endif
2682 AutoTArray<nsIFrame*, 4> subtrees;
2683 subtrees.AppendElement(aFrame);
2685 do {
2686 nsIFrame* subtreeRoot = subtrees.PopLastElement();
2688 // Grab |wasDirty| now so we can go ahead and update the bits on
2689 // subtreeRoot.
2690 bool wasDirty = subtreeRoot->IsSubtreeDirty();
2691 subtreeRoot->AddStateBits(aBitToAdd);
2693 // Determine whether we need to keep looking for the next ancestor
2694 // reflow root if subtreeRoot itself is a reflow root.
2695 bool targetNeedsReflowFromParent;
2696 switch (aRootHandling) {
2697 case ReflowRootHandling::PositionOrSizeChange:
2698 targetNeedsReflowFromParent = true;
2699 break;
2700 case ReflowRootHandling::NoPositionOrSizeChange:
2701 targetNeedsReflowFromParent = false;
2702 break;
2703 case ReflowRootHandling::InferFromBitToAdd:
2704 targetNeedsReflowFromParent = (aBitToAdd == NS_FRAME_IS_DIRTY);
2705 break;
2708 auto FrameIsReflowRoot = [](const nsIFrame* aFrame) {
2709 return aFrame->HasAnyStateBits(NS_FRAME_REFLOW_ROOT |
2710 NS_FRAME_DYNAMIC_REFLOW_ROOT);
2713 auto CanStopClearingAncestorIntrinsics = [&](const nsIFrame* aFrame) {
2714 return FrameIsReflowRoot(aFrame) && aFrame != subtreeRoot;
2717 auto IsReflowBoundary = [&](const nsIFrame* aFrame) {
2718 return FrameIsReflowRoot(aFrame) &&
2719 (aFrame != subtreeRoot || !targetNeedsReflowFromParent);
2722 // Mark the intrinsic widths as dirty on the frame, all of its ancestors,
2723 // and all of its descendants, if needed:
2725 if (aIntrinsicDirty != IntrinsicDirty::None) {
2726 // Mark argument and all ancestors dirty. (Unless we hit a reflow root
2727 // that should contain the reflow.
2728 for (nsIFrame* a = subtreeRoot;
2729 a && !CanStopClearingAncestorIntrinsics(a); a = a->GetParent()) {
2730 a->MarkIntrinsicISizesDirty();
2731 if (a->IsAbsolutelyPositioned()) {
2732 // If we get here, 'a' is abspos, so its subtree's intrinsic sizing
2733 // has no effect on its ancestors' intrinsic sizing. So, don't loop
2734 // upwards any further.
2735 break;
2740 const bool frameAncestorAndDescendantISizesDirty =
2741 (aIntrinsicDirty == IntrinsicDirty::FrameAncestorsAndDescendants);
2742 const bool dirty = (aBitToAdd == NS_FRAME_IS_DIRTY);
2743 if (frameAncestorAndDescendantISizesDirty || dirty) {
2744 // Mark all descendants dirty (using an nsTArray stack rather than
2745 // recursion).
2746 // Note that ReflowInput::InitResizeFlags has some similar
2747 // code; see comments there for how and why it differs.
2748 AutoTArray<nsIFrame*, 32> stack;
2749 stack.AppendElement(subtreeRoot);
2751 do {
2752 nsIFrame* f = stack.PopLastElement();
2754 if (frameAncestorAndDescendantISizesDirty && f->IsPlaceholderFrame()) {
2755 // Call `GetOutOfFlowFrame` directly because we can get here from
2756 // frame destruction and the placeholder might be already torn down.
2757 if (nsIFrame* oof =
2758 static_cast<nsPlaceholderFrame*>(f)->GetOutOfFlowFrame()) {
2759 if (!nsLayoutUtils::IsProperAncestorFrame(subtreeRoot, oof)) {
2760 // We have another distinct subtree we need to mark.
2761 subtrees.AppendElement(oof);
2766 for (const auto& childList : f->ChildLists()) {
2767 for (nsIFrame* kid : childList.mList) {
2768 if (frameAncestorAndDescendantISizesDirty) {
2769 kid->MarkIntrinsicISizesDirty();
2771 if (dirty) {
2772 kid->AddStateBits(NS_FRAME_IS_DIRTY);
2774 stack.AppendElement(kid);
2777 } while (stack.Length() != 0);
2780 // Skip setting dirty bits up the tree if we weren't given a bit to add.
2781 if (!aBitToAdd) {
2782 continue;
2785 // Set NS_FRAME_HAS_DIRTY_CHILDREN bits (via nsIFrame::ChildIsDirty)
2786 // up the tree until we reach either a frame that's already dirty or
2787 // a reflow root.
2788 nsIFrame* f = subtreeRoot;
2789 for (;;) {
2790 if (IsReflowBoundary(f) || !f->GetParent()) {
2791 // we've hit a reflow root or the root frame
2792 if (!wasDirty) {
2793 mDirtyRoots.Add(f);
2794 SetNeedLayoutFlush();
2796 #ifdef DEBUG
2797 else {
2798 VerifyHasDirtyRootAncestor(f);
2800 #endif
2802 break;
2805 nsIFrame* child = f;
2806 f = f->GetParent();
2807 wasDirty = f->IsSubtreeDirty();
2808 f->ChildIsDirty(child);
2809 NS_ASSERTION(f->HasAnyStateBits(NS_FRAME_HAS_DIRTY_CHILDREN),
2810 "ChildIsDirty didn't do its job");
2811 if (wasDirty) {
2812 // This frame was already marked dirty.
2813 #ifdef DEBUG
2814 VerifyHasDirtyRootAncestor(f);
2815 #endif
2816 break;
2819 } while (subtrees.Length() != 0);
2821 MaybeScheduleReflow();
2824 void PresShell::FrameNeedsToContinueReflow(nsIFrame* aFrame) {
2825 NS_ASSERTION(mIsReflowing, "Must be in reflow when marking path dirty.");
2826 MOZ_ASSERT(mCurrentReflowRoot, "Must have a current reflow root here");
2827 NS_ASSERTION(
2828 aFrame == mCurrentReflowRoot ||
2829 nsLayoutUtils::IsProperAncestorFrame(mCurrentReflowRoot, aFrame),
2830 "Frame passed in is not the descendant of mCurrentReflowRoot");
2831 NS_ASSERTION(aFrame->HasAnyStateBits(NS_FRAME_IN_REFLOW),
2832 "Frame passed in not in reflow?");
2834 mFramesToDirty.Insert(aFrame);
2837 already_AddRefed<nsIContent> PresShell::GetContentForScrolling() const {
2838 if (nsCOMPtr<nsIContent> focused = GetFocusedContentInOurWindow()) {
2839 return focused.forget();
2841 return GetSelectedContentForScrolling();
2844 already_AddRefed<nsIContent> PresShell::GetSelectedContentForScrolling() const {
2845 nsCOMPtr<nsIContent> selectedContent;
2846 if (mSelection) {
2847 Selection* domSelection = mSelection->GetSelection(SelectionType::eNormal);
2848 if (domSelection) {
2849 selectedContent =
2850 nsIContent::FromNodeOrNull(domSelection->GetFocusNode());
2853 return selectedContent.forget();
2856 nsIScrollableFrame* PresShell::GetScrollableFrameToScrollForContent(
2857 nsIContent* aContent, ScrollDirections aDirections) {
2858 nsIScrollableFrame* scrollFrame = nullptr;
2859 if (aContent) {
2860 nsIFrame* startFrame = aContent->GetPrimaryFrame();
2861 if (startFrame) {
2862 scrollFrame = startFrame->GetScrollTargetFrame();
2863 if (scrollFrame) {
2864 startFrame = scrollFrame->GetScrolledFrame();
2866 scrollFrame = nsLayoutUtils::GetNearestScrollableFrameForDirection(
2867 startFrame, aDirections);
2870 if (!scrollFrame) {
2871 scrollFrame = GetRootScrollFrameAsScrollable();
2872 if (!scrollFrame || !scrollFrame->GetScrolledFrame()) {
2873 return nullptr;
2875 scrollFrame = nsLayoutUtils::GetNearestScrollableFrameForDirection(
2876 scrollFrame->GetScrolledFrame(), aDirections);
2878 return scrollFrame;
2881 nsIScrollableFrame* PresShell::GetScrollableFrameToScroll(
2882 ScrollDirections aDirections) {
2883 nsCOMPtr<nsIContent> content = GetContentForScrolling();
2884 return GetScrollableFrameToScrollForContent(content.get(), aDirections);
2887 void PresShell::CancelAllPendingReflows() {
2888 mDirtyRoots.Clear();
2890 if (mObservingLayoutFlushes) {
2891 GetPresContext()->RefreshDriver()->RemoveLayoutFlushObserver(this);
2892 mObservingLayoutFlushes = false;
2895 ASSERT_REFLOW_SCHEDULED_STATE();
2898 static bool DestroyFramesAndStyleDataFor(
2899 Element* aElement, nsPresContext& aPresContext,
2900 RestyleManager::IncludeRoot aIncludeRoot) {
2901 bool didReconstruct =
2902 aPresContext.FrameConstructor()->DestroyFramesFor(aElement);
2903 RestyleManager::ClearServoDataFromSubtree(aElement, aIncludeRoot);
2904 return didReconstruct;
2907 void PresShell::SlotAssignmentWillChange(Element& aElement,
2908 HTMLSlotElement* aOldSlot,
2909 HTMLSlotElement* aNewSlot) {
2910 MOZ_ASSERT(aOldSlot != aNewSlot);
2912 if (MOZ_UNLIKELY(!mDidInitialize)) {
2913 return;
2916 // If the old slot is about to become empty and show fallback, let layout know
2917 // that it needs to do work.
2918 if (aOldSlot && aOldSlot->AssignedNodes().Length() == 1 &&
2919 aOldSlot->HasChildren()) {
2920 DestroyFramesForAndRestyle(aOldSlot);
2923 // Ensure the new element starts off clean.
2924 DestroyFramesAndStyleDataFor(&aElement, *mPresContext,
2925 RestyleManager::IncludeRoot::Yes);
2927 if (aNewSlot) {
2928 // If the new slot will stop showing fallback content, we need to reframe it
2929 // altogether.
2930 if (aNewSlot->AssignedNodes().IsEmpty() && aNewSlot->HasChildren()) {
2931 DestroyFramesForAndRestyle(aNewSlot);
2932 // Otherwise we just care about the element, but we need to ensure that
2933 // something takes care of traversing to the relevant slot, if needed.
2934 } else if (aNewSlot->HasServoData() &&
2935 !Servo_Element_IsDisplayNone(aNewSlot)) {
2936 // Set the reframe bits...
2937 aNewSlot->NoteDescendantsNeedFramesForServo();
2938 aElement.SetFlags(NODE_NEEDS_FRAME);
2939 // Now the style dirty bits. Note that we can't just do
2940 // aElement.NoteDirtyForServo(), because the new slot is not setup yet.
2941 aNewSlot->SetHasDirtyDescendantsForServo();
2942 aNewSlot->NoteDirtySubtreeForServo();
2947 #ifdef DEBUG
2948 static void AssertNoFramesOrStyleDataInDescendants(Element& aElement) {
2949 for (nsINode* node : ShadowIncludingTreeIterator(aElement)) {
2950 nsIContent* c = nsIContent::FromNode(node);
2951 if (c == &aElement) {
2952 continue;
2954 // FIXME(emilio): The <area> check is needed because of bug 135040.
2955 MOZ_ASSERT(!c->GetPrimaryFrame() || c->IsHTMLElement(nsGkAtoms::area));
2956 MOZ_ASSERT(!c->IsElement() || !c->AsElement()->HasServoData());
2959 #endif
2961 void PresShell::DestroyFramesForAndRestyle(Element* aElement) {
2962 #ifdef DEBUG
2963 auto postCondition = MakeScopeExit([&]() {
2964 MOZ_ASSERT(!aElement->GetPrimaryFrame());
2965 AssertNoFramesOrStyleDataInDescendants(*aElement);
2967 #endif
2969 MOZ_ASSERT(aElement);
2970 if (!aElement->HasServoData()) {
2971 // Nothing to do here, the element already is out of the flat tree or is not
2972 // styled.
2973 return;
2976 // Mark ourselves as not safe to flush while we're doing frame destruction.
2977 nsAutoScriptBlocker scriptBlocker;
2978 ++mChangeNestCount;
2980 const bool didReconstruct = FrameConstructor()->DestroyFramesFor(aElement);
2981 // Clear the style data from all the flattened tree descendants, but _not_
2982 // from us, since otherwise we wouldn't see the reframe.
2983 RestyleManager::ClearServoDataFromSubtree(aElement,
2984 RestyleManager::IncludeRoot::No);
2985 auto changeHint =
2986 didReconstruct ? nsChangeHint(0) : nsChangeHint_ReconstructFrame;
2987 mPresContext->RestyleManager()->PostRestyleEvent(
2988 aElement, RestyleHint::RestyleSubtree(), changeHint);
2990 --mChangeNestCount;
2993 void PresShell::ShadowRootWillBeAttached(Element& aElement) {
2994 #ifdef DEBUG
2995 auto postCondition = MakeScopeExit(
2996 [&]() { AssertNoFramesOrStyleDataInDescendants(aElement); });
2997 #endif
2999 if (!aElement.HasServoData()) {
3000 // Nothing to do here, the element already is out of the flat tree or is not
3001 // styled.
3002 return;
3005 if (!aElement.HasChildren()) {
3006 // The element has no children, just avoid the work.
3007 return;
3010 // Mark ourselves as not safe to flush while we're doing frame destruction.
3011 nsAutoScriptBlocker scriptBlocker;
3012 ++mChangeNestCount;
3014 // NOTE(emilio): We use FlattenedChildIterator intentionally here (rather than
3015 // StyleChildrenIterator), since we don't want to remove ::before / ::after
3016 // content.
3017 FlattenedChildIterator iter(&aElement);
3018 nsCSSFrameConstructor* fc = FrameConstructor();
3019 for (nsIContent* c = iter.GetNextChild(); c; c = iter.GetNextChild()) {
3020 fc->DestroyFramesFor(c);
3021 if (c->IsElement()) {
3022 RestyleManager::ClearServoDataFromSubtree(c->AsElement());
3026 #ifdef ACCESSIBILITY
3027 if (nsAccessibilityService* accService = GetAccService()) {
3028 accService->ScheduleAccessibilitySubtreeUpdate(this, &aElement);
3030 #endif
3032 --mChangeNestCount;
3035 void PresShell::PostRecreateFramesFor(Element* aElement) {
3036 if (MOZ_UNLIKELY(!mDidInitialize)) {
3037 // Nothing to do here. In fact, if we proceed and aElement is the root, we
3038 // will crash.
3039 return;
3042 mPresContext->RestyleManager()->PostRestyleEvent(
3043 aElement, RestyleHint{0}, nsChangeHint_ReconstructFrame);
3046 void PresShell::RestyleForAnimation(Element* aElement, RestyleHint aHint) {
3047 // Now that we no longer have separate non-animation and animation
3048 // restyles, this method having a distinct identity is less important,
3049 // but it still seems useful to offer as a "more public" API and as a
3050 // checkpoint for these restyles to go through.
3051 mPresContext->RestyleManager()->PostRestyleEvent(aElement, aHint,
3052 nsChangeHint(0));
3055 void PresShell::SetForwardingContainer(const WeakPtr<nsDocShell>& aContainer) {
3056 mForwardingContainer = aContainer;
3059 void PresShell::ClearFrameRefs(nsIFrame* aFrame) {
3060 mPresContext->EventStateManager()->ClearFrameRefs(aFrame);
3062 AutoWeakFrame* weakFrame = mAutoWeakFrames;
3063 while (weakFrame) {
3064 AutoWeakFrame* prev = weakFrame->GetPreviousWeakFrame();
3065 if (weakFrame->GetFrame() == aFrame) {
3066 // This removes weakFrame from mAutoWeakFrames.
3067 weakFrame->Clear(this);
3069 weakFrame = prev;
3072 AutoTArray<WeakFrame*, 4> toRemove;
3073 for (WeakFrame* weakFrame : mWeakFrames) {
3074 if (weakFrame->GetFrame() == aFrame) {
3075 toRemove.AppendElement(weakFrame);
3078 for (WeakFrame* weakFrame : toRemove) {
3079 weakFrame->Clear(this);
3083 UniquePtr<gfxContext> PresShell::CreateReferenceRenderingContext() {
3084 if (mPresContext->IsScreen()) {
3085 return gfxContext::CreateOrNull(
3086 gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget().get());
3089 // We assume the devCtx has positive width and height for this call.
3090 // However, width and height, may be outside of the reasonable range
3091 // so rc may still be null.
3092 nsDeviceContext* devCtx = mPresContext->DeviceContext();
3093 return devCtx->CreateReferenceRenderingContext();
3096 // https://html.spec.whatwg.org/#scroll-to-the-fragment-identifier
3097 nsresult PresShell::GoToAnchor(const nsAString& aAnchorName, bool aScroll,
3098 ScrollFlags aAdditionalScrollFlags) {
3099 if (!mDocument) {
3100 return NS_ERROR_FAILURE;
3103 const Element* root = mDocument->GetRootElement();
3104 if (root && root->IsSVGElement(nsGkAtoms::svg)) {
3105 // We need to execute this even if there is an empty anchor name
3106 // so that any existing SVG fragment identifier effect is removed
3107 if (SVGFragmentIdentifier::ProcessFragmentIdentifier(mDocument,
3108 aAnchorName)) {
3109 return NS_OK;
3113 // Hold a reference to the ESM in case event dispatch tears us down.
3114 RefPtr<EventStateManager> esm = mPresContext->EventStateManager();
3116 // 1. If there is no indicated part of the document, set the Document's target
3117 // element to null.
3119 // FIXME(emilio): Per spec empty fragment string should take the same
3120 // code-path as "top"!
3121 if (aAnchorName.IsEmpty()) {
3122 NS_ASSERTION(!aScroll, "can't scroll to empty anchor name");
3123 esm->SetContentState(nullptr, ElementState::URLTARGET);
3124 return NS_OK;
3127 // 2. If the indicated part of the document is the top of the document,
3128 // then:
3129 // (handled below when `target` is null, and anchor is `top`)
3131 // 3.1. Let target be element that is the indicated part of the document.
3133 // https://html.spec.whatwg.org/#target-element
3134 // https://html.spec.whatwg.org/#find-a-potential-indicated-element
3135 RefPtr<Element> target =
3136 nsContentUtils::GetTargetElement(mDocument, aAnchorName);
3138 // 1. If there is no indicated part of the document, set the Document's
3139 // target element to null.
3140 // 2.1. Set the Document's target element to null.
3141 // 3.2. Set the Document's target element to target.
3142 esm->SetContentState(target, ElementState::URLTARGET);
3144 // TODO: Spec probably needs a section to account for this.
3145 if (nsIScrollableFrame* rootScroll = GetRootScrollFrameAsScrollable()) {
3146 if (rootScroll->DidHistoryRestore()) {
3147 // Scroll position restored from history trumps scrolling to anchor.
3148 aScroll = false;
3149 rootScroll->ClearDidHistoryRestore();
3153 if (target) {
3154 if (aScroll) {
3155 // 3.3. TODO: Run the ancestor details revealing algorithm on target.
3156 // 3.4. Scroll target into view, with behavior set to "auto", block set to
3157 // "start", and inline set to "nearest".
3158 // FIXME(emilio): Not all callers pass ScrollSmoothAuto (but we use auto
3159 // smooth scroll for `top` regardless below, so maybe they should!).
3160 ScrollingInteractionContext scrollToAnchorContext(true);
3161 MOZ_TRY(ScrollContentIntoView(
3162 target, ScrollAxis(WhereToScroll::Start, WhenToScroll::Always),
3163 ScrollAxis(),
3164 ScrollFlags::AnchorScrollFlags | aAdditionalScrollFlags));
3166 if (nsIScrollableFrame* rootScroll = GetRootScrollFrameAsScrollable()) {
3167 mLastAnchorScrolledTo = target;
3168 mLastAnchorScrollPositionY = rootScroll->GetScrollPosition().y;
3173 // 3.6. Move the sequential focus navigation starting point to target.
3175 // Move the caret to the anchor. That way tabbing will start from the new
3176 // location.
3178 // TODO(emilio): Do we want to do this even if aScroll is false?
3180 // NOTE: Intentionally out of order for now with the focus steps, see
3181 // https://github.com/whatwg/html/issues/7759
3182 RefPtr<nsRange> jumpToRange = nsRange::Create(mDocument);
3183 nsCOMPtr<nsIContent> nodeToSelect = target.get();
3184 while (nodeToSelect->GetFirstChild()) {
3185 nodeToSelect = nodeToSelect->GetFirstChild();
3187 jumpToRange->SelectNodeContents(*nodeToSelect, IgnoreErrors());
3188 if (RefPtr sel = mSelection->GetSelection(SelectionType::eNormal)) {
3189 sel->RemoveAllRanges(IgnoreErrors());
3190 sel->AddRangeAndSelectFramesAndNotifyListeners(*jumpToRange,
3191 IgnoreErrors());
3192 if (!StaticPrefs::layout_selectanchor()) {
3193 // Use a caret (collapsed selection) at the start of the anchor.
3194 sel->CollapseToStart(IgnoreErrors());
3199 // 3.5. Run the focusing steps for target, with the Document's viewport as
3200 // the fallback target.
3202 // Note that ScrollContentIntoView flushes, so we don't need to do that
3203 // again here. We also don't need to scroll again either.
3205 // We intentionally focus the target only when aScroll is true, we need to
3206 // sort out if the spec needs to differentiate these cases. When aScroll is
3207 // false we still clear the focus unconditionally, that's legacy behavior,
3208 // maybe we shouldn't do it.
3210 // TODO(emilio): Do we really want to clear the focus even if aScroll is
3211 // false?
3212 const bool shouldFocusTarget = [&] {
3213 if (!aScroll) {
3214 return false;
3216 nsIFrame* targetFrame = target->GetPrimaryFrame();
3217 return targetFrame && targetFrame->IsFocusable();
3218 }();
3220 if (shouldFocusTarget) {
3221 FocusOptions options;
3222 options.mPreventScroll = true;
3223 target->Focus(options, CallerType::NonSystem, IgnoreErrors());
3224 } else if (RefPtr<nsIFocusManager> fm = nsFocusManager::GetFocusManager()) {
3225 if (nsPIDOMWindowOuter* win = mDocument->GetWindow()) {
3226 // Now focus the document itself if focus is on an element within it.
3227 nsCOMPtr<mozIDOMWindowProxy> focusedWindow;
3228 fm->GetFocusedWindow(getter_AddRefs(focusedWindow));
3229 if (SameCOMIdentity(win, focusedWindow)) {
3230 fm->ClearFocus(focusedWindow);
3235 // If the target is an animation element, activate the animation
3236 if (auto* animationElement = SVGAnimationElement::FromNode(target.get())) {
3237 animationElement->ActivateByHyperlink();
3240 #ifdef ACCESSIBILITY
3241 if (nsAccessibilityService* accService = GetAccService()) {
3242 accService->NotifyOfAnchorJumpTo(target);
3244 #endif
3245 } else if (nsContentUtils::EqualsIgnoreASCIICase(aAnchorName, u"top"_ns)) {
3246 // 2.2. Scroll to the beginning of the document for the Document.
3247 nsIScrollableFrame* sf = GetRootScrollFrameAsScrollable();
3248 // Check |aScroll| after setting |rv| so we set |rv| to the same
3249 // thing whether or not |aScroll| is true.
3250 if (aScroll && sf) {
3251 ScrollMode scrollMode =
3252 sf->IsSmoothScroll() ? ScrollMode::SmoothMsd : ScrollMode::Instant;
3253 // Scroll to the top of the page
3254 sf->ScrollTo(nsPoint(0, 0), scrollMode);
3256 } else {
3257 return NS_ERROR_FAILURE;
3260 return NS_OK;
3263 nsresult PresShell::ScrollToAnchor() {
3264 nsCOMPtr<nsIContent> lastAnchor = std::move(mLastAnchorScrolledTo);
3265 if (!lastAnchor) {
3266 return NS_OK;
3269 NS_ASSERTION(mDidInitialize, "should have done initial reflow by now");
3270 nsIScrollableFrame* rootScroll = GetRootScrollFrameAsScrollable();
3271 if (!rootScroll ||
3272 mLastAnchorScrollPositionY != rootScroll->GetScrollPosition().y) {
3273 return NS_OK;
3275 return ScrollContentIntoView(
3276 lastAnchor, ScrollAxis(WhereToScroll::Start, WhenToScroll::Always),
3277 ScrollAxis(), ScrollFlags::AnchorScrollFlags);
3281 * Helper (per-continuation) for ScrollContentIntoView.
3283 * @param aContainerFrame [in] the frame which aRect is relative to
3284 * @param aFrame [in] Frame whose bounds should be unioned
3285 * @param aUseWholeLineHeightForInlines [in] if true, then for inline frames
3286 * we should include the top of the line in the added rectangle
3287 * @param aRect [inout] rect into which its bounds should be unioned
3288 * @param aHaveRect [inout] whether aRect contains data yet
3289 * @param aPrevBlock [inout] the block aLines is a line iterator for
3290 * @param aLines [inout] the line iterator we're using
3291 * @param aCurLine [inout] the line to start looking from in this iterator
3293 static void AccumulateFrameBounds(nsIFrame* aContainerFrame, nsIFrame* aFrame,
3294 bool aUseWholeLineHeightForInlines,
3295 nsRect& aRect, bool& aHaveRect,
3296 nsIFrame*& aPrevBlock,
3297 nsILineIterator*& aLines, int32_t& aCurLine) {
3298 nsIFrame* frame = aFrame;
3299 nsRect frameBounds = nsRect(nsPoint(0, 0), aFrame->GetSize());
3301 // If this is an inline frame and either the bounds height is 0 (quirks
3302 // layout model) or aUseWholeLineHeightForInlines is set, we need to
3303 // change the top of the bounds to include the whole line.
3304 if (frameBounds.height == 0 || aUseWholeLineHeightForInlines) {
3305 nsIFrame* prevFrame = aFrame;
3306 nsIFrame* f = aFrame;
3308 while (f && f->IsLineParticipant() && !f->IsTransformed() &&
3309 !f->IsAbsPosContainingBlock()) {
3310 prevFrame = f;
3311 f = prevFrame->GetParent();
3314 if (f != aFrame && f && f->IsBlockFrame()) {
3315 // find the line containing aFrame and increase the top of |offset|.
3316 if (f != aPrevBlock) {
3317 aLines = f->GetLineIterator();
3318 aPrevBlock = f;
3319 aCurLine = 0;
3321 if (aLines) {
3322 int32_t index = aLines->FindLineContaining(prevFrame, aCurLine);
3323 if (index >= 0) {
3324 auto line = aLines->GetLine(index).unwrap();
3325 frameBounds += frame->GetOffsetTo(f);
3326 frame = f;
3327 if (line.mLineBounds.y < frameBounds.y) {
3328 frameBounds.height = frameBounds.YMost() - line.mLineBounds.y;
3329 frameBounds.y = line.mLineBounds.y;
3336 nsRect transformedBounds = nsLayoutUtils::TransformFrameRectToAncestor(
3337 frame, frameBounds, aContainerFrame);
3339 if (aHaveRect) {
3340 // We can't use nsRect::UnionRect since it drops empty rects on
3341 // the floor, and we need to include them. (Thus we need
3342 // aHaveRect to know when to drop the initial value on the floor.)
3343 aRect = aRect.UnionEdges(transformedBounds);
3344 } else {
3345 aHaveRect = true;
3346 aRect = transformedBounds;
3350 static bool ComputeNeedToScroll(WhenToScroll aWhenToScroll, nscoord aLineSize,
3351 nscoord aRectMin, nscoord aRectMax,
3352 nscoord aViewMin, nscoord aViewMax) {
3353 // See how the rect should be positioned in a given axis.
3354 switch (aWhenToScroll) {
3355 case WhenToScroll::Always:
3356 // The caller wants the frame as visible as possible
3357 return true;
3358 case WhenToScroll::IfNotVisible:
3359 if (aLineSize > (aRectMax - aRectMin)) {
3360 // If the line size is greater than the size of the rect
3361 // to scroll into view, do not use the line size to determine
3362 // if we need to scroll.
3363 aLineSize = 0;
3366 // Scroll only if no part of the frame is visible in this view.
3367 return aRectMax - aLineSize <= aViewMin ||
3368 aRectMin + aLineSize >= aViewMax;
3369 case WhenToScroll::IfNotFullyVisible:
3370 // Scroll only if part of the frame is hidden and more can fit in view
3371 return !(aRectMin >= aViewMin && aRectMax <= aViewMax) &&
3372 std::min(aViewMax, aRectMax) - std::max(aRectMin, aViewMin) <
3373 aViewMax - aViewMin;
3375 return false;
3378 static nscoord ComputeWhereToScroll(WhereToScroll aWhereToScroll,
3379 nscoord aOriginalCoord, nscoord aRectMin,
3380 nscoord aRectMax, nscoord aViewMin,
3381 nscoord aViewMax, nscoord* aRangeMin,
3382 nscoord* aRangeMax) {
3383 nscoord resultCoord = aOriginalCoord;
3384 nscoord scrollPortLength = aViewMax - aViewMin;
3385 if (!aWhereToScroll.mPercentage) {
3386 // Scroll the minimum amount necessary to show as much as possible of the
3387 // frame. If the frame is too large, don't hide any initially visible part
3388 // of it.
3389 nscoord min = std::min(aRectMin, aRectMax - scrollPortLength);
3390 nscoord max = std::max(aRectMin, aRectMax - scrollPortLength);
3391 resultCoord = std::min(std::max(aOriginalCoord, min), max);
3392 } else {
3393 float percent = aWhereToScroll.mPercentage.value() / 100.0f;
3394 nscoord frameAlignCoord =
3395 NSToCoordRound(aRectMin + (aRectMax - aRectMin) * percent);
3396 resultCoord = NSToCoordRound(frameAlignCoord - scrollPortLength * percent);
3398 // Force the scroll range to extend to include resultCoord.
3399 *aRangeMin = std::min(resultCoord, aRectMax - scrollPortLength);
3400 *aRangeMax = std::max(resultCoord, aRectMin);
3401 return resultCoord;
3404 static WhereToScroll GetApplicableWhereToScroll(
3405 const nsIScrollableFrame* aFrameAsScrollable,
3406 const nsIFrame* aScrollableFrame, const nsIFrame* aTarget,
3407 ScrollDirection aScrollDirection, WhereToScroll aOriginal) {
3408 MOZ_ASSERT(do_QueryFrame(aFrameAsScrollable) == aScrollableFrame);
3409 if (aTarget == aScrollableFrame) {
3410 return aOriginal;
3413 StyleScrollSnapAlignKeyword align =
3414 aScrollDirection == ScrollDirection::eHorizontal
3415 ? aFrameAsScrollable->GetScrollSnapAlignFor(aTarget).first
3416 : aFrameAsScrollable->GetScrollSnapAlignFor(aTarget).second;
3418 switch (align) {
3419 case StyleScrollSnapAlignKeyword::None:
3420 return aOriginal;
3421 case StyleScrollSnapAlignKeyword::Start:
3422 return WhereToScroll::Start;
3423 case StyleScrollSnapAlignKeyword::Center:
3424 return WhereToScroll::Center;
3425 case StyleScrollSnapAlignKeyword::End:
3426 return WhereToScroll::End;
3428 return aOriginal;
3432 * This function takes a scrollable frame, a rect in the coordinate system
3433 * of the scrolled frame, and a desired percentage-based scroll
3434 * position and attempts to scroll the rect to that position in the
3435 * visual viewport.
3437 * This needs to work even if aRect has a width or height of zero.
3439 static void ScrollToShowRect(nsIScrollableFrame* aFrameAsScrollable,
3440 const nsIFrame* aScrollableFrame,
3441 const nsIFrame* aTarget, const nsRect& aRect,
3442 const Sides aScrollPaddingSkipSides,
3443 const nsMargin& aMargin, ScrollAxis aVertical,
3444 ScrollAxis aHorizontal, ScrollFlags aScrollFlags) {
3445 nsPoint scrollPt = aFrameAsScrollable->GetVisualViewportOffset();
3446 const nsPoint originalScrollPt = scrollPt;
3447 const nsRect visibleRect(scrollPt,
3448 aFrameAsScrollable->GetVisualViewportSize());
3450 const nsMargin padding = [&] {
3451 nsMargin p = aFrameAsScrollable->GetScrollPadding();
3452 p.ApplySkipSides(aScrollPaddingSkipSides);
3453 return p + aMargin;
3454 }();
3456 const nsRect rectToScrollIntoView = [&] {
3457 nsRect r(aRect);
3458 r.Inflate(padding);
3459 return r.Intersect(aFrameAsScrollable->GetScrolledRect());
3460 }();
3462 nsSize lineSize;
3463 // Don't call GetLineScrollAmount unless we actually need it. Not only
3464 // does this save time, but it's not safe to call GetLineScrollAmount
3465 // during reflow (because it depends on font size inflation and doesn't
3466 // use the in-reflow-safe font-size inflation path). If we did call it,
3467 // it would assert and possible give the wrong result.
3468 if (aVertical.mWhenToScroll == WhenToScroll::IfNotVisible ||
3469 aHorizontal.mWhenToScroll == WhenToScroll::IfNotVisible) {
3470 lineSize = aFrameAsScrollable->GetLineScrollAmount();
3472 ScrollStyles ss = aFrameAsScrollable->GetScrollStyles();
3473 nsRect allowedRange(scrollPt, nsSize(0, 0));
3474 ScrollDirections directions =
3475 aFrameAsScrollable->GetAvailableScrollingDirections();
3477 if (((aScrollFlags & ScrollFlags::ScrollOverflowHidden) ||
3478 ss.mVertical != StyleOverflow::Hidden) &&
3479 (!aVertical.mOnlyIfPerceivedScrollableDirection ||
3480 (directions.contains(ScrollDirection::eVertical)))) {
3481 if (ComputeNeedToScroll(aVertical.mWhenToScroll, lineSize.height, aRect.y,
3482 aRect.YMost(), visibleRect.y + padding.top,
3483 visibleRect.YMost() - padding.bottom)) {
3484 // If the scroll-snap-align on the frame is valid, we need to respect it.
3485 WhereToScroll whereToScroll = GetApplicableWhereToScroll(
3486 aFrameAsScrollable, aScrollableFrame, aTarget,
3487 ScrollDirection::eVertical, aVertical.mWhereToScroll);
3489 nscoord maxHeight;
3490 scrollPt.y = ComputeWhereToScroll(
3491 whereToScroll, scrollPt.y, rectToScrollIntoView.y,
3492 rectToScrollIntoView.YMost(), visibleRect.y, visibleRect.YMost(),
3493 &allowedRange.y, &maxHeight);
3494 allowedRange.height = maxHeight - allowedRange.y;
3498 if (((aScrollFlags & ScrollFlags::ScrollOverflowHidden) ||
3499 ss.mHorizontal != StyleOverflow::Hidden) &&
3500 (!aHorizontal.mOnlyIfPerceivedScrollableDirection ||
3501 (directions.contains(ScrollDirection::eHorizontal)))) {
3502 if (ComputeNeedToScroll(aHorizontal.mWhenToScroll, lineSize.width, aRect.x,
3503 aRect.XMost(), visibleRect.x + padding.left,
3504 visibleRect.XMost() - padding.right)) {
3505 // If the scroll-snap-align on the frame is valid, we need to respect it.
3506 WhereToScroll whereToScroll = GetApplicableWhereToScroll(
3507 aFrameAsScrollable, aScrollableFrame, aTarget,
3508 ScrollDirection::eHorizontal, aHorizontal.mWhereToScroll);
3510 nscoord maxWidth;
3511 scrollPt.x = ComputeWhereToScroll(
3512 whereToScroll, scrollPt.x, rectToScrollIntoView.x,
3513 rectToScrollIntoView.XMost(), visibleRect.x, visibleRect.XMost(),
3514 &allowedRange.x, &maxWidth);
3515 allowedRange.width = maxWidth - allowedRange.x;
3519 // If we don't need to scroll, then don't try since it might cancel
3520 // a current smooth scroll operation.
3521 if (scrollPt == originalScrollPt) {
3522 return;
3525 ScrollMode scrollMode = ScrollMode::Instant;
3526 // Default to an instant scroll, but if the scroll behavior given is "auto"
3527 // or "smooth", use that as the specified behavior. If the user has disabled
3528 // smooth scrolls, a given mode of "auto" or "smooth" should not result in
3529 // a smooth scroll.
3530 ScrollBehavior behavior = ScrollBehavior::Instant;
3531 if (aScrollFlags & ScrollFlags::ScrollSmooth) {
3532 behavior = ScrollBehavior::Smooth;
3533 } else if (aScrollFlags & ScrollFlags::ScrollSmoothAuto) {
3534 behavior = ScrollBehavior::Auto;
3536 bool smoothScroll = aFrameAsScrollable->IsSmoothScroll(behavior);
3537 if (smoothScroll) {
3538 scrollMode = ScrollMode::SmoothMsd;
3540 nsIFrame* frame = do_QueryFrame(aFrameAsScrollable);
3541 AutoWeakFrame weakFrame(frame);
3542 aFrameAsScrollable->ScrollTo(scrollPt, scrollMode, &allowedRange,
3543 ScrollSnapFlags::IntendedEndPosition,
3544 aScrollFlags & ScrollFlags::TriggeredByScript
3545 ? ScrollTriggeredByScript::Yes
3546 : ScrollTriggeredByScript::No);
3547 if (!weakFrame.IsAlive()) {
3548 return;
3551 // If this is the RCD-RSF, also call ScrollToVisual() since we want to
3552 // scroll the rect into view visually, and that may require scrolling
3553 // the visual viewport in scenarios where there is not enough layout
3554 // scroll range.
3555 if (aFrameAsScrollable->IsRootScrollFrameOfDocument() &&
3556 frame->PresContext()->IsRootContentDocumentCrossProcess()) {
3557 frame->PresShell()->ScrollToVisual(scrollPt, FrameMetrics::eMainThread,
3558 scrollMode);
3562 nsresult PresShell::ScrollContentIntoView(nsIContent* aContent,
3563 ScrollAxis aVertical,
3564 ScrollAxis aHorizontal,
3565 ScrollFlags aScrollFlags) {
3566 NS_ENSURE_TRUE(aContent, NS_ERROR_NULL_POINTER);
3567 RefPtr<Document> composedDoc = aContent->GetComposedDoc();
3568 NS_ENSURE_STATE(composedDoc);
3570 NS_ASSERTION(mDidInitialize, "should have done initial reflow by now");
3572 if (mContentToScrollTo) {
3573 mContentToScrollTo->RemoveProperty(nsGkAtoms::scrolling);
3575 mContentToScrollTo = aContent;
3576 ScrollIntoViewData* data = new ScrollIntoViewData();
3577 data->mContentScrollVAxis = aVertical;
3578 data->mContentScrollHAxis = aHorizontal;
3579 data->mContentToScrollToFlags = aScrollFlags;
3580 if (NS_FAILED(mContentToScrollTo->SetProperty(
3581 nsGkAtoms::scrolling, data,
3582 nsINode::DeleteProperty<PresShell::ScrollIntoViewData>))) {
3583 mContentToScrollTo = nullptr;
3586 // If the target frame has an ancestor of a `content-visibility: auto`
3587 // element ensure that it is laid out, so that the boundary rectangle is
3588 // correct.
3589 // Additionally, ensure that all ancestor elements with 'content-visibility:
3590 // auto' are set to 'visible'. so that they are laid out as visible before
3591 // scrolling, improving the accuracy of the scroll position, especially when
3592 // the scroll target is within the overflow area. And here invoking
3593 // 'SetTemporarilyVisibleForScrolledIntoViewDescendant' would make the
3594 // intersection observer knows that it should generate entries for these
3595 // c-v:auto ancestors, so that the content relevancy could be checked again
3596 // after scrolling. https://drafts.csswg.org/css-contain-2/#cv-notes
3597 bool reflowedForHiddenContent = false;
3598 if (mContentToScrollTo) {
3599 if (nsIFrame* frame = mContentToScrollTo->GetPrimaryFrame()) {
3600 bool hasContentVisibilityAutoAncestor = false;
3601 auto* ancestor = frame->GetClosestContentVisibilityAncestor(
3602 nsIFrame::IncludeContentVisibility::Auto);
3603 while (ancestor) {
3604 if (auto* element = Element::FromNodeOrNull(ancestor->GetContent())) {
3605 hasContentVisibilityAutoAncestor = true;
3606 element->SetTemporarilyVisibleForScrolledIntoViewDescendant(true);
3607 element->SetVisibleForContentVisibility(true);
3609 ancestor = ancestor->GetClosestContentVisibilityAncestor(
3610 nsIFrame::IncludeContentVisibility::Auto);
3612 if (hasContentVisibilityAutoAncestor) {
3613 UpdateHiddenContentInForcedLayout(frame);
3614 // TODO: There might be the other already scheduled relevancy updates,
3615 // other than caused be scrollIntoView.
3616 UpdateContentRelevancyImmediately(ContentRelevancyReason::Visible);
3617 reflowedForHiddenContent = ReflowForHiddenContentIfNeeded();
3622 if (!reflowedForHiddenContent) {
3623 // Flush layout and attempt to scroll in the process.
3624 if (PresShell* presShell = composedDoc->GetPresShell()) {
3625 presShell->SetNeedLayoutFlush();
3627 composedDoc->FlushPendingNotifications(FlushType::InterruptibleLayout);
3630 // If mContentToScrollTo is non-null, that means we interrupted the reflow
3631 // (or suppressed it altogether because we're suppressing interruptible
3632 // flushes right now) and won't necessarily get the position correct, but do
3633 // a best-effort scroll here. The other option would be to do this inside
3634 // FlushPendingNotifications, but I'm not sure the repeated scrolling that
3635 // could trigger if reflows keep getting interrupted would be more desirable
3636 // than a single best-effort scroll followed by one final scroll on the first
3637 // completed reflow.
3638 if (mContentToScrollTo) {
3639 DoScrollContentIntoView();
3641 return NS_OK;
3644 static nsMargin GetScrollMargin(const nsIFrame* aFrame) {
3645 MOZ_ASSERT(aFrame);
3646 // If we're focusing something that can't be targeted by content, allow
3647 // content to customize the margin.
3649 // TODO: This is also a bit of an issue for delegated focus, see
3650 // https://github.com/whatwg/html/issues/7033.
3651 if (aFrame->GetContent() && aFrame->GetContent()->ChromeOnlyAccess()) {
3652 if (const nsIContent* userContent =
3653 aFrame->GetContent()->GetChromeOnlyAccessSubtreeRootParent()) {
3654 if (const nsIFrame* frame = userContent->GetPrimaryFrame()) {
3655 return frame->StyleMargin()->GetScrollMargin();
3659 return aFrame->StyleMargin()->GetScrollMargin();
3662 void PresShell::DoScrollContentIntoView() {
3663 NS_ASSERTION(mDidInitialize, "should have done initial reflow by now");
3665 nsIFrame* frame = mContentToScrollTo->GetPrimaryFrame();
3667 if (!frame || frame->IsHiddenByContentVisibilityOnAnyAncestor(
3668 nsIFrame::IncludeContentVisibility::Hidden)) {
3669 mContentToScrollTo->RemoveProperty(nsGkAtoms::scrolling);
3670 mContentToScrollTo = nullptr;
3671 return;
3674 if (frame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
3675 // The reflow flush before this scroll got interrupted, and this frame's
3676 // coords and size are all zero, and it has no content showing anyway.
3677 // Don't bother scrolling to it. We'll try again when we finish up layout.
3678 return;
3681 auto* data = static_cast<ScrollIntoViewData*>(
3682 mContentToScrollTo->GetProperty(nsGkAtoms::scrolling));
3683 if (MOZ_UNLIKELY(!data)) {
3684 mContentToScrollTo = nullptr;
3685 return;
3688 ScrollFrameIntoView(frame, Nothing(), data->mContentScrollVAxis,
3689 data->mContentScrollHAxis, data->mContentToScrollToFlags);
3692 bool PresShell::ScrollFrameIntoView(
3693 nsIFrame* aTargetFrame, const Maybe<nsRect>& aKnownRectRelativeToTarget,
3694 ScrollAxis aVertical, ScrollAxis aHorizontal, ScrollFlags aScrollFlags) {
3695 // The scroll margin only applies to the whole bounds of the element, so don't
3696 // apply it if we get an arbitrary rect / point to scroll to.
3697 const nsMargin scrollMargin =
3698 aKnownRectRelativeToTarget ? nsMargin() : GetScrollMargin(aTargetFrame);
3700 Sides skipPaddingSides;
3701 const auto MaybeSkipPaddingSides = [&](nsIFrame* aFrame) {
3702 if (!aFrame->IsStickyPositioned()) {
3703 return;
3705 const nsPoint pos = aFrame->GetPosition();
3706 const nsPoint normalPos = aFrame->GetNormalPosition();
3707 if (pos == normalPos) {
3708 return; // Frame is not stuck.
3710 // If we're targetting a sticky element, make sure not to apply
3711 // scroll-padding on the direction we're stuck.
3712 const auto& offsets = aFrame->StylePosition()->mOffset;
3713 for (auto side : AllPhysicalSides()) {
3714 if (offsets.Get(side).IsAuto()) {
3715 continue;
3717 // See if this axis is stuck.
3718 const bool yAxis = side == eSideTop || side == eSideBottom;
3719 const bool stuck = yAxis ? pos.y != normalPos.y : pos.x != normalPos.x;
3720 if (!stuck) {
3721 continue;
3723 skipPaddingSides |= SideToSideBit(side);
3727 nsIFrame* container = aTargetFrame;
3729 // This function needs to work even if rect has a width or height of 0.
3730 nsRect rect = [&] {
3731 if (aKnownRectRelativeToTarget) {
3732 return *aKnownRectRelativeToTarget;
3734 MaybeSkipPaddingSides(aTargetFrame);
3735 while (nsIFrame* parent = container->GetParent()) {
3736 container = parent;
3737 if (static_cast<nsIScrollableFrame*>(do_QueryFrame(container))) {
3738 // We really just need a non-fragmented frame so that we can accumulate
3739 // the bounds of all our continuations relative to it. We shouldn't jump
3740 // out of our nearest scrollable frame, and that's an ok reference
3741 // frame, so try to use that, or the root frame if there's nothing to
3742 // scroll in this document.
3743 break;
3745 MaybeSkipPaddingSides(container);
3747 MOZ_DIAGNOSTIC_ASSERT(container);
3749 nsRect targetFrameBounds;
3751 bool haveRect = false;
3752 const bool useWholeLineHeightForInlines =
3753 aVertical.mWhenToScroll != WhenToScroll::IfNotFullyVisible;
3754 AutoAssertNoDomMutations
3755 guard; // Ensure use of nsILineIterators is safe.
3756 nsIFrame* prevBlock = nullptr;
3757 // Reuse the same line iterator across calls to AccumulateFrameBounds.
3758 // We set it every time we detect a new block (stored in prevBlock).
3759 nsILineIterator* lines = nullptr;
3760 // The last line we found a continuation on in |lines|. We assume that
3761 // later continuations cannot come on earlier lines.
3762 int32_t curLine = 0;
3763 nsIFrame* frame = aTargetFrame;
3764 do {
3765 AccumulateFrameBounds(container, frame, useWholeLineHeightForInlines,
3766 targetFrameBounds, haveRect, prevBlock, lines,
3767 curLine);
3768 } while ((frame = frame->GetNextContinuation()));
3771 return targetFrameBounds;
3772 }();
3774 bool didScroll = false;
3775 const nsIFrame* target = aTargetFrame;
3776 // Walk up the frame hierarchy scrolling the rect into view and
3777 // keeping rect relative to container
3778 do {
3779 if (nsIScrollableFrame* sf = do_QueryFrame(container)) {
3780 nsPoint oldPosition = sf->GetScrollPosition();
3781 nsRect targetRect = rect;
3782 // Inflate the scrolled rect by the container's padding in each dimension,
3783 // unless we have 'overflow-clip-box-*: content-box' in that dimension.
3784 auto* disp = container->StyleDisplay();
3785 if (disp->mOverflowClipBoxBlock == StyleOverflowClipBox::ContentBox ||
3786 disp->mOverflowClipBoxInline == StyleOverflowClipBox::ContentBox) {
3787 WritingMode wm = container->GetWritingMode();
3788 bool cbH = (wm.IsVertical() ? disp->mOverflowClipBoxBlock
3789 : disp->mOverflowClipBoxInline) ==
3790 StyleOverflowClipBox::ContentBox;
3791 bool cbV = (wm.IsVertical() ? disp->mOverflowClipBoxInline
3792 : disp->mOverflowClipBoxBlock) ==
3793 StyleOverflowClipBox::ContentBox;
3794 nsMargin padding = container->GetUsedPadding();
3795 if (!cbH) {
3796 padding.left = padding.right = nscoord(0);
3798 if (!cbV) {
3799 padding.top = padding.bottom = nscoord(0);
3801 targetRect.Inflate(padding);
3804 targetRect -= sf->GetScrolledFrame()->GetPosition();
3807 AutoWeakFrame wf(container);
3808 ScrollToShowRect(sf, container, target, targetRect, skipPaddingSides,
3809 scrollMargin, aVertical, aHorizontal, aScrollFlags);
3810 if (!wf.IsAlive()) {
3811 return didScroll;
3815 nsPoint newPosition = sf->LastScrollDestination();
3816 // If the scroll position increased, that means our content moved up,
3817 // so our rect's offset should decrease
3818 rect += oldPosition - newPosition;
3820 if (oldPosition != newPosition) {
3821 didScroll = true;
3824 // only scroll one container when this flag is set
3825 if (aScrollFlags & ScrollFlags::ScrollFirstAncestorOnly) {
3826 break;
3829 // This scroll container will be the next target element in the nearest
3830 // ancestor scroll container.
3831 target = container;
3832 // We found a sticky scroll container, we shouldn't skip that side
3833 // anymore.
3834 skipPaddingSides = {};
3837 MaybeSkipPaddingSides(container);
3839 nsIFrame* parent;
3840 if (container->IsTransformed()) {
3841 container->GetTransformMatrix(ViewportType::Layout, RelativeTo{nullptr},
3842 &parent);
3843 rect =
3844 nsLayoutUtils::TransformFrameRectToAncestor(container, rect, parent);
3845 } else {
3846 rect += container->GetPosition();
3847 parent = container->GetParent();
3849 if (!parent && !(aScrollFlags & ScrollFlags::ScrollNoParentFrames)) {
3850 nsPoint extraOffset(0, 0);
3851 int32_t APD = container->PresContext()->AppUnitsPerDevPixel();
3852 parent = nsLayoutUtils::GetCrossDocParentFrameInProcess(container,
3853 &extraOffset);
3854 if (parent) {
3855 int32_t parentAPD = parent->PresContext()->AppUnitsPerDevPixel();
3856 rect = rect.ScaleToOtherAppUnitsRoundOut(APD, parentAPD);
3857 rect += extraOffset;
3858 } else {
3859 nsCOMPtr<nsIDocShell> docShell =
3860 container->PresContext()->GetDocShell();
3861 if (BrowserChild* browserChild = BrowserChild::GetFrom(docShell)) {
3862 // Defer to the parent document if this is an out-of-process iframe.
3863 Unused << browserChild->SendScrollRectIntoView(
3864 rect, aVertical, aHorizontal, aScrollFlags, APD);
3868 container = parent;
3869 } while (container);
3871 return didScroll;
3874 void PresShell::ScheduleViewManagerFlush() {
3875 if (MOZ_UNLIKELY(mIsDestroying)) {
3876 return;
3879 nsPresContext* presContext = GetPresContext();
3880 if (presContext) {
3881 presContext->RefreshDriver()->ScheduleViewManagerFlush();
3883 SetNeedLayoutFlush();
3886 void PresShell::DispatchSynthMouseMove(WidgetGUIEvent* aEvent) {
3887 AUTO_PROFILER_TRACING_MARKER_DOCSHELL("Paint", "DispatchSynthMouseMove",
3888 GRAPHICS, mPresContext->GetDocShell());
3889 nsEventStatus status = nsEventStatus_eIgnore;
3890 nsView* targetView = nsView::GetViewFor(aEvent->mWidget);
3891 if (!targetView) return;
3892 RefPtr<nsViewManager> viewManager = targetView->GetViewManager();
3893 viewManager->DispatchEvent(aEvent, targetView, &status);
3896 void PresShell::ClearMouseCaptureOnView(nsView* aView) {
3897 if (nsIContent* capturingContent = GetCapturingContent()) {
3898 if (aView) {
3899 // if a view was specified, ensure that the captured content is within
3900 // this view.
3901 nsIFrame* frame = capturingContent->GetPrimaryFrame();
3902 if (frame) {
3903 nsView* view = frame->GetClosestView();
3904 // if there is no view, capturing won't be handled any more, so
3905 // just release the capture.
3906 if (view) {
3907 do {
3908 if (view == aView) {
3909 ReleaseCapturingContent();
3910 // the view containing the captured content likely disappeared so
3911 // disable capture for now.
3912 AllowMouseCapture(false);
3913 break;
3916 view = view->GetParent();
3917 } while (view);
3918 // return if the view wasn't found
3919 return;
3924 ReleaseCapturingContent();
3927 // disable mouse capture until the next mousedown as a dialog has opened
3928 // or a drag has started. Otherwise, someone could start capture during
3929 // the modal dialog or drag.
3930 AllowMouseCapture(false);
3933 void PresShell::ClearMouseCapture() {
3934 ReleaseCapturingContent();
3935 AllowMouseCapture(false);
3938 void PresShell::ClearMouseCapture(nsIFrame* aFrame) {
3939 MOZ_ASSERT(aFrame);
3941 nsIContent* capturingContent = GetCapturingContent();
3942 if (!capturingContent) {
3943 return;
3946 nsIFrame* capturingFrame = capturingContent->GetPrimaryFrame();
3947 const bool shouldClear =
3948 !capturingFrame ||
3949 nsLayoutUtils::IsAncestorFrameCrossDocInProcess(aFrame, capturingFrame);
3950 if (shouldClear) {
3951 ClearMouseCapture();
3955 nsresult PresShell::CaptureHistoryState(nsILayoutHistoryState** aState) {
3956 MOZ_ASSERT(nullptr != aState, "null state pointer");
3958 // We actually have to mess with the docshell here, since we want to
3959 // store the state back in it.
3960 // XXXbz this isn't really right, since this is being called in the
3961 // content viewer's Hide() method... by that point the docshell's
3962 // state could be wrong. We should sort out a better ownership
3963 // model for the layout history state.
3964 nsCOMPtr<nsIDocShell> docShell(mPresContext->GetDocShell());
3965 if (!docShell) return NS_ERROR_FAILURE;
3967 nsCOMPtr<nsILayoutHistoryState> historyState;
3968 docShell->GetLayoutHistoryState(getter_AddRefs(historyState));
3969 if (!historyState) {
3970 // Create the document state object
3971 historyState = NS_NewLayoutHistoryState();
3972 docShell->SetLayoutHistoryState(historyState);
3975 *aState = historyState;
3976 NS_IF_ADDREF(*aState);
3978 // Capture frame state for the entire frame hierarchy
3979 nsIFrame* rootFrame = mFrameConstructor->GetRootFrame();
3980 if (!rootFrame) return NS_OK;
3982 mFrameConstructor->CaptureFrameState(rootFrame, historyState);
3984 return NS_OK;
3987 void PresShell::ScheduleBeforeFirstPaint() {
3988 if (!mDocument->IsResourceDoc()) {
3989 // Notify observers that a new page is about to be drawn. Execute this
3990 // as soon as it is safe to run JS, which is guaranteed to be before we
3991 // go back to the event loop and actually draw the page.
3992 MOZ_LOG(gLog, LogLevel::Debug,
3993 ("PresShell::ScheduleBeforeFirstPaint this=%p", this));
3995 nsContentUtils::AddScriptRunner(
3996 new nsBeforeFirstPaintDispatcher(mDocument));
4000 void PresShell::UnsuppressAndInvalidate() {
4001 // Note: We ignore the EnsureVisible check for resource documents, because
4002 // they won't have a docshell, so they'll always fail EnsureVisible.
4003 if ((!mDocument->IsResourceDoc() && !mPresContext->EnsureVisible()) ||
4004 mHaveShutDown) {
4005 // No point; we're about to be torn down anyway.
4006 return;
4009 ScheduleBeforeFirstPaint();
4011 PROFILER_MARKER_UNTYPED("UnsuppressAndInvalidate", GRAPHICS);
4013 mPaintingSuppressed = false;
4014 if (nsIFrame* rootFrame = mFrameConstructor->GetRootFrame()) {
4015 // let's assume that outline on a root frame is not supported
4016 rootFrame->InvalidateFrame();
4019 if (mPresContext->IsRootContentDocumentCrossProcess()) {
4020 if (auto* bc = BrowserChild::GetFrom(mDocument->GetDocShell())) {
4021 if (mDocument->IsInitialDocument()) {
4022 bc->SendDidUnsuppressPaintingNormalPriority();
4023 } else {
4024 bc->SendDidUnsuppressPainting();
4029 // now that painting is unsuppressed, focus may be set on the document
4030 if (nsPIDOMWindowOuter* win = mDocument->GetWindow()) {
4031 win->SetReadyForFocus();
4034 if (!mHaveShutDown) {
4035 SynthesizeMouseMove(false);
4036 ScheduleApproximateFrameVisibilityUpdateNow();
4040 void PresShell::CancelPaintSuppressionTimer() {
4041 if (mPaintSuppressionTimer) {
4042 mPaintSuppressionTimer->Cancel();
4043 mPaintSuppressionTimer = nullptr;
4047 void PresShell::UnsuppressPainting() {
4048 CancelPaintSuppressionTimer();
4050 if (mIsDocumentGone || !mPaintingSuppressed) {
4051 return;
4054 // If we have reflows pending, just wait until we process
4055 // the reflows and get all the frames where we want them
4056 // before actually unlocking the painting. Otherwise
4057 // go ahead and unlock now.
4058 if (!mDirtyRoots.IsEmpty())
4059 mShouldUnsuppressPainting = true;
4060 else
4061 UnsuppressAndInvalidate();
4064 // Post a request to handle an arbitrary callback after reflow has finished.
4065 nsresult PresShell::PostReflowCallback(nsIReflowCallback* aCallback) {
4066 void* result = AllocateByObjectID(eArenaObjectID_nsCallbackEventRequest,
4067 sizeof(nsCallbackEventRequest));
4068 nsCallbackEventRequest* request = (nsCallbackEventRequest*)result;
4070 request->callback = aCallback;
4071 request->next = nullptr;
4073 if (mLastCallbackEventRequest) {
4074 mLastCallbackEventRequest = mLastCallbackEventRequest->next = request;
4075 } else {
4076 mFirstCallbackEventRequest = request;
4077 mLastCallbackEventRequest = request;
4080 return NS_OK;
4083 void PresShell::CancelReflowCallback(nsIReflowCallback* aCallback) {
4084 nsCallbackEventRequest* before = nullptr;
4085 nsCallbackEventRequest* node = mFirstCallbackEventRequest;
4086 while (node) {
4087 nsIReflowCallback* callback = node->callback;
4089 if (callback == aCallback) {
4090 nsCallbackEventRequest* toFree = node;
4091 if (node == mFirstCallbackEventRequest) {
4092 node = node->next;
4093 mFirstCallbackEventRequest = node;
4094 NS_ASSERTION(before == nullptr, "impossible");
4095 } else {
4096 node = node->next;
4097 before->next = node;
4100 if (toFree == mLastCallbackEventRequest) {
4101 mLastCallbackEventRequest = before;
4104 FreeByObjectID(eArenaObjectID_nsCallbackEventRequest, toFree);
4105 } else {
4106 before = node;
4107 node = node->next;
4112 void PresShell::CancelPostedReflowCallbacks() {
4113 while (mFirstCallbackEventRequest) {
4114 nsCallbackEventRequest* node = mFirstCallbackEventRequest;
4115 mFirstCallbackEventRequest = node->next;
4116 if (!mFirstCallbackEventRequest) {
4117 mLastCallbackEventRequest = nullptr;
4119 nsIReflowCallback* callback = node->callback;
4120 FreeByObjectID(eArenaObjectID_nsCallbackEventRequest, node);
4121 if (callback) {
4122 callback->ReflowCallbackCanceled();
4127 void PresShell::HandlePostedReflowCallbacks(bool aInterruptible) {
4128 while (true) {
4129 // Call all our callbacks, tell us if we need to flush again.
4130 bool shouldFlush = false;
4131 while (mFirstCallbackEventRequest) {
4132 nsCallbackEventRequest* node = mFirstCallbackEventRequest;
4133 mFirstCallbackEventRequest = node->next;
4134 if (!mFirstCallbackEventRequest) {
4135 mLastCallbackEventRequest = nullptr;
4137 nsIReflowCallback* callback = node->callback;
4138 FreeByObjectID(eArenaObjectID_nsCallbackEventRequest, node);
4139 if (callback && callback->ReflowFinished()) {
4140 shouldFlush = true;
4144 if (!shouldFlush || mIsDestroying) {
4145 return;
4148 // The flush might cause us to have more callbacks.
4149 const auto flushType =
4150 aInterruptible ? FlushType::InterruptibleLayout : FlushType::Layout;
4151 FlushPendingNotifications(flushType);
4155 bool PresShell::IsSafeToFlush() const {
4156 // Not safe if we are getting torn down, reflowing, or in the middle of frame
4157 // construction.
4158 if (mIsReflowing || mChangeNestCount || mIsDestroying) {
4159 return false;
4162 // Not safe if we are painting
4163 if (nsViewManager* viewManager = GetViewManager()) {
4164 bool isPainting = false;
4165 viewManager->IsPainting(isPainting);
4166 if (isPainting) {
4167 return false;
4171 return true;
4174 void PresShell::NotifyFontFaceSetOnRefresh() {
4175 if (FontFaceSet* set = mDocument->GetFonts()) {
4176 set->DidRefresh();
4180 void PresShell::DoFlushPendingNotifications(FlushType aType) {
4181 // by default, flush animations if aType >= FlushType::Style
4182 mozilla::ChangesToFlush flush(aType, aType >= FlushType::Style);
4183 FlushPendingNotifications(flush);
4186 #ifdef DEBUG
4187 static void AssertFrameSubtreeIsSane(const nsIFrame& aRoot) {
4188 if (const nsIContent* content = aRoot.GetContent()) {
4189 MOZ_ASSERT(content->GetFlattenedTreeParentNodeForStyle(),
4190 "Node not in the flattened tree still has a frame?");
4193 for (const auto& childList : aRoot.ChildLists()) {
4194 for (const nsIFrame* child : childList.mList) {
4195 AssertFrameSubtreeIsSane(*child);
4199 #endif
4201 static inline void AssertFrameTreeIsSane(const PresShell& aPresShell) {
4202 #ifdef DEBUG
4203 if (const nsIFrame* root = aPresShell.GetRootFrame()) {
4204 AssertFrameSubtreeIsSane(*root);
4206 #endif
4209 static void TriggerPendingScrollTimelineAnimations(Document* aDocument) {
4210 auto* tracker = aDocument->GetScrollTimelineAnimationTracker();
4211 if (!tracker || !tracker->HasPendingAnimations()) {
4212 return;
4214 tracker->TriggerPendingAnimations();
4217 void PresShell::DoFlushPendingNotifications(mozilla::ChangesToFlush aFlush) {
4218 // FIXME(emilio, bug 1530177): Turn into a release assert when bug 1530188 and
4219 // bug 1530190 are fixed.
4220 MOZ_DIAGNOSTIC_ASSERT(!mForbiddenToFlush, "This is bad!");
4222 // Per our API contract, hold a strong ref to ourselves until we return.
4223 RefPtr<PresShell> kungFuDeathGrip = this;
4226 * VERY IMPORTANT: If you add some sort of new flushing to this
4227 * method, make sure to add the relevant SetNeedLayoutFlush or
4228 * SetNeedStyleFlush calls on the shell.
4230 FlushType flushType = aFlush.mFlushType;
4232 // If this is a layout flush, first update the relevancy of any content
4233 // of elements with `content-visibility: auto` so that the values
4234 // returned from script queries are up-to-date.
4235 if (flushType >= mozilla::FlushType::Layout) {
4236 UpdateRelevancyOfContentVisibilityAutoFrames();
4239 MOZ_ASSERT(NeedFlush(flushType), "Why did we get called?");
4241 AUTO_PROFILER_MARKER_TEXT(
4242 "DoFlushPendingNotifications", LAYOUT,
4243 MarkerOptions(MarkerStack::Capture(), MarkerInnerWindowIdFromDocShell(
4244 mPresContext->GetDocShell())),
4245 nsDependentCString(kFlushTypeNames[flushType]));
4246 AUTO_PROFILER_LABEL_DYNAMIC_CSTR_NONSENSITIVE(
4247 "PresShell::DoFlushPendingNotifications", LAYOUT,
4248 kFlushTypeNames[flushType]);
4250 #ifdef ACCESSIBILITY
4251 # ifdef DEBUG
4252 if (nsAccessibilityService* accService = GetAccService()) {
4253 NS_ASSERTION(!accService->IsProcessingRefreshDriverNotification(),
4254 "Flush during accessible tree update!");
4256 # endif
4257 #endif
4259 NS_ASSERTION(flushType >= FlushType::Style, "Why did we get called?");
4261 mNeedStyleFlush = false;
4262 mNeedThrottledAnimationFlush =
4263 mNeedThrottledAnimationFlush && !aFlush.mFlushAnimations;
4264 mNeedLayoutFlush =
4265 mNeedLayoutFlush && (flushType < FlushType::InterruptibleLayout);
4267 bool isSafeToFlush = IsSafeToFlush();
4269 // If layout could possibly trigger scripts, then it's only safe to flush if
4270 // it's safe to run script.
4271 bool hasHadScriptObject;
4272 if (mDocument->GetScriptHandlingObject(hasHadScriptObject) ||
4273 hasHadScriptObject) {
4274 isSafeToFlush = isSafeToFlush && nsContentUtils::IsSafeToRunScript();
4277 // Don't flush if the doc is already in the bfcache.
4278 if (MOZ_UNLIKELY(mDocument->GetPresShell() != this)) {
4279 MOZ_DIAGNOSTIC_ASSERT(!mDocument->GetPresShell(),
4280 "Where did this shell come from?");
4281 isSafeToFlush = false;
4284 MOZ_DIAGNOSTIC_ASSERT(!mIsDestroying || !isSafeToFlush);
4285 MOZ_DIAGNOSTIC_ASSERT(mIsDestroying || mViewManager);
4286 MOZ_DIAGNOSTIC_ASSERT(mIsDestroying || mDocument->HasShellOrBFCacheEntry());
4288 // Make sure the view manager stays alive.
4289 RefPtr<nsViewManager> viewManager = mViewManager;
4290 bool didStyleFlush = false;
4291 bool didLayoutFlush = false;
4292 if (isSafeToFlush) {
4293 // Record that we are in a flush, so that our optimization in
4294 // Document::FlushPendingNotifications doesn't skip any re-entrant
4295 // calls to us. Otherwise, we might miss some needed flushes, since
4296 // we clear mNeedStyleFlush / mNeedLayoutFlush here at the top of
4297 // the function but we might not have done the work yet.
4298 AutoRestore<bool> guard(mInFlush);
4299 mInFlush = true;
4301 // We need to make sure external resource documents are flushed too (for
4302 // example, svg filters that reference a filter in an external document
4303 // need the frames in the external document to be constructed for the
4304 // filter to work). We only need external resources to be flushed when the
4305 // main document is flushing >= FlushType::Frames, so we flush external
4306 // resources here instead of Document::FlushPendingNotifications.
4307 mDocument->FlushExternalResources(flushType);
4309 // Force flushing of any pending content notifications that might have
4310 // queued up while our event was pending. That will ensure that we don't
4311 // construct frames for content right now that's still waiting to be
4312 // notified on,
4313 mDocument->FlushPendingNotifications(FlushType::ContentAndNotify);
4315 mDocument->UpdateSVGUseElementShadowTrees();
4317 // Process pending restyles, since any flush of the presshell wants
4318 // up-to-date style data.
4319 if (MOZ_LIKELY(!mIsDestroying)) {
4320 viewManager->FlushDelayedResize();
4321 mPresContext->FlushPendingMediaFeatureValuesChanged();
4324 if (MOZ_LIKELY(!mIsDestroying)) {
4325 // Now that we have flushed media queries, update the rules before looking
4326 // up @font-face / @counter-style / @font-feature-values rules.
4327 StyleSet()->UpdateStylistIfNeeded();
4329 // Flush any pending update of the user font set, since that could
4330 // cause style changes (for updating ex/ch units, and to cause a
4331 // reflow).
4332 mDocument->FlushUserFontSet();
4334 mPresContext->FlushCounterStyles();
4336 mPresContext->FlushFontFeatureValues();
4338 mPresContext->FlushFontPaletteValues();
4340 // Flush any requested SMIL samples.
4341 if (mDocument->HasAnimationController()) {
4342 mDocument->GetAnimationController()->FlushResampleRequests();
4346 // The FlushResampleRequests() above flushed style changes.
4347 if (MOZ_LIKELY(!mIsDestroying) && aFlush.mFlushAnimations &&
4348 mPresContext->EffectCompositor()) {
4349 mPresContext->EffectCompositor()->PostRestyleForThrottledAnimations();
4352 // The FlushResampleRequests() above flushed style changes.
4353 if (MOZ_LIKELY(!mIsDestroying)) {
4354 nsAutoScriptBlocker scriptBlocker;
4355 Maybe<uint64_t> innerWindowID;
4356 if (auto* window = mDocument->GetInnerWindow()) {
4357 innerWindowID = Some(window->WindowID());
4359 AutoProfilerStyleMarker tracingStyleFlush(std::move(mStyleCause),
4360 innerWindowID);
4361 PerfStats::AutoMetricRecording<PerfStats::Metric::Styling> autoRecording;
4362 LAYOUT_TELEMETRY_RECORD_BASE(Restyle);
4364 mPresContext->RestyleManager()->ProcessPendingRestyles();
4365 mNeedStyleFlush = false;
4368 AssertFrameTreeIsSane(*this);
4370 didStyleFlush = true;
4372 // There might be more pending constructors now, but we're not going to
4373 // worry about them. They can't be triggered during reflow, so we should
4374 // be good.
4376 if (flushType >= (SuppressInterruptibleReflows()
4377 ? FlushType::Layout
4378 : FlushType::InterruptibleLayout) &&
4379 !mIsDestroying) {
4380 didLayoutFlush = true;
4381 if (DoFlushLayout(/* aInterruptible = */ flushType < FlushType::Layout)) {
4382 if (mContentToScrollTo) {
4383 DoScrollContentIntoView();
4384 if (mContentToScrollTo) {
4385 mContentToScrollTo->RemoveProperty(nsGkAtoms::scrolling);
4386 mContentToScrollTo = nullptr;
4392 FlushPendingScrollResnap();
4394 if (MOZ_LIKELY(!mIsDestroying)) {
4395 // Try to trigger pending scroll-driven animations after we flush
4396 // style and layout (if any). If we try to trigger them after flushing
4397 // style but the frame tree is not ready, we will check them again after
4398 // we flush layout because the requirement to trigger scroll-driven
4399 // animations is that the associated scroll containers are ready (i.e. the
4400 // scroll-timeline is active), and this depends on the readiness of the
4401 // scrollable frame and the primary frame of the scroll container.
4402 TriggerPendingScrollTimelineAnimations(mDocument);
4405 if (flushType >= FlushType::Layout) {
4406 if (!mIsDestroying) {
4407 viewManager->UpdateWidgetGeometry();
4412 if (!didStyleFlush && flushType >= FlushType::Style && !mIsDestroying) {
4413 SetNeedStyleFlush();
4414 if (aFlush.mFlushAnimations) {
4415 SetNeedThrottledAnimationFlush();
4419 if (!didLayoutFlush && flushType >= FlushType::InterruptibleLayout &&
4420 !mIsDestroying) {
4421 // We suppressed this flush either due to it not being safe to flush,
4422 // or due to SuppressInterruptibleReflows(). Either way, the
4423 // mNeedLayoutFlush flag needs to be re-set.
4424 SetNeedLayoutFlush();
4427 // Update flush counters
4428 if (didStyleFlush) {
4429 mLayoutTelemetry.IncReqsPerFlush(FlushType::Style);
4432 if (didLayoutFlush) {
4433 mLayoutTelemetry.IncReqsPerFlush(FlushType::Layout);
4436 // Record telemetry for the number of requests per each flush type.
4438 // Flushes happen as style or style+layout. This depends upon the `flushType`
4439 // where flushType >= InterruptibleLayout means flush layout and flushType >=
4440 // Style means flush style. We only report if didLayoutFlush or didStyleFlush
4441 // is true because we care if a flush really did take place. (Flush is guarded
4442 // by `isSafeToFlush == true`.)
4443 if (flushType >= FlushType::InterruptibleLayout && didLayoutFlush) {
4444 MOZ_ASSERT(didLayoutFlush == didStyleFlush);
4445 mLayoutTelemetry.PingReqsPerFlushTelemetry(FlushType::Layout);
4446 } else if (flushType >= FlushType::Style && didStyleFlush) {
4447 MOZ_ASSERT(!didLayoutFlush);
4448 mLayoutTelemetry.PingReqsPerFlushTelemetry(FlushType::Style);
4452 MOZ_CAN_RUN_SCRIPT_BOUNDARY void PresShell::CharacterDataChanged(
4453 nsIContent* aContent, const CharacterDataChangeInfo& aInfo) {
4454 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript());
4455 MOZ_ASSERT(!mIsDocumentGone, "Unexpected CharacterDataChanged");
4456 MOZ_ASSERT(aContent->OwnerDoc() == mDocument, "Unexpected document");
4458 nsAutoCauseReflowNotifier crNotifier(this);
4460 mPresContext->RestyleManager()->CharacterDataChanged(aContent, aInfo);
4461 mFrameConstructor->CharacterDataChanged(aContent, aInfo);
4464 MOZ_CAN_RUN_SCRIPT_BOUNDARY void PresShell::ElementStateChanged(
4465 Document* aDocument, Element* aElement, ElementState aStateMask) {
4466 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript());
4467 MOZ_ASSERT(!mIsDocumentGone, "Unexpected ContentStateChanged");
4468 MOZ_ASSERT(aDocument == mDocument, "Unexpected aDocument");
4470 if (mDidInitialize) {
4471 nsAutoCauseReflowNotifier crNotifier(this);
4472 mPresContext->RestyleManager()->ElementStateChanged(aElement, aStateMask);
4476 void PresShell::DocumentStatesChanged(DocumentState aStateMask) {
4477 MOZ_ASSERT(!mIsDocumentGone, "Unexpected DocumentStatesChanged");
4478 MOZ_ASSERT(mDocument);
4479 MOZ_ASSERT(!aStateMask.IsEmpty());
4481 if (mDidInitialize) {
4482 StyleSet()->InvalidateStyleForDocumentStateChanges(aStateMask);
4485 if (aStateMask.HasState(DocumentState::WINDOW_INACTIVE)) {
4486 if (nsIFrame* root = mFrameConstructor->GetRootFrame()) {
4487 root->SchedulePaint();
4492 MOZ_CAN_RUN_SCRIPT_BOUNDARY void PresShell::AttributeWillChange(
4493 Element* aElement, int32_t aNameSpaceID, nsAtom* aAttribute,
4494 int32_t aModType) {
4495 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript());
4496 MOZ_ASSERT(!mIsDocumentGone, "Unexpected AttributeWillChange");
4497 MOZ_ASSERT(aElement->OwnerDoc() == mDocument, "Unexpected document");
4499 // XXXwaterson it might be more elegant to wait until after the
4500 // initial reflow to begin observing the document. That would
4501 // squelch any other inappropriate notifications as well.
4502 if (mDidInitialize) {
4503 nsAutoCauseReflowNotifier crNotifier(this);
4504 mPresContext->RestyleManager()->AttributeWillChange(aElement, aNameSpaceID,
4505 aAttribute, aModType);
4509 MOZ_CAN_RUN_SCRIPT_BOUNDARY void PresShell::AttributeChanged(
4510 Element* aElement, int32_t aNameSpaceID, nsAtom* aAttribute,
4511 int32_t aModType, const nsAttrValue* aOldValue) {
4512 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript());
4513 MOZ_ASSERT(!mIsDocumentGone, "Unexpected AttributeChanged");
4514 MOZ_ASSERT(aElement->OwnerDoc() == mDocument, "Unexpected document");
4516 // XXXwaterson it might be more elegant to wait until after the
4517 // initial reflow to begin observing the document. That would
4518 // squelch any other inappropriate notifications as well.
4519 if (mDidInitialize) {
4520 nsAutoCauseReflowNotifier crNotifier(this);
4521 mPresContext->RestyleManager()->AttributeChanged(
4522 aElement, aNameSpaceID, aAttribute, aModType, aOldValue);
4526 MOZ_CAN_RUN_SCRIPT_BOUNDARY void PresShell::ContentAppended(
4527 nsIContent* aFirstNewContent) {
4528 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript());
4529 MOZ_ASSERT(!mIsDocumentGone, "Unexpected ContentAppended");
4530 MOZ_ASSERT(aFirstNewContent->OwnerDoc() == mDocument, "Unexpected document");
4532 // We never call ContentAppended with a document as the container, so we can
4533 // assert that we have an nsIContent parent.
4534 MOZ_ASSERT(aFirstNewContent->GetParent());
4535 MOZ_ASSERT(aFirstNewContent->GetParent()->IsElement() ||
4536 aFirstNewContent->GetParent()->IsShadowRoot());
4538 if (!mDidInitialize) {
4539 return;
4542 nsAutoCauseReflowNotifier crNotifier(this);
4544 // Call this here so it only happens for real content mutations and
4545 // not cases when the frame constructor calls its own methods to force
4546 // frame reconstruction.
4547 mPresContext->RestyleManager()->ContentAppended(aFirstNewContent);
4549 mFrameConstructor->ContentAppended(
4550 aFirstNewContent, nsCSSFrameConstructor::InsertionKind::Async);
4553 MOZ_CAN_RUN_SCRIPT_BOUNDARY void PresShell::ContentInserted(
4554 nsIContent* aChild) {
4555 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript());
4556 MOZ_ASSERT(!mIsDocumentGone, "Unexpected ContentInserted");
4557 MOZ_ASSERT(aChild->OwnerDoc() == mDocument, "Unexpected document");
4559 if (!mDidInitialize) {
4560 return;
4563 nsAutoCauseReflowNotifier crNotifier(this);
4565 // Call this here so it only happens for real content mutations and
4566 // not cases when the frame constructor calls its own methods to force
4567 // frame reconstruction.
4568 mPresContext->RestyleManager()->ContentInserted(aChild);
4570 mFrameConstructor->ContentInserted(
4571 aChild, nsCSSFrameConstructor::InsertionKind::Async);
4574 MOZ_CAN_RUN_SCRIPT_BOUNDARY void PresShell::ContentRemoved(
4575 nsIContent* aChild, nsIContent* aPreviousSibling) {
4576 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript());
4577 MOZ_ASSERT(!mIsDocumentGone, "Unexpected ContentRemoved");
4578 MOZ_ASSERT(aChild->OwnerDoc() == mDocument, "Unexpected document");
4579 nsINode* container = aChild->GetParentNode();
4581 // Notify the ESM that the content has been removed, so that
4582 // it can clean up any state related to the content.
4584 mPresContext->EventStateManager()->ContentRemoved(mDocument, aChild);
4586 nsAutoCauseReflowNotifier crNotifier(this);
4588 // Call this here so it only happens for real content mutations and
4589 // not cases when the frame constructor calls its own methods to force
4590 // frame reconstruction.
4591 nsIContent* oldNextSibling = nullptr;
4593 // Editor calls into here with NAC via HTMLEditor::DeleteRefToAnonymousNode.
4594 // This could be asserted if that caller is fixed.
4595 if (MOZ_LIKELY(!aChild->IsRootOfNativeAnonymousSubtree())) {
4596 oldNextSibling = aPreviousSibling ? aPreviousSibling->GetNextSibling()
4597 : container->GetFirstChild();
4600 // After removing aChild from tree we should save information about live
4601 // ancestor
4602 if (mPointerEventTarget &&
4603 mPointerEventTarget->IsInclusiveDescendantOf(aChild)) {
4604 mPointerEventTarget = aChild->GetParent();
4607 mFrameConstructor->ContentRemoved(aChild, oldNextSibling,
4608 nsCSSFrameConstructor::REMOVE_CONTENT);
4610 // NOTE(emilio): It's important that this goes after the frame constructor
4611 // stuff, otherwise the frame constructor can't see elements which are
4612 // display: contents / display: none, because we'd have cleared all the style
4613 // data from there.
4614 mPresContext->RestyleManager()->ContentRemoved(aChild, oldNextSibling);
4617 void PresShell::NotifyCounterStylesAreDirty() {
4618 // TODO: Looks like that nsFrameConstructor::NotifyCounterStylesAreDirty()
4619 // does not run script. If so, we don't need to block script with
4620 // nsAutoCauseReflowNotifier here. Instead, there should be methods
4621 // and stack only class which manages only mChangeNestCount for
4622 // avoiding unnecessary `MOZ_CAN_RUN_SCRIPT` marking.
4623 nsAutoCauseReflowNotifier reflowNotifier(this);
4624 mFrameConstructor->NotifyCounterStylesAreDirty();
4627 bool PresShell::FrameIsAncestorOfDirtyRoot(nsIFrame* aFrame) const {
4628 return mDirtyRoots.FrameIsAncestorOfAnyElement(aFrame);
4631 void PresShell::ReconstructFrames() {
4632 MOZ_ASSERT(!mFrameConstructor->GetRootFrame() || mDidInitialize,
4633 "Must not have root frame before initial reflow");
4634 if (!mDidInitialize || mIsDestroying) {
4635 // Nothing to do here
4636 return;
4639 if (Element* root = mDocument->GetRootElement()) {
4640 PostRecreateFramesFor(root);
4643 mDocument->FlushPendingNotifications(FlushType::Frames);
4646 nsresult PresShell::RenderDocument(const nsRect& aRect,
4647 RenderDocumentFlags aFlags,
4648 nscolor aBackgroundColor,
4649 gfxContext* aThebesContext) {
4650 NS_ENSURE_TRUE(!(aFlags & RenderDocumentFlags::IsUntrusted),
4651 NS_ERROR_NOT_IMPLEMENTED);
4653 nsRootPresContext* rootPresContext = mPresContext->GetRootPresContext();
4654 if (rootPresContext) {
4655 rootPresContext->FlushWillPaintObservers();
4656 if (mIsDestroying) return NS_OK;
4659 nsAutoScriptBlocker blockScripts;
4661 // Set up the rectangle as the path in aThebesContext
4662 gfxRect r(0, 0, nsPresContext::AppUnitsToFloatCSSPixels(aRect.width),
4663 nsPresContext::AppUnitsToFloatCSSPixels(aRect.height));
4664 aThebesContext->NewPath();
4665 #ifdef MOZ_GFX_OPTIMIZE_MOBILE
4666 aThebesContext->SnappedRectangle(r);
4667 #else
4668 aThebesContext->Rectangle(r);
4669 #endif
4671 nsIFrame* rootFrame = mFrameConstructor->GetRootFrame();
4672 if (!rootFrame) {
4673 // Nothing to paint, just fill the rect
4674 aThebesContext->SetColor(sRGBColor::FromABGR(aBackgroundColor));
4675 aThebesContext->Fill();
4676 return NS_OK;
4679 gfxContextAutoSaveRestore save(aThebesContext);
4681 MOZ_ASSERT(aThebesContext->CurrentOp() == CompositionOp::OP_OVER);
4683 aThebesContext->Clip();
4685 nsDeviceContext* devCtx = mPresContext->DeviceContext();
4687 gfxPoint offset(-nsPresContext::AppUnitsToFloatCSSPixels(aRect.x),
4688 -nsPresContext::AppUnitsToFloatCSSPixels(aRect.y));
4689 gfxFloat scale =
4690 gfxFloat(devCtx->AppUnitsPerDevPixel()) / AppUnitsPerCSSPixel();
4692 // Since canvas APIs use floats to set up their matrices, we may have some
4693 // slight rounding errors here. We use NudgeToIntegers() here to adjust
4694 // matrix components that are integers up to the accuracy of floats to be
4695 // those integers.
4696 gfxMatrix newTM = aThebesContext->CurrentMatrixDouble()
4697 .PreTranslate(offset)
4698 .PreScale(scale, scale)
4699 .NudgeToIntegers();
4700 aThebesContext->SetMatrixDouble(newTM);
4702 AutoSaveRestoreRenderingState _(this);
4704 bool wouldFlushRetainedLayers = false;
4705 PaintFrameFlags flags = PaintFrameFlags::IgnoreSuppression;
4706 if (aThebesContext->CurrentMatrix().HasNonIntegerTranslation()) {
4707 flags |= PaintFrameFlags::InTransform;
4709 if (!(aFlags & RenderDocumentFlags::AsyncDecodeImages)) {
4710 flags |= PaintFrameFlags::SyncDecodeImages;
4712 if (aFlags & RenderDocumentFlags::UseHighQualityScaling) {
4713 flags |= PaintFrameFlags::UseHighQualityScaling;
4715 if (aFlags & RenderDocumentFlags::UseWidgetLayers) {
4716 // We only support using widget layers on display root's with widgets.
4717 nsView* view = rootFrame->GetView();
4718 if (view && view->GetWidget() &&
4719 nsLayoutUtils::GetDisplayRootFrame(rootFrame) == rootFrame) {
4720 WindowRenderer* renderer = view->GetWidget()->GetWindowRenderer();
4721 // WebRenderLayerManagers in content processes
4722 // don't support taking snapshots.
4723 if (renderer &&
4724 (!renderer->AsKnowsCompositor() || XRE_IsParentProcess())) {
4725 flags |= PaintFrameFlags::WidgetLayers;
4729 if (!(aFlags & RenderDocumentFlags::DrawCaret)) {
4730 wouldFlushRetainedLayers = true;
4731 flags |= PaintFrameFlags::HideCaret;
4733 if (aFlags & RenderDocumentFlags::IgnoreViewportScrolling) {
4734 wouldFlushRetainedLayers = !IgnoringViewportScrolling();
4735 mRenderingStateFlags |= RenderingStateFlags::IgnoringViewportScrolling;
4737 if (aFlags & RenderDocumentFlags::ResetViewportScrolling) {
4738 wouldFlushRetainedLayers = true;
4739 flags |= PaintFrameFlags::ResetViewportScrolling;
4741 if (aFlags & RenderDocumentFlags::DrawWindowNotFlushing) {
4742 mRenderingStateFlags |= RenderingStateFlags::DrawWindowNotFlushing;
4744 if (aFlags & RenderDocumentFlags::DocumentRelative) {
4745 // XXX be smarter about this ... drawWindow might want a rect
4746 // that's "pretty close" to what our retained layer tree covers.
4747 // In that case, it wouldn't disturb normal rendering too much,
4748 // and we should allow it.
4749 wouldFlushRetainedLayers = true;
4750 flags |= PaintFrameFlags::DocumentRelative;
4753 // Don't let drawWindow blow away our retained layer tree
4754 if ((flags & PaintFrameFlags::WidgetLayers) && wouldFlushRetainedLayers) {
4755 flags &= ~PaintFrameFlags::WidgetLayers;
4758 nsLayoutUtils::PaintFrame(aThebesContext, rootFrame, nsRegion(aRect),
4759 aBackgroundColor,
4760 nsDisplayListBuilderMode::Painting, flags);
4762 return NS_OK;
4766 * Clip the display list aList to a range. Returns the clipped
4767 * rectangle surrounding the range.
4769 nsRect PresShell::ClipListToRange(nsDisplayListBuilder* aBuilder,
4770 nsDisplayList* aList, nsRange* aRange) {
4771 // iterate though the display items and add up the bounding boxes of each.
4772 // This will allow the total area of the frames within the range to be
4773 // determined. To do this, remove an item from the bottom of the list, check
4774 // whether it should be part of the range, and if so, append it to the top
4775 // of the temporary list tmpList. If the item is a text frame at the end of
4776 // the selection range, clip it to the portion of the text frame that is
4777 // part of the selection. Then, append the wrapper to the top of the list.
4778 // Otherwise, just delete the item and don't append it.
4779 nsRect surfaceRect;
4781 for (nsDisplayItem* i : aList->TakeItems()) {
4782 if (i->GetType() == DisplayItemType::TYPE_CONTAINER) {
4783 aList->AppendToTop(i);
4784 surfaceRect.UnionRect(
4785 surfaceRect, ClipListToRange(aBuilder, i->GetChildren(), aRange));
4786 continue;
4789 // itemToInsert indiciates the item that should be inserted into the
4790 // temporary list. If null, no item should be inserted.
4791 nsDisplayItem* itemToInsert = nullptr;
4792 nsIFrame* frame = i->Frame();
4793 nsIContent* content = frame->GetContent();
4794 if (content) {
4795 bool atStart = (content == aRange->GetStartContainer());
4796 bool atEnd = (content == aRange->GetEndContainer());
4797 if ((atStart || atEnd) && frame->IsTextFrame()) {
4798 auto [frameStartOffset, frameEndOffset] = frame->GetOffsets();
4800 int32_t hilightStart =
4801 atStart ? std::max(static_cast<int32_t>(aRange->StartOffset()),
4802 frameStartOffset)
4803 : frameStartOffset;
4804 int32_t hilightEnd =
4805 atEnd ? std::min(static_cast<int32_t>(aRange->EndOffset()),
4806 frameEndOffset)
4807 : frameEndOffset;
4808 if (hilightStart < hilightEnd) {
4809 // determine the location of the start and end edges of the range.
4810 nsPoint startPoint, endPoint;
4811 frame->GetPointFromOffset(hilightStart, &startPoint);
4812 frame->GetPointFromOffset(hilightEnd, &endPoint);
4814 // The clip rectangle is determined by taking the the start and
4815 // end points of the range, offset from the reference frame.
4816 // Because of rtl, the end point may be to the left of (or above,
4817 // in vertical mode) the start point, so x (or y) is set to the
4818 // lower of the values.
4819 nsRect textRect(aBuilder->ToReferenceFrame(frame), frame->GetSize());
4820 if (frame->GetWritingMode().IsVertical()) {
4821 nscoord y = std::min(startPoint.y, endPoint.y);
4822 textRect.y += y;
4823 textRect.height = std::max(startPoint.y, endPoint.y) - y;
4824 } else {
4825 nscoord x = std::min(startPoint.x, endPoint.x);
4826 textRect.x += x;
4827 textRect.width = std::max(startPoint.x, endPoint.x) - x;
4829 surfaceRect.UnionRect(surfaceRect, textRect);
4831 const ActiveScrolledRoot* asr = i->GetActiveScrolledRoot();
4833 DisplayItemClip newClip;
4834 newClip.SetTo(textRect);
4836 const DisplayItemClipChain* newClipChain =
4837 aBuilder->AllocateDisplayItemClipChain(newClip, asr, nullptr);
4839 i->IntersectClip(aBuilder, newClipChain, true);
4840 itemToInsert = i;
4843 // Don't try to descend into subdocuments.
4844 // If this ever changes we'd need to add handling for subdocuments with
4845 // different zoom levels.
4846 else if (content->GetUncomposedDoc() ==
4847 aRange->GetStartContainer()->GetUncomposedDoc()) {
4848 // if the node is within the range, append it to the temporary list
4849 bool before, after;
4850 nsresult rv =
4851 RangeUtils::CompareNodeToRange(content, aRange, &before, &after);
4852 if (NS_SUCCEEDED(rv) && !before && !after) {
4853 itemToInsert = i;
4854 bool snap;
4855 surfaceRect.UnionRect(surfaceRect, i->GetBounds(aBuilder, &snap));
4860 // insert the item into the list if necessary. If the item has a child
4861 // list, insert that as well
4862 nsDisplayList* sublist = i->GetSameCoordinateSystemChildren();
4863 if (itemToInsert || sublist) {
4864 aList->AppendToTop(itemToInsert ? itemToInsert : i);
4865 // if the item is a list, iterate over it as well
4866 if (sublist)
4867 surfaceRect.UnionRect(surfaceRect,
4868 ClipListToRange(aBuilder, sublist, aRange));
4869 } else {
4870 // otherwise, just delete the item and don't readd it to the list
4871 i->Destroy(aBuilder);
4875 return surfaceRect;
4878 #ifdef DEBUG
4879 # include <stdio.h>
4881 static bool gDumpRangePaintList = false;
4882 #endif
4884 UniquePtr<RangePaintInfo> PresShell::CreateRangePaintInfo(
4885 nsRange* aRange, nsRect& aSurfaceRect, bool aForPrimarySelection) {
4886 nsIFrame* ancestorFrame = nullptr;
4887 nsIFrame* rootFrame = GetRootFrame();
4889 // If the start or end of the range is the document, just use the root
4890 // frame, otherwise get the common ancestor of the two endpoints of the
4891 // range.
4892 nsINode* startContainer = aRange->GetStartContainer();
4893 nsINode* endContainer = aRange->GetEndContainer();
4894 Document* doc = startContainer->GetComposedDoc();
4895 if (startContainer == doc || endContainer == doc) {
4896 ancestorFrame = rootFrame;
4897 } else {
4898 nsINode* ancestor = nsContentUtils::GetClosestCommonInclusiveAncestor(
4899 startContainer, endContainer);
4900 NS_ASSERTION(!ancestor || ancestor->IsContent(),
4901 "common ancestor is not content");
4903 while (ancestor && ancestor->IsContent()) {
4904 ancestorFrame = ancestor->AsContent()->GetPrimaryFrame();
4905 if (ancestorFrame) {
4906 break;
4909 ancestor = ancestor->GetParentOrShadowHostNode();
4912 // use the nearest ancestor frame that includes all continuations as the
4913 // root for building the display list
4914 while (ancestorFrame &&
4915 nsLayoutUtils::GetNextContinuationOrIBSplitSibling(ancestorFrame))
4916 ancestorFrame = ancestorFrame->GetParent();
4919 if (!ancestorFrame) {
4920 return nullptr;
4923 // get a display list containing the range
4924 auto info = MakeUnique<RangePaintInfo>(aRange, ancestorFrame);
4925 info->mBuilder.SetIncludeAllOutOfFlows();
4926 if (aForPrimarySelection) {
4927 info->mBuilder.SetSelectedFramesOnly();
4929 info->mBuilder.EnterPresShell(ancestorFrame);
4931 ContentSubtreeIterator subtreeIter;
4932 nsresult rv = subtreeIter.Init(aRange);
4933 if (NS_FAILED(rv)) {
4934 return nullptr;
4937 auto BuildDisplayListForNode = [&](nsINode* aNode) {
4938 if (MOZ_UNLIKELY(!aNode->IsContent())) {
4939 return;
4941 nsIFrame* frame = aNode->AsContent()->GetPrimaryFrame();
4942 // XXX deal with frame being null due to display:contents
4943 for (; frame;
4944 frame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(frame)) {
4945 info->mBuilder.SetVisibleRect(frame->InkOverflowRect());
4946 info->mBuilder.SetDirtyRect(frame->InkOverflowRect());
4947 frame->BuildDisplayListForStackingContext(&info->mBuilder, &info->mList);
4950 if (startContainer->NodeType() == nsINode::TEXT_NODE) {
4951 BuildDisplayListForNode(startContainer);
4953 for (; !subtreeIter.IsDone(); subtreeIter.Next()) {
4954 nsCOMPtr<nsINode> node = subtreeIter.GetCurrentNode();
4955 BuildDisplayListForNode(node);
4957 if (endContainer != startContainer &&
4958 endContainer->NodeType() == nsINode::TEXT_NODE) {
4959 BuildDisplayListForNode(endContainer);
4962 // If one of the ancestor presShells (including this one) has a resolution
4963 // set, we may have some APZ zoom applied. That means we may want to rasterize
4964 // the nodes at that zoom level. Populate `info` with the relevant information
4965 // so that the caller can decide what to do. Also wrap the display list in
4966 // appropriate nsDisplayAsyncZoom display items. This code handles the general
4967 // case with nested async zooms (even though that never actually happens),
4968 // because it fell out of the implementation for free.
4970 // TODO: Do we need to do the same for ancestor transforms?
4971 for (nsPresContext* ctx = GetPresContext(); ctx;
4972 ctx = ctx->GetParentPresContext()) {
4973 PresShell* shell = ctx->PresShell();
4974 float resolution = shell->GetResolution();
4976 // If we are at the root document in the process, try to see if documents
4977 // in enclosing processes have a resolution and include that as well.
4978 if (!ctx->GetParentPresContext()) {
4979 // xScale is an arbitrary choice. Outside of edge cases involving CSS
4980 // transforms, xScale == yScale so it doesn't matter.
4981 resolution *= ViewportUtils::TryInferEnclosingResolution(shell).xScale;
4984 if (resolution == 1.0) {
4985 continue;
4988 info->mResolution *= resolution;
4989 nsIFrame* rootScrollFrame = shell->GetRootScrollFrame();
4990 ViewID zoomedId =
4991 nsLayoutUtils::FindOrCreateIDFor(rootScrollFrame->GetContent());
4993 nsDisplayList wrapped(&info->mBuilder);
4994 wrapped.AppendNewToTop<nsDisplayAsyncZoom>(&info->mBuilder, rootScrollFrame,
4995 &info->mList, nullptr, zoomedId);
4996 info->mList.AppendToTop(&wrapped);
4999 #ifdef DEBUG
5000 if (gDumpRangePaintList) {
5001 fprintf(stderr, "CreateRangePaintInfo --- before ClipListToRange:\n");
5002 nsIFrame::PrintDisplayList(&(info->mBuilder), info->mList);
5004 #endif
5006 nsRect rangeRect = ClipListToRange(&info->mBuilder, &info->mList, aRange);
5008 info->mBuilder.LeavePresShell(ancestorFrame, &info->mList);
5010 #ifdef DEBUG
5011 if (gDumpRangePaintList) {
5012 fprintf(stderr, "CreateRangePaintInfo --- after ClipListToRange:\n");
5013 nsIFrame::PrintDisplayList(&(info->mBuilder), info->mList);
5015 #endif
5017 // determine the offset of the reference frame for the display list
5018 // to the root frame. This will allow the coordinates used when painting
5019 // to all be offset from the same point
5020 info->mRootOffset = ancestorFrame->GetBoundingClientRect().TopLeft();
5021 rangeRect.MoveBy(info->mRootOffset);
5022 aSurfaceRect.UnionRect(aSurfaceRect, rangeRect);
5024 return info;
5027 already_AddRefed<SourceSurface> PresShell::PaintRangePaintInfo(
5028 const nsTArray<UniquePtr<RangePaintInfo>>& aItems, Selection* aSelection,
5029 const Maybe<CSSIntRegion>& aRegion, nsRect aArea,
5030 const LayoutDeviceIntPoint aPoint, LayoutDeviceIntRect* aScreenRect,
5031 RenderImageFlags aFlags) {
5032 nsPresContext* pc = GetPresContext();
5033 if (!pc || aArea.width == 0 || aArea.height == 0) return nullptr;
5035 // use the rectangle to create the surface
5036 LayoutDeviceIntRect pixelArea = LayoutDeviceIntRect::FromAppUnitsToOutside(
5037 aArea, pc->AppUnitsPerDevPixel());
5039 // if the image should not be resized, scale must be 1
5040 float scale = 1.0;
5042 nsRect maxSize;
5043 pc->DeviceContext()->GetClientRect(maxSize);
5045 // check if the image should be resized
5046 bool resize = !!(aFlags & RenderImageFlags::AutoScale);
5048 if (resize) {
5049 // check if image-resizing-algorithm should be used
5050 if (aFlags & RenderImageFlags::IsImage) {
5051 // get max screensize
5052 int32_t maxWidth = pc->AppUnitsToDevPixels(maxSize.width);
5053 int32_t maxHeight = pc->AppUnitsToDevPixels(maxSize.height);
5054 // resize image relative to the screensize
5055 // get best height/width relative to screensize
5056 float bestHeight = float(maxHeight) * RELATIVE_SCALEFACTOR;
5057 float bestWidth = float(maxWidth) * RELATIVE_SCALEFACTOR;
5058 // calculate scale for bestWidth
5059 float adjustedScale = bestWidth / float(pixelArea.width);
5060 // get the worst height (height when width is perfect)
5061 float worstHeight = float(pixelArea.height) * adjustedScale;
5062 // get the difference of best and worst height
5063 float difference = bestHeight - worstHeight;
5064 // halve the difference and add it to worstHeight to get
5065 // the best compromise between bestHeight and bestWidth,
5066 // then calculate the corresponding scale factor
5067 adjustedScale = (worstHeight + difference / 2) / float(pixelArea.height);
5068 // prevent upscaling
5069 scale = std::min(scale, adjustedScale);
5070 } else {
5071 // get half of max screensize
5072 int32_t maxWidth = pc->AppUnitsToDevPixels(maxSize.width >> 1);
5073 int32_t maxHeight = pc->AppUnitsToDevPixels(maxSize.height >> 1);
5074 if (pixelArea.width > maxWidth || pixelArea.height > maxHeight) {
5075 // divide the maximum size by the image size in both directions.
5076 // Whichever direction produces the smallest result determines how much
5077 // should be scaled.
5078 if (pixelArea.width > maxWidth)
5079 scale = std::min(scale, float(maxWidth) / pixelArea.width);
5080 if (pixelArea.height > maxHeight)
5081 scale = std::min(scale, float(maxHeight) / pixelArea.height);
5085 // Pick a resolution scale factor that is the highest we need for any of
5086 // the items. This means some items may get rendered at a higher-than-needed
5087 // resolution but at least nothing will be avoidably blurry.
5088 float resolutionScale = 1.0;
5089 for (const UniquePtr<RangePaintInfo>& rangeInfo : aItems) {
5090 resolutionScale = std::max(resolutionScale, rangeInfo->mResolution);
5092 float unclampedResolution = resolutionScale;
5093 // Clamp the resolution scale so that `pixelArea` when scaled by `scale` and
5094 // `resolutionScale` isn't bigger than `maxSize`. This prevents creating
5095 // giant/unbounded images.
5096 resolutionScale =
5097 std::min(resolutionScale, maxSize.width / (scale * pixelArea.width));
5098 resolutionScale =
5099 std::min(resolutionScale, maxSize.height / (scale * pixelArea.height));
5100 // The following assert should only get hit if pixelArea scaled by `scale`
5101 // alone would already have been bigger than `maxSize`, which should never
5102 // be the case. For release builds we handle gracefully by reverting
5103 // resolutionScale to 1.0 to avoid unexpected consequences.
5104 MOZ_ASSERT(resolutionScale >= 1.0);
5105 resolutionScale = std::max(1.0f, resolutionScale);
5107 scale *= resolutionScale;
5109 // Now we need adjust the output screen position of the surface based on the
5110 // scaling factor and any APZ zoom that may be in effect. The goal is here
5111 // to set `aScreenRect`'s top-left corner (in screen-relative LD pixels)
5112 // such that the scaling effect on the surface appears anchored at `aPoint`
5113 // ("anchor" here is like "transform-origin"). When this code is e.g. used
5114 // to generate a drag image for dragging operations, `aPoint` refers to the
5115 // position of the mouse cursor (also in screen-relative LD pixels), and the
5116 // user-visible effect of doing this is that the point at which the user
5117 // clicked to start the drag remains under the mouse during the drag.
5119 // In order to do this we first compute the top-left corner of the
5120 // pixelArea is screen-relative LD pixels.
5121 LayoutDevicePoint visualPoint = ViewportUtils::ToScreenRelativeVisual(
5122 LayoutDevicePoint(pixelArea.TopLeft()), pc);
5123 // And then adjust the output screen position based on that, which we can do
5124 // since everything here is screen-relative LD pixels. Note that the scale
5125 // factor we use here is the effective "transform" scale applied to the
5126 // content we're painting, relative to the scale at which it would normally
5127 // get painted at as part of page rendering (`unclampedResolution`).
5128 float scaleRelativeToNormalContent = scale / unclampedResolution;
5129 aScreenRect->x =
5130 NSToIntFloor(aPoint.x - float(aPoint.x.value - visualPoint.x.value) *
5131 scaleRelativeToNormalContent);
5132 aScreenRect->y =
5133 NSToIntFloor(aPoint.y - float(aPoint.y.value - visualPoint.y.value) *
5134 scaleRelativeToNormalContent);
5136 pixelArea.width = NSToIntFloor(float(pixelArea.width) * scale);
5137 pixelArea.height = NSToIntFloor(float(pixelArea.height) * scale);
5138 if (!pixelArea.width || !pixelArea.height) {
5139 return nullptr;
5141 } else {
5142 // move aScreenRect to the position of the surface in screen coordinates
5143 LayoutDevicePoint visualPoint = ViewportUtils::ToScreenRelativeVisual(
5144 LayoutDevicePoint(pixelArea.TopLeft()), pc);
5145 aScreenRect->MoveTo(RoundedToInt(visualPoint));
5147 aScreenRect->width = pixelArea.width;
5148 aScreenRect->height = pixelArea.height;
5150 RefPtr<DrawTarget> dt =
5151 gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
5152 IntSize(pixelArea.width, pixelArea.height), SurfaceFormat::B8G8R8A8);
5153 if (!dt || !dt->IsValid()) {
5154 return nullptr;
5157 gfxContext ctx(dt);
5159 if (aRegion) {
5160 RefPtr<PathBuilder> builder = dt->CreatePathBuilder(FillRule::FILL_WINDING);
5162 // Convert aRegion from CSS pixels to dev pixels
5163 nsIntRegion region = aRegion->ToAppUnits(AppUnitsPerCSSPixel())
5164 .ToOutsidePixels(pc->AppUnitsPerDevPixel());
5165 for (auto iter = region.RectIter(); !iter.Done(); iter.Next()) {
5166 const IntRect& rect = iter.Get();
5168 builder->MoveTo(rect.TopLeft());
5169 builder->LineTo(rect.TopRight());
5170 builder->LineTo(rect.BottomRight());
5171 builder->LineTo(rect.BottomLeft());
5172 builder->LineTo(rect.TopLeft());
5175 RefPtr<Path> path = builder->Finish();
5176 ctx.Clip(path);
5179 gfxMatrix initialTM = ctx.CurrentMatrixDouble();
5181 if (resize) {
5182 initialTM.PreScale(scale, scale);
5185 // translate so that points are relative to the surface area
5186 gfxPoint surfaceOffset = nsLayoutUtils::PointToGfxPoint(
5187 -aArea.TopLeft(), pc->AppUnitsPerDevPixel());
5188 initialTM.PreTranslate(surfaceOffset);
5190 // temporarily hide the selection so that text is drawn normally. If a
5191 // selection is being rendered, use that, otherwise use the presshell's
5192 // selection.
5193 RefPtr<nsFrameSelection> frameSelection;
5194 if (aSelection) {
5195 frameSelection = aSelection->GetFrameSelection();
5196 } else {
5197 frameSelection = FrameSelection();
5199 int16_t oldDisplaySelection = frameSelection->GetDisplaySelection();
5200 frameSelection->SetDisplaySelection(nsISelectionController::SELECTION_HIDDEN);
5202 // next, paint each range in the selection
5203 for (const UniquePtr<RangePaintInfo>& rangeInfo : aItems) {
5204 // the display lists paint relative to the offset from the reference
5205 // frame, so account for that translation too:
5206 gfxPoint rootOffset = nsLayoutUtils::PointToGfxPoint(
5207 rangeInfo->mRootOffset, pc->AppUnitsPerDevPixel());
5208 ctx.SetMatrixDouble(initialTM.PreTranslate(rootOffset));
5209 aArea.MoveBy(-rangeInfo->mRootOffset.x, -rangeInfo->mRootOffset.y);
5210 nsRegion visible(aArea);
5211 rangeInfo->mList.PaintRoot(&rangeInfo->mBuilder, &ctx,
5212 nsDisplayList::PAINT_DEFAULT, Nothing());
5213 aArea.MoveBy(rangeInfo->mRootOffset.x, rangeInfo->mRootOffset.y);
5216 // restore the old selection display state
5217 frameSelection->SetDisplaySelection(oldDisplaySelection);
5219 return dt->Snapshot();
5222 already_AddRefed<SourceSurface> PresShell::RenderNode(
5223 nsINode* aNode, const Maybe<CSSIntRegion>& aRegion,
5224 const LayoutDeviceIntPoint aPoint, LayoutDeviceIntRect* aScreenRect,
5225 RenderImageFlags aFlags) {
5226 // area will hold the size of the surface needed to draw the node, measured
5227 // from the root frame.
5228 nsRect area;
5229 nsTArray<UniquePtr<RangePaintInfo>> rangeItems;
5231 // nothing to draw if the node isn't in a document
5232 if (!aNode->IsInComposedDoc()) {
5233 return nullptr;
5236 RefPtr<nsRange> range = nsRange::Create(aNode);
5237 IgnoredErrorResult rv;
5238 range->SelectNode(*aNode, rv);
5239 if (rv.Failed()) {
5240 return nullptr;
5243 UniquePtr<RangePaintInfo> info = CreateRangePaintInfo(range, area, false);
5244 if (info) {
5245 // XXX(Bug 1631371) Check if this should use a fallible operation as it
5246 // pretended earlier, or change the return type to void.
5247 rangeItems.AppendElement(std::move(info));
5250 Maybe<CSSIntRegion> region = aRegion;
5251 if (region) {
5252 // combine the area with the supplied region
5253 CSSIntRect rrectPixels = region->GetBounds();
5255 nsRect rrect = ToAppUnits(rrectPixels, AppUnitsPerCSSPixel());
5256 area.IntersectRect(area, rrect);
5258 nsPresContext* pc = GetPresContext();
5259 if (!pc) return nullptr;
5261 // move the region so that it is offset from the topleft corner of the
5262 // surface
5263 region->MoveBy(-nsPresContext::AppUnitsToIntCSSPixels(area.x),
5264 -nsPresContext::AppUnitsToIntCSSPixels(area.y));
5267 return PaintRangePaintInfo(rangeItems, nullptr, region, area, aPoint,
5268 aScreenRect, aFlags);
5271 already_AddRefed<SourceSurface> PresShell::RenderSelection(
5272 Selection* aSelection, const LayoutDeviceIntPoint aPoint,
5273 LayoutDeviceIntRect* aScreenRect, RenderImageFlags aFlags) {
5274 // area will hold the size of the surface needed to draw the selection,
5275 // measured from the root frame.
5276 nsRect area;
5277 nsTArray<UniquePtr<RangePaintInfo>> rangeItems;
5279 // iterate over each range and collect them into the rangeItems array.
5280 // This is done so that the size of selection can be determined so as
5281 // to allocate a surface area
5282 const uint32_t rangeCount = aSelection->RangeCount();
5283 NS_ASSERTION(rangeCount > 0, "RenderSelection called with no selection");
5284 for (const uint32_t r : IntegerRange(rangeCount)) {
5285 MOZ_ASSERT(aSelection->RangeCount() == rangeCount);
5286 RefPtr<nsRange> range = aSelection->GetRangeAt(r);
5288 UniquePtr<RangePaintInfo> info = CreateRangePaintInfo(range, area, true);
5289 if (info) {
5290 // XXX(Bug 1631371) Check if this should use a fallible operation as it
5291 // pretended earlier.
5292 rangeItems.AppendElement(std::move(info));
5296 return PaintRangePaintInfo(rangeItems, aSelection, Nothing(), area, aPoint,
5297 aScreenRect, aFlags);
5300 void AddDisplayItemToBottom(nsDisplayListBuilder* aBuilder,
5301 nsDisplayList* aList, nsDisplayItem* aItem) {
5302 if (!aItem) {
5303 return;
5306 nsDisplayList list(aBuilder);
5307 list.AppendToTop(aItem);
5308 list.AppendToTop(aList);
5309 aList->AppendToTop(&list);
5312 static bool AddCanvasBackgroundColor(const nsDisplayList* aList,
5313 nsIFrame* aCanvasFrame, nscolor aColor,
5314 bool aCSSBackgroundColor) {
5315 for (nsDisplayItem* i : *aList) {
5316 const DisplayItemType type = i->GetType();
5318 if (i->Frame() == aCanvasFrame &&
5319 type == DisplayItemType::TYPE_CANVAS_BACKGROUND_COLOR) {
5320 auto* bg = static_cast<nsDisplayCanvasBackgroundColor*>(i);
5321 bg->SetExtraBackgroundColor(aColor);
5322 return true;
5325 const bool isBlendContainer =
5326 type == DisplayItemType::TYPE_BLEND_CONTAINER ||
5327 type == DisplayItemType::TYPE_TABLE_BLEND_CONTAINER;
5329 nsDisplayList* sublist = i->GetSameCoordinateSystemChildren();
5330 if (sublist && !(isBlendContainer && !aCSSBackgroundColor) &&
5331 AddCanvasBackgroundColor(sublist, aCanvasFrame, aColor,
5332 aCSSBackgroundColor)) {
5333 return true;
5336 return false;
5339 void PresShell::AddCanvasBackgroundColorItem(nsDisplayListBuilder* aBuilder,
5340 nsDisplayList* aList,
5341 nsIFrame* aFrame,
5342 const nsRect& aBounds,
5343 nscolor aBackstopColor) {
5344 if (aBounds.IsEmpty()) {
5345 return;
5347 const bool isViewport = aFrame->IsViewportFrame();
5348 nscolor canvasColor;
5349 if (isViewport) {
5350 canvasColor = mCanvasBackground.mViewportColor;
5351 } else if (aFrame->IsPageContentFrame()) {
5352 canvasColor = mCanvasBackground.mPageColor;
5353 } else {
5354 // We don't want to add an item for the canvas background color if the frame
5355 // (sub)tree we are painting doesn't include any canvas frames.
5356 return;
5358 const nscolor bgcolor = NS_ComposeColors(aBackstopColor, canvasColor);
5359 if (NS_GET_A(bgcolor) == 0) {
5360 return;
5363 // To make layers work better, we want to avoid having a big non-scrolled
5364 // color background behind a scrolled transparent background. Instead, we'll
5365 // try to move the color background into the scrolled content by making
5366 // nsDisplayCanvasBackground paint it.
5367 bool addedScrollingBackgroundColor = false;
5368 if (isViewport) {
5369 if (nsIScrollableFrame* sf = GetRootScrollFrameAsScrollable()) {
5370 nsCanvasFrame* canvasFrame = do_QueryFrame(sf->GetScrolledFrame());
5371 if (canvasFrame && canvasFrame->IsVisibleForPainting()) {
5372 // TODO: We should be able to set canvas background color during display
5373 // list building to avoid calling this function.
5374 addedScrollingBackgroundColor = AddCanvasBackgroundColor(
5375 aList, canvasFrame, bgcolor, mCanvasBackground.mCSSSpecified);
5380 // With async scrolling, we'd like to have two instances of the background
5381 // color: one that scrolls with the content (for the reasons stated above),
5382 // and one underneath which does not scroll with the content, but which can
5383 // be shown during checkerboarding and overscroll and the dynamic toolbar
5384 // movement.
5385 // We can only do that if the color is opaque.
5386 bool forceUnscrolledItem =
5387 nsLayoutUtils::UsesAsyncScrolling(aFrame) && NS_GET_A(bgcolor) == 255;
5389 if (!addedScrollingBackgroundColor || forceUnscrolledItem) {
5390 const bool isRootContentDocumentCrossProcess =
5391 mPresContext->IsRootContentDocumentCrossProcess();
5392 MOZ_ASSERT_IF(
5393 !aFrame->GetParent() && isRootContentDocumentCrossProcess &&
5394 mPresContext->HasDynamicToolbar(),
5395 aBounds.Size() ==
5396 nsLayoutUtils::ExpandHeightForDynamicToolbar(
5397 mPresContext, aFrame->InkOverflowRectRelativeToSelf().Size()));
5399 nsDisplaySolidColor* item = MakeDisplayItem<nsDisplaySolidColor>(
5400 aBuilder, aFrame, aBounds, bgcolor);
5401 if (addedScrollingBackgroundColor && isRootContentDocumentCrossProcess) {
5402 item->SetIsCheckerboardBackground();
5404 AddDisplayItemToBottom(aBuilder, aList, item);
5408 bool PresShell::IsTransparentContainerElement() const {
5409 if (mDocument->IsInitialDocument()) {
5410 switch (StaticPrefs::layout_css_initial_document_transparency()) {
5411 case 3:
5412 return true;
5413 case 2:
5414 if (!mDocument->IsTopLevelContentDocument()) {
5415 return true;
5417 [[fallthrough]];
5418 case 1:
5419 if (mDocument->IsLikelyContentInaccessibleTopLevelAboutBlank()) {
5420 return true;
5422 [[fallthrough]];
5423 default:
5424 break;
5428 nsPresContext* pc = GetPresContext();
5429 if (!pc->IsRootContentDocumentCrossProcess()) {
5430 if (mDocument->IsInChromeDocShell()) {
5431 return true;
5433 // Frames are transparent except if their used embedder color-scheme is
5434 // mismatched, in which case we use an opaque background to avoid
5435 // black-on-black or white-on-white text, see
5436 // https://github.com/w3c/csswg-drafts/issues/4772
5437 if (BrowsingContext* bc = mDocument->GetBrowsingContext()) {
5438 switch (bc->GetEmbedderColorSchemes().mUsed) {
5439 case dom::PrefersColorSchemeOverride::Light:
5440 return pc->DefaultBackgroundColorScheme() == ColorScheme::Light;
5441 case dom::PrefersColorSchemeOverride::Dark:
5442 return pc->DefaultBackgroundColorScheme() == ColorScheme::Dark;
5443 case dom::PrefersColorSchemeOverride::None:
5444 break;
5447 return true;
5450 nsIDocShell* docShell = pc->GetDocShell();
5451 if (!docShell) {
5452 return false;
5454 nsPIDOMWindowOuter* pwin = docShell->GetWindow();
5455 if (!pwin) {
5456 return false;
5458 if (Element* containerElement = pwin->GetFrameElementInternal()) {
5459 return containerElement->HasAttr(nsGkAtoms::transparent);
5461 if (BrowserChild* tab = BrowserChild::GetFrom(docShell)) {
5462 // Check if presShell is the top PresShell. Only the top can influence the
5463 // canvas background color.
5464 return this == tab->GetTopLevelPresShell() && tab->IsTransparent();
5466 return false;
5469 nscolor PresShell::GetDefaultBackgroundColorToDraw() const {
5470 if (!mPresContext) {
5471 return NS_RGB(255, 255, 255);
5473 return mPresContext->DefaultBackgroundColor();
5476 void PresShell::UpdateCanvasBackground() {
5477 mCanvasBackground = ComputeCanvasBackground();
5480 struct SingleCanvasBackground {
5481 nscolor mColor = 0;
5482 bool mCSSSpecified = false;
5485 static SingleCanvasBackground ComputeSingleCanvasBackground(nsIFrame* aCanvas) {
5486 MOZ_ASSERT(aCanvas->IsCanvasFrame());
5487 const nsIFrame* bgFrame = nsCSSRendering::FindBackgroundFrame(aCanvas);
5488 nscolor color = NS_RGBA(0, 0, 0, 0);
5489 bool drawBackgroundImage = false;
5490 bool drawBackgroundColor = false;
5491 if (!bgFrame->IsThemed()) {
5492 // Ignore the CSS background-color if -moz-appearance is used.
5493 color = nsCSSRendering::DetermineBackgroundColor(
5494 aCanvas->PresContext(), bgFrame->Style(), aCanvas, drawBackgroundImage,
5495 drawBackgroundColor);
5497 return {color, drawBackgroundColor};
5500 PresShell::CanvasBackground PresShell::ComputeCanvasBackground() const {
5501 // If we have a frame tree and it has style information that
5502 // specifies the background color of the canvas, update our local
5503 // cache of that color.
5504 nsIFrame* canvas = GetCanvasFrame();
5505 if (!canvas) {
5506 nscolor color = GetDefaultBackgroundColorToDraw();
5507 // If the root element of the document (ie html) has style 'display: none'
5508 // then the document's background color does not get drawn; return the color
5509 // we actually draw.
5510 return {color, color, false};
5513 auto viewportBg = ComputeSingleCanvasBackground(canvas);
5514 if (!IsTransparentContainerElement()) {
5515 viewportBg.mColor =
5516 NS_ComposeColors(GetDefaultBackgroundColorToDraw(), viewportBg.mColor);
5518 nscolor pageColor = viewportBg.mColor;
5519 nsCanvasFrame* docElementCb =
5520 mFrameConstructor->GetDocElementContainingBlock();
5521 if (canvas != docElementCb) {
5522 // We're in paged mode / print / print-preview, and just computed the "root"
5523 // canvas background. Compute the doc element containing block background
5524 // too.
5525 MOZ_ASSERT(mPresContext->IsRootPaginatedDocument());
5526 pageColor = ComputeSingleCanvasBackground(docElementCb).mColor;
5528 return {viewportBg.mColor, pageColor, viewportBg.mCSSSpecified};
5531 nscolor PresShell::ComputeBackstopColor(nsView* aDisplayRoot) {
5532 nsIWidget* widget = aDisplayRoot->GetWidget();
5533 if (widget &&
5534 (widget->GetTransparencyMode() != widget::TransparencyMode::Opaque ||
5535 widget->WidgetPaintsBackground())) {
5536 // Within a transparent widget, so the backstop color must be
5537 // totally transparent.
5538 return NS_RGBA(0, 0, 0, 0);
5540 // Within an opaque widget (or no widget at all), so the backstop
5541 // color must be totally opaque. The user's default background
5542 // as reported by the prescontext is guaranteed to be opaque.
5543 return GetDefaultBackgroundColorToDraw();
5546 struct PaintParams {
5547 nscolor mBackgroundColor;
5550 WindowRenderer* PresShell::GetWindowRenderer() {
5551 NS_ASSERTION(mViewManager, "Should have view manager");
5553 nsView* rootView = mViewManager->GetRootView();
5554 if (rootView) {
5555 if (nsIWidget* widget = rootView->GetWidget()) {
5556 return widget->GetWindowRenderer();
5559 return nullptr;
5562 bool PresShell::AsyncPanZoomEnabled() {
5563 NS_ASSERTION(mViewManager, "Should have view manager");
5564 nsView* rootView = mViewManager->GetRootView();
5565 if (rootView) {
5566 if (nsIWidget* widget = rootView->GetWidget()) {
5567 return widget->AsyncPanZoomEnabled();
5570 return gfxPlatform::AsyncPanZoomEnabled();
5573 nsresult PresShell::SetResolutionAndScaleTo(float aResolution,
5574 ResolutionChangeOrigin aOrigin) {
5575 if (!(aResolution > 0.0)) {
5576 return NS_ERROR_ILLEGAL_VALUE;
5578 if (aResolution == mResolution.valueOr(0.0)) {
5579 MOZ_ASSERT(mResolution.isSome());
5580 return NS_OK;
5583 // GetResolution handles mResolution being nothing by returning 1 so this
5584 // is checking that the resolution is actually changing.
5585 bool resolutionUpdated = (aResolution != GetResolution());
5587 mLastResolutionChangeOrigin = aOrigin;
5589 RenderingState state(this);
5590 state.mResolution = Some(aResolution);
5591 SetRenderingState(state);
5592 if (mMobileViewportManager) {
5593 mMobileViewportManager->ResolutionUpdated(aOrigin);
5595 // Changing the resolution changes the visual viewport size which may
5596 // make the current visual viewport offset out-of-bounds (if the size
5597 // increased). APZ will reconcile this by sending a clamped visual
5598 // viewport offset on the next repaint, but to avoid main-thread code
5599 // observing an out-of-bounds offset until then, reclamp it here.
5600 if (IsVisualViewportOffsetSet()) {
5601 SetVisualViewportOffset(GetVisualViewportOffset(),
5602 GetLayoutViewportOffset());
5604 if (aOrigin == ResolutionChangeOrigin::Apz) {
5605 mResolutionUpdatedByApz = true;
5606 } else if (resolutionUpdated) {
5607 mResolutionUpdated = true;
5610 if (auto* window = nsGlobalWindowInner::Cast(mDocument->GetInnerWindow())) {
5611 window->VisualViewport()->PostResizeEvent();
5614 return NS_OK;
5617 float PresShell::GetCumulativeResolution() const {
5618 float resolution = GetResolution();
5619 nsPresContext* parentCtx = GetPresContext()->GetParentPresContext();
5620 if (parentCtx) {
5621 resolution *= parentCtx->PresShell()->GetCumulativeResolution();
5623 return resolution;
5626 void PresShell::SetRestoreResolution(float aResolution,
5627 LayoutDeviceIntSize aDisplaySize) {
5628 if (mMobileViewportManager) {
5629 mMobileViewportManager->SetRestoreResolution(aResolution, aDisplaySize);
5633 void PresShell::SetRenderingState(const RenderingState& aState) {
5634 if (GetResolution() != aState.mResolution.valueOr(1.f)) {
5635 if (nsIFrame* frame = GetRootFrame()) {
5636 frame->SchedulePaint();
5640 mRenderingStateFlags = aState.mRenderingStateFlags;
5641 mResolution = aState.mResolution;
5642 #ifdef ACCESSIBILITY
5643 if (nsAccessibilityService* accService = GetAccService()) {
5644 accService->NotifyOfResolutionChange(this, GetResolution());
5646 #endif
5649 void PresShell::SynthesizeMouseMove(bool aFromScroll) {
5650 if (!StaticPrefs::layout_reflow_synthMouseMove()) return;
5652 if (mPaintingSuppressed || !mIsActive || !mPresContext) {
5653 return;
5656 if (!mPresContext->IsRoot()) {
5657 if (PresShell* rootPresShell = GetRootPresShell()) {
5658 rootPresShell->SynthesizeMouseMove(aFromScroll);
5660 return;
5663 if (mMouseLocation == nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE))
5664 return;
5666 if (!mSynthMouseMoveEvent.IsPending()) {
5667 RefPtr<nsSynthMouseMoveEvent> ev =
5668 new nsSynthMouseMoveEvent(this, aFromScroll);
5670 GetPresContext()->RefreshDriver()->AddRefreshObserver(
5671 ev, FlushType::Display, "Synthetic mouse move event");
5672 mSynthMouseMoveEvent = std::move(ev);
5676 static nsView* FindFloatingViewContaining(nsPresContext* aRootPresContext,
5677 nsIWidget* aRootWidget,
5678 const LayoutDeviceIntPoint& aPt) {
5679 nsIFrame* popupFrame = nsLayoutUtils::GetPopupFrameForPoint(
5680 aRootPresContext, aRootWidget, aPt,
5681 nsLayoutUtils::GetPopupFrameForPointFlags::OnlyReturnFramesWithWidgets);
5682 return popupFrame ? popupFrame->GetView() : nullptr;
5686 * This finds the first view with a frame that contains the given point in a
5687 * postorder traversal of the view tree, assuming that the point is not in a
5688 * floating view. It assumes that only floating views extend outside the bounds
5689 * of their parents.
5691 * This methods should only be called if FindFloatingViewContaining returns
5692 * null.
5694 * aPt is relative aRelativeToView with the viewport type
5695 * aRelativeToViewportType. aRelativeToView will always have a frame. If aView
5696 * has a frame then aRelativeToView will be aView. (The reason aRelativeToView
5697 * and aView are separate is because we need to traverse into views without
5698 * frames (ie the inner view of a subdocument frame) but we can only easily
5699 * transform between views using TransformPoint which takes frames.)
5701 static nsView* FindViewContaining(nsView* aRelativeToView,
5702 ViewportType aRelativeToViewportType,
5703 nsView* aView, nsPoint aPt) {
5704 MOZ_ASSERT(aRelativeToView->GetFrame());
5706 if (aView->GetVisibility() == ViewVisibility::Hide) {
5707 return nullptr;
5710 nsIFrame* frame = aView->GetFrame();
5711 if (frame) {
5712 if (!frame->PresShell()->IsActive() ||
5713 !frame->IsVisibleConsideringAncestors(
5714 nsIFrame::VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY)) {
5715 return nullptr;
5718 // We start out in visual coords and then if we cross the zoom boundary we
5719 // become in layout coords. The zoom boundary always occurs in a document
5720 // with IsRootContentDocumentCrossProcess. The root view of such a document
5721 // is outside the zoom boundary and any child view must be inside the zoom
5722 // boundary because we only create views for certain kinds of frames and
5723 // none of them can be between the root frame and the zoom boundary.
5724 bool crossingZoomBoundary = false;
5725 if (aRelativeToViewportType == ViewportType::Visual) {
5726 if (!aRelativeToView->GetParent() ||
5727 aRelativeToView->GetViewManager() !=
5728 aRelativeToView->GetParent()->GetViewManager()) {
5729 if (aRelativeToView->GetFrame()
5730 ->PresContext()
5731 ->IsRootContentDocumentCrossProcess()) {
5732 crossingZoomBoundary = true;
5737 ViewportType nextRelativeToViewportType = aRelativeToViewportType;
5738 if (crossingZoomBoundary) {
5739 nextRelativeToViewportType = ViewportType::Layout;
5742 nsLayoutUtils::TransformResult result = nsLayoutUtils::TransformPoint(
5743 RelativeTo{aRelativeToView->GetFrame(), aRelativeToViewportType},
5744 RelativeTo{frame, nextRelativeToViewportType}, aPt);
5745 if (result != nsLayoutUtils::TRANSFORM_SUCCEEDED) {
5746 return nullptr;
5749 // Even though aPt is in visual coordinates until we cross the zoom boundary
5750 // it is valid to compare it to view coords (which are in layout coords)
5751 // because visual coords are the same as layout coords for every view
5752 // outside of the zoom boundary except for the root view of the root content
5753 // document.
5754 // For the root view of the root content document, its bounds don't
5755 // actually correspond to what is visible when we have a
5756 // MobileViewportManager. So we skip the hit test. This is okay because the
5757 // point has already been hit test: 1) if we are the root view in the
5758 // process then the point comes from a real mouse event so it must have been
5759 // over our widget, or 2) if we are the root of a subdocument then
5760 // hittesting against the view of the subdocument frame that contains us
5761 // already happened and succeeded before getting here.
5762 if (!crossingZoomBoundary) {
5763 if (!aView->GetDimensions().Contains(aPt)) {
5764 return nullptr;
5768 aRelativeToView = aView;
5769 aRelativeToViewportType = nextRelativeToViewportType;
5772 for (nsView* v = aView->GetFirstChild(); v; v = v->GetNextSibling()) {
5773 nsView* r =
5774 FindViewContaining(aRelativeToView, aRelativeToViewportType, v, aPt);
5775 if (r) return r;
5778 return frame ? aView : nullptr;
5781 static BrowserBridgeChild* GetChildBrowser(nsView* aView) {
5782 if (!aView) {
5783 return nullptr;
5785 nsIFrame* frame = aView->GetFrame();
5786 if (!frame && aView->GetParent()) {
5787 // If frame is null then view is an anonymous inner view, and we want
5788 // the frame from the corresponding outer view.
5789 frame = aView->GetParent()->GetFrame();
5791 if (!frame || !frame->GetContent()) {
5792 return nullptr;
5794 return BrowserBridgeChild::GetFrom(frame->GetContent());
5797 void PresShell::ProcessSynthMouseMoveEvent(bool aFromScroll) {
5798 // If drag session has started, we shouldn't synthesize mousemove event.
5799 nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
5800 if (dragSession) {
5801 mSynthMouseMoveEvent.Forget();
5802 return;
5805 // allow new event to be posted while handling this one only if the
5806 // source of the event is a scroll (to prevent infinite reflow loops)
5807 if (aFromScroll) {
5808 mSynthMouseMoveEvent.Forget();
5811 nsView* rootView = mViewManager ? mViewManager->GetRootView() : nullptr;
5812 if (mMouseLocation == nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE) ||
5813 !rootView || !rootView->HasWidget() || !mPresContext) {
5814 mSynthMouseMoveEvent.Forget();
5815 return;
5818 NS_ASSERTION(mPresContext->IsRoot(), "Only a root pres shell should be here");
5820 // Hold a ref to ourselves so DispatchEvent won't destroy us (since
5821 // we need to access members after we call DispatchEvent).
5822 RefPtr<PresShell> kungFuDeathGrip(this);
5824 #ifdef DEBUG_MOUSE_LOCATION
5825 printf("[ps=%p]synthesizing mouse move to (%d,%d)\n", this, mMouseLocation.x,
5826 mMouseLocation.y);
5827 #endif
5829 int32_t APD = mPresContext->AppUnitsPerDevPixel();
5831 // We need a widget to put in the event we are going to dispatch so we look
5832 // for a view that has a widget and the mouse location is over. We first look
5833 // for floating views, if there isn't one we use the root view. |view| holds
5834 // that view.
5835 nsView* view = nullptr;
5837 // The appunits per devpixel ratio of |view|.
5838 int32_t viewAPD;
5840 // mRefPoint will be mMouseLocation relative to the widget of |view|, the
5841 // widget we will put in the event we dispatch, in viewAPD appunits
5842 nsPoint refpoint(0, 0);
5844 // We always dispatch the event to the pres shell that contains the view that
5845 // the mouse is over. pointVM is the VM of that pres shell.
5846 nsViewManager* pointVM = nullptr;
5848 if (rootView->GetFrame()) {
5849 view = FindFloatingViewContaining(
5850 mPresContext, rootView->GetWidget(),
5851 LayoutDeviceIntPoint::FromAppUnitsToNearest(
5852 mMouseLocation + rootView->ViewToWidgetOffset(), APD));
5855 nsView* pointView = view;
5856 if (!view) {
5857 view = rootView;
5858 if (rootView->GetFrame()) {
5859 pointView = FindViewContaining(rootView, ViewportType::Visual, rootView,
5860 mMouseLocation);
5861 } else {
5862 pointView = rootView;
5864 // pointView can be null in situations related to mouse capture
5865 pointVM = (pointView ? pointView : view)->GetViewManager();
5866 refpoint = mMouseLocation + rootView->ViewToWidgetOffset();
5867 viewAPD = APD;
5868 } else {
5869 pointVM = view->GetViewManager();
5870 nsIFrame* frame = view->GetFrame();
5871 NS_ASSERTION(frame, "floating views can't be anonymous");
5872 viewAPD = frame->PresContext()->AppUnitsPerDevPixel();
5873 refpoint = mMouseLocation;
5874 DebugOnly<nsLayoutUtils::TransformResult> result =
5875 nsLayoutUtils::TransformPoint(
5876 RelativeTo{rootView->GetFrame(), ViewportType::Visual},
5877 RelativeTo{frame, ViewportType::Layout}, refpoint);
5878 MOZ_ASSERT(result == nsLayoutUtils::TRANSFORM_SUCCEEDED);
5879 refpoint += view->ViewToWidgetOffset();
5881 NS_ASSERTION(view->GetWidget(), "view should have a widget here");
5882 WidgetMouseEvent event(true, eMouseMove, view->GetWidget(),
5883 WidgetMouseEvent::eSynthesized);
5885 // If the last cursor location was set by a synthesized mouse event for tests,
5886 // running test should expect a restyle or a DOM mutation under the cursor may
5887 // cause mouse boundary events in a remote process if the cursor is over a
5888 // remote content. Therefore, the events should not be ignored by
5889 // PresShell::HandleEvent in the remote process. So we need to mark the
5890 // synthesized eMouseMove as "synthesized for tests".
5891 event.mFlags.mIsSynthesizedForTests =
5892 mMouseLocationWasSetBySynthesizedMouseEventForTests;
5894 event.mRefPoint =
5895 LayoutDeviceIntPoint::FromAppUnitsToNearest(refpoint, viewAPD);
5896 event.mButtons = PresShell::sMouseButtons;
5897 // XXX set event.mModifiers ?
5898 // XXX mnakano I think that we should get the latest information from widget.
5900 if (BrowserBridgeChild* bbc = GetChildBrowser(pointView)) {
5901 // If we have a BrowserBridgeChild, we're going to be dispatching this
5902 // mouse event into an OOP iframe of the current document.
5903 event.mLayersId = bbc->GetLayersId();
5904 bbc->SendDispatchSynthesizedMouseEvent(event);
5905 } else if (RefPtr<PresShell> presShell = pointVM->GetPresShell()) {
5906 // Since this gets run in a refresh tick there isn't an InputAPZContext on
5907 // the stack from the nsBaseWidget. We need to simulate one with at least
5908 // the correct target guid, so that the correct callback transform gets
5909 // applied if this event goes to a child process. The input block id is set
5910 // to 0 because this is a synthetic event which doesn't really belong to any
5911 // input block. Same for the APZ response field.
5912 InputAPZContext apzContext(mMouseEventTargetGuid, 0, nsEventStatus_eIgnore);
5913 presShell->DispatchSynthMouseMove(&event);
5916 if (!aFromScroll) {
5917 mSynthMouseMoveEvent.Forget();
5921 /* static */
5922 void PresShell::MarkFramesInListApproximatelyVisible(
5923 const nsDisplayList& aList) {
5924 for (nsDisplayItem* item : aList) {
5925 nsDisplayList* sublist = item->GetChildren();
5926 if (sublist) {
5927 MarkFramesInListApproximatelyVisible(*sublist);
5928 continue;
5931 nsIFrame* frame = item->Frame();
5932 MOZ_ASSERT(frame);
5934 if (!frame->TrackingVisibility()) {
5935 continue;
5938 // Use the presshell containing the frame.
5939 PresShell* presShell = frame->PresShell();
5940 MOZ_ASSERT(!presShell->AssumeAllFramesVisible());
5941 if (presShell->mApproximatelyVisibleFrames.EnsureInserted(frame)) {
5942 // The frame was added to mApproximatelyVisibleFrames, so increment its
5943 // visible count.
5944 frame->IncApproximateVisibleCount();
5949 /* static */
5950 void PresShell::DecApproximateVisibleCount(
5951 VisibleFrames& aFrames, const Maybe<OnNonvisible>& aNonvisibleAction
5952 /* = Nothing() */) {
5953 for (nsIFrame* frame : aFrames) {
5954 // Decrement the frame's visible count if we're still tracking its
5955 // visibility. (We may not be, if the frame disabled visibility tracking
5956 // after we added it to the visible frames list.)
5957 if (frame->TrackingVisibility()) {
5958 frame->DecApproximateVisibleCount(aNonvisibleAction);
5963 void PresShell::RebuildApproximateFrameVisibilityDisplayList(
5964 const nsDisplayList& aList) {
5965 MOZ_ASSERT(!mApproximateFrameVisibilityVisited, "already visited?");
5966 mApproximateFrameVisibilityVisited = true;
5968 // Remove the entries of the mApproximatelyVisibleFrames hashtable and put
5969 // them in oldApproxVisibleFrames.
5970 VisibleFrames oldApproximatelyVisibleFrames =
5971 std::move(mApproximatelyVisibleFrames);
5973 MarkFramesInListApproximatelyVisible(aList);
5975 DecApproximateVisibleCount(oldApproximatelyVisibleFrames);
5978 /* static */
5979 void PresShell::ClearApproximateFrameVisibilityVisited(nsView* aView,
5980 bool aClear) {
5981 nsViewManager* vm = aView->GetViewManager();
5982 if (aClear) {
5983 PresShell* presShell = vm->GetPresShell();
5984 if (!presShell->mApproximateFrameVisibilityVisited) {
5985 presShell->ClearApproximatelyVisibleFramesList();
5987 presShell->mApproximateFrameVisibilityVisited = false;
5989 for (nsView* v = aView->GetFirstChild(); v; v = v->GetNextSibling()) {
5990 ClearApproximateFrameVisibilityVisited(v, v->GetViewManager() != vm);
5994 void PresShell::ClearApproximatelyVisibleFramesList(
5995 const Maybe<OnNonvisible>& aNonvisibleAction
5996 /* = Nothing() */) {
5997 DecApproximateVisibleCount(mApproximatelyVisibleFrames, aNonvisibleAction);
5998 mApproximatelyVisibleFrames.Clear();
6001 void PresShell::MarkFramesInSubtreeApproximatelyVisible(
6002 nsIFrame* aFrame, const nsRect& aRect, bool aRemoveOnly /* = false */) {
6003 MOZ_DIAGNOSTIC_ASSERT(aFrame, "aFrame arg should be a valid frame pointer");
6004 MOZ_ASSERT(aFrame->PresShell() == this, "wrong presshell");
6006 if (aFrame->TrackingVisibility() && aFrame->StyleVisibility()->IsVisible() &&
6007 (!aRemoveOnly ||
6008 aFrame->GetVisibility() == Visibility::ApproximatelyVisible)) {
6009 MOZ_ASSERT(!AssumeAllFramesVisible());
6010 if (mApproximatelyVisibleFrames.EnsureInserted(aFrame)) {
6011 // The frame was added to mApproximatelyVisibleFrames, so increment its
6012 // visible count.
6013 aFrame->IncApproximateVisibleCount();
6017 nsSubDocumentFrame* subdocFrame = do_QueryFrame(aFrame);
6018 if (subdocFrame) {
6019 PresShell* presShell = subdocFrame->GetSubdocumentPresShellForPainting(
6020 nsSubDocumentFrame::IGNORE_PAINT_SUPPRESSION);
6021 if (presShell && !presShell->AssumeAllFramesVisible()) {
6022 nsRect rect = aRect;
6023 nsIFrame* root = presShell->GetRootFrame();
6024 if (root) {
6025 rect.MoveBy(aFrame->GetOffsetToCrossDoc(root));
6026 } else {
6027 rect.MoveBy(-aFrame->GetContentRectRelativeToSelf().TopLeft());
6029 rect = rect.ScaleToOtherAppUnitsRoundOut(
6030 aFrame->PresContext()->AppUnitsPerDevPixel(),
6031 presShell->GetPresContext()->AppUnitsPerDevPixel());
6033 presShell->RebuildApproximateFrameVisibility(&rect);
6035 return;
6038 nsRect rect = aRect;
6040 nsIScrollableFrame* scrollFrame = do_QueryFrame(aFrame);
6041 if (scrollFrame) {
6042 bool ignoreDisplayPort = false;
6043 if (DisplayPortUtils::IsMissingDisplayPortBaseRect(aFrame->GetContent())) {
6044 // We can properly set the base rect for root scroll frames on top level
6045 // and root content documents. Otherwise the base rect we compute might
6046 // be way too big without the limiting that
6047 // nsHTMLScrollFrame::DecideScrollableLayer does, so we just ignore the
6048 // displayport in that case.
6049 nsPresContext* pc = aFrame->PresContext();
6050 if (scrollFrame->IsRootScrollFrameOfDocument() &&
6051 (pc->IsRootContentDocumentCrossProcess() ||
6052 (pc->IsChrome() && !pc->GetParentPresContext()))) {
6053 nsRect baseRect(
6054 nsPoint(), nsLayoutUtils::CalculateCompositionSizeForFrame(aFrame));
6055 DisplayPortUtils::SetDisplayPortBase(aFrame->GetContent(), baseRect);
6056 } else {
6057 ignoreDisplayPort = true;
6061 nsRect displayPort;
6062 bool usingDisplayport =
6063 !ignoreDisplayPort &&
6064 DisplayPortUtils::GetDisplayPortForVisibilityTesting(
6065 aFrame->GetContent(), &displayPort);
6067 scrollFrame->NotifyApproximateFrameVisibilityUpdate(!usingDisplayport);
6069 if (usingDisplayport) {
6070 rect = displayPort;
6071 } else {
6072 rect = rect.Intersect(scrollFrame->GetScrollPortRect());
6074 rect = scrollFrame->ExpandRectToNearlyVisible(rect);
6077 bool preserves3DChildren = aFrame->Extend3DContext();
6079 for (const auto& [list, listID] : aFrame->ChildLists()) {
6080 if (listID == FrameChildListID::Popup) {
6081 // We assume all frames in popups are visible, so we skip them here.
6082 continue;
6085 for (nsIFrame* child : list) {
6086 // Note: This assert should be trivially satisfied, just by virtue of how
6087 // nsFrameList and its iterator works (with nullptr being an end-of-list
6088 // sentinel which should terminate the loop). But we do somehow get
6089 // crash reports inside this loop that suggest `child` is null...
6090 MOZ_DIAGNOSTIC_ASSERT(child, "shouldn't have null values in child lists");
6091 nsRect r = rect - child->GetPosition();
6092 if (!r.IntersectRect(r, child->InkOverflowRect())) {
6093 continue;
6095 if (child->IsTransformed()) {
6096 // for children of a preserve3d element we just pass down the same dirty
6097 // rect
6098 if (!preserves3DChildren ||
6099 !child->Combines3DTransformWithAncestors()) {
6100 const nsRect overflow = child->InkOverflowRectRelativeToSelf();
6101 nsRect out;
6102 if (nsDisplayTransform::UntransformRect(r, overflow, child, &out)) {
6103 r = out;
6104 } else {
6105 r.SetEmpty();
6109 MarkFramesInSubtreeApproximatelyVisible(child, r, aRemoveOnly);
6114 void PresShell::RebuildApproximateFrameVisibility(
6115 nsRect* aRect, bool aRemoveOnly /* = false */) {
6116 MOZ_ASSERT(!mApproximateFrameVisibilityVisited, "already visited?");
6117 mApproximateFrameVisibilityVisited = true;
6119 nsIFrame* rootFrame = GetRootFrame();
6120 if (!rootFrame) {
6121 return;
6124 // Remove the entries of the mApproximatelyVisibleFrames hashtable and put
6125 // them in oldApproximatelyVisibleFrames.
6126 VisibleFrames oldApproximatelyVisibleFrames =
6127 std::move(mApproximatelyVisibleFrames);
6129 nsRect vis(nsPoint(0, 0), rootFrame->GetSize());
6130 if (aRect) {
6131 vis = *aRect;
6134 // If we are in-process root but not the top level content, we need to take
6135 // the intersection with the iframe visible rect.
6136 if (mPresContext->IsRootContentDocumentInProcess() &&
6137 !mPresContext->IsRootContentDocumentCrossProcess()) {
6138 // There are two possibilities that we can't get the iframe's visible
6139 // rect other than the iframe is out side of ancestors' display ports.
6140 // a) the BrowserChild is being torn down
6141 // b) the visible rect hasn't been delivered the BrowserChild
6142 // In both cases we consider the visible rect is empty.
6143 Maybe<nsRect> visibleRect;
6144 if (BrowserChild* browserChild = BrowserChild::GetFrom(this)) {
6145 visibleRect = browserChild->GetVisibleRect();
6147 vis = vis.Intersect(visibleRect.valueOr(nsRect()));
6150 MarkFramesInSubtreeApproximatelyVisible(rootFrame, vis, aRemoveOnly);
6152 DecApproximateVisibleCount(oldApproximatelyVisibleFrames);
6155 void PresShell::UpdateApproximateFrameVisibility() {
6156 DoUpdateApproximateFrameVisibility(/* aRemoveOnly = */ false);
6159 void PresShell::DoUpdateApproximateFrameVisibility(bool aRemoveOnly) {
6160 MOZ_ASSERT(
6161 !mPresContext || mPresContext->IsRootContentDocumentInProcess(),
6162 "Updating approximate frame visibility on a non-root content document?");
6164 mUpdateApproximateFrameVisibilityEvent.Revoke();
6166 if (mHaveShutDown || mIsDestroying) {
6167 return;
6170 // call update on that frame
6171 nsIFrame* rootFrame = GetRootFrame();
6172 if (!rootFrame) {
6173 ClearApproximatelyVisibleFramesList(Some(OnNonvisible::DiscardImages));
6174 return;
6177 RebuildApproximateFrameVisibility(/* aRect = */ nullptr, aRemoveOnly);
6178 ClearApproximateFrameVisibilityVisited(rootFrame->GetView(), true);
6180 #ifdef DEBUG_FRAME_VISIBILITY_DISPLAY_LIST
6181 // This can be used to debug the frame walker by comparing beforeFrameList
6182 // and mApproximatelyVisibleFrames in RebuildFrameVisibilityDisplayList to see
6183 // if they produce the same results (mApproximatelyVisibleFrames holds the
6184 // frames the display list thinks are visible, beforeFrameList holds the
6185 // frames the frame walker thinks are visible).
6186 nsDisplayListBuilder builder(
6187 rootFrame, nsDisplayListBuilderMode::FRAME_VISIBILITY, false);
6188 nsRect updateRect(nsPoint(0, 0), rootFrame->GetSize());
6189 nsIFrame* rootScroll = GetRootScrollFrame();
6190 if (rootScroll) {
6191 nsIContent* content = rootScroll->GetContent();
6192 if (content) {
6193 Unused << nsLayoutUtils::GetDisplayPortForVisibilityTesting(
6194 content, &updateRect, RelativeTo::ScrollFrame);
6197 if (IgnoringViewportScrolling()) {
6198 builder.SetIgnoreScrollFrame(rootScroll);
6201 builder.IgnorePaintSuppression();
6202 builder.EnterPresShell(rootFrame);
6203 nsDisplayList list;
6204 rootFrame->BuildDisplayListForStackingContext(&builder, updateRect, &list);
6205 builder.LeavePresShell(rootFrame, &list);
6207 RebuildApproximateFrameVisibilityDisplayList(list);
6209 ClearApproximateFrameVisibilityVisited(rootFrame->GetView(), true);
6211 list.DeleteAll(&builder);
6212 #endif
6215 bool PresShell::AssumeAllFramesVisible() {
6216 if (!StaticPrefs::layout_framevisibility_enabled() || !mPresContext ||
6217 !mDocument) {
6218 return true;
6221 // We assume all frames are visible in print, print preview, chrome, and
6222 // resource docs and don't keep track of them.
6223 if (mPresContext->Type() == nsPresContext::eContext_PrintPreview ||
6224 mPresContext->Type() == nsPresContext::eContext_Print ||
6225 mPresContext->IsChrome() || mDocument->IsResourceDoc()) {
6226 return true;
6229 // If we're assuming all frames are visible in the top level content
6230 // document, we need to in subdocuments as well. Otherwise we can get in a
6231 // situation where things like animations won't work in subdocuments because
6232 // their frames appear not to be visible, since we won't schedule an image
6233 // visibility update if the top level content document is assuming all
6234 // frames are visible.
6236 // Note that it's not safe to call IsRootContentDocumentInProcess() if we're
6237 // currently being destroyed, so we have to check that first.
6238 if (!mHaveShutDown && !mIsDestroying &&
6239 !mPresContext->IsRootContentDocumentInProcess()) {
6240 nsPresContext* presContext =
6241 mPresContext->GetInProcessRootContentDocumentPresContext();
6242 if (presContext && presContext->PresShell()->AssumeAllFramesVisible()) {
6243 return true;
6247 return false;
6250 void PresShell::ScheduleApproximateFrameVisibilityUpdateSoon() {
6251 if (AssumeAllFramesVisible()) {
6252 return;
6255 if (!mPresContext) {
6256 return;
6259 nsRefreshDriver* refreshDriver = mPresContext->RefreshDriver();
6260 if (!refreshDriver) {
6261 return;
6264 // Ask the refresh driver to update frame visibility soon.
6265 refreshDriver->ScheduleFrameVisibilityUpdate();
6268 void PresShell::ScheduleApproximateFrameVisibilityUpdateNow() {
6269 if (AssumeAllFramesVisible()) {
6270 return;
6273 if (!mPresContext->IsRootContentDocumentInProcess()) {
6274 nsPresContext* presContext =
6275 mPresContext->GetInProcessRootContentDocumentPresContext();
6276 if (!presContext) return;
6277 MOZ_ASSERT(presContext->IsRootContentDocumentInProcess(),
6278 "Didn't get a root prescontext from "
6279 "GetInProcessRootContentDocumentPresContext?");
6280 presContext->PresShell()->ScheduleApproximateFrameVisibilityUpdateNow();
6281 return;
6284 if (mHaveShutDown || mIsDestroying) {
6285 return;
6288 if (mUpdateApproximateFrameVisibilityEvent.IsPending()) {
6289 return;
6292 RefPtr<nsRunnableMethod<PresShell>> event =
6293 NewRunnableMethod("PresShell::UpdateApproximateFrameVisibility", this,
6294 &PresShell::UpdateApproximateFrameVisibility);
6295 nsresult rv = mDocument->Dispatch(do_AddRef(event));
6297 if (NS_SUCCEEDED(rv)) {
6298 mUpdateApproximateFrameVisibilityEvent = std::move(event);
6302 void PresShell::EnsureFrameInApproximatelyVisibleList(nsIFrame* aFrame) {
6303 if (!aFrame->TrackingVisibility()) {
6304 return;
6307 if (AssumeAllFramesVisible()) {
6308 aFrame->IncApproximateVisibleCount();
6309 return;
6312 #ifdef DEBUG
6313 // Make sure it's in this pres shell.
6314 nsCOMPtr<nsIContent> content = aFrame->GetContent();
6315 if (content) {
6316 PresShell* presShell = content->OwnerDoc()->GetPresShell();
6317 MOZ_ASSERT(!presShell || presShell == this, "wrong shell");
6319 #endif
6321 if (mApproximatelyVisibleFrames.EnsureInserted(aFrame)) {
6322 // We inserted a new entry.
6323 aFrame->IncApproximateVisibleCount();
6327 void PresShell::RemoveFrameFromApproximatelyVisibleList(nsIFrame* aFrame) {
6328 #ifdef DEBUG
6329 // Make sure it's in this pres shell.
6330 nsCOMPtr<nsIContent> content = aFrame->GetContent();
6331 if (content) {
6332 PresShell* presShell = content->OwnerDoc()->GetPresShell();
6333 MOZ_ASSERT(!presShell || presShell == this, "wrong shell");
6335 #endif
6337 if (AssumeAllFramesVisible()) {
6338 MOZ_ASSERT(mApproximatelyVisibleFrames.Count() == 0,
6339 "Shouldn't have any frames in the table");
6340 return;
6343 if (mApproximatelyVisibleFrames.EnsureRemoved(aFrame) &&
6344 aFrame->TrackingVisibility()) {
6345 // aFrame was in the hashtable, and we're still tracking its visibility,
6346 // so we need to decrement its visible count.
6347 aFrame->DecApproximateVisibleCount();
6351 void PresShell::PaintAndRequestComposite(nsView* aView, PaintFlags aFlags) {
6352 if (!mIsActive) {
6353 return;
6356 WindowRenderer* renderer = aView->GetWidget()->GetWindowRenderer();
6357 NS_ASSERTION(renderer, "Must be in paint event");
6358 if (renderer->AsFallback()) {
6359 // The fallback renderer doesn't do any retaining, so we
6360 // just need to notify the view and widget that we're invalid, and
6361 // we'll do a paint+composite from the PaintWindow callback.
6362 GetViewManager()->InvalidateView(aView);
6363 return;
6366 // Otherwise we're a retained WebRenderLayerManager, so we want to call
6367 // Paint to update with any changes and push those to WR.
6368 PaintInternalFlags flags = PaintInternalFlags::None;
6369 if (aFlags & PaintFlags::PaintSyncDecodeImages) {
6370 flags |= PaintInternalFlags::PaintSyncDecodeImages;
6372 PaintInternal(aView, flags);
6375 void PresShell::SyncPaintFallback(nsView* aView) {
6376 if (!mIsActive) {
6377 return;
6380 WindowRenderer* renderer = aView->GetWidget()->GetWindowRenderer();
6381 NS_ASSERTION(renderer->AsFallback(),
6382 "Can't do Sync paint for remote renderers");
6383 if (!renderer->AsFallback()) {
6384 return;
6387 PaintInternal(aView, PaintInternalFlags::PaintComposite);
6388 GetPresContext()->NotifyDidPaintForSubtree();
6391 void PresShell::PaintInternal(nsView* aViewToPaint, PaintInternalFlags aFlags) {
6392 nsCString url;
6393 nsIURI* uri = mDocument->GetDocumentURI();
6394 Document* contentRoot = GetPrimaryContentDocument();
6395 if (contentRoot) {
6396 uri = contentRoot->GetDocumentURI();
6398 url = uri ? uri->GetSpecOrDefault() : "N/A"_ns;
6399 AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING_RELEVANT_FOR_JS(
6400 "Paint", GRAPHICS, Substring(url, std::min(size_t(128), url.Length())));
6402 Maybe<js::AutoAssertNoContentJS> nojs;
6404 // On Android, Flash can call into content JS during painting, so we can't
6405 // assert there. However, we don't rely on this assertion on Android because
6406 // we don't paint while JS is running.
6407 #if !defined(MOZ_WIDGET_ANDROID)
6408 if (!(aFlags & PaintInternalFlags::PaintComposite)) {
6409 // We need to allow content JS when the flag is set since we may trigger
6410 // MozAfterPaint events in content in those cases.
6411 nojs.emplace(dom::danger::GetJSContext());
6413 #endif
6415 NS_ASSERTION(!mIsDestroying, "painting a destroyed PresShell");
6416 NS_ASSERTION(aViewToPaint, "null view");
6418 MOZ_ASSERT(!mApproximateFrameVisibilityVisited, "Should have been cleared");
6420 if (!mIsActive) {
6421 return;
6424 if (StaticPrefs::apz_keyboard_enabled_AtStartup()) {
6425 // Update the focus target for async keyboard scrolling. This will be
6426 // forwarded to APZ by nsDisplayList::PaintRoot. We need to to do this
6427 // before we enter the paint phase because dispatching eVoid events can
6428 // cause layout to happen.
6429 mAPZFocusTarget = FocusTarget(this, mAPZFocusSequenceNumber);
6432 nsPresContext* presContext = GetPresContext();
6433 AUTO_LAYOUT_PHASE_ENTRY_POINT(presContext, Paint);
6435 nsIFrame* frame = aViewToPaint->GetFrame();
6437 WindowRenderer* renderer = aViewToPaint->GetWidget()->GetWindowRenderer();
6438 NS_ASSERTION(renderer, "Must be in paint event");
6439 WebRenderLayerManager* layerManager = renderer->AsWebRender();
6441 // Whether or not we should set first paint when painting is suppressed
6442 // is debatable. For now we'll do it because B2G relied on first paint
6443 // to configure the viewport and we only want to do that when we have
6444 // real content to paint. See Bug 798245
6445 if (mIsFirstPaint && !mPaintingSuppressed) {
6446 MOZ_LOG(gLog, LogLevel::Debug,
6447 ("PresShell::Paint, first paint, this=%p", this));
6449 if (layerManager) {
6450 layerManager->SetIsFirstPaint();
6452 mIsFirstPaint = false;
6455 if (!renderer->BeginTransaction(url)) {
6456 return;
6459 // Send an updated focus target with this transaction. Be sure to do this
6460 // before we paint in the case this is an empty transaction.
6461 if (layerManager) {
6462 layerManager->SetFocusTarget(mAPZFocusTarget);
6465 if (frame) {
6466 if (!(aFlags & PaintInternalFlags::PaintSyncDecodeImages) &&
6467 !frame->HasAnyStateBits(NS_FRAME_UPDATE_LAYER_TREE)) {
6468 if (layerManager) {
6469 layerManager->SetTransactionIdAllocator(presContext->RefreshDriver());
6472 if (renderer->EndEmptyTransaction(
6473 (aFlags & PaintInternalFlags::PaintComposite)
6474 ? WindowRenderer::END_DEFAULT
6475 : WindowRenderer::END_NO_COMPOSITE)) {
6476 return;
6479 frame->RemoveStateBits(NS_FRAME_UPDATE_LAYER_TREE);
6482 nscolor bgcolor = ComputeBackstopColor(aViewToPaint);
6483 PaintFrameFlags flags =
6484 PaintFrameFlags::WidgetLayers | PaintFrameFlags::ExistingTransaction;
6486 // We force sync-decode for printing / print-preview (printing already does
6487 // this from nsPageSequenceFrame::PrintNextSheet).
6488 // We also force sync-decoding via pref for reftests.
6489 if (aFlags & PaintInternalFlags::PaintSyncDecodeImages ||
6490 mDocument->IsStaticDocument() ||
6491 StaticPrefs::image_decode_sync_enabled()) {
6492 flags |= PaintFrameFlags::SyncDecodeImages;
6494 if (renderer->GetBackendType() == layers::LayersBackend::LAYERS_WR) {
6495 flags |= PaintFrameFlags::ForWebRender;
6498 if (frame) {
6499 // We can paint directly into the widget using its layer manager.
6500 nsLayoutUtils::PaintFrame(nullptr, frame, nsRegion(), bgcolor,
6501 nsDisplayListBuilderMode::Painting, flags);
6502 return;
6505 bgcolor = NS_ComposeColors(bgcolor, mCanvasBackground.mViewportColor);
6507 if (renderer->GetBackendType() == layers::LayersBackend::LAYERS_WR) {
6508 LayoutDeviceRect bounds = LayoutDeviceRect::FromAppUnits(
6509 presContext->GetVisibleArea(), presContext->AppUnitsPerDevPixel());
6510 WebRenderBackgroundData data(wr::ToLayoutRect(bounds),
6511 wr::ToColorF(ToDeviceColor(bgcolor)));
6512 WrFiltersHolder wrFilters;
6514 layerManager->SetTransactionIdAllocator(presContext->RefreshDriver());
6515 layerManager->EndTransactionWithoutLayer(nullptr, nullptr,
6516 std::move(wrFilters), &data, 0);
6517 return;
6520 FallbackRenderer* fallback = renderer->AsFallback();
6521 MOZ_ASSERT(fallback);
6523 if (aFlags & PaintInternalFlags::PaintComposite) {
6524 nsIntRect bounds = presContext->GetVisibleArea().ToOutsidePixels(
6525 presContext->AppUnitsPerDevPixel());
6526 fallback->EndTransactionWithColor(bounds, ToDeviceColor(bgcolor));
6530 // static
6531 void PresShell::SetCapturingContent(nsIContent* aContent, CaptureFlags aFlags,
6532 WidgetEvent* aEvent) {
6533 // If capture was set for pointer lock, don't unlock unless we are coming
6534 // out of pointer lock explicitly.
6535 if (!aContent && sCapturingContentInfo.mPointerLock &&
6536 !(aFlags & CaptureFlags::PointerLock)) {
6537 return;
6540 sCapturingContentInfo.mContent = nullptr;
6541 sCapturingContentInfo.mRemoteTarget = nullptr;
6543 // only set capturing content if allowed or the
6544 // CaptureFlags::IgnoreAllowedState or CaptureFlags::PointerLock are used.
6545 if ((aFlags & CaptureFlags::IgnoreAllowedState) ||
6546 sCapturingContentInfo.mAllowed || (aFlags & CaptureFlags::PointerLock)) {
6547 if (aContent) {
6548 sCapturingContentInfo.mContent = aContent;
6550 if (aEvent) {
6551 MOZ_ASSERT(XRE_IsParentProcess());
6552 MOZ_ASSERT(aEvent->mMessage == eMouseDown);
6553 MOZ_ASSERT(aEvent->HasBeenPostedToRemoteProcess());
6554 sCapturingContentInfo.mRemoteTarget =
6555 BrowserParent::GetLastMouseRemoteTarget();
6556 MOZ_ASSERT(sCapturingContentInfo.mRemoteTarget);
6558 // CaptureFlags::PointerLock is the same as
6559 // CaptureFlags::RetargetToElement & CaptureFlags::IgnoreAllowedState.
6560 sCapturingContentInfo.mRetargetToElement =
6561 !!(aFlags & CaptureFlags::RetargetToElement) ||
6562 !!(aFlags & CaptureFlags::PointerLock);
6563 sCapturingContentInfo.mPreventDrag =
6564 !!(aFlags & CaptureFlags::PreventDragStart);
6565 sCapturingContentInfo.mPointerLock = !!(aFlags & CaptureFlags::PointerLock);
6569 nsIContent* PresShell::GetCurrentEventContent() {
6570 if (mCurrentEventContent &&
6571 mCurrentEventContent->GetComposedDoc() != mDocument) {
6572 mCurrentEventContent = nullptr;
6573 mCurrentEventFrame = nullptr;
6575 return mCurrentEventContent;
6578 nsIFrame* PresShell::GetCurrentEventFrame() {
6579 if (MOZ_UNLIKELY(mIsDestroying)) {
6580 return nullptr;
6583 // GetCurrentEventContent() makes sure the content is still in the
6584 // same document that this pres shell belongs to. If not, then the
6585 // frame shouldn't get an event, nor should we even assume its safe
6586 // to try and find the frame.
6587 nsIContent* content = GetCurrentEventContent();
6588 if (!mCurrentEventFrame && content) {
6589 mCurrentEventFrame = content->GetPrimaryFrame();
6590 MOZ_ASSERT(!mCurrentEventFrame ||
6591 mCurrentEventFrame->PresContext()->GetPresShell() == this);
6593 return mCurrentEventFrame;
6596 already_AddRefed<nsIContent> PresShell::GetEventTargetContent(
6597 WidgetEvent* aEvent) {
6598 nsCOMPtr<nsIContent> content = GetCurrentEventContent();
6599 if (!content) {
6600 nsIFrame* currentEventFrame = GetCurrentEventFrame();
6601 if (currentEventFrame) {
6602 currentEventFrame->GetContentForEvent(aEvent, getter_AddRefs(content));
6603 NS_ASSERTION(!content || content->GetComposedDoc() == mDocument,
6604 "handing out content from a different doc");
6607 return content.forget();
6610 void PresShell::PushCurrentEventInfo(nsIFrame* aFrame, nsIContent* aContent) {
6611 if (mCurrentEventFrame || mCurrentEventContent) {
6612 mCurrentEventFrameStack.InsertElementAt(0, mCurrentEventFrame);
6613 mCurrentEventContentStack.InsertObjectAt(mCurrentEventContent, 0);
6615 mCurrentEventFrame = aFrame;
6616 mCurrentEventContent = aContent;
6619 void PresShell::PopCurrentEventInfo() {
6620 mCurrentEventFrame = nullptr;
6621 mCurrentEventContent = nullptr;
6623 if (0 != mCurrentEventFrameStack.Length()) {
6624 mCurrentEventFrame = mCurrentEventFrameStack.ElementAt(0);
6625 mCurrentEventFrameStack.RemoveElementAt(0);
6626 mCurrentEventContent = mCurrentEventContentStack.ObjectAt(0);
6627 mCurrentEventContentStack.RemoveObjectAt(0);
6629 // Don't use it if it has moved to a different document.
6630 if (mCurrentEventContent &&
6631 mCurrentEventContent->GetComposedDoc() != mDocument) {
6632 mCurrentEventContent = nullptr;
6633 mCurrentEventFrame = nullptr;
6638 // static
6639 bool PresShell::EventHandler::InZombieDocument(nsIContent* aContent) {
6640 // If a content node points to a null document, or the document is not
6641 // attached to a window, then it is possibly in a zombie document,
6642 // about to be replaced by a newly loading document.
6643 // Such documents cannot handle DOM events.
6644 // It might actually be in a node not attached to any document,
6645 // in which case there is not parent presshell to retarget it to.
6646 Document* doc = aContent->GetComposedDoc();
6647 return !doc || !doc->GetWindow();
6650 already_AddRefed<nsPIDOMWindowOuter> PresShell::GetRootWindow() {
6651 nsCOMPtr<nsPIDOMWindowOuter> window = mDocument->GetWindow();
6652 if (window) {
6653 nsCOMPtr<nsPIDOMWindowOuter> rootWindow = window->GetPrivateRoot();
6654 NS_ASSERTION(rootWindow, "nsPIDOMWindow::GetPrivateRoot() returns NULL");
6655 return rootWindow.forget();
6658 // If we don't have DOM window, we're zombie, we should find the root window
6659 // with our parent shell.
6660 RefPtr<PresShell> parentPresShell = GetParentPresShellForEventHandling();
6661 NS_ENSURE_TRUE(parentPresShell, nullptr);
6662 return parentPresShell->GetRootWindow();
6665 already_AddRefed<nsPIDOMWindowOuter>
6666 PresShell::GetFocusedDOMWindowInOurWindow() {
6667 nsCOMPtr<nsPIDOMWindowOuter> rootWindow = GetRootWindow();
6668 NS_ENSURE_TRUE(rootWindow, nullptr);
6669 nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
6670 nsFocusManager::GetFocusedDescendant(rootWindow,
6671 nsFocusManager::eIncludeAllDescendants,
6672 getter_AddRefs(focusedWindow));
6673 return focusedWindow.forget();
6676 already_AddRefed<nsIContent> PresShell::GetFocusedContentInOurWindow() const {
6677 nsFocusManager* fm = nsFocusManager::GetFocusManager();
6678 if (fm && mDocument) {
6679 RefPtr<Element> focusedElement;
6680 fm->GetFocusedElementForWindow(mDocument->GetWindow(), false, nullptr,
6681 getter_AddRefs(focusedElement));
6682 return focusedElement.forget();
6684 return nullptr;
6687 already_AddRefed<PresShell> PresShell::GetParentPresShellForEventHandling() {
6688 if (!mPresContext) {
6689 return nullptr;
6692 // Now, find the parent pres shell and send the event there
6693 RefPtr<nsDocShell> docShell = mPresContext->GetDocShell();
6694 if (!docShell) {
6695 docShell = mForwardingContainer.get();
6698 // Might have gone away, or never been around to start with
6699 if (!docShell) {
6700 return nullptr;
6703 BrowsingContext* bc = docShell->GetBrowsingContext();
6704 if (!bc) {
6705 return nullptr;
6708 RefPtr<BrowsingContext> parentBC;
6709 if (XRE_IsParentProcess()) {
6710 parentBC = bc->Canonical()->GetParentCrossChromeBoundary();
6711 } else {
6712 parentBC = bc->GetParent();
6715 RefPtr<nsIDocShell> parentDocShell =
6716 parentBC ? parentBC->GetDocShell() : nullptr;
6717 if (!parentDocShell) {
6718 return nullptr;
6721 RefPtr<PresShell> parentPresShell = parentDocShell->GetPresShell();
6722 return parentPresShell.forget();
6725 nsresult PresShell::EventHandler::RetargetEventToParent(
6726 WidgetGUIEvent* aGUIEvent, nsEventStatus* aEventStatus) {
6727 // Send this events straight up to the parent pres shell.
6728 // We do this for keystroke events in zombie documents or if either a frame
6729 // or a root content is not present.
6730 // That way at least the UI key bindings can work.
6732 RefPtr<PresShell> parentPresShell = GetParentPresShellForEventHandling();
6733 NS_ENSURE_TRUE(parentPresShell, NS_ERROR_FAILURE);
6735 // Fake the event as though it's from the parent pres shell's root frame.
6736 return parentPresShell->HandleEvent(parentPresShell->GetRootFrame(),
6737 aGUIEvent, true, aEventStatus);
6740 void PresShell::DisableNonTestMouseEvents(bool aDisable) {
6741 sDisableNonTestMouseEvents = aDisable;
6744 bool PresShell::MouseLocationWasSetBySynthesizedMouseEventForTests() const {
6745 if (!mPresContext) {
6746 return false;
6748 if (mPresContext->IsRoot()) {
6749 return mMouseLocationWasSetBySynthesizedMouseEventForTests;
6751 PresShell* rootPresShell = GetRootPresShell();
6752 return rootPresShell &&
6753 rootPresShell->mMouseLocationWasSetBySynthesizedMouseEventForTests;
6756 nsPoint PresShell::GetEventLocation(const WidgetMouseEvent& aEvent) const {
6757 nsIFrame* rootFrame = GetRootFrame();
6758 if (rootFrame) {
6759 RelativeTo relativeTo{rootFrame};
6760 if (rootFrame->PresContext()->IsRootContentDocumentCrossProcess()) {
6761 relativeTo.mViewportType = ViewportType::Visual;
6763 return nsLayoutUtils::GetEventCoordinatesRelativeTo(&aEvent, relativeTo);
6766 nsView* rootView = mViewManager->GetRootView();
6767 return nsLayoutUtils::TranslateWidgetToView(mPresContext, aEvent.mWidget,
6768 aEvent.mRefPoint, rootView);
6771 void PresShell::RecordPointerLocation(WidgetGUIEvent* aEvent) {
6772 if (!mPresContext) {
6773 return;
6776 if (!mPresContext->IsRoot()) {
6777 PresShell* rootPresShell = GetRootPresShell();
6778 if (rootPresShell) {
6779 rootPresShell->RecordPointerLocation(aEvent);
6781 return;
6784 if ((aEvent->mMessage == eMouseMove &&
6785 aEvent->AsMouseEvent()->mReason == WidgetMouseEvent::eReal) ||
6786 aEvent->mMessage == eMouseEnterIntoWidget ||
6787 aEvent->mMessage == eMouseDown || aEvent->mMessage == eMouseUp) {
6788 mMouseLocation = GetEventLocation(*aEvent->AsMouseEvent());
6789 mMouseEventTargetGuid = InputAPZContext::GetTargetLayerGuid();
6790 mMouseLocationWasSetBySynthesizedMouseEventForTests =
6791 aEvent->mFlags.mIsSynthesizedForTests;
6792 #ifdef DEBUG_MOUSE_LOCATION
6793 if (aEvent->mMessage == eMouseEnterIntoWidget) {
6794 printf("[ps=%p]got mouse enter for %p\n", this, aEvent->mWidget);
6796 printf("[ps=%p]setting mouse location to (%d,%d)\n", this, mMouseLocation.x,
6797 mMouseLocation.y);
6798 #endif
6799 if (aEvent->mMessage == eMouseEnterIntoWidget) {
6800 SynthesizeMouseMove(false);
6802 } else if (aEvent->mMessage == eMouseExitFromWidget) {
6803 // Although we only care about the mouse moving into an area for which this
6804 // pres shell doesn't receive mouse move events, we don't check which widget
6805 // the mouse exit was for since this seems to vary by platform. Hopefully
6806 // this won't matter at all since we'll get the mouse move or enter after
6807 // the mouse exit when the mouse moves from one of our widgets into another.
6808 mMouseLocation = nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
6809 mMouseEventTargetGuid = InputAPZContext::GetTargetLayerGuid();
6810 mMouseLocationWasSetBySynthesizedMouseEventForTests =
6811 aEvent->mFlags.mIsSynthesizedForTests;
6812 #ifdef DEBUG_MOUSE_LOCATION
6813 printf("[ps=%p]got mouse exit for %p\n", this, aEvent->mWidget);
6814 printf("[ps=%p]clearing mouse location\n", this);
6815 #endif
6816 } else if ((aEvent->mMessage == ePointerMove &&
6817 aEvent->AsMouseEvent()->mReason == WidgetMouseEvent::eReal) ||
6818 aEvent->mMessage == ePointerDown ||
6819 aEvent->mMessage == ePointerUp) {
6820 // TODO: instead, encapsulate `mMouseLocation` and
6821 // `mLastOverWindowPointerLocation` in a struct.
6822 mLastOverWindowPointerLocation = GetEventLocation(*aEvent->AsMouseEvent());
6826 void PresShell::nsSynthMouseMoveEvent::Revoke() {
6827 if (mPresShell) {
6828 mPresShell->GetPresContext()->RefreshDriver()->RemoveRefreshObserver(
6829 this, FlushType::Display);
6830 mPresShell = nullptr;
6834 // static
6835 nsIFrame* PresShell::EventHandler::GetNearestFrameContainingPresShell(
6836 PresShell* aPresShell) {
6837 nsViewManager* vm = aPresShell->GetViewManager();
6838 if (!vm) {
6839 return nullptr;
6841 nsView* view = vm->GetRootView();
6842 while (view && !view->GetFrame()) {
6843 view = view->GetParent();
6846 nsIFrame* frame = nullptr;
6847 if (view) {
6848 frame = view->GetFrame();
6851 return frame;
6854 static CallState FlushThrottledStyles(Document& aDocument) {
6855 PresShell* presShell = aDocument.GetPresShell();
6856 if (presShell && presShell->IsVisible()) {
6857 if (nsPresContext* presContext = presShell->GetPresContext()) {
6858 presContext->RestyleManager()->UpdateOnlyAnimationStyles();
6862 aDocument.EnumerateSubDocuments(FlushThrottledStyles);
6863 return CallState::Continue;
6866 bool PresShell::CanDispatchEvent(const WidgetGUIEvent* aEvent) const {
6867 bool rv =
6868 mPresContext && !mHaveShutDown && nsContentUtils::IsSafeToRunScript();
6869 if (aEvent) {
6870 rv &= (aEvent && aEvent->mWidget && !aEvent->mWidget->Destroyed());
6872 return rv;
6875 /* static */
6876 PresShell* PresShell::GetShellForEventTarget(nsIFrame* aFrame,
6877 nsIContent* aContent) {
6878 if (aFrame) {
6879 return aFrame->PresShell();
6881 if (aContent) {
6882 Document* doc = aContent->GetComposedDoc();
6883 if (!doc) {
6884 return nullptr;
6886 return doc->GetPresShell();
6888 return nullptr;
6891 /* static */
6892 PresShell* PresShell::GetShellForTouchEvent(WidgetGUIEvent* aEvent) {
6893 switch (aEvent->mMessage) {
6894 case eTouchMove:
6895 case eTouchCancel:
6896 case eTouchEnd: {
6897 // get the correct shell to dispatch to
6898 WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
6899 for (dom::Touch* touch : touchEvent->mTouches) {
6900 if (!touch) {
6901 return nullptr;
6904 RefPtr<dom::Touch> oldTouch =
6905 TouchManager::GetCapturedTouch(touch->Identifier());
6906 if (!oldTouch) {
6907 return nullptr;
6910 nsIContent* const content =
6911 nsIContent::FromEventTargetOrNull(oldTouch->GetTarget());
6912 if (!content) {
6913 return nullptr;
6916 if (PresShell* const presShell = content->OwnerDoc()->GetPresShell()) {
6917 return presShell;
6920 return nullptr;
6922 default:
6923 return nullptr;
6927 nsresult PresShell::HandleEvent(nsIFrame* aFrameForPresShell,
6928 WidgetGUIEvent* aGUIEvent,
6929 bool aDontRetargetEvents,
6930 nsEventStatus* aEventStatus) {
6931 MOZ_ASSERT(aGUIEvent);
6932 // Running tests must not expect that some mouse boundary events are fired
6933 // when something occurs in the parent process, e.g., when a popup is
6934 // opened/closed at the last mouse cursor position in the parent process (the
6935 // position may be different from the position which stored in this process).
6936 // Therefore, let's ignore synthesized mouse events coming form another
6937 // process if and only if they are not caused by the API.
6938 if (aGUIEvent->CameFromAnotherProcess() && XRE_IsContentProcess() &&
6939 !aGUIEvent->mFlags.mIsSynthesizedForTests &&
6940 MouseLocationWasSetBySynthesizedMouseEventForTests()) {
6941 switch (aGUIEvent->mMessage) {
6942 // Synthesized eMouseMove will case mouse boundary events like mouseover,
6943 // mouseout, and :hover state is changed at dispatching the events.
6944 case eMouseMove:
6945 // eMouseExitFromWidget comes from the parent process if the cursor
6946 // crosses a puppet widget boundary. Then, the event will be handled as a
6947 // synthesized eMouseMove in this process and may cause unexpected
6948 // `mouseout` and `mouseleave`.
6949 case eMouseExitFromWidget:
6950 // eMouseEnterIntoWidget causes updating the hover state under the event
6951 // position which may be different from the last cursor position
6952 // synthesized in this process.
6953 case eMouseEnterIntoWidget:
6954 if (!aGUIEvent->AsMouseEvent()->IsReal()) {
6955 return NS_OK;
6957 break;
6958 default:
6959 break;
6963 // Here we are granting some delays to ensure that user input events are
6964 // created while the page content may not be visible to the user are not
6965 // processed.
6966 // The main purpose of this is to avoid user inputs are handled in the
6967 // new document where as the user inputs were originally targeting some
6968 // content in the old document.
6969 if (!CanHandleUserInputEvents(aGUIEvent)) {
6970 return NS_OK;
6973 if (mPresContext) {
6974 switch (aGUIEvent->mMessage) {
6975 case eMouseMove:
6976 if (!aGUIEvent->AsMouseEvent()->IsReal()) {
6977 break;
6979 [[fallthrough]];
6980 case eMouseDown:
6981 case eMouseUp: {
6982 // We should flush pending mousemove event now because some mouse
6983 // boundary events which should've already been dispatched before a user
6984 // input may have not been dispatched. E.g., if a mousedown event
6985 // listener removed or appended an element under the cursor and mouseup
6986 // event comes immediately after that, mouseover or mouseout may have
6987 // not been dispatched on the new element yet.
6988 // XXX If eMouseMove is not propery dispatched before eMouseDown and
6989 // a `mousedown` event listener removes the event target or its
6990 // ancestor, eMouseOver will be dispatched between eMouseDown and
6991 // eMouseUp. That could cause unexpected behavior if a `mouseover`
6992 // event listener assumes it's always disptached before `mousedown`.
6993 // However, we're not sure whether it could happen with users' input.
6994 // FIXME: Perhaps, we need to do this for all events which are directly
6995 // caused by user input, e.g., eKeyDown, etc.
6996 RefPtr<PresShell> rootPresShell =
6997 mPresContext->IsRoot() ? this : GetRootPresShell();
6998 if (rootPresShell && rootPresShell->mSynthMouseMoveEvent.IsPending()) {
6999 AutoWeakFrame frameForPresShellWeak(aFrameForPresShell);
7000 RefPtr<nsSynthMouseMoveEvent> synthMouseMoveEvent =
7001 rootPresShell->mSynthMouseMoveEvent.get();
7002 synthMouseMoveEvent->Run();
7003 if (IsDestroying()) {
7004 return NS_OK;
7006 // XXX If the frame or "this" is reframed, it might be better to
7007 // recompute the frame. However, it could treat the user input on
7008 // unexpected element. Therefore, we should not do that until we'd
7009 // get a bug report caused by that.
7010 if (MOZ_UNLIKELY(!frameForPresShellWeak.IsAlive())) {
7011 return NS_OK;
7014 break;
7016 default:
7017 break;
7021 EventHandler eventHandler(*this);
7022 return eventHandler.HandleEvent(aFrameForPresShell, aGUIEvent,
7023 aDontRetargetEvents, aEventStatus);
7026 nsresult PresShell::EventHandler::HandleEvent(nsIFrame* aFrameForPresShell,
7027 WidgetGUIEvent* aGUIEvent,
7028 bool aDontRetargetEvents,
7029 nsEventStatus* aEventStatus) {
7030 MOZ_ASSERT(aGUIEvent);
7031 MOZ_DIAGNOSTIC_ASSERT(aGUIEvent->IsTrusted());
7032 MOZ_ASSERT(aEventStatus);
7034 NS_ASSERTION(aFrameForPresShell, "aFrameForPresShell should be not null");
7036 // Update the latest focus sequence number with this new sequence number;
7037 // the next transasction that gets sent to the compositor will carry this over
7038 if (mPresShell->mAPZFocusSequenceNumber < aGUIEvent->mFocusSequenceNumber) {
7039 mPresShell->mAPZFocusSequenceNumber = aGUIEvent->mFocusSequenceNumber;
7042 if (mPresShell->IsDestroying() ||
7043 (PresShell::sDisableNonTestMouseEvents &&
7044 !aGUIEvent->mFlags.mIsSynthesizedForTests &&
7045 aGUIEvent->HasMouseEventMessage())) {
7046 return NS_OK;
7049 mPresShell->RecordPointerLocation(aGUIEvent);
7051 if (MaybeHandleEventWithAccessibleCaret(aFrameForPresShell, aGUIEvent,
7052 aEventStatus)) {
7053 // Handled by AccessibleCaretEventHub.
7054 return NS_OK;
7057 if (MaybeDiscardEvent(aGUIEvent)) {
7058 // Cannot handle the event for now.
7059 return NS_OK;
7062 if (!aDontRetargetEvents) {
7063 // If aGUIEvent should be handled in another PresShell, we should call its
7064 // HandleEvent() and do nothing here.
7065 nsresult rv = NS_OK;
7066 if (MaybeHandleEventWithAnotherPresShell(aFrameForPresShell, aGUIEvent,
7067 aEventStatus, &rv)) {
7068 // Handled by another PresShell or nobody can handle the event.
7069 return rv;
7073 if (MaybeDiscardOrDelayKeyboardEvent(aGUIEvent)) {
7074 // The event is discarded or put into the delayed event queue.
7075 return NS_OK;
7078 if (aGUIEvent->IsUsingCoordinates()) {
7079 return HandleEventUsingCoordinates(aFrameForPresShell, aGUIEvent,
7080 aEventStatus, aDontRetargetEvents);
7083 // Activation events need to be dispatched even if no frame was found, since
7084 // we don't want the focus to be out of sync.
7085 if (!aFrameForPresShell) {
7086 if (!NS_EVENT_NEEDS_FRAME(aGUIEvent)) {
7087 // Push nullptr for both current event target content and frame since
7088 // there is no frame but the event does not require a frame.
7089 AutoCurrentEventInfoSetter eventInfoSetter(*this);
7090 return HandleEventWithCurrentEventInfo(aGUIEvent, aEventStatus, true,
7091 nullptr);
7094 if (aGUIEvent->HasKeyEventMessage()) {
7095 // Keypress events in new blank tabs should not be completely thrown away.
7096 // Retarget them -- the parent chrome shell might make use of them.
7097 return RetargetEventToParent(aGUIEvent, aEventStatus);
7100 return NS_OK;
7103 if (aGUIEvent->IsTargetedAtFocusedContent()) {
7104 return HandleEventAtFocusedContent(aGUIEvent, aEventStatus);
7107 return HandleEventWithFrameForPresShell(aFrameForPresShell, aGUIEvent,
7108 aEventStatus);
7111 nsresult PresShell::EventHandler::HandleEventUsingCoordinates(
7112 nsIFrame* aFrameForPresShell, WidgetGUIEvent* aGUIEvent,
7113 nsEventStatus* aEventStatus, bool aDontRetargetEvents) {
7114 MOZ_ASSERT(aGUIEvent);
7115 MOZ_ASSERT(aGUIEvent->IsUsingCoordinates());
7116 MOZ_ASSERT(aEventStatus);
7118 // Flush pending notifications to handle the event with the latest layout.
7119 // But if it causes destroying the frame for mPresShell, stop handling the
7120 // event. (why?)
7121 AutoWeakFrame weakFrame(aFrameForPresShell);
7122 MaybeFlushPendingNotifications(aGUIEvent);
7123 if (!weakFrame.IsAlive()) {
7124 *aEventStatus = nsEventStatus_eIgnore;
7125 return NS_OK;
7128 // XXX Retrieving capturing content here. However, some of the following
7129 // methods allow to run script. So, isn't it possible the capturing
7130 // content outdated?
7131 nsCOMPtr<nsIContent> capturingContent =
7132 EventHandler::GetCapturingContentFor(aGUIEvent);
7134 if (GetDocument() && aGUIEvent->mClass == eTouchEventClass) {
7135 PointerLockManager::Unlock();
7138 nsIFrame* frameForPresShell = MaybeFlushThrottledStyles(aFrameForPresShell);
7139 if (NS_WARN_IF(!frameForPresShell)) {
7140 return NS_OK;
7143 bool isCapturingContentIgnored = false;
7144 bool isCaptureRetargeted = false;
7145 nsIFrame* rootFrameToHandleEvent = ComputeRootFrameToHandleEvent(
7146 frameForPresShell, aGUIEvent, capturingContent,
7147 &isCapturingContentIgnored, &isCaptureRetargeted);
7148 if (isCapturingContentIgnored) {
7149 capturingContent = nullptr;
7152 // The order to generate pointer event is
7153 // 1. check pending pointer capture.
7154 // 2. check if there is a capturing content.
7155 // 3. hit test
7156 // 4. dispatch pointer events
7157 // 5. check whether the targets of all Touch instances are in the same
7158 // document and suppress invalid instances.
7159 // 6. dispatch mouse or touch events.
7161 // Try to keep frame for following check, because frame can be damaged
7162 // during MaybeProcessPointerCapture.
7164 AutoWeakFrame frameKeeper(rootFrameToHandleEvent);
7165 PointerEventHandler::MaybeProcessPointerCapture(aGUIEvent);
7166 // Prevent application crashes, in case damaged frame.
7167 if (!frameKeeper.IsAlive()) {
7168 NS_WARNING("Nothing to handle this event!");
7169 return NS_OK;
7173 // Only capture mouse events and pointer events.
7174 RefPtr<Element> pointerCapturingElement =
7175 PointerEventHandler::GetPointerCapturingElement(aGUIEvent);
7177 if (pointerCapturingElement) {
7178 rootFrameToHandleEvent = pointerCapturingElement->GetPrimaryFrame();
7179 if (!rootFrameToHandleEvent) {
7180 return HandleEventWithPointerCapturingContentWithoutItsFrame(
7181 aFrameForPresShell, aGUIEvent, pointerCapturingElement, aEventStatus);
7185 WidgetMouseEvent* mouseEvent = aGUIEvent->AsMouseEvent();
7186 bool isWindowLevelMouseExit =
7187 (aGUIEvent->mMessage == eMouseExitFromWidget) &&
7188 (mouseEvent &&
7189 (mouseEvent->mExitFrom.value() == WidgetMouseEvent::ePlatformTopLevel ||
7190 mouseEvent->mExitFrom.value() == WidgetMouseEvent::ePuppet));
7192 // Get the frame at the event point. However, don't do this if we're
7193 // capturing and retargeting the event because the captured frame will
7194 // be used instead below. Also keep using the root frame if we're dealing
7195 // with a window-level mouse exit event since we want to start sending
7196 // mouse out events at the root EventStateManager.
7197 EventTargetData eventTargetData(rootFrameToHandleEvent);
7198 if (!isCaptureRetargeted && !isWindowLevelMouseExit &&
7199 !pointerCapturingElement) {
7200 if (!ComputeEventTargetFrameAndPresShellAtEventPoint(
7201 rootFrameToHandleEvent, aGUIEvent, &eventTargetData)) {
7202 *aEventStatus = nsEventStatus_eIgnore;
7203 return NS_OK;
7207 // if a node is capturing the mouse, check if the event needs to be
7208 // retargeted at the capturing content instead. This will be the case when
7209 // capture retargeting is being used, no frame was found or the frame's
7210 // content is not a descendant of the capturing content.
7211 if (capturingContent && !pointerCapturingElement &&
7212 (PresShell::sCapturingContentInfo.mRetargetToElement ||
7213 !eventTargetData.GetFrameContent() ||
7214 !nsContentUtils::ContentIsCrossDocDescendantOf(
7215 eventTargetData.GetFrameContent(), capturingContent))) {
7216 // A check was already done above to ensure that capturingContent is
7217 // in this presshell.
7218 NS_ASSERTION(capturingContent->OwnerDoc() == GetDocument(),
7219 "Unexpected document");
7220 nsIFrame* capturingFrame = capturingContent->GetPrimaryFrame();
7221 if (capturingFrame) {
7222 eventTargetData.SetFrameAndComputePresShell(capturingFrame);
7226 if (NS_WARN_IF(!eventTargetData.GetFrame())) {
7227 return NS_OK;
7230 // Suppress mouse event if it's being targeted at an element inside
7231 // a document which needs events suppressed
7232 if (MaybeDiscardOrDelayMouseEvent(eventTargetData.GetFrame(), aGUIEvent)) {
7233 return NS_OK;
7236 // Check if we have an active EventStateManager which isn't the
7237 // EventStateManager of the current PresContext. If that is the case, and
7238 // mouse is over some ancestor document, forward event handling to the
7239 // active document. This way content can get mouse events even when mouse
7240 // is over the chrome or outside the window.
7241 if (eventTargetData.MaybeRetargetToActiveDocument(aGUIEvent) &&
7242 NS_WARN_IF(!eventTargetData.GetFrame())) {
7243 return NS_OK;
7246 // Wheel events only apply to elements. If this is a wheel event, attempt to
7247 // update the event target from the current wheel transaction before we
7248 // compute the element from the target frame.
7249 eventTargetData.UpdateWheelEventTarget(aGUIEvent);
7251 if (!eventTargetData.ComputeElementFromFrame(aGUIEvent)) {
7252 return NS_OK;
7254 // Note that even if ComputeElementFromFrame() returns true,
7255 // eventTargetData.mContent can be nullptr here.
7257 // Dispatch a pointer event if Pointer Events is enabled. Note that if
7258 // pointer event listeners change the layout, eventTargetData is
7259 // automatically updated.
7260 if (!DispatchPrecedingPointerEvent(
7261 aFrameForPresShell, aGUIEvent, pointerCapturingElement,
7262 aDontRetargetEvents, &eventTargetData, aEventStatus)) {
7263 return NS_OK;
7266 // Handle the event in the correct shell.
7267 // We pass the subshell's root frame as the frame to start from. This is
7268 // the only correct alternative; if the event was captured then it
7269 // must have been captured by us or some ancestor shell and we
7270 // now ask the subshell to dispatch it normally.
7271 EventHandler eventHandler(*eventTargetData.mPresShell);
7272 AutoCurrentEventInfoSetter eventInfoSetter(eventHandler, eventTargetData);
7273 // eventTargetData is on the stack and is guaranteed to keep its
7274 // mOverrideClickTarget alive, so we can just use MOZ_KnownLive here.
7275 nsresult rv = eventHandler.HandleEventWithCurrentEventInfo(
7276 aGUIEvent, aEventStatus, true,
7277 MOZ_KnownLive(eventTargetData.mOverrideClickTarget));
7278 if (NS_FAILED(rv) ||
7279 MOZ_UNLIKELY(eventTargetData.mPresShell->IsDestroying())) {
7280 return rv;
7283 if (aGUIEvent->mMessage == eTouchEnd) {
7284 MaybeSynthesizeCompatMouseEventsForTouchEnd(aGUIEvent->AsTouchEvent(),
7285 aEventStatus);
7288 return NS_OK;
7291 bool PresShell::EventHandler::MaybeFlushPendingNotifications(
7292 WidgetGUIEvent* aGUIEvent) {
7293 MOZ_ASSERT(aGUIEvent);
7295 switch (aGUIEvent->mMessage) {
7296 case eMouseDown:
7297 case eMouseUp: {
7298 RefPtr<nsPresContext> presContext = mPresShell->GetPresContext();
7299 if (NS_WARN_IF(!presContext)) {
7300 return false;
7302 uint64_t framesConstructedCount = presContext->FramesConstructedCount();
7303 uint64_t framesReflowedCount = presContext->FramesReflowedCount();
7305 MOZ_KnownLive(mPresShell)->FlushPendingNotifications(FlushType::Layout);
7306 return framesConstructedCount != presContext->FramesConstructedCount() ||
7307 framesReflowedCount != presContext->FramesReflowedCount();
7309 default:
7310 return false;
7314 // The type of coordinates to use for hit-testing input events
7315 // that are relative to the RCD's viewport frame.
7316 // On most platforms, use visual coordinates so that scrollbars
7317 // can be targeted.
7318 // On mobile, use layout coordinates because hit-testing in
7319 // visual coordinates clashes with mobile viewport sizing, where
7320 // the ViewportFrame is sized to the initial containing block
7321 // (ICB) size, which is in layout coordinates. This is fine
7322 // because we don't need to be able to target scrollbars on mobile
7323 // (scrollbar dragging isn't supported).
7324 static ViewportType ViewportTypeForInputEventsRelativeToRoot() {
7325 #ifdef MOZ_WIDGET_ANDROID
7326 return ViewportType::Layout;
7327 #else
7328 return ViewportType::Visual;
7329 #endif
7332 nsIFrame* PresShell::EventHandler::GetFrameToHandleNonTouchEvent(
7333 nsIFrame* aRootFrameToHandleEvent, WidgetGUIEvent* aGUIEvent) {
7334 MOZ_ASSERT(aGUIEvent);
7335 MOZ_ASSERT(aGUIEvent->mClass != eTouchEventClass);
7337 ViewportType viewportType = ViewportType::Layout;
7338 if (aRootFrameToHandleEvent->Type() == LayoutFrameType::Viewport) {
7339 nsPresContext* pc = aRootFrameToHandleEvent->PresContext();
7340 if (pc->IsChrome()) {
7341 viewportType = ViewportType::Visual;
7342 } else if (pc->IsRootContentDocumentCrossProcess()) {
7343 viewportType = ViewportTypeForInputEventsRelativeToRoot();
7346 RelativeTo relativeTo{aRootFrameToHandleEvent, viewportType};
7347 nsPoint eventPoint =
7348 nsLayoutUtils::GetEventCoordinatesRelativeTo(aGUIEvent, relativeTo);
7350 uint32_t flags = 0;
7351 if (aGUIEvent->mClass == eMouseEventClass) {
7352 WidgetMouseEvent* mouseEvent = aGUIEvent->AsMouseEvent();
7353 if (mouseEvent && mouseEvent->mIgnoreRootScrollFrame) {
7354 flags |= INPUT_IGNORE_ROOT_SCROLL_FRAME;
7358 nsIFrame* targetFrame =
7359 FindFrameTargetedByInputEvent(aGUIEvent, relativeTo, eventPoint, flags);
7360 if (!targetFrame) {
7361 return aRootFrameToHandleEvent;
7364 if (targetFrame->PresShell() == mPresShell) {
7365 // If found target is in mPresShell, we've already found it in the latest
7366 // layout so that we can use it.
7367 return targetFrame;
7370 // If target is in a child document, we've not flushed its layout yet.
7371 PresShell* childPresShell = targetFrame->PresShell();
7372 EventHandler childEventHandler(*childPresShell);
7373 AutoWeakFrame weakFrame(aRootFrameToHandleEvent);
7374 bool layoutChanged =
7375 childEventHandler.MaybeFlushPendingNotifications(aGUIEvent);
7376 if (!weakFrame.IsAlive()) {
7377 // Stop handling the event if the root frame to handle event is destroyed
7378 // by the reflow. (but why?)
7379 return nullptr;
7381 if (!layoutChanged) {
7382 // If the layout in the child PresShell hasn't been changed, we don't
7383 // need to recompute the target.
7384 return targetFrame;
7387 // Finally, we need to recompute the target with the latest layout.
7388 targetFrame =
7389 FindFrameTargetedByInputEvent(aGUIEvent, relativeTo, eventPoint, flags);
7391 return targetFrame ? targetFrame : aRootFrameToHandleEvent;
7394 bool PresShell::EventHandler::ComputeEventTargetFrameAndPresShellAtEventPoint(
7395 nsIFrame* aRootFrameToHandleEvent, WidgetGUIEvent* aGUIEvent,
7396 EventTargetData* aEventTargetData) {
7397 MOZ_ASSERT(aRootFrameToHandleEvent);
7398 MOZ_ASSERT(aGUIEvent);
7399 MOZ_ASSERT(aEventTargetData);
7401 if (aGUIEvent->mClass == eTouchEventClass) {
7402 nsIFrame* targetFrameAtTouchEvent = TouchManager::SetupTarget(
7403 aGUIEvent->AsTouchEvent(), aRootFrameToHandleEvent);
7404 aEventTargetData->SetFrameAndComputePresShell(targetFrameAtTouchEvent);
7405 return true;
7408 nsIFrame* targetFrame =
7409 GetFrameToHandleNonTouchEvent(aRootFrameToHandleEvent, aGUIEvent);
7410 aEventTargetData->SetFrameAndComputePresShell(targetFrame);
7411 return !!aEventTargetData->GetFrame();
7414 bool PresShell::EventHandler::DispatchPrecedingPointerEvent(
7415 nsIFrame* aFrameForPresShell, WidgetGUIEvent* aGUIEvent,
7416 nsIContent* aPointerCapturingContent, bool aDontRetargetEvents,
7417 EventTargetData* aEventTargetData, nsEventStatus* aEventStatus) {
7418 MOZ_ASSERT(aFrameForPresShell);
7419 MOZ_ASSERT(aGUIEvent);
7420 MOZ_ASSERT(aEventTargetData);
7421 MOZ_ASSERT(aEventStatus);
7423 // Dispatch pointer events from the mouse or touch events. Regarding
7424 // pointer events from mouse, we should dispatch those pointer events to
7425 // the same target as the source mouse events. We pass the frame found
7426 // in hit test to PointerEventHandler and dispatch pointer events to it.
7428 // Regarding pointer events from touch, the behavior is different. Touch
7429 // events are dispatched to the same target as the target of touchstart.
7430 // Multiple touch points must be dispatched to the same document. Pointer
7431 // events from touch can be dispatched to different documents. We Pass the
7432 // original frame to PointerEventHandler, reentry PresShell::HandleEvent,
7433 // and do hit test for each point.
7434 nsIFrame* targetFrame = aGUIEvent->mClass == eTouchEventClass
7435 ? aFrameForPresShell
7436 : aEventTargetData->GetFrame();
7438 if (aPointerCapturingContent) {
7439 aEventTargetData->mOverrideClickTarget =
7440 GetOverrideClickTarget(aGUIEvent, aFrameForPresShell);
7441 aEventTargetData->mPresShell =
7442 PresShell::GetShellForEventTarget(nullptr, aPointerCapturingContent);
7443 if (!aEventTargetData->mPresShell) {
7444 // If we can't process event for the capturing content, release
7445 // the capture.
7446 PointerEventHandler::ReleaseIfCaptureByDescendant(
7447 aPointerCapturingContent);
7448 return false;
7451 targetFrame = aPointerCapturingContent->GetPrimaryFrame();
7452 aEventTargetData->SetFrameAndContent(targetFrame, aPointerCapturingContent);
7455 AutoWeakFrame weakTargetFrame(targetFrame);
7456 AutoWeakFrame weakFrame(aEventTargetData->GetFrame());
7457 nsCOMPtr<nsIContent> pointerEventTargetContent(
7458 aEventTargetData->GetContent());
7459 RefPtr<PresShell> presShell(aEventTargetData->mPresShell);
7460 nsCOMPtr<nsIContent> mouseOrTouchEventTargetContent;
7461 PointerEventHandler::DispatchPointerFromMouseOrTouch(
7462 presShell, aEventTargetData->GetFrame(), pointerEventTargetContent,
7463 aGUIEvent, aDontRetargetEvents, aEventStatus,
7464 getter_AddRefs(mouseOrTouchEventTargetContent));
7466 // If the target frame is alive, the caller should keep handling the event
7467 // unless event target frame is destroyed.
7468 if (weakTargetFrame.IsAlive() && weakFrame.IsAlive()) {
7469 aEventTargetData->UpdateTouchEventTarget(aGUIEvent);
7470 return true;
7473 presShell->FlushPendingNotifications(FlushType::Layout);
7474 if (MOZ_UNLIKELY(mPresShell->IsDestroying())) {
7475 return false;
7478 // The spec defines that mouse events must be dispatched to the same target as
7479 // the pointer event.
7480 // The Touch Events spec defines that touch events must be dispatched to the
7481 // same target as touch start and the other browsers dispatch touch events
7482 // even if the touch event target is not connected to the document.
7483 // Retargetting the event is handled by AutoPointerEventTargetUpdater and
7484 // mouseOrTouchEventTargetContent stores the result.
7486 // If the target is no longer participating in its ownerDocument's tree,
7487 // fire the event at the original target's nearest ancestor node.
7488 if (!mouseOrTouchEventTargetContent) {
7489 MOZ_ASSERT(aGUIEvent->mClass == eMouseEventClass);
7490 return false;
7493 aEventTargetData->SetFrameAndContent(
7494 mouseOrTouchEventTargetContent->GetPrimaryFrame(),
7495 mouseOrTouchEventTargetContent);
7496 aEventTargetData->mPresShell =
7497 mouseOrTouchEventTargetContent->IsInComposedDoc()
7498 ? PresShell::GetShellForEventTarget(aEventTargetData->GetFrame(),
7499 aEventTargetData->GetContent())
7500 : mouseOrTouchEventTargetContent->OwnerDoc()->GetPresShell();
7502 // If new target PresShel is not found, we cannot keep handling the event.
7503 if (!aEventTargetData->mPresShell) {
7504 return false;
7507 aEventTargetData->UpdateTouchEventTarget(aGUIEvent);
7508 return true;
7512 * Event retargetting may retarget a mouse event and change the reference point.
7513 * If event retargetting changes the reference point of a event that accessible
7514 * caret will not handle, restore the original reference point.
7516 class AutoEventTargetPointResetter {
7517 public:
7518 explicit AutoEventTargetPointResetter(WidgetGUIEvent* aGUIEvent)
7519 : mGUIEvent(aGUIEvent),
7520 mRefPoint(aGUIEvent->mRefPoint),
7521 mHandledByAccessibleCaret(false) {}
7523 void SetHandledByAccessibleCaret() { mHandledByAccessibleCaret = true; }
7525 ~AutoEventTargetPointResetter() {
7526 if (!mHandledByAccessibleCaret) {
7527 mGUIEvent->mRefPoint = mRefPoint;
7531 private:
7532 WidgetGUIEvent* mGUIEvent;
7533 LayoutDeviceIntPoint mRefPoint;
7534 bool mHandledByAccessibleCaret;
7537 bool PresShell::EventHandler::MaybeHandleEventWithAccessibleCaret(
7538 nsIFrame* aFrameForPresShell, WidgetGUIEvent* aGUIEvent,
7539 nsEventStatus* aEventStatus) {
7540 MOZ_ASSERT(aGUIEvent);
7541 MOZ_ASSERT(aEventStatus);
7543 // Don't dispatch event to AccessibleCaretEventHub when the event status
7544 // is nsEventStatus_eConsumeNoDefault. This might be happened when content
7545 // preventDefault on the pointer events. In such case, we also call
7546 // preventDefault on mouse events to stop default behaviors.
7547 if (*aEventStatus == nsEventStatus_eConsumeNoDefault) {
7548 return false;
7551 if (!AccessibleCaretEnabled(GetDocument()->GetDocShell())) {
7552 return false;
7555 // AccessibleCaretEventHub handles only mouse, touch, and keyboard events.
7556 if (aGUIEvent->mClass != eMouseEventClass &&
7557 aGUIEvent->mClass != eTouchEventClass &&
7558 aGUIEvent->mClass != eKeyboardEventClass) {
7559 return false;
7562 AutoEventTargetPointResetter autoEventTargetPointResetter(aGUIEvent);
7563 // First, try the event hub at the event point to handle a long press to
7564 // select a word in an unfocused window.
7565 do {
7566 EventTargetData eventTargetData(nullptr);
7567 if (!ComputeEventTargetFrameAndPresShellAtEventPoint(
7568 aFrameForPresShell, aGUIEvent, &eventTargetData)) {
7569 break;
7572 if (!eventTargetData.mPresShell) {
7573 break;
7576 RefPtr<AccessibleCaretEventHub> eventHub =
7577 eventTargetData.mPresShell->GetAccessibleCaretEventHub();
7578 if (!eventHub) {
7579 break;
7582 *aEventStatus = eventHub->HandleEvent(aGUIEvent);
7583 if (*aEventStatus != nsEventStatus_eConsumeNoDefault) {
7584 break;
7587 // If the event is consumed, cancel APZC panning by setting
7588 // mMultipleActionsPrevented.
7589 aGUIEvent->mFlags.mMultipleActionsPrevented = true;
7590 autoEventTargetPointResetter.SetHandledByAccessibleCaret();
7591 return true;
7592 } while (false);
7594 // Then, we target the event to the event hub at the focused window.
7595 nsCOMPtr<nsPIDOMWindowOuter> window = GetFocusedDOMWindowInOurWindow();
7596 if (!window) {
7597 return false;
7599 RefPtr<Document> retargetEventDoc = window->GetExtantDoc();
7600 if (!retargetEventDoc) {
7601 return false;
7603 RefPtr<PresShell> presShell = retargetEventDoc->GetPresShell();
7604 if (!presShell) {
7605 return false;
7608 RefPtr<AccessibleCaretEventHub> eventHub =
7609 presShell->GetAccessibleCaretEventHub();
7610 if (!eventHub) {
7611 return false;
7613 *aEventStatus = eventHub->HandleEvent(aGUIEvent);
7614 if (*aEventStatus != nsEventStatus_eConsumeNoDefault) {
7615 return false;
7617 // If the event is consumed, cancel APZC panning by setting
7618 // mMultipleActionsPrevented.
7619 aGUIEvent->mFlags.mMultipleActionsPrevented = true;
7620 autoEventTargetPointResetter.SetHandledByAccessibleCaret();
7621 return true;
7624 void PresShell::EventHandler::MaybeSynthesizeCompatMouseEventsForTouchEnd(
7625 const WidgetTouchEvent* aTouchEndEvent,
7626 const nsEventStatus* aStatus) const {
7627 MOZ_ASSERT(aTouchEndEvent->mMessage == eTouchEnd);
7629 // If the eTouchEnd event is dispatched via APZ, APZCCallbackHelper dispatches
7630 // a set of mouse events with better handling. Therefore, we don't need to
7631 // handle that here.
7632 if (!aTouchEndEvent->mFlags.mIsSynthesizedForTests ||
7633 StaticPrefs::test_events_async_enabled()) {
7634 return;
7637 // If the tap was consumed or 2 or more touches occurred, we don't need the
7638 // compatibility mouse events.
7639 if (*aStatus == nsEventStatus_eConsumeNoDefault ||
7640 !TouchManager::IsSingleTapEndToDoDefault(aTouchEndEvent)) {
7641 return;
7644 if (NS_WARN_IF(!aTouchEndEvent->mWidget)) {
7645 return;
7648 nsCOMPtr<nsIWidget> widget = aTouchEndEvent->mWidget;
7650 // NOTE: I think that we don't need to implement a double click here becase
7651 // WebDriver does not support a way to synthesize a double click and Chrome
7652 // does not fire "dblclick" even if doing `pointerDown().pointerUp()` twice.
7653 // FIXME: Currently we don't support long tap.
7654 RefPtr<PresShell> presShell = mPresShell;
7655 for (const EventMessage message : {eMouseMove, eMouseDown, eMouseUp}) {
7656 if (MOZ_UNLIKELY(presShell->IsDestroying())) {
7657 break;
7659 nsIFrame* frameForPresShell = GetNearestFrameContainingPresShell(presShell);
7660 if (!frameForPresShell) {
7661 break;
7663 WidgetMouseEvent event(true, message, widget, WidgetMouseEvent::eReal,
7664 WidgetMouseEvent::eNormal);
7665 event.mRefPoint = aTouchEndEvent->mTouches[0]->mRefPoint;
7666 event.mButton = MouseButton::ePrimary;
7667 event.mButtons = message == eMouseDown ? MouseButtonsFlag::ePrimaryFlag
7668 : MouseButtonsFlag::eNoButtons;
7669 event.mInputSource = MouseEvent_Binding::MOZ_SOURCE_TOUCH;
7670 event.mClickCount = message == eMouseMove ? 0 : 1;
7671 event.mModifiers = aTouchEndEvent->mModifiers;
7672 event.convertToPointer = false;
7673 nsEventStatus mouseEventStatus = nsEventStatus_eIgnore;
7674 presShell->HandleEvent(frameForPresShell, &event, false, &mouseEventStatus);
7678 bool PresShell::EventHandler::MaybeDiscardEvent(WidgetGUIEvent* aGUIEvent) {
7679 MOZ_ASSERT(aGUIEvent);
7681 // If it is safe to dispatch events now, don't discard the event.
7682 if (nsContentUtils::IsSafeToRunScript()) {
7683 return false;
7686 // If the event does not cause dispatching DOM event (i.e., internal event),
7687 // we can keep handling it even when it's not safe to run script.
7688 if (!aGUIEvent->IsAllowedToDispatchDOMEvent()) {
7689 return false;
7692 // If the event is a composition event, we need to let IMEStateManager know
7693 // it's discarded because it needs to listen all composition events to manage
7694 // TextComposition instance.
7695 if (aGUIEvent->mClass == eCompositionEventClass) {
7696 IMEStateManager::OnCompositionEventDiscarded(
7697 aGUIEvent->AsCompositionEvent());
7700 #ifdef DEBUG
7701 if (aGUIEvent->IsIMERelatedEvent()) {
7702 nsPrintfCString warning("%s event is discarded",
7703 ToChar(aGUIEvent->mMessage));
7704 NS_WARNING(warning.get());
7706 #endif // #ifdef DEBUG
7708 nsContentUtils::WarnScriptWasIgnored(GetDocument());
7709 return true;
7712 // static
7713 nsIContent* PresShell::EventHandler::GetCapturingContentFor(
7714 WidgetGUIEvent* aGUIEvent) {
7715 return (aGUIEvent->mClass == ePointerEventClass ||
7716 aGUIEvent->mClass == eWheelEventClass ||
7717 aGUIEvent->HasMouseEventMessage())
7718 ? PresShell::GetCapturingContent()
7719 : nullptr;
7722 bool PresShell::EventHandler::GetRetargetEventDocument(
7723 WidgetGUIEvent* aGUIEvent, Document** aRetargetEventDocument) {
7724 MOZ_ASSERT(aGUIEvent);
7725 MOZ_ASSERT(aRetargetEventDocument);
7727 *aRetargetEventDocument = nullptr;
7729 // key and IME related events should not cross top level window boundary.
7730 // Basically, such input events should be fired only on focused widget.
7731 // However, some IMEs might need to clean up composition after focused
7732 // window is deactivated. And also some tests on MozMill want to test key
7733 // handling on deactivated window because MozMill window can be activated
7734 // during tests. So, there is no merit the events should be redirected to
7735 // active window. So, the events should be handled on the last focused
7736 // content in the last focused DOM window in same top level window.
7737 // Note, if no DOM window has been focused yet, we can discard the events.
7738 if (aGUIEvent->IsTargetedAtFocusedWindow()) {
7739 nsCOMPtr<nsPIDOMWindowOuter> window = GetFocusedDOMWindowInOurWindow();
7740 // No DOM window in same top level window has not been focused yet,
7741 // discard the events.
7742 if (!window) {
7743 return false;
7746 RefPtr<Document> retargetEventDoc = window->GetExtantDoc();
7747 if (!retargetEventDoc) {
7748 return false;
7750 retargetEventDoc.forget(aRetargetEventDocument);
7751 return true;
7754 nsIContent* capturingContent =
7755 EventHandler::GetCapturingContentFor(aGUIEvent);
7756 if (capturingContent) {
7757 // if the mouse is being captured then retarget the mouse event at the
7758 // document that is being captured.
7759 RefPtr<Document> retargetEventDoc = capturingContent->GetComposedDoc();
7760 retargetEventDoc.forget(aRetargetEventDocument);
7761 return true;
7764 #ifdef ANDROID
7765 if (aGUIEvent->mClass == eTouchEventClass ||
7766 aGUIEvent->mClass == eMouseEventClass ||
7767 aGUIEvent->mClass == eWheelEventClass) {
7768 RefPtr<Document> retargetEventDoc = mPresShell->GetPrimaryContentDocument();
7769 retargetEventDoc.forget(aRetargetEventDocument);
7770 return true;
7772 #endif // #ifdef ANDROID
7774 // When we don't find another document to handle the event, we need to keep
7775 // handling the event by ourselves.
7776 return true;
7779 nsIFrame* PresShell::EventHandler::GetFrameForHandlingEventWith(
7780 WidgetGUIEvent* aGUIEvent, Document* aRetargetDocument,
7781 nsIFrame* aFrameForPresShell) {
7782 MOZ_ASSERT(aGUIEvent);
7783 MOZ_ASSERT(aRetargetDocument);
7785 RefPtr<PresShell> retargetPresShell = aRetargetDocument->GetPresShell();
7786 // Even if the document doesn't have PresShell, i.e., it's invisible, we
7787 // need to dispatch only KeyboardEvent in its nearest visible document
7788 // because key focus shouldn't be caught by invisible document.
7789 if (!retargetPresShell) {
7790 if (!aGUIEvent->HasKeyEventMessage()) {
7791 return nullptr;
7793 Document* retargetEventDoc = aRetargetDocument;
7794 while (!retargetPresShell) {
7795 retargetEventDoc = retargetEventDoc->GetInProcessParentDocument();
7796 if (!retargetEventDoc) {
7797 return nullptr;
7799 retargetPresShell = retargetEventDoc->GetPresShell();
7803 // If the found PresShell is this instance, caller needs to keep handling
7804 // aGUIEvent by itself. Therefore, return the given frame which was set
7805 // to aFrame of HandleEvent().
7806 if (retargetPresShell == mPresShell) {
7807 return aFrameForPresShell;
7810 // Use root frame of the new PresShell if there is.
7811 nsIFrame* rootFrame = retargetPresShell->GetRootFrame();
7812 if (rootFrame) {
7813 return rootFrame;
7816 // Otherwise, and if aGUIEvent requires content of PresShell, caller should
7817 // stop handling the event.
7818 if (aGUIEvent->mMessage == eQueryTextContent ||
7819 aGUIEvent->IsContentCommandEvent()) {
7820 return nullptr;
7823 // Otherwise, use nearest ancestor frame which includes the PresShell.
7824 return GetNearestFrameContainingPresShell(retargetPresShell);
7827 bool PresShell::EventHandler::MaybeHandleEventWithAnotherPresShell(
7828 nsIFrame* aFrameForPresShell, WidgetGUIEvent* aGUIEvent,
7829 nsEventStatus* aEventStatus, nsresult* aRv) {
7830 MOZ_ASSERT(aGUIEvent);
7831 MOZ_ASSERT(aEventStatus);
7832 MOZ_ASSERT(aRv);
7834 *aRv = NS_OK;
7836 RefPtr<Document> retargetEventDoc;
7837 if (!GetRetargetEventDocument(aGUIEvent, getter_AddRefs(retargetEventDoc))) {
7838 // Nobody can handle this event. So, treat as handled by somebody to make
7839 // caller do nothing anymore.
7840 return true;
7843 // If there is no proper retarget document, the caller should handle the
7844 // event by itself.
7845 if (!retargetEventDoc) {
7846 return false;
7849 nsIFrame* frame = GetFrameForHandlingEventWith(aGUIEvent, retargetEventDoc,
7850 aFrameForPresShell);
7851 if (!frame) {
7852 // Nobody can handle this event. So, treat as handled by somebody to make
7853 // caller do nothing anymore.
7854 return true;
7857 // If we reached same frame as set to HandleEvent(), the caller should handle
7858 // the event by itself.
7859 if (frame == aFrameForPresShell) {
7860 return false;
7863 // We need to handle aGUIEvent with another PresShell.
7864 RefPtr<PresShell> presShell = frame->PresContext()->PresShell();
7865 *aRv = presShell->HandleEvent(frame, aGUIEvent, true, aEventStatus);
7866 return true;
7869 bool PresShell::EventHandler::MaybeDiscardOrDelayKeyboardEvent(
7870 WidgetGUIEvent* aGUIEvent) {
7871 MOZ_ASSERT(aGUIEvent);
7873 if (aGUIEvent->mClass != eKeyboardEventClass) {
7874 return false;
7877 Document* document = GetDocument();
7878 if (!document || !document->EventHandlingSuppressed()) {
7879 return false;
7882 MOZ_ASSERT_IF(InputTaskManager::CanSuspendInputEvent(),
7883 !InputTaskManager::Get()->IsSuspended());
7885 if (aGUIEvent->mMessage == eKeyDown) {
7886 mPresShell->mNoDelayedKeyEvents = true;
7887 } else if (!mPresShell->mNoDelayedKeyEvents) {
7888 UniquePtr<DelayedKeyEvent> delayedKeyEvent =
7889 MakeUnique<DelayedKeyEvent>(aGUIEvent->AsKeyboardEvent());
7890 mPresShell->mDelayedEvents.AppendElement(std::move(delayedKeyEvent));
7892 aGUIEvent->mFlags.mIsSuppressedOrDelayed = true;
7893 return true;
7896 bool PresShell::EventHandler::MaybeDiscardOrDelayMouseEvent(
7897 nsIFrame* aFrameToHandleEvent, WidgetGUIEvent* aGUIEvent) {
7898 MOZ_ASSERT(aFrameToHandleEvent);
7899 MOZ_ASSERT(aGUIEvent);
7901 if (aGUIEvent->mClass != eMouseEventClass) {
7902 return false;
7905 if (!aFrameToHandleEvent->PresContext()
7906 ->Document()
7907 ->EventHandlingSuppressed()) {
7908 return false;
7911 MOZ_ASSERT_IF(InputTaskManager::CanSuspendInputEvent() &&
7912 aGUIEvent->mMessage != eMouseMove,
7913 !InputTaskManager::Get()->IsSuspended());
7915 RefPtr<PresShell> ps = aFrameToHandleEvent->PresShell();
7917 if (aGUIEvent->mMessage == eMouseDown) {
7918 ps->mNoDelayedMouseEvents = true;
7919 } else if (!ps->mNoDelayedMouseEvents &&
7920 (aGUIEvent->mMessage == eMouseUp ||
7921 // contextmenu is triggered after right mouseup on Windows and
7922 // right mousedown on other platforms.
7923 aGUIEvent->mMessage == eContextMenu ||
7924 aGUIEvent->mMessage == eMouseExitFromWidget)) {
7925 UniquePtr<DelayedMouseEvent> delayedMouseEvent =
7926 MakeUnique<DelayedMouseEvent>(aGUIEvent->AsMouseEvent());
7927 ps->mDelayedEvents.AppendElement(std::move(delayedMouseEvent));
7930 // If there is a suppressed event listener associated with the document,
7931 // notify it about the suppressed mouse event. This allows devtools
7932 // features to continue receiving mouse events even when the devtools
7933 // debugger has paused execution in a page.
7934 RefPtr<EventListener> suppressedListener = aFrameToHandleEvent->PresContext()
7935 ->Document()
7936 ->GetSuppressedEventListener();
7937 if (!suppressedListener ||
7938 aGUIEvent->AsMouseEvent()->mReason == WidgetMouseEvent::eSynthesized) {
7939 return true;
7942 nsCOMPtr<nsIContent> targetContent;
7943 aFrameToHandleEvent->GetContentForEvent(aGUIEvent,
7944 getter_AddRefs(targetContent));
7945 if (targetContent) {
7946 aGUIEvent->mTarget = targetContent;
7949 nsCOMPtr<EventTarget> eventTarget = aGUIEvent->mTarget;
7950 RefPtr<Event> event = EventDispatcher::CreateEvent(
7951 eventTarget, aFrameToHandleEvent->PresContext(), aGUIEvent, u""_ns);
7953 suppressedListener->HandleEvent(*event);
7954 return true;
7957 nsIFrame* PresShell::EventHandler::MaybeFlushThrottledStyles(
7958 nsIFrame* aFrameForPresShell) {
7959 if (!GetDocument()) {
7960 // XXX Only when mPresShell has document, we'll try to look for a frame
7961 // containing mPresShell even if given frame is nullptr. Does this
7962 // make sense?
7963 return aFrameForPresShell;
7966 PresShell* rootPresShell = mPresShell->GetRootPresShell();
7967 if (NS_WARN_IF(!rootPresShell)) {
7968 return nullptr;
7970 Document* rootDocument = rootPresShell->GetDocument();
7971 if (NS_WARN_IF(!rootDocument)) {
7972 return nullptr;
7975 AutoWeakFrame weakFrameForPresShell(aFrameForPresShell);
7976 { // scope for scriptBlocker.
7977 nsAutoScriptBlocker scriptBlocker;
7978 FlushThrottledStyles(*rootDocument);
7981 if (weakFrameForPresShell.IsAlive()) {
7982 return aFrameForPresShell;
7985 return GetNearestFrameContainingPresShell(mPresShell);
7988 nsIFrame* PresShell::EventHandler::ComputeRootFrameToHandleEvent(
7989 nsIFrame* aFrameForPresShell, WidgetGUIEvent* aGUIEvent,
7990 nsIContent* aCapturingContent, bool* aIsCapturingContentIgnored,
7991 bool* aIsCaptureRetargeted) {
7992 MOZ_ASSERT(aFrameForPresShell);
7993 MOZ_ASSERT(aGUIEvent);
7994 MOZ_ASSERT(aIsCapturingContentIgnored);
7995 MOZ_ASSERT(aIsCaptureRetargeted);
7997 nsIFrame* rootFrameToHandleEvent = ComputeRootFrameToHandleEventWithPopup(
7998 aFrameForPresShell, aGUIEvent, aCapturingContent,
7999 aIsCapturingContentIgnored);
8000 if (*aIsCapturingContentIgnored) {
8001 // If the capturing content is ignored, we don't need to respect it.
8002 return rootFrameToHandleEvent;
8005 if (!aCapturingContent) {
8006 return rootFrameToHandleEvent;
8009 // If we have capturing content, let's compute root frame with it again.
8010 return ComputeRootFrameToHandleEventWithCapturingContent(
8011 rootFrameToHandleEvent, aCapturingContent, aIsCapturingContentIgnored,
8012 aIsCaptureRetargeted);
8015 nsIFrame* PresShell::EventHandler::ComputeRootFrameToHandleEventWithPopup(
8016 nsIFrame* aRootFrameToHandleEvent, WidgetGUIEvent* aGUIEvent,
8017 nsIContent* aCapturingContent, bool* aIsCapturingContentIgnored) {
8018 MOZ_ASSERT(aRootFrameToHandleEvent);
8019 MOZ_ASSERT(aGUIEvent);
8020 MOZ_ASSERT(aIsCapturingContentIgnored);
8022 *aIsCapturingContentIgnored = false;
8024 nsPresContext* framePresContext = aRootFrameToHandleEvent->PresContext();
8025 nsPresContext* rootPresContext = framePresContext->GetRootPresContext();
8026 NS_ASSERTION(rootPresContext == GetPresContext()->GetRootPresContext(),
8027 "How did we end up outside the connected "
8028 "prescontext/viewmanager hierarchy?");
8029 nsIFrame* popupFrame = nsLayoutUtils::GetPopupFrameForEventCoordinates(
8030 rootPresContext, aGUIEvent);
8031 if (!popupFrame) {
8032 return aRootFrameToHandleEvent;
8035 // If a remote browser is currently capturing input break out if we
8036 // detect a chrome generated popup.
8037 // XXXedgar, do we need to check fission OOP iframe?
8038 if (aCapturingContent &&
8039 EventStateManager::IsTopLevelRemoteTarget(aCapturingContent)) {
8040 *aIsCapturingContentIgnored = true;
8043 // If the popupFrame is an ancestor of the 'frame', the frame should
8044 // handle the event, otherwise, the popup should handle it.
8045 if (nsContentUtils::ContentIsCrossDocDescendantOf(
8046 framePresContext->GetPresShell()->GetDocument(),
8047 popupFrame->GetContent())) {
8048 return aRootFrameToHandleEvent;
8051 // If we aren't starting our event dispatch from the root frame of the
8052 // root prescontext, then someone must be capturing the mouse. In that
8053 // case we only want to use the popup list if the capture is
8054 // inside the popup.
8055 if (framePresContext == rootPresContext &&
8056 aRootFrameToHandleEvent == FrameConstructor()->GetRootFrame()) {
8057 return popupFrame;
8060 if (aCapturingContent && !*aIsCapturingContentIgnored &&
8061 aCapturingContent->IsInclusiveDescendantOf(popupFrame->GetContent())) {
8062 return popupFrame;
8065 return aRootFrameToHandleEvent;
8068 nsIFrame*
8069 PresShell::EventHandler::ComputeRootFrameToHandleEventWithCapturingContent(
8070 nsIFrame* aRootFrameToHandleEvent, nsIContent* aCapturingContent,
8071 bool* aIsCapturingContentIgnored, bool* aIsCaptureRetargeted) {
8072 MOZ_ASSERT(aRootFrameToHandleEvent);
8073 MOZ_ASSERT(aCapturingContent);
8074 MOZ_ASSERT(aIsCapturingContentIgnored);
8075 MOZ_ASSERT(aIsCaptureRetargeted);
8077 *aIsCapturingContentIgnored = false;
8078 *aIsCaptureRetargeted = false;
8080 // If a capture is active, determine if the BrowsingContext is active. If
8081 // not, clear the capture and target the mouse event normally instead. This
8082 // would occur if the mouse button is held down while a tab change occurs.
8083 // If the BrowsingContext is active, look for a scrolling container.
8084 BrowsingContext* bc = GetPresContext()->Document()->GetBrowsingContext();
8085 if (!bc || !bc->IsActive()) {
8086 ClearMouseCapture();
8087 *aIsCapturingContentIgnored = true;
8088 return aRootFrameToHandleEvent;
8091 if (PresShell::sCapturingContentInfo.mRetargetToElement) {
8092 *aIsCaptureRetargeted = true;
8093 return aRootFrameToHandleEvent;
8096 // A check was already done above to ensure that aCapturingContent is
8097 // in this presshell.
8098 NS_ASSERTION(aCapturingContent->OwnerDoc() == GetDocument(),
8099 "Unexpected document");
8100 nsIFrame* captureFrame = aCapturingContent->GetPrimaryFrame();
8101 if (!captureFrame) {
8102 return aRootFrameToHandleEvent;
8105 // scrollable frames should use the scrolling container as the root instead
8106 // of the document
8107 nsIScrollableFrame* scrollFrame = do_QueryFrame(captureFrame);
8108 return scrollFrame ? scrollFrame->GetScrolledFrame()
8109 : aRootFrameToHandleEvent;
8112 nsresult
8113 PresShell::EventHandler::HandleEventWithPointerCapturingContentWithoutItsFrame(
8114 nsIFrame* aFrameForPresShell, WidgetGUIEvent* aGUIEvent,
8115 nsIContent* aPointerCapturingContent, nsEventStatus* aEventStatus) {
8116 MOZ_ASSERT(aGUIEvent);
8117 MOZ_ASSERT(aPointerCapturingContent);
8118 MOZ_ASSERT(!aPointerCapturingContent->GetPrimaryFrame(),
8119 "Handle the event with frame rather than only with the content");
8120 MOZ_ASSERT(aEventStatus);
8122 RefPtr<PresShell> presShellForCapturingContent =
8123 PresShell::GetShellForEventTarget(nullptr, aPointerCapturingContent);
8124 if (!presShellForCapturingContent) {
8125 // If we can't process event for the capturing content, release
8126 // the capture.
8127 PointerEventHandler::ReleaseIfCaptureByDescendant(aPointerCapturingContent);
8128 // Since we don't dispatch ePointeUp nor ePointerCancel in this case,
8129 // EventStateManager::PostHandleEvent does not have a chance to dispatch
8130 // ePointerLostCapture event. Therefore, we need to dispatch it here.
8131 PointerEventHandler::MaybeImplicitlyReleasePointerCapture(aGUIEvent);
8132 return NS_OK;
8135 nsCOMPtr<nsIContent> overrideClickTarget =
8136 GetOverrideClickTarget(aGUIEvent, aFrameForPresShell);
8138 // Dispatch events to the capturing content even it's frame is
8139 // destroyed.
8140 PointerEventHandler::DispatchPointerFromMouseOrTouch(
8141 presShellForCapturingContent, nullptr, aPointerCapturingContent,
8142 aGUIEvent, false, aEventStatus, nullptr);
8144 if (presShellForCapturingContent == mPresShell) {
8145 return HandleEventWithTarget(aGUIEvent, nullptr, aPointerCapturingContent,
8146 aEventStatus, true, nullptr,
8147 overrideClickTarget);
8150 EventHandler eventHandlerForCapturingContent(
8151 std::move(presShellForCapturingContent));
8152 return eventHandlerForCapturingContent.HandleEventWithTarget(
8153 aGUIEvent, nullptr, aPointerCapturingContent, aEventStatus, true, nullptr,
8154 overrideClickTarget);
8157 nsresult PresShell::EventHandler::HandleEventAtFocusedContent(
8158 WidgetGUIEvent* aGUIEvent, nsEventStatus* aEventStatus) {
8159 MOZ_ASSERT(aGUIEvent);
8160 MOZ_ASSERT(aGUIEvent->IsTargetedAtFocusedContent());
8161 MOZ_ASSERT(aEventStatus);
8163 AutoCurrentEventInfoSetter eventInfoSetter(*this);
8165 RefPtr<Element> eventTargetElement =
8166 ComputeFocusedEventTargetElement(aGUIEvent);
8168 mPresShell->mCurrentEventFrame = nullptr;
8169 if (eventTargetElement) {
8170 nsresult rv = NS_OK;
8171 if (MaybeHandleEventWithAnotherPresShell(eventTargetElement, aGUIEvent,
8172 aEventStatus, &rv)) {
8173 return rv;
8177 // If we cannot handle the event with mPresShell, let's try to handle it
8178 // with parent PresShell.
8179 mPresShell->mCurrentEventContent = eventTargetElement;
8180 if (!mPresShell->GetCurrentEventContent() ||
8181 !mPresShell->GetCurrentEventFrame() ||
8182 InZombieDocument(mPresShell->mCurrentEventContent)) {
8183 return RetargetEventToParent(aGUIEvent, aEventStatus);
8186 nsresult rv =
8187 HandleEventWithCurrentEventInfo(aGUIEvent, aEventStatus, true, nullptr);
8188 return rv;
8191 Element* PresShell::EventHandler::ComputeFocusedEventTargetElement(
8192 WidgetGUIEvent* aGUIEvent) {
8193 MOZ_ASSERT(aGUIEvent);
8194 MOZ_ASSERT(aGUIEvent->IsTargetedAtFocusedContent());
8196 // key and IME related events go to the focused frame in this DOM window.
8197 nsPIDOMWindowOuter* window = GetDocument()->GetWindow();
8198 nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
8199 Element* eventTargetElement = nsFocusManager::GetFocusedDescendant(
8200 window, nsFocusManager::eOnlyCurrentWindow,
8201 getter_AddRefs(focusedWindow));
8203 // otherwise, if there is no focused content or the focused content has
8204 // no frame, just use the root content. This ensures that key events
8205 // still get sent to the window properly if nothing is focused or if a
8206 // frame goes away while it is focused.
8207 if (!eventTargetElement || !eventTargetElement->GetPrimaryFrame()) {
8208 eventTargetElement = GetDocument()->GetUnfocusedKeyEventTarget();
8211 switch (aGUIEvent->mMessage) {
8212 case eKeyDown:
8213 sLastKeyDownEventTargetElement = eventTargetElement;
8214 return eventTargetElement;
8215 case eKeyPress:
8216 case eKeyUp:
8217 if (!sLastKeyDownEventTargetElement) {
8218 return eventTargetElement;
8220 // If a different element is now focused for the keypress/keyup event
8221 // than what was focused during the keydown event, check if the new
8222 // focused element is not in a chrome document any more, and if so,
8223 // retarget the event back at the keydown target. This prevents a
8224 // content area from grabbing the focus from chrome in-between key
8225 // events.
8226 if (eventTargetElement) {
8227 bool keyDownIsChrome = nsContentUtils::IsChromeDoc(
8228 sLastKeyDownEventTargetElement->GetComposedDoc());
8229 if (keyDownIsChrome != nsContentUtils::IsChromeDoc(
8230 eventTargetElement->GetComposedDoc()) ||
8231 (keyDownIsChrome && BrowserParent::GetFrom(eventTargetElement))) {
8232 eventTargetElement = sLastKeyDownEventTargetElement;
8236 if (aGUIEvent->mMessage == eKeyUp) {
8237 sLastKeyDownEventTargetElement = nullptr;
8239 [[fallthrough]];
8240 default:
8241 return eventTargetElement;
8245 bool PresShell::EventHandler::MaybeHandleEventWithAnotherPresShell(
8246 Element* aEventTargetElement, WidgetGUIEvent* aGUIEvent,
8247 nsEventStatus* aEventStatus, nsresult* aRv) {
8248 MOZ_ASSERT(aEventTargetElement);
8249 MOZ_ASSERT(aGUIEvent);
8250 MOZ_ASSERT(!aGUIEvent->IsUsingCoordinates());
8251 MOZ_ASSERT(aEventStatus);
8252 MOZ_ASSERT(aRv);
8254 Document* eventTargetDocument = aEventTargetElement->OwnerDoc();
8255 if (!eventTargetDocument || eventTargetDocument == GetDocument()) {
8256 *aRv = NS_OK;
8257 return false;
8260 RefPtr<PresShell> eventTargetPresShell = eventTargetDocument->GetPresShell();
8261 if (!eventTargetPresShell) {
8262 *aRv = NS_OK;
8263 return true; // No PresShell can handle the event.
8266 EventHandler eventHandler(std::move(eventTargetPresShell));
8267 *aRv = eventHandler.HandleRetargetedEvent(aGUIEvent, aEventStatus,
8268 aEventTargetElement);
8269 return true;
8272 nsresult PresShell::EventHandler::HandleEventWithFrameForPresShell(
8273 nsIFrame* aFrameForPresShell, WidgetGUIEvent* aGUIEvent,
8274 nsEventStatus* aEventStatus) {
8275 MOZ_ASSERT(aGUIEvent);
8276 MOZ_ASSERT(!aGUIEvent->IsUsingCoordinates());
8277 MOZ_ASSERT(!aGUIEvent->IsTargetedAtFocusedContent());
8278 MOZ_ASSERT(aEventStatus);
8280 AutoCurrentEventInfoSetter eventInfoSetter(*this, aFrameForPresShell,
8281 nullptr);
8283 nsresult rv = NS_OK;
8284 if (mPresShell->GetCurrentEventFrame()) {
8285 rv =
8286 HandleEventWithCurrentEventInfo(aGUIEvent, aEventStatus, true, nullptr);
8289 return rv;
8292 Document* PresShell::GetPrimaryContentDocument() {
8293 nsPresContext* context = GetPresContext();
8294 if (!context || !context->IsRoot()) {
8295 return nullptr;
8298 nsCOMPtr<nsIDocShellTreeItem> shellAsTreeItem = context->GetDocShell();
8299 if (!shellAsTreeItem) {
8300 return nullptr;
8303 nsCOMPtr<nsIDocShellTreeOwner> owner;
8304 shellAsTreeItem->GetTreeOwner(getter_AddRefs(owner));
8305 if (!owner) {
8306 return nullptr;
8309 // now get the primary content shell (active tab)
8310 nsCOMPtr<nsIDocShellTreeItem> item;
8311 owner->GetPrimaryContentShell(getter_AddRefs(item));
8312 nsCOMPtr<nsIDocShell> childDocShell = do_QueryInterface(item);
8313 if (!childDocShell) {
8314 return nullptr;
8317 return childDocShell->GetExtantDocument();
8320 nsresult PresShell::EventHandler::HandleEventWithTarget(
8321 WidgetEvent* aEvent, nsIFrame* aNewEventFrame, nsIContent* aNewEventContent,
8322 nsEventStatus* aEventStatus, bool aIsHandlingNativeEvent,
8323 nsIContent** aTargetContent, nsIContent* aOverrideClickTarget) {
8324 MOZ_ASSERT(aEvent);
8325 MOZ_DIAGNOSTIC_ASSERT(aEvent->IsTrusted());
8327 #if DEBUG
8328 MOZ_ASSERT(!aNewEventFrame ||
8329 aNewEventFrame->PresContext()->GetPresShell() == mPresShell,
8330 "wrong shell");
8331 if (aNewEventContent) {
8332 Document* doc = aNewEventContent->GetComposedDoc();
8333 NS_ASSERTION(doc, "event for content that isn't in a document");
8334 // NOTE: We don't require that the document still have a PresShell.
8335 // See bug 1375940.
8337 #endif
8338 NS_ENSURE_STATE(!aNewEventContent ||
8339 aNewEventContent->GetComposedDoc() == GetDocument());
8340 if (aEvent->mClass == ePointerEventClass) {
8341 mPresShell->RecordPointerLocation(aEvent->AsMouseEvent());
8343 AutoPointerEventTargetUpdater updater(mPresShell, aEvent, aNewEventFrame,
8344 aNewEventContent, aTargetContent);
8345 AutoCurrentEventInfoSetter eventInfoSetter(*this, aNewEventFrame,
8346 aNewEventContent);
8347 nsresult rv = HandleEventWithCurrentEventInfo(aEvent, aEventStatus, false,
8348 aOverrideClickTarget);
8349 return rv;
8352 namespace {
8354 class MOZ_RAII AutoEventHandler final {
8355 public:
8356 AutoEventHandler(WidgetEvent* aEvent, Document* aDocument) : mEvent(aEvent) {
8357 MOZ_ASSERT(mEvent);
8358 MOZ_ASSERT(mEvent->IsTrusted());
8360 if (mEvent->mMessage == eMouseDown) {
8361 PresShell::ReleaseCapturingContent();
8362 PresShell::AllowMouseCapture(true);
8364 if (NeedsToUpdateCurrentMouseBtnState()) {
8365 WidgetMouseEvent* mouseEvent = mEvent->AsMouseEvent();
8366 if (mouseEvent) {
8367 EventStateManager::sCurrentMouseBtn = mouseEvent->mButton;
8372 ~AutoEventHandler() {
8373 if (mEvent->mMessage == eMouseDown) {
8374 PresShell::AllowMouseCapture(false);
8376 if (NeedsToUpdateCurrentMouseBtnState()) {
8377 EventStateManager::sCurrentMouseBtn = MouseButton::eNotPressed;
8381 protected:
8382 bool NeedsToUpdateCurrentMouseBtnState() const {
8383 return mEvent->mMessage == eMouseDown || mEvent->mMessage == eMouseUp ||
8384 mEvent->mMessage == ePointerDown || mEvent->mMessage == ePointerUp;
8387 WidgetEvent* mEvent;
8390 } // anonymous namespace
8392 nsresult PresShell::EventHandler::HandleEventWithCurrentEventInfo(
8393 WidgetEvent* aEvent, nsEventStatus* aEventStatus,
8394 bool aIsHandlingNativeEvent, nsIContent* aOverrideClickTarget) {
8395 MOZ_ASSERT(aEvent);
8396 MOZ_ASSERT(aEventStatus);
8398 RefPtr<EventStateManager> manager = GetPresContext()->EventStateManager();
8400 // If we cannot handle the event with mPresShell because of no target,
8401 // just record the response time.
8402 // XXX Is this intentional? In such case, the score is really good because
8403 // of nothing to do. So, it may make average and median better.
8404 if (NS_EVENT_NEEDS_FRAME(aEvent) && !mPresShell->GetCurrentEventFrame() &&
8405 !mPresShell->GetCurrentEventContent()) {
8406 RecordEventHandlingResponsePerformance(aEvent);
8407 return NS_OK;
8410 if (mPresShell->mCurrentEventContent && aEvent->IsTargetedAtFocusedWindow() &&
8411 aEvent->AllowFlushingPendingNotifications()) {
8412 if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) {
8413 // This may run script now. So, mPresShell might be destroyed after here.
8414 nsCOMPtr<nsIContent> currentEventContent =
8415 mPresShell->mCurrentEventContent;
8416 fm->FlushBeforeEventHandlingIfNeeded(currentEventContent);
8420 bool touchIsNew = false;
8421 if (!PrepareToDispatchEvent(aEvent, aEventStatus, &touchIsNew)) {
8422 return NS_OK;
8425 // We finished preparing to dispatch the event. So, let's record the
8426 // performance.
8427 RecordEventPreparationPerformance(aEvent);
8429 AutoHandlingUserInputStatePusher userInpStatePusher(
8430 UserActivation::IsUserInteractionEvent(aEvent), aEvent);
8431 AutoEventHandler eventHandler(aEvent, GetDocument());
8432 AutoPopupStatePusher popupStatePusher(
8433 PopupBlocker::GetEventPopupControlState(aEvent));
8435 // FIXME. If the event was reused, we need to clear the old target,
8436 // bug 329430
8437 aEvent->mTarget = nullptr;
8439 HandlingTimeAccumulator handlingTimeAccumulator(*this, aEvent);
8441 nsresult rv = DispatchEvent(manager, aEvent, touchIsNew, aEventStatus,
8442 aOverrideClickTarget);
8444 if (!mPresShell->IsDestroying() && aIsHandlingNativeEvent) {
8445 // Ensure that notifications to IME should be sent before getting next
8446 // native event from the event queue.
8447 // XXX Should we check the event message or event class instead of
8448 // using aIsHandlingNativeEvent?
8449 manager->TryToFlushPendingNotificationsToIME();
8452 FinalizeHandlingEvent(aEvent, aEventStatus);
8454 RecordEventHandlingResponsePerformance(aEvent);
8456 return rv; // Result of DispatchEvent()
8459 nsresult PresShell::EventHandler::DispatchEvent(
8460 EventStateManager* aEventStateManager, WidgetEvent* aEvent,
8461 bool aTouchIsNew, nsEventStatus* aEventStatus,
8462 nsIContent* aOverrideClickTarget) {
8463 MOZ_ASSERT(aEventStateManager);
8464 MOZ_ASSERT(aEvent);
8465 MOZ_ASSERT(aEventStatus);
8467 // 1. Give event to event manager for pre event state changes and
8468 // generation of synthetic events.
8469 { // Scope for presContext
8470 RefPtr<nsPresContext> presContext = GetPresContext();
8471 nsCOMPtr<nsIContent> eventContent = mPresShell->mCurrentEventContent;
8472 nsresult rv = aEventStateManager->PreHandleEvent(
8473 presContext, aEvent, mPresShell->mCurrentEventFrame, eventContent,
8474 aEventStatus, aOverrideClickTarget);
8475 if (NS_FAILED(rv)) {
8476 return rv;
8480 // 2. Give event to the DOM for third party and JS use.
8481 bool wasHandlingKeyBoardEvent = nsContentUtils::IsHandlingKeyBoardEvent();
8482 if (aEvent->mClass == eKeyboardEventClass) {
8483 nsContentUtils::SetIsHandlingKeyBoardEvent(true);
8485 // If EventStateManager or something wants reply from remote process and
8486 // needs to win any other event listeners in chrome, the event is both
8487 // stopped its propagation and marked as "waiting reply from remote
8488 // process". In this case, PresShell shouldn't dispatch the event into
8489 // the DOM tree because they don't have a chance to stop propagation in
8490 // the system event group. On the other hand, if its propagation is not
8491 // stopped, that means that the event may be reserved by chrome. If it's
8492 // reserved by chrome, the event shouldn't be sent to any remote
8493 // processes. In this case, PresShell needs to dispatch the event to
8494 // the DOM tree for checking if it's reserved.
8495 if (aEvent->IsAllowedToDispatchDOMEvent() &&
8496 !(aEvent->PropagationStopped() &&
8497 aEvent->IsWaitingReplyFromRemoteProcess())) {
8498 MOZ_ASSERT(nsContentUtils::IsSafeToRunScript(),
8499 "Somebody changed aEvent to cause a DOM event!");
8500 nsPresShellEventCB eventCB(mPresShell);
8501 if (nsIFrame* target = mPresShell->GetCurrentEventFrame()) {
8502 if (target->OnlySystemGroupDispatch(aEvent->mMessage)) {
8503 aEvent->StopPropagation();
8506 if (aEvent->mClass == eTouchEventClass) {
8507 DispatchTouchEventToDOM(aEvent, aEventStatus, &eventCB, aTouchIsNew);
8508 } else {
8509 DispatchEventToDOM(aEvent, aEventStatus, &eventCB);
8513 nsContentUtils::SetIsHandlingKeyBoardEvent(wasHandlingKeyBoardEvent);
8515 if (mPresShell->IsDestroying()) {
8516 return NS_OK;
8519 // 3. Give event to event manager for post event state changes and
8520 // generation of synthetic events.
8521 // Refetch the prescontext, in case it changed.
8522 RefPtr<nsPresContext> presContext = GetPresContext();
8523 return aEventStateManager->PostHandleEvent(
8524 presContext, aEvent, mPresShell->GetCurrentEventFrame(), aEventStatus,
8525 aOverrideClickTarget);
8528 bool PresShell::EventHandler::PrepareToDispatchEvent(
8529 WidgetEvent* aEvent, nsEventStatus* aEventStatus, bool* aTouchIsNew) {
8530 MOZ_ASSERT(aEvent->IsTrusted());
8531 MOZ_ASSERT(aEventStatus);
8532 MOZ_ASSERT(aTouchIsNew);
8534 *aTouchIsNew = false;
8535 if (aEvent->IsUserAction()) {
8536 mPresShell->mHasHandledUserInput = true;
8539 switch (aEvent->mMessage) {
8540 case eKeyPress:
8541 case eKeyDown:
8542 case eKeyUp: {
8543 WidgetKeyboardEvent* keyboardEvent = aEvent->AsKeyboardEvent();
8544 MaybeHandleKeyboardEventBeforeDispatch(keyboardEvent);
8545 return true;
8547 case eMouseMove: {
8548 bool allowCapture = EventStateManager::GetActiveEventStateManager() &&
8549 GetPresContext() &&
8550 GetPresContext()->EventStateManager() ==
8551 EventStateManager::GetActiveEventStateManager();
8552 PresShell::AllowMouseCapture(allowCapture);
8553 return true;
8555 case eDrop: {
8556 nsCOMPtr<nsIDragSession> session = nsContentUtils::GetDragSession();
8557 if (session) {
8558 bool onlyChromeDrop = false;
8559 session->GetOnlyChromeDrop(&onlyChromeDrop);
8560 if (onlyChromeDrop) {
8561 aEvent->mFlags.mOnlyChromeDispatch = true;
8564 return true;
8566 case eDragExit: {
8567 if (!StaticPrefs::dom_event_dragexit_enabled()) {
8568 aEvent->mFlags.mOnlyChromeDispatch = true;
8570 return true;
8572 case eContextMenu: {
8573 // If we cannot open context menu even though eContextMenu is fired, we
8574 // should stop dispatching it into the DOM.
8575 WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
8576 if (mouseEvent->IsContextMenuKeyEvent() &&
8577 !AdjustContextMenuKeyEvent(mouseEvent)) {
8578 return false;
8581 // If "Shift" state is active, context menu should be forcibly opened even
8582 // if web apps want to prevent it since we respect our users' intention.
8583 // In this case, we don't fire "contextmenu" event on web content because
8584 // of not cancelable.
8585 if (mouseEvent->IsShift() &&
8586 StaticPrefs::dom_event_contextmenu_shift_suppresses_event()) {
8587 aEvent->mFlags.mOnlyChromeDispatch = true;
8588 aEvent->mFlags.mRetargetToNonNativeAnonymous = true;
8590 return true;
8592 case eTouchStart:
8593 case eTouchMove:
8594 case eTouchEnd:
8595 case eTouchCancel:
8596 case eTouchPointerCancel:
8597 return mPresShell->mTouchManager.PreHandleEvent(
8598 aEvent, aEventStatus, *aTouchIsNew, mPresShell->mCurrentEventContent);
8599 default:
8600 return true;
8604 void PresShell::EventHandler::FinalizeHandlingEvent(
8605 WidgetEvent* aEvent, const nsEventStatus* aStatus) {
8606 switch (aEvent->mMessage) {
8607 case eKeyPress:
8608 case eKeyDown:
8609 case eKeyUp: {
8610 if (aEvent->AsKeyboardEvent()->mKeyCode == NS_VK_ESCAPE) {
8611 if (aEvent->mMessage == eKeyUp) {
8612 // Reset this flag after key up is handled.
8613 mPresShell->mIsLastChromeOnlyEscapeKeyConsumed = false;
8614 } else {
8615 if (aEvent->mFlags.mOnlyChromeDispatch &&
8616 aEvent->mFlags.mDefaultPreventedByChrome) {
8617 mPresShell->mIsLastChromeOnlyEscapeKeyConsumed = true;
8619 if (aEvent->mMessage == eKeyDown &&
8620 !aEvent->mFlags.mDefaultPrevented) {
8621 if (RefPtr<Document> doc = GetDocument()) {
8622 doc->HandleEscKey();
8627 if (aEvent->mMessage == eKeyDown) {
8628 mPresShell->mIsLastKeyDownCanceled = aEvent->mFlags.mDefaultPrevented;
8630 return;
8632 case eMouseUp:
8633 // reset the capturing content now that the mouse button is up
8634 PresShell::ReleaseCapturingContent();
8635 return;
8636 case eMouseMove:
8637 PresShell::AllowMouseCapture(false);
8638 return;
8639 case eDrag:
8640 case eDragEnd:
8641 case eDragEnter:
8642 case eDragExit:
8643 case eDragLeave:
8644 case eDragOver:
8645 case eDrop: {
8646 // After any drag event other than dragstart (which is handled
8647 // separately, as we need to collect the data first), the DataTransfer
8648 // needs to be made protected, and then disconnected.
8649 DataTransfer* dataTransfer = aEvent->AsDragEvent()->mDataTransfer;
8650 if (dataTransfer) {
8651 dataTransfer->Disconnect();
8653 return;
8655 case eTouchStart:
8656 case eTouchMove:
8657 case eTouchEnd:
8658 case eTouchCancel:
8659 case eTouchPointerCancel:
8660 case eMouseLongTap:
8661 case eContextMenu: {
8662 mPresShell->mTouchManager.PostHandleEvent(aEvent, aStatus);
8663 break;
8665 default:
8666 return;
8670 void PresShell::EventHandler::MaybeHandleKeyboardEventBeforeDispatch(
8671 WidgetKeyboardEvent* aKeyboardEvent) {
8672 MOZ_ASSERT(aKeyboardEvent);
8674 if (aKeyboardEvent->mKeyCode != NS_VK_ESCAPE) {
8675 return;
8678 // If we're in fullscreen mode, exit from it forcibly when Escape key is
8679 // pressed.
8680 Document* doc = mPresShell->GetCurrentEventContent()
8681 ? mPresShell->mCurrentEventContent->OwnerDoc()
8682 : nullptr;
8683 Document* root = nsContentUtils::GetInProcessSubtreeRootDocument(doc);
8684 if (root && root->GetFullscreenElement()) {
8685 // Prevent default action on ESC key press when exiting
8686 // DOM fullscreen mode. This prevents the browser ESC key
8687 // handler from stopping all loads in the document, which
8688 // would cause <video> loads to stop.
8689 // XXX We need to claim the Escape key event which will be
8690 // dispatched only into chrome is already consumed by
8691 // content because we need to prevent its default here
8692 // for some reasons (not sure) but we need to detect
8693 // if a chrome event handler will call PreventDefault()
8694 // again and check it later.
8695 aKeyboardEvent->PreventDefaultBeforeDispatch(CrossProcessForwarding::eStop);
8696 aKeyboardEvent->mFlags.mOnlyChromeDispatch = true;
8698 // The event listeners in chrome can prevent this ESC behavior by
8699 // calling prevent default on the preceding keydown/press events.
8700 if (!mPresShell->mIsLastChromeOnlyEscapeKeyConsumed &&
8701 aKeyboardEvent->mMessage == eKeyUp) {
8702 // ESC key released while in DOM fullscreen mode.
8703 // Fully exit all browser windows and documents from
8704 // fullscreen mode.
8705 Document::AsyncExitFullscreen(nullptr);
8709 nsCOMPtr<Document> pointerLockedDoc = PointerLockManager::GetLockedDocument();
8710 if (!mPresShell->mIsLastChromeOnlyEscapeKeyConsumed && pointerLockedDoc) {
8711 // XXX See above comment to understand the reason why this needs
8712 // to claim that the Escape key event is consumed by content
8713 // even though it will be dispatched only into chrome.
8714 aKeyboardEvent->PreventDefaultBeforeDispatch(CrossProcessForwarding::eStop);
8715 aKeyboardEvent->mFlags.mOnlyChromeDispatch = true;
8716 if (aKeyboardEvent->mMessage == eKeyUp) {
8717 PointerLockManager::Unlock();
8722 void PresShell::EventHandler::RecordEventPreparationPerformance(
8723 const WidgetEvent* aEvent) {
8724 MOZ_ASSERT(aEvent);
8726 switch (aEvent->mMessage) {
8727 case eKeyPress:
8728 case eKeyDown:
8729 case eKeyUp:
8730 if (aEvent->AsKeyboardEvent()->ShouldInteractionTimeRecorded()) {
8731 GetPresContext()->RecordInteractionTime(
8732 nsPresContext::InteractionType::KeyInteraction, aEvent->mTimeStamp);
8734 Telemetry::AccumulateTimeDelta(Telemetry::INPUT_EVENT_QUEUED_KEYBOARD_MS,
8735 aEvent->mTimeStamp);
8736 return;
8738 case eMouseDown:
8739 case eMouseUp:
8740 Telemetry::AccumulateTimeDelta(Telemetry::INPUT_EVENT_QUEUED_CLICK_MS,
8741 aEvent->mTimeStamp);
8742 [[fallthrough]];
8743 case ePointerDown:
8744 case ePointerUp:
8745 GetPresContext()->RecordInteractionTime(
8746 nsPresContext::InteractionType::ClickInteraction, aEvent->mTimeStamp);
8747 return;
8749 case eMouseMove:
8750 if (aEvent->mFlags.mHandledByAPZ) {
8751 Telemetry::AccumulateTimeDelta(
8752 Telemetry::INPUT_EVENT_QUEUED_APZ_MOUSE_MOVE_MS,
8753 aEvent->mTimeStamp);
8755 GetPresContext()->RecordInteractionTime(
8756 nsPresContext::InteractionType::MouseMoveInteraction,
8757 aEvent->mTimeStamp);
8758 return;
8760 case eWheel:
8761 if (aEvent->mFlags.mHandledByAPZ) {
8762 Telemetry::AccumulateTimeDelta(
8763 Telemetry::INPUT_EVENT_QUEUED_APZ_WHEEL_MS, aEvent->mTimeStamp);
8765 return;
8767 case eTouchMove:
8768 if (aEvent->mFlags.mHandledByAPZ) {
8769 Telemetry::AccumulateTimeDelta(
8770 Telemetry::INPUT_EVENT_QUEUED_APZ_TOUCH_MOVE_MS,
8771 aEvent->mTimeStamp);
8773 return;
8775 default:
8776 return;
8780 void PresShell::EventHandler::RecordEventHandlingResponsePerformance(
8781 const WidgetEvent* aEvent) {
8782 if (!Telemetry::CanRecordBase() || aEvent->mTimeStamp.IsNull() ||
8783 aEvent->mTimeStamp <= mPresShell->mLastOSWake ||
8784 !aEvent->AsInputEvent()) {
8785 return;
8788 TimeStamp now = TimeStamp::Now();
8789 double millis = (now - aEvent->mTimeStamp).ToMilliseconds();
8790 Telemetry::Accumulate(Telemetry::INPUT_EVENT_RESPONSE_MS, millis);
8791 if (GetDocument() &&
8792 GetDocument()->GetReadyStateEnum() != Document::READYSTATE_COMPLETE) {
8793 Telemetry::Accumulate(Telemetry::LOAD_INPUT_EVENT_RESPONSE_MS, millis);
8796 if (!sLastInputProcessed || sLastInputProcessed < aEvent->mTimeStamp) {
8797 if (sLastInputProcessed) {
8798 // This input event was created after we handled the last one.
8799 // Accumulate the previous events' coalesced duration.
8800 double lastMillis =
8801 (sLastInputProcessed - sLastInputCreated).ToMilliseconds();
8802 Telemetry::Accumulate(Telemetry::INPUT_EVENT_RESPONSE_COALESCED_MS,
8803 lastMillis);
8805 if (MOZ_UNLIKELY(!PresShell::sProcessInteractable)) {
8806 // For content process, we use the ready state of
8807 // top-level-content-document to know if the process has finished the
8808 // start-up.
8809 // For parent process, see the topic
8810 // 'sessionstore-one-or-no-tab-restored' in PresShell::Observe.
8811 if (XRE_IsContentProcess() && GetDocument() &&
8812 GetDocument()->IsTopLevelContentDocument()) {
8813 switch (GetDocument()->GetReadyStateEnum()) {
8814 case Document::READYSTATE_INTERACTIVE:
8815 case Document::READYSTATE_COMPLETE:
8816 PresShell::sProcessInteractable = true;
8817 break;
8818 default:
8819 break;
8823 if (MOZ_LIKELY(PresShell::sProcessInteractable)) {
8824 Telemetry::Accumulate(Telemetry::INPUT_EVENT_RESPONSE_POST_STARTUP_MS,
8825 lastMillis);
8826 } else {
8827 Telemetry::Accumulate(Telemetry::INPUT_EVENT_RESPONSE_STARTUP_MS,
8828 lastMillis);
8831 sLastInputCreated = aEvent->mTimeStamp;
8832 } else if (aEvent->mTimeStamp < sLastInputCreated) {
8833 // This event was created before the last input. May be processing out
8834 // of order, so coalesce backwards, too.
8835 sLastInputCreated = aEvent->mTimeStamp;
8837 sLastInputProcessed = now;
8840 // static
8841 nsIPrincipal*
8842 PresShell::EventHandler::GetDocumentPrincipalToCompareWithBlacklist(
8843 PresShell& aPresShell) {
8844 nsPresContext* presContext = aPresShell.GetPresContext();
8845 if (NS_WARN_IF(!presContext)) {
8846 return nullptr;
8848 return presContext->Document()->GetPrincipalForPrefBasedHacks();
8851 nsresult PresShell::EventHandler::DispatchEventToDOM(
8852 WidgetEvent* aEvent, nsEventStatus* aEventStatus,
8853 nsPresShellEventCB* aEventCB) {
8854 nsresult rv = NS_OK;
8855 nsCOMPtr<nsINode> eventTarget = mPresShell->mCurrentEventContent;
8856 nsPresShellEventCB* eventCBPtr = aEventCB;
8857 if (!eventTarget) {
8858 nsCOMPtr<nsIContent> targetContent;
8859 if (mPresShell->mCurrentEventFrame) {
8860 rv = mPresShell->mCurrentEventFrame->GetContentForEvent(
8861 aEvent, getter_AddRefs(targetContent));
8863 if (NS_SUCCEEDED(rv) && targetContent) {
8864 eventTarget = targetContent;
8865 } else if (GetDocument()) {
8866 eventTarget = GetDocument();
8867 // If we don't have any content, the callback wouldn't probably
8868 // do nothing.
8869 eventCBPtr = nullptr;
8872 if (eventTarget) {
8873 if (eventTarget->OwnerDoc()->ShouldResistFingerprinting(
8874 RFPTarget::WidgetEvents) &&
8875 aEvent->IsBlockedForFingerprintingResistance()) {
8876 aEvent->mFlags.mOnlySystemGroupDispatchInContent = true;
8877 } else if (aEvent->mMessage == eKeyPress) {
8878 // If eKeyPress event is marked as not dispatched in the default event
8879 // group in web content, it's caused by non-printable key or key
8880 // combination. In this case, UI Events declares that browsers
8881 // shouldn't dispatch keypress event. However, some web apps may be
8882 // broken with this strict behavior due to historical issue.
8883 // Therefore, we need to keep dispatching keypress event for such keys
8884 // even with breaking the standard.
8885 // Similarly, the other browsers sets non-zero value of keyCode or
8886 // charCode of keypress event to the other. Therefore, we should
8887 // behave so, however, some web apps may be broken. On such web apps,
8888 // we should keep using legacy our behavior.
8889 if (!mPresShell->mInitializedWithKeyPressEventDispatchingBlacklist) {
8890 mPresShell->mInitializedWithKeyPressEventDispatchingBlacklist = true;
8891 nsCOMPtr<nsIPrincipal> principal =
8892 GetDocumentPrincipalToCompareWithBlacklist(*mPresShell);
8893 if (principal) {
8894 mPresShell->mForceDispatchKeyPressEventsForNonPrintableKeys =
8895 principal->IsURIInPrefList(
8896 "dom.keyboardevent.keypress.hack.dispatch_non_printable_"
8897 "keys") ||
8898 principal->IsURIInPrefList(
8899 "dom.keyboardevent.keypress.hack."
8900 "dispatch_non_printable_keys.addl");
8902 mPresShell->mForceUseLegacyKeyCodeAndCharCodeValues |=
8903 principal->IsURIInPrefList(
8904 "dom.keyboardevent.keypress.hack."
8905 "use_legacy_keycode_and_charcode") ||
8906 principal->IsURIInPrefList(
8907 "dom.keyboardevent.keypress.hack."
8908 "use_legacy_keycode_and_charcode.addl");
8911 if (mPresShell->mForceDispatchKeyPressEventsForNonPrintableKeys) {
8912 aEvent->mFlags.mOnlySystemGroupDispatchInContent = false;
8914 if (mPresShell->mForceUseLegacyKeyCodeAndCharCodeValues) {
8915 aEvent->AsKeyboardEvent()->mUseLegacyKeyCodeAndCharCodeValues = true;
8919 if (aEvent->mClass == eCompositionEventClass) {
8920 RefPtr<nsPresContext> presContext = GetPresContext();
8921 RefPtr<BrowserParent> browserParent =
8922 IMEStateManager::GetActiveBrowserParent();
8923 IMEStateManager::DispatchCompositionEvent(
8924 eventTarget, presContext, browserParent, aEvent->AsCompositionEvent(),
8925 aEventStatus, eventCBPtr);
8926 } else {
8927 if (aEvent->mClass == eMouseEventClass) {
8928 PresShell::sMouseButtons = aEvent->AsMouseEvent()->mButtons;
8930 RefPtr<nsPresContext> presContext = GetPresContext();
8931 EventDispatcher::Dispatch(eventTarget, presContext, aEvent, nullptr,
8932 aEventStatus, eventCBPtr);
8935 return rv;
8938 void PresShell::EventHandler::DispatchTouchEventToDOM(
8939 WidgetEvent* aEvent, nsEventStatus* aEventStatus,
8940 nsPresShellEventCB* aEventCB, bool aTouchIsNew) {
8941 // calling preventDefault on touchstart or the first touchmove for a
8942 // point prevents mouse events. calling it on the touchend should
8943 // prevent click dispatching.
8944 bool canPrevent = (aEvent->mMessage == eTouchStart) ||
8945 (aEvent->mMessage == eTouchMove && aTouchIsNew) ||
8946 (aEvent->mMessage == eTouchEnd);
8947 bool preventDefault = false;
8948 nsEventStatus tmpStatus = nsEventStatus_eIgnore;
8949 WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
8951 // loop over all touches and dispatch events on any that have changed
8952 for (dom::Touch* touch : touchEvent->mTouches) {
8953 // We should remove all suppressed touch instances in
8954 // TouchManager::PreHandleEvent.
8955 MOZ_ASSERT(!touch->mIsTouchEventSuppressed);
8957 if (!touch || !touch->mChanged) {
8958 continue;
8961 nsCOMPtr<EventTarget> targetPtr = touch->mTarget;
8962 nsCOMPtr<nsIContent> content = do_QueryInterface(targetPtr);
8963 if (!content) {
8964 continue;
8967 Document* doc = content->OwnerDoc();
8968 nsIContent* capturingContent = PresShell::GetCapturingContent();
8969 if (capturingContent) {
8970 if (capturingContent->OwnerDoc() != doc) {
8971 // Wrong document, don't dispatch anything.
8972 continue;
8974 content = capturingContent;
8976 // copy the event
8977 MOZ_ASSERT(touchEvent->IsTrusted());
8978 WidgetTouchEvent newEvent(true, touchEvent->mMessage, touchEvent->mWidget);
8979 newEvent.AssignTouchEventData(*touchEvent, false);
8980 newEvent.mTarget = targetPtr;
8981 newEvent.mFlags.mHandledByAPZ = touchEvent->mFlags.mHandledByAPZ;
8983 RefPtr<PresShell> contentPresShell;
8984 if (doc == GetDocument()) {
8985 contentPresShell = doc->GetPresShell();
8986 if (contentPresShell) {
8987 // XXXsmaug huge hack. Pushing possibly capturing content,
8988 // even though event target is something else.
8989 contentPresShell->PushCurrentEventInfo(content->GetPrimaryFrame(),
8990 content);
8994 RefPtr<nsPresContext> presContext = doc->GetPresContext();
8995 if (!presContext) {
8996 if (contentPresShell) {
8997 contentPresShell->PopCurrentEventInfo();
8999 continue;
9002 tmpStatus = nsEventStatus_eIgnore;
9003 EventDispatcher::Dispatch(targetPtr, presContext, &newEvent, nullptr,
9004 &tmpStatus, aEventCB);
9005 if (nsEventStatus_eConsumeNoDefault == tmpStatus ||
9006 newEvent.mFlags.mMultipleActionsPrevented) {
9007 preventDefault = true;
9010 if (newEvent.mFlags.mMultipleActionsPrevented) {
9011 touchEvent->mFlags.mMultipleActionsPrevented = true;
9014 if (contentPresShell) {
9015 contentPresShell->PopCurrentEventInfo();
9019 if (preventDefault && canPrevent) {
9020 *aEventStatus = nsEventStatus_eConsumeNoDefault;
9021 } else {
9022 *aEventStatus = nsEventStatus_eIgnore;
9026 // Dispatch event to content only (NOT full processing)
9027 // See also HandleEventWithTarget which does full event processing.
9028 nsresult PresShell::HandleDOMEventWithTarget(nsIContent* aTargetContent,
9029 WidgetEvent* aEvent,
9030 nsEventStatus* aStatus) {
9031 nsresult rv = NS_OK;
9033 PushCurrentEventInfo(nullptr, aTargetContent);
9035 // Bug 41013: Check if the event should be dispatched to content.
9036 // It's possible that we are in the middle of destroying the window
9037 // and the js context is out of date. This check detects the case
9038 // that caused a crash in bug 41013, but there may be a better way
9039 // to handle this situation!
9040 nsCOMPtr<nsISupports> container = mPresContext->GetContainerWeak();
9041 if (container) {
9042 // Dispatch event to content
9043 rv = EventDispatcher::Dispatch(aTargetContent, mPresContext, aEvent,
9044 nullptr, aStatus);
9047 PopCurrentEventInfo();
9048 return rv;
9051 // See the method above.
9052 nsresult PresShell::HandleDOMEventWithTarget(nsIContent* aTargetContent,
9053 Event* aEvent,
9054 nsEventStatus* aStatus) {
9055 nsresult rv = NS_OK;
9057 PushCurrentEventInfo(nullptr, aTargetContent);
9058 nsCOMPtr<nsISupports> container = mPresContext->GetContainerWeak();
9059 if (container) {
9060 rv = EventDispatcher::DispatchDOMEvent(aTargetContent, nullptr, aEvent,
9061 mPresContext, aStatus);
9064 PopCurrentEventInfo();
9065 return rv;
9068 bool PresShell::EventHandler::AdjustContextMenuKeyEvent(
9069 WidgetMouseEvent* aMouseEvent) {
9070 // if a menu is open, open the context menu relative to the active item on the
9071 // menu.
9072 if (nsXULPopupManager* pm = nsXULPopupManager::GetInstance()) {
9073 nsIFrame* popupFrame = pm->GetTopPopup(widget::PopupType::Menu);
9074 if (popupFrame) {
9075 nsIFrame* itemFrame = (static_cast<nsMenuPopupFrame*>(popupFrame))
9076 ->GetCurrentMenuItemFrame();
9077 if (!itemFrame) itemFrame = popupFrame;
9079 nsCOMPtr<nsIWidget> widget = popupFrame->GetNearestWidget();
9080 aMouseEvent->mWidget = widget;
9081 LayoutDeviceIntPoint widgetPoint = widget->WidgetToScreenOffset();
9082 aMouseEvent->mRefPoint =
9083 LayoutDeviceIntPoint::FromAppUnitsToNearest(
9084 itemFrame->GetScreenRectInAppUnits().BottomLeft(),
9085 itemFrame->PresContext()->AppUnitsPerDevPixel()) -
9086 widgetPoint;
9088 mPresShell->mCurrentEventContent = itemFrame->GetContent();
9089 mPresShell->mCurrentEventFrame = itemFrame;
9091 return true;
9095 // If we're here because of the key-equiv for showing context menus, we
9096 // have to twiddle with the NS event to make sure the context menu comes
9097 // up in the upper left of the relevant content area before we create
9098 // the DOM event. Since we never call InitMouseEvent() on the event,
9099 // the client X/Y will be 0,0. We can make use of that if the widget is null.
9100 // Use the root view manager's widget since it's most likely to have one,
9101 // and the coordinates returned by GetCurrentItemAndPositionForElement
9102 // are relative to the widget of the root of the root view manager.
9103 nsRootPresContext* rootPC = GetPresContext()->GetRootPresContext();
9104 aMouseEvent->mRefPoint = LayoutDeviceIntPoint(0, 0);
9105 if (rootPC) {
9106 aMouseEvent->mWidget =
9107 rootPC->PresShell()->GetViewManager()->GetRootWidget();
9108 if (aMouseEvent->mWidget) {
9109 // default the refpoint to the topleft of our document
9110 nsPoint offset(0, 0);
9111 nsIFrame* rootFrame = FrameConstructor()->GetRootFrame();
9112 if (rootFrame) {
9113 nsView* view = rootFrame->GetClosestView(&offset);
9114 offset += view->GetOffsetToWidget(aMouseEvent->mWidget);
9115 aMouseEvent->mRefPoint = LayoutDeviceIntPoint::FromAppUnitsToNearest(
9116 offset, GetPresContext()->AppUnitsPerDevPixel());
9119 } else {
9120 aMouseEvent->mWidget = nullptr;
9123 // see if we should use the caret position for the popup
9124 LayoutDeviceIntPoint caretPoint;
9125 // Beware! This may flush notifications via synchronous
9126 // ScrollSelectionIntoView.
9127 if (PrepareToUseCaretPosition(MOZ_KnownLive(aMouseEvent->mWidget),
9128 caretPoint)) {
9129 // caret position is good
9130 int32_t devPixelRatio = GetPresContext()->AppUnitsPerDevPixel();
9131 caretPoint = LayoutDeviceIntPoint::FromAppUnitsToNearest(
9132 ViewportUtils::LayoutToVisual(
9133 LayoutDeviceIntPoint::ToAppUnits(caretPoint, devPixelRatio),
9134 GetPresContext()->PresShell()),
9135 devPixelRatio);
9136 aMouseEvent->mRefPoint = caretPoint;
9137 return true;
9140 // If we're here because of the key-equiv for showing context menus, we
9141 // have to reset the event target to the currently focused element. Get it
9142 // from the focus controller.
9143 RefPtr<Element> currentFocus;
9144 nsFocusManager* fm = nsFocusManager::GetFocusManager();
9145 if (fm) {
9146 currentFocus = fm->GetFocusedElement();
9149 // Reset event coordinates relative to focused frame in view
9150 if (currentFocus) {
9151 nsCOMPtr<nsIContent> currentPointElement;
9152 GetCurrentItemAndPositionForElement(
9153 currentFocus, getter_AddRefs(currentPointElement),
9154 aMouseEvent->mRefPoint, MOZ_KnownLive(aMouseEvent->mWidget));
9155 if (currentPointElement) {
9156 mPresShell->mCurrentEventContent = currentPointElement;
9157 mPresShell->mCurrentEventFrame = nullptr;
9158 mPresShell->GetCurrentEventFrame();
9162 return true;
9165 // PresShell::EventHandler::PrepareToUseCaretPosition
9167 // This checks to see if we should use the caret position for popup context
9168 // menus. Returns true if the caret position should be used, and the
9169 // coordinates of that position is returned in aTargetPt. This function
9170 // will also scroll the window as needed to make the caret visible.
9172 // The event widget should be the widget that generated the event, and
9173 // whose coordinate system the resulting event's mRefPoint should be
9174 // relative to. The returned point is in device pixels realtive to the
9175 // widget passed in.
9176 bool PresShell::EventHandler::PrepareToUseCaretPosition(
9177 nsIWidget* aEventWidget, LayoutDeviceIntPoint& aTargetPt) {
9178 nsresult rv;
9180 // check caret visibility
9181 RefPtr<nsCaret> caret = mPresShell->GetCaret();
9182 NS_ENSURE_TRUE(caret, false);
9184 bool caretVisible = caret->IsVisible();
9185 if (!caretVisible) return false;
9187 // caret selection, this is a temporary weak reference, so no refcounting is
9188 // needed
9189 Selection* domSelection = caret->GetSelection();
9190 NS_ENSURE_TRUE(domSelection, false);
9192 // since the match could be an anonymous textnode inside a
9193 // <textarea> or text <input>, we need to get the outer frame
9194 // note: frames are not refcounted
9195 nsIFrame* frame = nullptr; // may be nullptr
9196 nsINode* node = domSelection->GetFocusNode();
9197 NS_ENSURE_TRUE(node, false);
9198 nsCOMPtr<nsIContent> content = nsIContent::FromNode(node);
9199 if (content) {
9200 nsIContent* nonNative = content->FindFirstNonChromeOnlyAccessContent();
9201 content = nonNative;
9204 if (content) {
9205 // It seems like ScrollSelectionIntoView should be enough, but it's
9206 // not. The problem is that scrolling the selection into view when it is
9207 // below the current viewport will align the top line of the frame exactly
9208 // with the bottom of the window. This is fine, BUT, the popup event causes
9209 // the control to be re-focused which does this exact call to
9210 // ScrollContentIntoView, which has a one-pixel disagreement of whether the
9211 // frame is actually in view. The result is that the frame is aligned with
9212 // the top of the window, but the menu is still at the bottom.
9214 // Doing this call first forces the frame to be in view, eliminating the
9215 // problem. The only difference in the result is that if your cursor is in
9216 // an edit box below the current view, you'll get the edit box aligned with
9217 // the top of the window. This is arguably better behavior anyway.
9218 rv = MOZ_KnownLive(mPresShell)
9219 ->ScrollContentIntoView(
9220 content,
9221 ScrollAxis(WhereToScroll::Nearest, WhenToScroll::IfNotVisible),
9222 ScrollAxis(WhereToScroll::Nearest, WhenToScroll::IfNotVisible),
9223 ScrollFlags::ScrollOverflowHidden);
9224 NS_ENSURE_SUCCESS(rv, false);
9225 frame = content->GetPrimaryFrame();
9226 NS_WARNING_ASSERTION(frame, "No frame for focused content?");
9229 // Actually scroll the selection (ie caret) into view. Note that this must
9230 // be synchronous since we will be checking the caret position on the screen.
9232 // Be easy about errors, and just don't scroll in those cases. Better to have
9233 // the correct menu at a weird place than the wrong menu.
9234 // After ScrollSelectionIntoView(), the pending notifications might be
9235 // flushed and PresShell/PresContext/Frames may be dead. See bug 418470.
9236 nsCOMPtr<nsISelectionController> selCon;
9237 if (frame)
9238 frame->GetSelectionController(GetPresContext(), getter_AddRefs(selCon));
9239 else
9240 selCon = static_cast<nsISelectionController*>(mPresShell);
9241 if (selCon) {
9242 rv = selCon->ScrollSelectionIntoView(
9243 nsISelectionController::SELECTION_NORMAL,
9244 nsISelectionController::SELECTION_FOCUS_REGION,
9245 nsISelectionController::SCROLL_SYNCHRONOUS);
9246 NS_ENSURE_SUCCESS(rv, false);
9249 nsPresContext* presContext = GetPresContext();
9251 // get caret position relative to the closest view
9252 nsRect caretCoords;
9253 nsIFrame* caretFrame = caret->GetGeometry(&caretCoords);
9254 if (!caretFrame) return false;
9255 nsPoint viewOffset;
9256 nsView* view = caretFrame->GetClosestView(&viewOffset);
9257 if (!view) return false;
9258 // and then get the caret coords relative to the event widget
9259 if (aEventWidget) {
9260 viewOffset += view->GetOffsetToWidget(aEventWidget);
9262 caretCoords.MoveBy(viewOffset);
9264 // caret coordinates are in app units, convert to pixels
9265 aTargetPt.x =
9266 presContext->AppUnitsToDevPixels(caretCoords.x + caretCoords.width);
9267 aTargetPt.y =
9268 presContext->AppUnitsToDevPixels(caretCoords.y + caretCoords.height);
9270 // make sure rounding doesn't return a pixel which is outside the caret
9271 // (e.g. one line lower)
9272 aTargetPt.y -= 1;
9274 return true;
9277 void PresShell::EventHandler::GetCurrentItemAndPositionForElement(
9278 Element* aFocusedElement, nsIContent** aTargetToUse,
9279 LayoutDeviceIntPoint& aTargetPt, nsIWidget* aRootWidget) {
9280 nsCOMPtr<nsIContent> focusedContent = aFocusedElement;
9281 MOZ_KnownLive(mPresShell)
9282 ->ScrollContentIntoView(focusedContent, ScrollAxis(), ScrollAxis(),
9283 ScrollFlags::ScrollOverflowHidden);
9285 nsPresContext* presContext = GetPresContext();
9287 bool istree = false, checkLineHeight = true;
9288 nscoord extraTreeY = 0;
9290 // Set the position to just underneath the current item for multi-select
9291 // lists or just underneath the selected item for single-select lists. If
9292 // the element is not a list, or there is no selection, leave the position
9293 // as is.
9294 nsCOMPtr<Element> item;
9295 nsCOMPtr<nsIDOMXULMultiSelectControlElement> multiSelect =
9296 aFocusedElement->AsXULMultiSelectControl();
9297 if (multiSelect) {
9298 checkLineHeight = false;
9300 int32_t currentIndex;
9301 multiSelect->GetCurrentIndex(&currentIndex);
9302 if (currentIndex >= 0) {
9303 RefPtr<XULTreeElement> tree = XULTreeElement::FromNode(focusedContent);
9304 // Tree view special case (tree items have no frames)
9305 // Get the focused row and add its coordinates, which are already in
9306 // pixels
9307 // XXX Boris, should we create a new interface so that this doesn't
9308 // need to know about trees? Something like nsINodelessChildCreator
9309 // which could provide the current focus coordinates?
9310 if (tree) {
9311 tree->EnsureRowIsVisible(currentIndex);
9312 int32_t firstVisibleRow = tree->GetFirstVisibleRow();
9313 int32_t rowHeight = tree->RowHeight();
9315 extraTreeY += nsPresContext::CSSPixelsToAppUnits(
9316 (currentIndex - firstVisibleRow + 1) * rowHeight);
9317 istree = true;
9319 RefPtr<nsTreeColumns> cols = tree->GetColumns();
9320 if (cols) {
9321 nsTreeColumn* col = cols->GetFirstColumn();
9322 if (col) {
9323 RefPtr<Element> colElement = col->Element();
9324 nsIFrame* frame = colElement->GetPrimaryFrame();
9325 if (frame) {
9326 extraTreeY += frame->GetSize().height;
9330 } else {
9331 multiSelect->GetCurrentItem(getter_AddRefs(item));
9334 } else {
9335 // don't check menulists as the selected item will be inside a popup.
9336 nsCOMPtr<nsIDOMXULMenuListElement> menulist =
9337 aFocusedElement->AsXULMenuList();
9338 if (!menulist) {
9339 nsCOMPtr<nsIDOMXULSelectControlElement> select =
9340 aFocusedElement->AsXULSelectControl();
9341 if (select) {
9342 checkLineHeight = false;
9343 select->GetSelectedItem(getter_AddRefs(item));
9348 if (item) {
9349 focusedContent = item;
9352 nsIFrame* frame = focusedContent->GetPrimaryFrame();
9353 if (frame) {
9354 NS_ASSERTION(
9355 frame->PresContext() == GetPresContext(),
9356 "handling event for focused content that is not in our document?");
9358 nsPoint frameOrigin(0, 0);
9360 // Get the frame's origin within its view
9361 nsView* view = frame->GetClosestView(&frameOrigin);
9362 NS_ASSERTION(view, "No view for frame");
9364 // View's origin relative the widget
9365 if (aRootWidget) {
9366 frameOrigin += view->GetOffsetToWidget(aRootWidget);
9369 // Start context menu down and to the right from top left of frame
9370 // use the lineheight. This is a good distance to move the context
9371 // menu away from the top left corner of the frame. If we always
9372 // used the frame height, the context menu could end up far away,
9373 // for example when we're focused on linked images.
9374 // On the other hand, we want to use the frame height if it's less
9375 // than the current line height, so that the context menu appears
9376 // associated with the correct frame.
9377 nscoord extra = 0;
9378 if (!istree) {
9379 extra = frame->GetSize().height;
9380 if (checkLineHeight) {
9381 nsIScrollableFrame* scrollFrame =
9382 nsLayoutUtils::GetNearestScrollableFrame(
9383 frame, nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN |
9384 nsLayoutUtils::SCROLLABLE_FIXEDPOS_FINDS_ROOT);
9385 if (scrollFrame) {
9386 nsSize scrollAmount = scrollFrame->GetLineScrollAmount();
9387 nsIFrame* f = do_QueryFrame(scrollFrame);
9388 int32_t APD = presContext->AppUnitsPerDevPixel();
9389 int32_t scrollAPD = f->PresContext()->AppUnitsPerDevPixel();
9390 scrollAmount = scrollAmount.ScaleToOtherAppUnits(scrollAPD, APD);
9391 if (extra > scrollAmount.height) {
9392 extra = scrollAmount.height;
9398 aTargetPt.x = presContext->AppUnitsToDevPixels(frameOrigin.x);
9399 aTargetPt.y =
9400 presContext->AppUnitsToDevPixels(frameOrigin.y + extra + extraTreeY);
9403 NS_IF_ADDREF(*aTargetToUse = focusedContent);
9406 bool PresShell::ShouldIgnoreInvalidation() {
9407 return mPaintingSuppressed || !mIsActive || mIsNeverPainting;
9410 void PresShell::WillPaint() {
9411 // Check the simplest things first. In particular, it's important to
9412 // check mIsActive before making any of the more expensive calls such
9413 // as GetRootPresContext, for the case of a browser with a large
9414 // number of tabs.
9415 // Don't bother doing anything if some viewmanager in our tree is painting
9416 // while we still have painting suppressed or we are not active.
9417 if (!mIsActive || mPaintingSuppressed || !IsVisible()) {
9418 return;
9421 nsRootPresContext* rootPresContext = mPresContext->GetRootPresContext();
9422 if (!rootPresContext) {
9423 // In some edge cases, such as when we don't have a root frame yet,
9424 // we can't find the root prescontext. There's nothing to do in that
9425 // case.
9426 return;
9429 rootPresContext->FlushWillPaintObservers();
9430 if (mIsDestroying) return;
9432 // Process reflows, if we have them, to reduce flicker due to invalidates and
9433 // reflow being interspersed. Note that we _do_ allow this to be
9434 // interruptible; if we can't do all the reflows it's better to flicker a bit
9435 // than to freeze up.
9436 FlushPendingNotifications(
9437 ChangesToFlush(FlushType::InterruptibleLayout, false));
9440 void PresShell::DidPaintWindow() {
9441 nsRootPresContext* rootPresContext = mPresContext->GetRootPresContext();
9442 if (rootPresContext != mPresContext) {
9443 // This could be a popup's presshell. No point in notifying XPConnect
9444 // about compositing of popups.
9445 return;
9448 if (!mHasReceivedPaintMessage) {
9449 mHasReceivedPaintMessage = true;
9451 nsCOMPtr<nsIObserverService> obsvc = services::GetObserverService();
9452 if (obsvc && mDocument) {
9453 nsPIDOMWindowOuter* window = mDocument->GetWindow();
9454 if (window && nsGlobalWindowOuter::Cast(window)->IsChromeWindow()) {
9455 obsvc->NotifyObservers(window, "widget-first-paint", nullptr);
9461 bool PresShell::IsVisible() const {
9462 if (!mIsActive || !mViewManager) return false;
9464 nsView* view = mViewManager->GetRootView();
9465 if (!view) return true;
9467 // inner view of subdoc frame
9468 view = view->GetParent();
9469 if (!view) return true;
9471 // subdoc view
9472 view = view->GetParent();
9473 if (!view) return true;
9475 nsIFrame* frame = view->GetFrame();
9476 if (!frame) return true;
9478 return frame->IsVisibleConsideringAncestors(
9479 nsIFrame::VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY);
9482 void PresShell::SuppressDisplayport(bool aEnabled) {
9483 if (aEnabled) {
9484 mActiveSuppressDisplayport++;
9485 } else if (mActiveSuppressDisplayport > 0) {
9486 bool isSuppressed = IsDisplayportSuppressed();
9487 mActiveSuppressDisplayport--;
9488 if (isSuppressed && !IsDisplayportSuppressed()) {
9489 // We unsuppressed the displayport, trigger a paint
9490 if (nsIFrame* rootFrame = mFrameConstructor->GetRootFrame()) {
9491 rootFrame->SchedulePaint();
9497 static bool sDisplayPortSuppressionRespected = true;
9499 void PresShell::RespectDisplayportSuppression(bool aEnabled) {
9500 bool isSuppressed = IsDisplayportSuppressed();
9501 sDisplayPortSuppressionRespected = aEnabled;
9502 if (isSuppressed && !IsDisplayportSuppressed()) {
9503 // We unsuppressed the displayport, trigger a paint
9504 if (nsIFrame* rootFrame = mFrameConstructor->GetRootFrame()) {
9505 rootFrame->SchedulePaint();
9510 bool PresShell::IsDisplayportSuppressed() {
9511 return sDisplayPortSuppressionRespected && mActiveSuppressDisplayport > 0;
9514 static CallState FreezeSubDocument(Document& aDocument) {
9515 if (PresShell* presShell = aDocument.GetPresShell()) {
9516 presShell->Freeze();
9518 return CallState::Continue;
9521 void PresShell::Freeze(bool aIncludeSubDocuments) {
9522 mUpdateApproximateFrameVisibilityEvent.Revoke();
9524 MaybeReleaseCapturingContent();
9526 if (mCaret) {
9527 SetCaretEnabled(false);
9530 mPaintingSuppressed = true;
9532 if (aIncludeSubDocuments && mDocument) {
9533 mDocument->EnumerateSubDocuments(FreezeSubDocument);
9536 nsPresContext* presContext = GetPresContext();
9537 if (presContext) {
9538 presContext->DisableInteractionTimeRecording();
9539 if (presContext->RefreshDriver()->GetPresContext() == presContext) {
9540 presContext->RefreshDriver()->Freeze();
9543 if (nsPresContext* rootPresContext = presContext->GetRootPresContext()) {
9544 rootPresContext->ResetUserInputEventsAllowed();
9548 mFrozen = true;
9549 if (mDocument) {
9550 UpdateImageLockingState();
9554 void PresShell::FireOrClearDelayedEvents(bool aFireEvents) {
9555 mNoDelayedMouseEvents = false;
9556 mNoDelayedKeyEvents = false;
9557 if (!aFireEvents) {
9558 mDelayedEvents.Clear();
9559 return;
9562 if (mDocument) {
9563 RefPtr<Document> doc = mDocument;
9564 while (!mIsDestroying && mDelayedEvents.Length() &&
9565 !doc->EventHandlingSuppressed()) {
9566 UniquePtr<DelayedEvent> ev = std::move(mDelayedEvents[0]);
9567 mDelayedEvents.RemoveElementAt(0);
9568 if (ev->IsKeyPressEvent() && mIsLastKeyDownCanceled) {
9569 continue;
9571 ev->Dispatch();
9573 if (!doc->EventHandlingSuppressed()) {
9574 mDelayedEvents.Clear();
9579 void PresShell::Thaw(bool aIncludeSubDocuments) {
9580 nsPresContext* presContext = GetPresContext();
9581 if (presContext &&
9582 presContext->RefreshDriver()->GetPresContext() == presContext) {
9583 presContext->RefreshDriver()->Thaw();
9586 if (aIncludeSubDocuments && mDocument) {
9587 mDocument->EnumerateSubDocuments([](Document& aSubDoc) {
9588 if (PresShell* presShell = aSubDoc.GetPresShell()) {
9589 presShell->Thaw();
9591 return CallState::Continue;
9595 // Get the activeness of our presshell, as this might have changed
9596 // while we were in the bfcache
9597 ActivenessMaybeChanged();
9599 // We're now unfrozen
9600 mFrozen = false;
9601 UpdateImageLockingState();
9603 UnsuppressPainting();
9605 // In case the above UnsuppressPainting call didn't start the
9606 // refresh driver, we manually start the refresh driver to
9607 // ensure nsPresContext::MaybeIncreaseMeasuredTicksSinceLoading
9608 // can be called for user input events handling.
9609 if (presContext && presContext->IsRoot()) {
9610 if (!presContext->RefreshDriver()->HasPendingTick()) {
9611 presContext->RefreshDriver()->InitializeTimer();
9616 //--------------------------------------------------------
9617 // Start of protected and private methods on the PresShell
9618 //--------------------------------------------------------
9620 void PresShell::MaybeScheduleReflow() {
9621 ASSERT_REFLOW_SCHEDULED_STATE();
9622 if (mObservingLayoutFlushes || mIsDestroying || mIsReflowing ||
9623 mDirtyRoots.IsEmpty())
9624 return;
9626 if (!mPresContext->HasPendingInterrupt() || !ScheduleReflowOffTimer()) {
9627 ScheduleReflow();
9630 ASSERT_REFLOW_SCHEDULED_STATE();
9633 void PresShell::ScheduleReflow() {
9634 ASSERT_REFLOW_SCHEDULED_STATE();
9635 DoObserveLayoutFlushes();
9636 ASSERT_REFLOW_SCHEDULED_STATE();
9639 void PresShell::WillCauseReflow() {
9640 nsContentUtils::AddScriptBlocker();
9641 ++mChangeNestCount;
9644 void PresShell::DidCauseReflow() {
9645 NS_ASSERTION(mChangeNestCount != 0, "Unexpected call to DidCauseReflow()");
9646 --mChangeNestCount;
9647 nsContentUtils::RemoveScriptBlocker();
9650 void PresShell::WillDoReflow() {
9651 mDocument->FlushUserFontSet();
9653 mPresContext->FlushCounterStyles();
9655 mPresContext->FlushFontFeatureValues();
9657 mPresContext->FlushFontPaletteValues();
9659 mLastReflowStart = GetPerformanceNowUnclamped();
9662 void PresShell::DidDoReflow(bool aInterruptible) {
9663 MOZ_ASSERT(mPendingDidDoReflow);
9664 if (!nsContentUtils::IsSafeToRunScript()) {
9665 // If we're reflowing while script-blocked (e.g. from container query
9666 // updates), defer our reflow callbacks until the end of our next layout
9667 // flush.
9668 SetNeedLayoutFlush();
9669 return;
9672 auto clearPendingDidDoReflow =
9673 MakeScopeExit([&] { mPendingDidDoReflow = false; });
9675 mHiddenContentInForcedLayout.Clear();
9677 HandlePostedReflowCallbacks(aInterruptible);
9679 if (mIsDestroying) {
9680 return;
9684 nsAutoScriptBlocker scriptBlocker;
9685 AutoAssertNoFlush noReentrantFlush(*this);
9686 if (nsCOMPtr<nsIDocShell> docShell = mPresContext->GetDocShell()) {
9687 DOMHighResTimeStamp now = GetPerformanceNowUnclamped();
9688 docShell->NotifyReflowObservers(aInterruptible, mLastReflowStart, now);
9691 if (StaticPrefs::layout_reflow_synthMouseMove()) {
9692 SynthesizeMouseMove(false);
9695 mPresContext->NotifyMissingFonts();
9698 if (mIsDestroying) {
9699 return;
9702 if (mDirtyRoots.IsEmpty()) {
9703 // We only unsuppress painting if we're out of reflows. It's pointless to
9704 // do so if reflows are still pending, since reflows are just going to
9705 // thrash the frames around some more. By waiting we avoid an overeager
9706 // "jitter" effect.
9707 if (mShouldUnsuppressPainting) {
9708 mShouldUnsuppressPainting = false;
9709 UnsuppressAndInvalidate();
9711 } else {
9712 // If any new reflow commands were enqueued during the reflow, schedule
9713 // another reflow event to process them. Note that we want to do this
9714 // after DidDoReflow(), since that method can change whether there are
9715 // dirty roots around by flushing, and there's no point in posting a
9716 // reflow event just to have the flush revoke it.
9717 MaybeScheduleReflow();
9718 // And record that we might need flushing
9719 SetNeedLayoutFlush();
9723 DOMHighResTimeStamp PresShell::GetPerformanceNowUnclamped() {
9724 DOMHighResTimeStamp now = 0;
9726 if (nsPIDOMWindowInner* window = mDocument->GetInnerWindow()) {
9727 Performance* perf = window->GetPerformance();
9729 if (perf) {
9730 now = perf->NowUnclamped();
9734 return now;
9737 void PresShell::sReflowContinueCallback(nsITimer* aTimer, void* aPresShell) {
9738 RefPtr<PresShell> self = static_cast<PresShell*>(aPresShell);
9740 MOZ_ASSERT(aTimer == self->mReflowContinueTimer, "Unexpected timer");
9741 self->mReflowContinueTimer = nullptr;
9742 self->ScheduleReflow();
9745 bool PresShell::ScheduleReflowOffTimer() {
9746 MOZ_ASSERT(!mObservingLayoutFlushes, "Shouldn't get here");
9747 ASSERT_REFLOW_SCHEDULED_STATE();
9749 if (!mReflowContinueTimer) {
9750 nsresult rv = NS_NewTimerWithFuncCallback(
9751 getter_AddRefs(mReflowContinueTimer), sReflowContinueCallback, this, 30,
9752 nsITimer::TYPE_ONE_SHOT, "sReflowContinueCallback",
9753 GetMainThreadSerialEventTarget());
9754 return NS_SUCCEEDED(rv);
9756 return true;
9759 bool PresShell::DoReflow(nsIFrame* target, bool aInterruptible,
9760 OverflowChangedTracker* aOverflowTracker) {
9761 [[maybe_unused]] nsIURI* uri = mDocument->GetDocumentURI();
9762 AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING_RELEVANT_FOR_JS(
9763 "Reflow", LAYOUT_Reflow, uri ? uri->GetSpecOrDefault() : "N/A"_ns);
9765 LAYOUT_TELEMETRY_RECORD_BASE(Reflow);
9767 PerfStats::AutoMetricRecording<PerfStats::Metric::Reflowing> autoRecording;
9769 gfxTextPerfMetrics* tp = mPresContext->GetTextPerfMetrics();
9770 TimeStamp timeStart;
9771 if (tp) {
9772 tp->Accumulate();
9773 tp->reflowCount++;
9774 timeStart = TimeStamp::Now();
9777 // Schedule a paint, but don't actually mark this frame as changed for
9778 // retained DL building purposes. If any child frames get moved, then
9779 // they will schedule paint again. We could probaby skip this, and just
9780 // schedule a similar paint when a frame is deleted.
9781 target->SchedulePaint(nsIFrame::PAINT_DEFAULT, false);
9783 Maybe<uint64_t> innerWindowID;
9784 if (auto* window = mDocument->GetInnerWindow()) {
9785 innerWindowID = Some(window->WindowID());
9787 AutoProfilerTracing tracingLayoutFlush(
9788 "Paint", aInterruptible ? "Reflow (interruptible)" : "Reflow (sync)",
9789 geckoprofiler::category::LAYOUT, std::move(mReflowCause), innerWindowID);
9790 mReflowCause = nullptr;
9792 FlushPendingScrollAnchorSelections();
9794 if (mReflowContinueTimer) {
9795 mReflowContinueTimer->Cancel();
9796 mReflowContinueTimer = nullptr;
9799 const bool isRoot = target == mFrameConstructor->GetRootFrame();
9801 MOZ_ASSERT(isRoot || aOverflowTracker,
9802 "caller must provide overflow tracker when reflowing "
9803 "non-root frames");
9805 // CreateReferenceRenderingContext can return nullptr
9806 UniquePtr<gfxContext> rcx(CreateReferenceRenderingContext());
9808 #ifdef DEBUG
9809 mCurrentReflowRoot = target;
9810 #endif
9812 // If the target frame is the root of the frame hierarchy, then
9813 // use all the available space. If it's simply a `reflow root',
9814 // then use the target frame's size as the available space.
9815 WritingMode wm = target->GetWritingMode();
9816 LogicalSize size(wm);
9817 if (isRoot) {
9818 size = LogicalSize(wm, mPresContext->GetVisibleArea().Size());
9819 } else {
9820 size = target->GetLogicalSize();
9823 OverflowAreas oldOverflow; // initialized and used only when !isRoot
9824 if (!isRoot) {
9825 oldOverflow = target->GetOverflowAreas();
9828 NS_ASSERTION(!target->GetNextInFlow() && !target->GetPrevInFlow(),
9829 "reflow roots should never split");
9831 // Don't pass size directly to the reflow input, since a
9832 // constrained height implies page/column breaking.
9833 LogicalSize reflowSize(wm, size.ISize(wm), NS_UNCONSTRAINEDSIZE);
9834 ReflowInput reflowInput(mPresContext, target, rcx.get(), reflowSize,
9835 ReflowInput::InitFlag::CallerWillInit);
9836 reflowInput.mOrthogonalLimit = size.BSize(wm);
9838 if (isRoot) {
9839 reflowInput.Init(mPresContext);
9841 // When the root frame is being reflowed with unconstrained block-size
9842 // (which happens when we're called from
9843 // nsDocumentViewer::SizeToContent), we're effectively doing a
9844 // resize in the block direction, since it changes the meaning of
9845 // percentage block-sizes even if no block-sizes actually changed.
9846 // The same applies when we reflow again after that computation. This is
9847 // an unusual case, and isn't caught by ReflowInput::InitResizeFlags.
9848 bool hasUnconstrainedBSize = size.BSize(wm) == NS_UNCONSTRAINEDSIZE;
9850 if (hasUnconstrainedBSize || mLastRootReflowHadUnconstrainedBSize) {
9851 reflowInput.SetBResize(true);
9854 mLastRootReflowHadUnconstrainedBSize = hasUnconstrainedBSize;
9855 } else {
9856 // Initialize reflow input with current used border and padding,
9857 // in case this was set specially by the parent frame when the reflow root
9858 // was reflowed by its parent.
9859 reflowInput.Init(mPresContext, Nothing(),
9860 Some(target->GetLogicalUsedBorder(wm)),
9861 Some(target->GetLogicalUsedPadding(wm)));
9864 // fix the computed height
9865 NS_ASSERTION(reflowInput.ComputedPhysicalMargin() == nsMargin(0, 0, 0, 0),
9866 "reflow input should not set margin for reflow roots");
9867 if (size.BSize(wm) != NS_UNCONSTRAINEDSIZE) {
9868 nscoord computedBSize =
9869 size.BSize(wm) -
9870 reflowInput.ComputedLogicalBorderPadding(wm).BStartEnd(wm);
9871 computedBSize = std::max(computedBSize, 0);
9872 reflowInput.SetComputedBSize(computedBSize);
9874 NS_ASSERTION(
9875 reflowInput.ComputedISize() ==
9876 size.ISize(wm) -
9877 reflowInput.ComputedLogicalBorderPadding(wm).IStartEnd(wm),
9878 "reflow input computed incorrect inline size");
9880 mPresContext->ReflowStarted(aInterruptible);
9881 mIsReflowing = true;
9883 nsReflowStatus status;
9884 ReflowOutput desiredSize(reflowInput);
9885 target->Reflow(mPresContext, desiredSize, reflowInput, status);
9887 // If an incremental reflow is initiated at a frame other than the
9888 // root frame, then its desired size had better not change! If it's
9889 // initiated at the root, then the size better not change unless its
9890 // height was unconstrained to start with.
9891 nsRect boundsRelativeToTarget =
9892 nsRect(0, 0, desiredSize.Width(), desiredSize.Height());
9893 const bool isBSizeLimitReflow =
9894 isRoot && size.BSize(wm) == NS_UNCONSTRAINEDSIZE;
9895 NS_ASSERTION(isBSizeLimitReflow || desiredSize.Size(wm) == size,
9896 "non-root frame's desired size changed during an "
9897 "incremental reflow");
9898 NS_ASSERTION(status.IsEmpty(), "reflow roots should never split");
9900 target->SetSize(boundsRelativeToTarget.Size());
9902 // Always use boundsRelativeToTarget here, not desiredSize.InkOverflowRect(),
9903 // because for root frames (where they could be different, since root frames
9904 // are allowed to have overflow) the root view bounds need to match the
9905 // viewport bounds; the view manager "window dimensions" code depends on it.
9906 if (target->HasView()) {
9907 nsContainerFrame::SyncFrameViewAfterReflow(
9908 mPresContext, target, target->GetView(), boundsRelativeToTarget);
9909 if (target->IsViewportFrame()) {
9910 SyncWindowProperties(/* aSync = */ false);
9914 target->DidReflow(mPresContext, nullptr);
9915 if (target->IsInScrollAnchorChain()) {
9916 ScrollAnchorContainer* container = ScrollAnchorContainer::FindFor(target);
9917 PostPendingScrollAnchorAdjustment(container);
9919 if (MOZ_UNLIKELY(isBSizeLimitReflow)) {
9920 mPresContext->SetVisibleArea(boundsRelativeToTarget);
9923 #ifdef DEBUG
9924 mCurrentReflowRoot = nullptr;
9925 #endif
9927 if (!isRoot && oldOverflow != target->GetOverflowAreas()) {
9928 // The overflow area changed. Propagate this change to ancestors.
9929 aOverflowTracker->AddFrame(target->GetParent(),
9930 OverflowChangedTracker::CHILDREN_CHANGED);
9933 NS_ASSERTION(
9934 mPresContext->HasPendingInterrupt() || mFramesToDirty.Count() == 0,
9935 "Why do we need to dirty anything if not interrupted?");
9937 mIsReflowing = false;
9938 bool interrupted = mPresContext->HasPendingInterrupt();
9939 if (interrupted) {
9940 // Make sure target gets reflowed again.
9941 for (const auto& key : mFramesToDirty) {
9942 // Mark frames dirty until target frame.
9943 for (nsIFrame* f = key; f && !f->IsSubtreeDirty(); f = f->GetParent()) {
9944 f->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
9945 if (f->IsFlexItem()) {
9946 nsFlexContainerFrame::MarkCachedFlexMeasurementsDirty(f);
9949 if (f == target) {
9950 break;
9955 NS_ASSERTION(target->IsSubtreeDirty(), "Why is the target not dirty?");
9956 mDirtyRoots.Add(target);
9957 SetNeedLayoutFlush();
9959 // Clear mFramesToDirty after we've done the target->IsSubtreeDirty()
9960 // assertion so that if it fails it's easier to see what's going on.
9961 #ifdef NOISY_INTERRUPTIBLE_REFLOW
9962 printf("mFramesToDirty.Count() == %u\n", mFramesToDirty.Count());
9963 #endif /* NOISY_INTERRUPTIBLE_REFLOW */
9964 mFramesToDirty.Clear();
9966 // Any FlushPendingNotifications with interruptible reflows
9967 // should be suppressed now. We don't want to do extra reflow work
9968 // before our reflow event happens.
9969 mWasLastReflowInterrupted = true;
9970 MaybeScheduleReflow();
9973 // dump text perf metrics for reflows with significant text processing
9974 if (tp) {
9975 if (tp->current.numChars > 100) {
9976 TimeDuration reflowTime = TimeStamp::Now() - timeStart;
9977 LogTextPerfStats(tp, this, tp->current, reflowTime.ToMilliseconds(),
9978 eLog_reflow, nullptr);
9980 tp->Accumulate();
9983 return !interrupted;
9986 #ifdef DEBUG
9987 void PresShell::DoVerifyReflow() {
9988 if (GetVerifyReflowEnable()) {
9989 // First synchronously render what we have so far so that we can
9990 // see it.
9991 nsView* rootView = mViewManager->GetRootView();
9992 mViewManager->InvalidateView(rootView);
9994 FlushPendingNotifications(FlushType::Layout);
9995 mInVerifyReflow = true;
9996 bool ok = VerifyIncrementalReflow();
9997 mInVerifyReflow = false;
9998 if (VerifyReflowFlags::All & gVerifyReflowFlags) {
9999 printf("ProcessReflowCommands: finished (%s)\n", ok ? "ok" : "failed");
10002 if (!mDirtyRoots.IsEmpty()) {
10003 printf("XXX yikes! reflow commands queued during verify-reflow\n");
10007 #endif
10009 // used with Telemetry metrics
10010 #define NS_LONG_REFLOW_TIME_MS 5000
10012 bool PresShell::ProcessReflowCommands(bool aInterruptible) {
10013 if (mDirtyRoots.IsEmpty() && !mShouldUnsuppressPainting &&
10014 !mPendingDidDoReflow) {
10015 // Nothing to do; bail out
10016 return true;
10019 const bool wasProcessingReflowCommands = mProcessingReflowCommands;
10020 auto restoreProcessingReflowCommands = MakeScopeExit(
10021 [&] { mProcessingReflowCommands = wasProcessingReflowCommands; });
10022 mProcessingReflowCommands = true;
10024 auto timerStart = mozilla::TimeStamp::Now();
10025 bool interrupted = false;
10026 if (!mDirtyRoots.IsEmpty()) {
10027 #ifdef DEBUG
10028 if (VerifyReflowFlags::DumpCommands & gVerifyReflowFlags) {
10029 printf("ProcessReflowCommands: begin incremental reflow\n");
10031 #endif
10033 // If reflow is interruptible, then make a note of our deadline.
10034 const PRIntervalTime deadline =
10035 aInterruptible
10036 ? PR_IntervalNow() + PR_MicrosecondsToInterval(gMaxRCProcessingTime)
10037 : (PRIntervalTime)0;
10039 // Scope for the reflow entry point
10040 nsAutoScriptBlocker scriptBlocker;
10041 WillDoReflow();
10042 AUTO_LAYOUT_PHASE_ENTRY_POINT(GetPresContext(), Reflow);
10043 nsViewManager::AutoDisableRefresh refreshBlocker(mViewManager);
10045 OverflowChangedTracker overflowTracker;
10047 do {
10048 // Send an incremental reflow notification to the target frame.
10049 nsIFrame* target = mDirtyRoots.PopShallowestRoot();
10051 if (!target->IsSubtreeDirty()) {
10052 // It's not dirty anymore, which probably means the notification
10053 // was posted in the middle of a reflow (perhaps with a reflow
10054 // root in the middle). Don't do anything.
10055 continue;
10058 interrupted = !DoReflow(target, aInterruptible, &overflowTracker);
10060 // Keep going until we're out of reflow commands, or we've run
10061 // past our deadline, or we're interrupted.
10062 } while (!interrupted && !mDirtyRoots.IsEmpty() &&
10063 (!aInterruptible || PR_IntervalNow() < deadline));
10065 interrupted = !mDirtyRoots.IsEmpty();
10067 overflowTracker.Flush();
10069 if (!interrupted) {
10070 // We didn't get interrupted. Go ahead and perform scroll anchor
10071 // adjustments.
10072 FlushPendingScrollAnchorAdjustments();
10074 mPendingDidDoReflow = true;
10077 // Exiting the scriptblocker might have killed us. If we were processing
10078 // scroll commands, let the outermost call deal with it.
10079 if (!mIsDestroying && mPendingDidDoReflow && !wasProcessingReflowCommands) {
10080 DidDoReflow(aInterruptible);
10083 #ifdef DEBUG
10084 if (VerifyReflowFlags::DumpCommands & gVerifyReflowFlags) {
10085 printf("\nPresShell::ProcessReflowCommands() finished: this=%p\n",
10086 (void*)this);
10088 DoVerifyReflow();
10089 #endif
10092 TimeDuration elapsed = TimeStamp::Now() - timerStart;
10093 int32_t intElapsed = int32_t(elapsed.ToMilliseconds());
10094 if (intElapsed > NS_LONG_REFLOW_TIME_MS) {
10095 Telemetry::Accumulate(Telemetry::LONG_REFLOW_INTERRUPTIBLE,
10096 aInterruptible ? 1 : 0);
10100 return !interrupted;
10103 bool PresShell::DoFlushLayout(bool aInterruptible) {
10104 mFrameConstructor->RecalcQuotesAndCounters();
10105 return ProcessReflowCommands(aInterruptible);
10108 void PresShell::WindowSizeMoveDone() {
10109 if (mPresContext) {
10110 EventStateManager::ClearGlobalActiveContent(nullptr);
10111 ClearMouseCapture();
10115 NS_IMETHODIMP
10116 PresShell::Observe(nsISupports* aSubject, const char* aTopic,
10117 const char16_t* aData) {
10118 if (mIsDestroying) {
10119 NS_WARNING("our observers should have been unregistered by now");
10120 return NS_OK;
10123 if (!nsCRT::strcmp(aTopic, "memory-pressure")) {
10124 if (!AssumeAllFramesVisible() &&
10125 mPresContext->IsRootContentDocumentInProcess()) {
10126 DoUpdateApproximateFrameVisibility(/* aRemoveOnly = */ true);
10128 return NS_OK;
10131 if (!nsCRT::strcmp(aTopic, NS_WIDGET_WAKE_OBSERVER_TOPIC)) {
10132 mLastOSWake = TimeStamp::Now();
10133 return NS_OK;
10136 // For parent process, user may expect the UI is interactable after a
10137 // tab (previously opened page or home page) has restored.
10138 if (!nsCRT::strcmp(aTopic, "sessionstore-one-or-no-tab-restored")) {
10139 MOZ_ASSERT(XRE_IsParentProcess());
10140 sProcessInteractable = true;
10142 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
10143 if (os) {
10144 os->RemoveObserver(this, "sessionstore-one-or-no-tab-restored");
10146 return NS_OK;
10149 if (!nsCRT::strcmp(aTopic, "font-info-updated")) {
10150 // See how gfxPlatform::ForceGlobalReflow encodes this.
10151 bool needsReframe = aData && !!aData[0];
10152 mPresContext->ForceReflowForFontInfoUpdate(needsReframe);
10153 return NS_OK;
10156 // The "look-and-feel-changed" notification for JS observers will be
10157 // dispatched HandleGlobalThemeChange once LookAndFeel caches are cleared.
10158 if (!nsCRT::strcmp(aTopic, "internal-look-and-feel-changed")) {
10159 // See how LookAndFeel::NotifyChangedAllWindows encodes this.
10160 auto kind = widget::ThemeChangeKind(aData[0]);
10161 mPresContext->ThemeChanged(kind);
10162 return NS_OK;
10165 NS_WARNING("unrecognized topic in PresShell::Observe");
10166 return NS_ERROR_FAILURE;
10169 bool PresShell::AddRefreshObserver(nsARefreshObserver* aObserver,
10170 FlushType aFlushType,
10171 const char* aObserverDescription) {
10172 nsPresContext* presContext = GetPresContext();
10173 if (MOZ_UNLIKELY(!presContext)) {
10174 return false;
10176 presContext->RefreshDriver()->AddRefreshObserver(aObserver, aFlushType,
10177 aObserverDescription);
10178 return true;
10181 bool PresShell::RemoveRefreshObserver(nsARefreshObserver* aObserver,
10182 FlushType aFlushType) {
10183 nsPresContext* presContext = GetPresContext();
10184 return presContext && presContext->RefreshDriver()->RemoveRefreshObserver(
10185 aObserver, aFlushType);
10188 bool PresShell::AddPostRefreshObserver(nsAPostRefreshObserver* aObserver) {
10189 nsPresContext* presContext = GetPresContext();
10190 if (!presContext) {
10191 return false;
10193 presContext->RefreshDriver()->AddPostRefreshObserver(aObserver);
10194 return true;
10197 bool PresShell::RemovePostRefreshObserver(nsAPostRefreshObserver* aObserver) {
10198 nsPresContext* presContext = GetPresContext();
10199 if (!presContext) {
10200 return false;
10202 presContext->RefreshDriver()->RemovePostRefreshObserver(aObserver);
10203 return true;
10206 void PresShell::DoObserveStyleFlushes() {
10207 MOZ_ASSERT(!ObservingStyleFlushes());
10208 mObservingStyleFlushes = true;
10210 if (MOZ_LIKELY(!mDocument->GetBFCacheEntry())) {
10211 mPresContext->RefreshDriver()->AddStyleFlushObserver(this);
10215 void PresShell::DoObserveLayoutFlushes() {
10216 MOZ_ASSERT(!ObservingLayoutFlushes());
10217 mObservingLayoutFlushes = true;
10219 if (MOZ_LIKELY(!mDocument->GetBFCacheEntry())) {
10220 mPresContext->RefreshDriver()->AddLayoutFlushObserver(this);
10224 //------------------------------------------------------
10225 // End of protected and private methods on the PresShell
10226 //------------------------------------------------------
10228 //------------------------------------------------------------------
10229 //-- Delayed event Classes Impls
10230 //------------------------------------------------------------------
10232 PresShell::DelayedInputEvent::DelayedInputEvent()
10233 : DelayedEvent(), mEvent(nullptr) {}
10235 PresShell::DelayedInputEvent::~DelayedInputEvent() { delete mEvent; }
10237 void PresShell::DelayedInputEvent::Dispatch() {
10238 if (!mEvent || !mEvent->mWidget) {
10239 return;
10241 nsCOMPtr<nsIWidget> widget = mEvent->mWidget;
10242 nsEventStatus status;
10243 widget->DispatchEvent(mEvent, status);
10246 PresShell::DelayedMouseEvent::DelayedMouseEvent(WidgetMouseEvent* aEvent) {
10247 MOZ_DIAGNOSTIC_ASSERT(aEvent->IsTrusted());
10248 WidgetMouseEvent* mouseEvent =
10249 new WidgetMouseEvent(true, aEvent->mMessage, aEvent->mWidget,
10250 aEvent->mReason, aEvent->mContextMenuTrigger);
10251 mouseEvent->AssignMouseEventData(*aEvent, false);
10252 mEvent = mouseEvent;
10255 PresShell::DelayedKeyEvent::DelayedKeyEvent(WidgetKeyboardEvent* aEvent) {
10256 MOZ_DIAGNOSTIC_ASSERT(aEvent->IsTrusted());
10257 WidgetKeyboardEvent* keyEvent =
10258 new WidgetKeyboardEvent(true, aEvent->mMessage, aEvent->mWidget);
10259 keyEvent->AssignKeyEventData(*aEvent, false);
10260 keyEvent->mFlags.mIsSynthesizedForTests =
10261 aEvent->mFlags.mIsSynthesizedForTests;
10262 keyEvent->mFlags.mIsSuppressedOrDelayed = true;
10263 mEvent = keyEvent;
10266 bool PresShell::DelayedKeyEvent::IsKeyPressEvent() {
10267 return mEvent->mMessage == eKeyPress;
10270 // Start of DEBUG only code
10272 #ifdef DEBUG
10274 static void LogVerifyMessage(nsIFrame* k1, nsIFrame* k2, const char* aMsg) {
10275 nsAutoString n1, n2;
10276 if (k1) {
10277 k1->GetFrameName(n1);
10278 } else {
10279 n1.AssignLiteral(u"(null)");
10282 if (k2) {
10283 k2->GetFrameName(n2);
10284 } else {
10285 n2.AssignLiteral(u"(null)");
10288 printf("verifyreflow: %s %p != %s %p %s\n",
10289 NS_LossyConvertUTF16toASCII(n1).get(), (void*)k1,
10290 NS_LossyConvertUTF16toASCII(n2).get(), (void*)k2, aMsg);
10293 static void LogVerifyMessage(nsIFrame* k1, nsIFrame* k2, const char* aMsg,
10294 const nsRect& r1, const nsRect& r2) {
10295 printf("VerifyReflow Error:\n");
10296 nsAutoString name;
10298 if (k1) {
10299 k1->GetFrameName(name);
10300 printf(" %s %p ", NS_LossyConvertUTF16toASCII(name).get(), (void*)k1);
10302 printf("{%d, %d, %d, %d} != \n", r1.x, r1.y, r1.width, r1.height);
10304 if (k2) {
10305 k2->GetFrameName(name);
10306 printf(" %s %p ", NS_LossyConvertUTF16toASCII(name).get(), (void*)k2);
10308 printf("{%d, %d, %d, %d}\n %s\n", r2.x, r2.y, r2.width, r2.height, aMsg);
10311 static void LogVerifyMessage(nsIFrame* k1, nsIFrame* k2, const char* aMsg,
10312 const nsIntRect& r1, const nsIntRect& r2) {
10313 printf("VerifyReflow Error:\n");
10314 nsAutoString name;
10316 if (k1) {
10317 k1->GetFrameName(name);
10318 printf(" %s %p ", NS_LossyConvertUTF16toASCII(name).get(), (void*)k1);
10320 printf("{%d, %d, %d, %d} != \n", r1.x, r1.y, r1.width, r1.height);
10322 if (k2) {
10323 k2->GetFrameName(name);
10324 printf(" %s %p ", NS_LossyConvertUTF16toASCII(name).get(), (void*)k2);
10326 printf("{%d, %d, %d, %d}\n %s\n", r2.x, r2.y, r2.width, r2.height, aMsg);
10329 static bool CompareTrees(nsPresContext* aFirstPresContext,
10330 nsIFrame* aFirstFrame,
10331 nsPresContext* aSecondPresContext,
10332 nsIFrame* aSecondFrame) {
10333 if (!aFirstPresContext || !aFirstFrame || !aSecondPresContext ||
10334 !aSecondFrame)
10335 return true;
10336 // XXX Evil hack to reduce false positives; I can't seem to figure
10337 // out how to flush scrollbar changes correctly
10338 // if (aFirstFrame->IsScrollbarFrame())
10339 // return true;
10340 bool ok = true;
10341 const auto& childLists1 = aFirstFrame->ChildLists();
10342 const auto& childLists2 = aSecondFrame->ChildLists();
10343 auto iterLists1 = childLists1.begin();
10344 auto iterLists2 = childLists2.begin();
10345 do {
10346 const nsFrameList& kids1 = iterLists1 != childLists1.end()
10347 ? iterLists1->mList
10348 : nsFrameList::EmptyList();
10349 const nsFrameList& kids2 = iterLists2 != childLists2.end()
10350 ? iterLists2->mList
10351 : nsFrameList::EmptyList();
10352 int32_t l1 = kids1.GetLength();
10353 int32_t l2 = kids2.GetLength();
10354 if (l1 != l2) {
10355 ok = false;
10356 LogVerifyMessage(kids1.FirstChild(), kids2.FirstChild(),
10357 "child counts don't match: ");
10358 printf("%d != %d\n", l1, l2);
10359 if (!(VerifyReflowFlags::All & gVerifyReflowFlags)) {
10360 break;
10364 LayoutDeviceIntRect r1, r2;
10365 nsView* v1;
10366 nsView* v2;
10367 for (auto kids1Iter = kids1.begin(), kids2Iter = kids2.begin();;
10368 ++kids1Iter, ++kids2Iter) {
10369 nsIFrame* k1 = *kids1Iter;
10370 nsIFrame* k2 = *kids2Iter;
10371 if (((nullptr == k1) && (nullptr != k2)) ||
10372 ((nullptr != k1) && (nullptr == k2))) {
10373 ok = false;
10374 LogVerifyMessage(k1, k2, "child lists are different\n");
10375 break;
10376 } else if (nullptr != k1) {
10377 // Verify that the frames are the same size
10378 if (!k1->GetRect().IsEqualInterior(k2->GetRect())) {
10379 ok = false;
10380 LogVerifyMessage(k1, k2, "(frame rects)", k1->GetRect(),
10381 k2->GetRect());
10384 // Make sure either both have views or neither have views; if they
10385 // do have views, make sure the views are the same size. If the
10386 // views have widgets, make sure they both do or neither does. If
10387 // they do, make sure the widgets are the same size.
10388 v1 = k1->GetView();
10389 v2 = k2->GetView();
10390 if (((nullptr == v1) && (nullptr != v2)) ||
10391 ((nullptr != v1) && (nullptr == v2))) {
10392 ok = false;
10393 LogVerifyMessage(k1, k2, "child views are not matched\n");
10394 } else if (nullptr != v1) {
10395 if (!v1->GetBounds().IsEqualInterior(v2->GetBounds())) {
10396 LogVerifyMessage(k1, k2, "(view rects)", v1->GetBounds(),
10397 v2->GetBounds());
10400 nsIWidget* w1 = v1->GetWidget();
10401 nsIWidget* w2 = v2->GetWidget();
10402 if (((nullptr == w1) && (nullptr != w2)) ||
10403 ((nullptr != w1) && (nullptr == w2))) {
10404 ok = false;
10405 LogVerifyMessage(k1, k2, "child widgets are not matched\n");
10406 } else if (nullptr != w1) {
10407 r1 = w1->GetBounds();
10408 r2 = w2->GetBounds();
10409 if (!r1.IsEqualEdges(r2)) {
10410 LogVerifyMessage(k1, k2, "(widget rects)", r1.ToUnknownRect(),
10411 r2.ToUnknownRect());
10415 if (!ok && !(VerifyReflowFlags::All & gVerifyReflowFlags)) {
10416 break;
10419 // XXX Should perhaps compare their float managers.
10421 // Compare the sub-trees too
10422 if (!CompareTrees(aFirstPresContext, k1, aSecondPresContext, k2)) {
10423 ok = false;
10424 if (!(VerifyReflowFlags::All & gVerifyReflowFlags)) {
10425 break;
10428 } else {
10429 break;
10432 if (!ok && (!(VerifyReflowFlags::All & gVerifyReflowFlags))) {
10433 break;
10436 ++iterLists1;
10437 ++iterLists2;
10438 const bool lists1Done = iterLists1 == childLists1.end();
10439 const bool lists2Done = iterLists2 == childLists2.end();
10440 if (lists1Done != lists2Done ||
10441 (!lists1Done && iterLists1->mID != iterLists2->mID)) {
10442 if (!(VerifyReflowFlags::All & gVerifyReflowFlags)) {
10443 ok = false;
10445 LogVerifyMessage(kids1.FirstChild(), kids2.FirstChild(),
10446 "child list names are not matched: ");
10447 fprintf(stdout, "%s != %s\n",
10448 !lists1Done ? ChildListName(iterLists1->mID) : "(null)",
10449 !lists2Done ? ChildListName(iterLists2->mID) : "(null)");
10450 break;
10452 } while (ok && iterLists1 != childLists1.end());
10454 return ok;
10456 #endif
10458 #if 0
10459 static nsIFrame*
10460 FindTopFrame(nsIFrame* aRoot)
10462 if (aRoot) {
10463 nsIContent* content = aRoot->GetContent();
10464 if (content) {
10465 nsAtom* tag;
10466 content->GetTag(tag);
10467 if (nullptr != tag) {
10468 NS_RELEASE(tag);
10469 return aRoot;
10473 // Try one of the children
10474 for (nsIFrame* kid : aRoot->PrincipalChildList()) {
10475 nsIFrame* result = FindTopFrame(kid);
10476 if (nullptr != result) {
10477 return result;
10481 return nullptr;
10483 #endif
10485 #ifdef DEBUG
10487 // After an incremental reflow, we verify the correctness by doing a
10488 // full reflow into a fresh frame tree.
10489 bool PresShell::VerifyIncrementalReflow() {
10490 if (VerifyReflowFlags::Noisy & gVerifyReflowFlags) {
10491 printf("Building Verification Tree...\n");
10494 // Create a presentation context to view the new frame tree
10495 RefPtr<nsPresContext> cx = new nsRootPresContext(
10496 mDocument, mPresContext->IsPaginated()
10497 ? nsPresContext::eContext_PrintPreview
10498 : nsPresContext::eContext_Galley);
10499 NS_ENSURE_TRUE(cx, false);
10501 nsDeviceContext* dc = mPresContext->DeviceContext();
10502 nsresult rv = cx->Init(dc);
10503 NS_ENSURE_SUCCESS(rv, false);
10505 // Get our scrolling preference
10506 nsView* rootView = mViewManager->GetRootView();
10507 NS_ENSURE_TRUE(rootView->HasWidget(), false);
10508 nsIWidget* parentWidget = rootView->GetWidget();
10510 // Create a new view manager.
10511 RefPtr<nsViewManager> vm = new nsViewManager();
10512 NS_ENSURE_TRUE(vm, false);
10513 rv = vm->Init(dc);
10514 NS_ENSURE_SUCCESS(rv, false);
10516 // Create a child window of the parent that is our "root view/window"
10517 // Create a view
10518 nsRect tbounds = mPresContext->GetVisibleArea();
10519 nsView* view = vm->CreateView(tbounds, nullptr);
10520 NS_ENSURE_TRUE(view, false);
10522 // now create the widget for the view
10523 rv = view->CreateWidgetForParent(parentWidget, nullptr, true);
10524 NS_ENSURE_SUCCESS(rv, false);
10526 // Setup hierarchical relationship in view manager
10527 vm->SetRootView(view);
10529 // Make the new presentation context the same size as our
10530 // presentation context.
10531 cx->SetVisibleArea(mPresContext->GetVisibleArea());
10533 RefPtr<PresShell> presShell = mDocument->CreatePresShell(cx, vm);
10534 NS_ENSURE_TRUE(presShell, false);
10536 // Note that after we create the shell, we must make sure to destroy it
10537 presShell->SetVerifyReflowEnable(
10538 false); // turn off verify reflow while we're
10539 // reflowing the test frame tree
10540 vm->SetPresShell(presShell);
10542 nsAutoCauseReflowNotifier crNotifier(this);
10543 presShell->Initialize();
10545 presShell->FlushPendingNotifications(FlushType::Layout);
10546 presShell->SetVerifyReflowEnable(
10547 true); // turn on verify reflow again now that
10548 // we're done reflowing the test frame tree
10549 // Force the non-primary presshell to unsuppress; it doesn't want to normally
10550 // because it thinks it's hidden
10551 presShell->mPaintingSuppressed = false;
10552 if (VerifyReflowFlags::Noisy & gVerifyReflowFlags) {
10553 printf("Verification Tree built, comparing...\n");
10556 // Now that the document has been reflowed, use its frame tree to
10557 // compare against our frame tree.
10558 nsIFrame* root1 = mFrameConstructor->GetRootFrame();
10559 nsIFrame* root2 = presShell->GetRootFrame();
10560 bool ok = CompareTrees(mPresContext, root1, cx, root2);
10561 if (!ok && (VerifyReflowFlags::Noisy & gVerifyReflowFlags)) {
10562 printf("Verify reflow failed, primary tree:\n");
10563 root1->List(stdout);
10564 printf("Verification tree:\n");
10565 root2->List(stdout);
10568 # if 0
10569 // Sample code for dumping page to png
10570 // XXX Needs to be made more flexible
10571 if (!ok) {
10572 nsString stra;
10573 static int num = 0;
10574 stra.AppendLiteral("C:\\mozilla\\mozilla\\debug\\filea");
10575 stra.AppendInt(num);
10576 stra.AppendLiteral(".png");
10577 gfxUtils::WriteAsPNG(presShell, stra);
10578 nsString strb;
10579 strb.AppendLiteral("C:\\mozilla\\mozilla\\debug\\fileb");
10580 strb.AppendInt(num);
10581 strb.AppendLiteral(".png");
10582 gfxUtils::WriteAsPNG(presShell, strb);
10583 ++num;
10585 # endif
10587 presShell->EndObservingDocument();
10588 presShell->Destroy();
10589 if (VerifyReflowFlags::Noisy & gVerifyReflowFlags) {
10590 printf("Finished Verifying Reflow...\n");
10593 return ok;
10596 // Layout debugging hooks
10597 void PresShell::ListComputedStyles(FILE* out, int32_t aIndent) {
10598 nsIFrame* rootFrame = GetRootFrame();
10599 if (rootFrame) {
10600 rootFrame->Style()->List(out, aIndent);
10603 // The root element's frame's ComputedStyle is the root of a separate tree.
10604 Element* rootElement = mDocument->GetRootElement();
10605 if (rootElement) {
10606 nsIFrame* rootElementFrame = rootElement->GetPrimaryFrame();
10607 if (rootElementFrame) {
10608 rootElementFrame->Style()->List(out, aIndent);
10612 #endif
10614 #if defined(DEBUG) || defined(MOZ_LAYOUT_DEBUGGER)
10615 void PresShell::ListStyleSheets(FILE* out, int32_t aIndent) {
10616 auto ListStyleSheetsAtOrigin = [this, out, aIndent](StyleOrigin origin) {
10617 int32_t sheetCount = StyleSet()->SheetCount(origin);
10618 for (int32_t i = 0; i < sheetCount; ++i) {
10619 StyleSet()->SheetAt(origin, i)->List(out, aIndent);
10623 ListStyleSheetsAtOrigin(StyleOrigin::UserAgent);
10624 ListStyleSheetsAtOrigin(StyleOrigin::User);
10625 ListStyleSheetsAtOrigin(StyleOrigin::Author);
10627 #endif
10629 //=============================================================
10630 //=============================================================
10631 //-- Debug Reflow Counts
10632 //=============================================================
10633 //=============================================================
10634 #ifdef MOZ_REFLOW_PERF
10635 //-------------------------------------------------------------
10636 void PresShell::DumpReflows() {
10637 if (mReflowCountMgr) {
10638 nsAutoCString uriStr;
10639 if (mDocument) {
10640 nsIURI* uri = mDocument->GetDocumentURI();
10641 if (uri) {
10642 uri->GetPathQueryRef(uriStr);
10645 mReflowCountMgr->DisplayTotals(uriStr.get());
10646 mReflowCountMgr->DisplayHTMLTotals(uriStr.get());
10647 mReflowCountMgr->DisplayDiffsInTotals();
10651 //-------------------------------------------------------------
10652 void PresShell::CountReflows(const char* aName, nsIFrame* aFrame) {
10653 if (mReflowCountMgr) {
10654 mReflowCountMgr->Add(aName, aFrame);
10658 //-------------------------------------------------------------
10659 void PresShell::PaintCount(const char* aName, gfxContext* aRenderingContext,
10660 nsPresContext* aPresContext, nsIFrame* aFrame,
10661 const nsPoint& aOffset, uint32_t aColor) {
10662 if (mReflowCountMgr) {
10663 mReflowCountMgr->PaintCount(aName, aRenderingContext, aPresContext, aFrame,
10664 aOffset, aColor);
10668 //-------------------------------------------------------------
10669 void PresShell::SetPaintFrameCount(bool aPaintFrameCounts) {
10670 if (mReflowCountMgr) {
10671 mReflowCountMgr->SetPaintFrameCounts(aPaintFrameCounts);
10675 bool PresShell::IsPaintingFrameCounts() {
10676 if (mReflowCountMgr) return mReflowCountMgr->IsPaintingFrameCounts();
10677 return false;
10680 //------------------------------------------------------------------
10681 //-- Reflow Counter Classes Impls
10682 //------------------------------------------------------------------
10684 //------------------------------------------------------------------
10685 ReflowCounter::ReflowCounter(ReflowCountMgr* aMgr) : mMgr(aMgr) {
10686 ClearTotals();
10687 SetTotalsCache();
10690 //------------------------------------------------------------------
10691 ReflowCounter::~ReflowCounter() = default;
10693 //------------------------------------------------------------------
10694 void ReflowCounter::ClearTotals() { mTotal = 0; }
10696 //------------------------------------------------------------------
10697 void ReflowCounter::SetTotalsCache() { mCacheTotal = mTotal; }
10699 //------------------------------------------------------------------
10700 void ReflowCounter::CalcDiffInTotals() { mCacheTotal = mTotal - mCacheTotal; }
10702 //------------------------------------------------------------------
10703 void ReflowCounter::DisplayTotals(const char* aStr) {
10704 DisplayTotals(mTotal, aStr ? aStr : "Totals");
10707 //------------------------------------------------------------------
10708 void ReflowCounter::DisplayDiffTotals(const char* aStr) {
10709 DisplayTotals(mCacheTotal, aStr ? aStr : "Diff Totals");
10712 //------------------------------------------------------------------
10713 void ReflowCounter::DisplayHTMLTotals(const char* aStr) {
10714 DisplayHTMLTotals(mTotal, aStr ? aStr : "Totals");
10717 //------------------------------------------------------------------
10718 void ReflowCounter::DisplayTotals(uint32_t aTotal, const char* aTitle) {
10719 // figure total
10720 if (aTotal == 0) {
10721 return;
10723 ReflowCounter* gTots = (ReflowCounter*)mMgr->LookUp(kGrandTotalsStr);
10725 printf("%25s\t", aTitle);
10726 printf("%d\t", aTotal);
10727 if (gTots != this && aTotal > 0) {
10728 gTots->Add(aTotal);
10732 //------------------------------------------------------------------
10733 void ReflowCounter::DisplayHTMLTotals(uint32_t aTotal, const char* aTitle) {
10734 if (aTotal == 0) {
10735 return;
10738 ReflowCounter* gTots = (ReflowCounter*)mMgr->LookUp(kGrandTotalsStr);
10739 FILE* fd = mMgr->GetOutFile();
10740 if (!fd) {
10741 return;
10744 fprintf(fd, "<tr><td><center>%s</center></td>", aTitle);
10745 fprintf(fd, "<td><center>%d</center></td></tr>\n", aTotal);
10747 if (gTots != this && aTotal > 0) {
10748 gTots->Add(aTotal);
10752 //------------------------------------------------------------------
10753 //-- ReflowCountMgr
10754 //------------------------------------------------------------------
10756 # define KEY_BUF_SIZE_FOR_PTR \
10757 24 // adequate char[] buffer to sprintf a pointer
10759 ReflowCountMgr::ReflowCountMgr() : mCounts(10), mIndiFrameCounts(10) {
10760 mCycledOnce = false;
10761 mDumpFrameCounts = false;
10762 mDumpFrameByFrameCounts = false;
10763 mPaintFrameByFrameCounts = false;
10766 //------------------------------------------------------------------
10767 ReflowCountMgr::~ReflowCountMgr() = default;
10769 //------------------------------------------------------------------
10770 ReflowCounter* ReflowCountMgr::LookUp(const char* aName) {
10771 return mCounts.Get(aName);
10774 //------------------------------------------------------------------
10775 void ReflowCountMgr::Add(const char* aName, nsIFrame* aFrame) {
10776 NS_ASSERTION(aName != nullptr, "Name shouldn't be null!");
10778 if (mDumpFrameCounts) {
10779 auto* const counter = mCounts.GetOrInsertNew(aName, this);
10780 counter->Add();
10783 if ((mDumpFrameByFrameCounts || mPaintFrameByFrameCounts) &&
10784 aFrame != nullptr) {
10785 char key[KEY_BUF_SIZE_FOR_PTR];
10786 SprintfLiteral(key, "%p", (void*)aFrame);
10787 auto* const counter =
10788 mIndiFrameCounts
10789 .LookupOrInsertWith(key,
10790 [&aName, &aFrame, this]() {
10791 auto counter =
10792 MakeUnique<IndiReflowCounter>(this);
10793 counter->mFrame = aFrame;
10794 counter->mName.AssignASCII(aName);
10795 return counter;
10797 .get();
10798 // this eliminates extra counts from super classes
10799 if (counter && counter->mName.EqualsASCII(aName)) {
10800 counter->mCount++;
10801 counter->mCounter.Add(1);
10806 //------------------------------------------------------------------
10807 void ReflowCountMgr::PaintCount(const char* aName,
10808 gfxContext* aRenderingContext,
10809 nsPresContext* aPresContext, nsIFrame* aFrame,
10810 const nsPoint& aOffset, uint32_t aColor) {
10811 if (mPaintFrameByFrameCounts && aFrame != nullptr) {
10812 char key[KEY_BUF_SIZE_FOR_PTR];
10813 SprintfLiteral(key, "%p", (void*)aFrame);
10814 IndiReflowCounter* counter = mIndiFrameCounts.Get(key);
10815 if (counter != nullptr && counter->mName.EqualsASCII(aName)) {
10816 DrawTarget* drawTarget = aRenderingContext->GetDrawTarget();
10817 int32_t appUnitsPerDevPixel = aPresContext->AppUnitsPerDevPixel();
10819 aRenderingContext->Save();
10820 gfxPoint devPixelOffset =
10821 nsLayoutUtils::PointToGfxPoint(aOffset, appUnitsPerDevPixel);
10822 aRenderingContext->SetMatrixDouble(
10823 aRenderingContext->CurrentMatrixDouble().PreTranslate(
10824 devPixelOffset));
10826 // We don't care about the document language or user fonts here;
10827 // just get a default Latin font.
10828 nsFont font(StyleGenericFontFamily::Serif, Length::FromPixels(11));
10829 nsFontMetrics::Params params;
10830 params.language = nsGkAtoms::x_western;
10831 params.textPerf = aPresContext->GetTextPerfMetrics();
10832 params.featureValueLookup = aPresContext->GetFontFeatureValuesLookup();
10833 RefPtr<nsFontMetrics> fm = aPresContext->GetMetricsFor(font, params);
10835 char buf[16];
10836 int len = SprintfLiteral(buf, "%d", counter->mCount);
10837 nscoord x = 0, y = fm->MaxAscent();
10838 nscoord width, height = fm->MaxHeight();
10839 fm->SetTextRunRTL(false);
10840 width = fm->GetWidth(buf, len, drawTarget);
10842 sRGBColor color;
10843 sRGBColor color2;
10844 if (aColor != 0) {
10845 color = sRGBColor::FromABGR(aColor);
10846 color2 = sRGBColor(0.f, 0.f, 0.f);
10847 } else {
10848 gfx::Float rc = 0.f, gc = 0.f, bc = 0.f;
10849 if (counter->mCount < 5) {
10850 rc = 1.f;
10851 gc = 1.f;
10852 } else if (counter->mCount < 11) {
10853 gc = 1.f;
10854 } else {
10855 rc = 1.f;
10857 color = sRGBColor(rc, gc, bc);
10858 color2 = sRGBColor(rc / 2, gc / 2, bc / 2);
10861 nsRect rect(0, 0, width + 15, height + 15);
10862 Rect devPxRect =
10863 NSRectToSnappedRect(rect, appUnitsPerDevPixel, *drawTarget);
10864 ColorPattern black(ToDeviceColor(sRGBColor::OpaqueBlack()));
10865 drawTarget->FillRect(devPxRect, black);
10867 aRenderingContext->SetColor(color2);
10868 fm->DrawString(buf, len, x + 15, y + 15, aRenderingContext);
10869 aRenderingContext->SetColor(color);
10870 fm->DrawString(buf, len, x, y, aRenderingContext);
10872 aRenderingContext->Restore();
10877 //------------------------------------------------------------------
10878 void ReflowCountMgr::DoGrandTotals() {
10879 mCounts.WithEntryHandle(kGrandTotalsStr, [this](auto&& entry) {
10880 if (!entry) {
10881 entry.Insert(MakeUnique<ReflowCounter>(this));
10882 } else {
10883 entry.Data()->ClearTotals();
10887 printf("\t\t\t\tTotal\n");
10888 for (uint32_t i = 0; i < 78; i++) {
10889 printf("-");
10891 printf("\n");
10892 for (const auto& entry : mCounts) {
10893 entry.GetData()->DisplayTotals(entry.GetKey());
10897 static void RecurseIndiTotals(
10898 nsPresContext* aPresContext,
10899 nsClassHashtable<nsCharPtrHashKey, IndiReflowCounter>& aHT,
10900 nsIFrame* aParentFrame, int32_t aLevel) {
10901 if (aParentFrame == nullptr) {
10902 return;
10905 char key[KEY_BUF_SIZE_FOR_PTR];
10906 SprintfLiteral(key, "%p", (void*)aParentFrame);
10907 IndiReflowCounter* counter = aHT.Get(key);
10908 if (counter) {
10909 counter->mHasBeenOutput = true;
10910 char* name = ToNewCString(counter->mName);
10911 for (int32_t i = 0; i < aLevel; i++) printf(" ");
10912 printf("%s - %p [%d][", name, (void*)aParentFrame, counter->mCount);
10913 printf("%d", counter->mCounter.GetTotal());
10914 printf("]\n");
10915 free(name);
10918 for (nsIFrame* child : aParentFrame->PrincipalChildList()) {
10919 RecurseIndiTotals(aPresContext, aHT, child, aLevel + 1);
10923 //------------------------------------------------------------------
10924 void ReflowCountMgr::DoIndiTotalsTree() {
10925 printf("\n------------------------------------------------\n");
10926 printf("-- Individual Frame Counts\n");
10927 printf("------------------------------------------------\n");
10929 if (mPresShell) {
10930 nsIFrame* rootFrame = mPresShell->GetRootFrame();
10931 RecurseIndiTotals(mPresContext, mIndiFrameCounts, rootFrame, 0);
10932 printf("------------------------------------------------\n");
10933 printf("-- Individual Counts of Frames not in Root Tree\n");
10934 printf("------------------------------------------------\n");
10935 for (const auto& counter : mIndiFrameCounts.Values()) {
10936 if (!counter->mHasBeenOutput) {
10937 char* name = ToNewCString(counter->mName);
10938 printf("%s - %p [%d][", name, (void*)counter->mFrame,
10939 counter->mCount);
10940 printf("%d", counter->mCounter.GetTotal());
10941 printf("]\n");
10942 free(name);
10948 //------------------------------------------------------------------
10949 void ReflowCountMgr::DoGrandHTMLTotals() {
10950 mCounts.WithEntryHandle(kGrandTotalsStr, [this](auto&& entry) {
10951 if (!entry) {
10952 entry.Insert(MakeUnique<ReflowCounter>(this));
10953 } else {
10954 entry.Data()->ClearTotals();
10958 static const char* title[] = {"Class", "Reflows"};
10959 fprintf(mFD, "<tr>");
10960 for (uint32_t i = 0; i < ArrayLength(title); i++) {
10961 fprintf(mFD, "<td><center><b>%s<b></center></td>", title[i]);
10963 fprintf(mFD, "</tr>\n");
10965 for (const auto& entry : mCounts) {
10966 entry.GetData()->DisplayHTMLTotals(entry.GetKey());
10970 //------------------------------------
10971 void ReflowCountMgr::DisplayTotals(const char* aStr) {
10972 # ifdef DEBUG_rods
10973 printf("%s\n", aStr ? aStr : "No name");
10974 # endif
10975 if (mDumpFrameCounts) {
10976 DoGrandTotals();
10978 if (mDumpFrameByFrameCounts) {
10979 DoIndiTotalsTree();
10982 //------------------------------------
10983 void ReflowCountMgr::DisplayHTMLTotals(const char* aStr) {
10984 # ifdef WIN32x // XXX NOT XP!
10985 char name[1024];
10987 char* sptr = strrchr(aStr, '/');
10988 if (sptr) {
10989 sptr++;
10990 strcpy(name, sptr);
10991 char* eptr = strrchr(name, '.');
10992 if (eptr) {
10993 *eptr = 0;
10995 strcat(name, "_stats.html");
10997 mFD = fopen(name, "w");
10998 if (mFD) {
10999 fprintf(mFD, "<html><head><title>Reflow Stats</title></head><body>\n");
11000 const char* title = aStr ? aStr : "No name";
11001 fprintf(mFD,
11002 "<center><b>%s</b><br><table border=1 "
11003 "style=\"background-color:#e0e0e0\">",
11004 title);
11005 DoGrandHTMLTotals();
11006 fprintf(mFD, "</center></table>\n");
11007 fprintf(mFD, "</body></html>\n");
11008 fclose(mFD);
11009 mFD = nullptr;
11011 # endif // not XP!
11014 //------------------------------------------------------------------
11015 void ReflowCountMgr::ClearTotals() {
11016 for (const auto& data : mCounts.Values()) {
11017 data->ClearTotals();
11021 //------------------------------------------------------------------
11022 void ReflowCountMgr::ClearGrandTotals() {
11023 mCounts.WithEntryHandle(kGrandTotalsStr, [&](auto&& entry) {
11024 if (!entry) {
11025 entry.Insert(MakeUnique<ReflowCounter>(this));
11026 } else {
11027 entry.Data()->ClearTotals();
11028 entry.Data()->SetTotalsCache();
11033 //------------------------------------------------------------------
11034 void ReflowCountMgr::DisplayDiffsInTotals() {
11035 if (mCycledOnce) {
11036 printf("Differences\n");
11037 for (int32_t i = 0; i < 78; i++) {
11038 printf("-");
11040 printf("\n");
11041 ClearGrandTotals();
11044 for (const auto& entry : mCounts) {
11045 if (mCycledOnce) {
11046 entry.GetData()->CalcDiffInTotals();
11047 entry.GetData()->DisplayDiffTotals(entry.GetKey());
11049 entry.GetData()->SetTotalsCache();
11052 mCycledOnce = true;
11055 #endif // MOZ_REFLOW_PERF
11057 nsIFrame* PresShell::GetAbsoluteContainingBlock(nsIFrame* aFrame) {
11058 return FrameConstructor()->GetAbsoluteContainingBlock(
11059 aFrame, nsCSSFrameConstructor::ABS_POS);
11062 void PresShell::ActivenessMaybeChanged() {
11063 if (!mDocument) {
11064 return;
11066 SetIsActive(ComputeActiveness());
11069 // A PresShell being active means that it is visible (or close to be visible, if
11070 // the front-end is warming it). That means that when it is active we always
11071 // tick its refresh driver at full speed if needed.
11073 // Image documents behave specially in the sense that they are always "active"
11074 // and never "in the active tab". However these documents tick manually so
11075 // there's not much to worry about there.
11076 bool PresShell::ComputeActiveness() const {
11077 MOZ_LOG(gLog, LogLevel::Debug,
11078 ("PresShell::ComputeActiveness(%s, %d)\n",
11079 mDocument->GetDocumentURI()
11080 ? mDocument->GetDocumentURI()->GetSpecOrDefault().get()
11081 : "(no uri)",
11082 mIsActive));
11084 Document* doc = mDocument;
11086 if (doc->IsBeingUsedAsImage()) {
11087 // Documents used as an image can remain active. They do not tick their
11088 // refresh driver if not painted, and they can't run script or such so they
11089 // can't really observe much else.
11091 // Image docs can be displayed in multiple docs at the same time so the "in
11092 // active tab" bool doesn't make much sense for them.
11093 return true;
11096 if (Document* displayDoc = doc->GetDisplayDocument()) {
11097 // Ok, we're an external resource document -- we need to use our display
11098 // document's docshell to determine "IsActive" status, since we lack
11099 // a browsing context of our own.
11100 MOZ_ASSERT(!doc->GetBrowsingContext(),
11101 "external resource doc shouldn't have its own BC");
11102 doc = displayDoc;
11105 BrowsingContext* bc = doc->GetBrowsingContext();
11106 const bool inActiveTab = bc && bc->IsActive();
11108 MOZ_LOG(gLog, LogLevel::Debug,
11109 (" > BrowsingContext %p active: %d", bc, inActiveTab));
11111 Document* root = nsContentUtils::GetInProcessSubtreeRootDocument(doc);
11112 if (auto* browserChild = BrowserChild::GetFrom(root->GetDocShell())) {
11113 // We might want to activate a tab even though the browsing-context is not
11114 // active if the BrowserChild is considered visible. This serves two
11115 // purposes:
11117 // * For top-level tabs, we use this for tab warming. The browsing-context
11118 // might still be inactive, but we want to activate the pres shell and
11119 // the refresh driver.
11121 // * For oop iframes, we do want to throttle them if they're not visible.
11123 // TODO(emilio): Consider unifying the in-process vs. fission iframe
11124 // throttling code (in-process throttling for non-visible iframes lives
11125 // right now in Document::ShouldThrottleFrameRequests(), but that only
11126 // throttles rAF).
11127 if (!browserChild->IsVisible()) {
11128 MOZ_LOG(gLog, LogLevel::Debug,
11129 (" > BrowserChild %p is not visible", browserChild));
11130 return false;
11133 // If the browser is visible but just due to be preserving layers
11134 // artificially, we do want to fall back to the browsing context activeness
11135 // instead. Otherwise we do want to be active for the use cases above.
11136 if (!browserChild->IsPreservingLayers()) {
11137 MOZ_LOG(gLog, LogLevel::Debug,
11138 (" > BrowserChild %p is visible and not preserving layers",
11139 browserChild));
11140 return true;
11142 MOZ_LOG(
11143 gLog, LogLevel::Debug,
11144 (" > BrowserChild %p is visible and preserving layers", browserChild));
11146 return inActiveTab;
11149 void PresShell::SetIsActive(bool aIsActive) {
11150 MOZ_ASSERT(mDocument, "should only be called with a document");
11152 const bool activityChanged = mIsActive != aIsActive;
11154 mIsActive = aIsActive;
11156 nsPresContext* presContext = GetPresContext();
11157 if (presContext &&
11158 presContext->RefreshDriver()->GetPresContext() == presContext) {
11159 presContext->RefreshDriver()->SetActivity(aIsActive);
11162 if (activityChanged) {
11163 // Propagate state-change to my resource documents' PresShells and other
11164 // subdocuments.
11166 // Note that it is fine to not propagate to fission iframes. Those will
11167 // become active / inactive as needed as a result of they getting painted /
11168 // not painted eventually.
11169 auto recurse = [aIsActive](Document& aSubDoc) {
11170 if (PresShell* presShell = aSubDoc.GetPresShell()) {
11171 presShell->SetIsActive(aIsActive);
11173 return CallState::Continue;
11175 mDocument->EnumerateExternalResources(recurse);
11176 mDocument->EnumerateSubDocuments(recurse);
11179 UpdateImageLockingState();
11181 if (activityChanged) {
11182 #if defined(MOZ_WIDGET_ANDROID)
11183 if (!aIsActive && presContext &&
11184 presContext->IsRootContentDocumentCrossProcess()) {
11185 if (BrowserChild* browserChild = BrowserChild::GetFrom(this)) {
11186 // Reset the dynamic toolbar offset state.
11187 presContext->UpdateDynamicToolbarOffset(0);
11190 #endif
11193 if (aIsActive) {
11194 #ifdef ACCESSIBILITY
11195 if (nsAccessibilityService* accService = GetAccService()) {
11196 accService->PresShellActivated(this);
11198 #endif
11199 if (nsIFrame* rootFrame = GetRootFrame()) {
11200 rootFrame->SchedulePaint();
11205 RefPtr<MobileViewportManager> PresShell::GetMobileViewportManager() const {
11206 return mMobileViewportManager;
11209 Maybe<MobileViewportManager::ManagerType> UseMobileViewportManager(
11210 PresShell* aPresShell, Document* aDocument) {
11211 // If we're not using APZ, we won't be able to zoom, so there is no
11212 // point in having an MVM.
11213 if (nsPresContext* presContext = aPresShell->GetPresContext()) {
11214 if (nsIWidget* widget = presContext->GetNearestWidget()) {
11215 if (!widget->AsyncPanZoomEnabled()) {
11216 return Nothing();
11220 if (nsLayoutUtils::ShouldHandleMetaViewport(aDocument)) {
11221 return Some(MobileViewportManager::ManagerType::VisualAndMetaViewport);
11223 if (StaticPrefs::apz_mvm_force_enabled() ||
11224 nsLayoutUtils::AllowZoomingForDocument(aDocument)) {
11225 return Some(MobileViewportManager::ManagerType::VisualViewportOnly);
11227 return Nothing();
11230 void PresShell::MaybeRecreateMobileViewportManager(bool aAfterInitialization) {
11231 // Determine if we require a MobileViewportManager, and what kind if so. We
11232 // need one any time we allow resolution zooming for a document, and any time
11233 // we want to obey <meta name="viewport"> tags for it.
11234 Maybe<MobileViewportManager::ManagerType> mvmType =
11235 UseMobileViewportManager(this, mDocument);
11237 if (mvmType.isNothing() && !mMobileViewportManager) {
11238 // We don't need one and don't have it. So we're done.
11239 return;
11241 if (mvmType && mMobileViewportManager &&
11242 *mvmType == mMobileViewportManager->GetManagerType()) {
11243 // We need one and we have one of the correct type, so we're done.
11244 return;
11247 if (mMobileViewportManager) {
11248 // We have one, but we need to either destroy it completely to replace it
11249 // with another one of the correct type. So either way, let's destroy the
11250 // one we have.
11251 mMobileViewportManager->Destroy();
11252 mMobileViewportManager = nullptr;
11253 mMVMContext = nullptr;
11255 ResetVisualViewportSize();
11257 // After we clear out the MVM and the MVMContext, also reset the
11258 // resolution to its pre-MVM value.
11259 SetResolutionAndScaleTo(mDocument->GetSavedResolutionBeforeMVM(),
11260 ResolutionChangeOrigin::MainThreadRestore);
11262 if (aAfterInitialization) {
11263 // Force a reflow to our correct view manager size.
11264 ForceResizeReflowWithCurrentDimensions();
11268 if (mvmType) {
11269 // Let's create the MVM of the type that we need. At this point we shouldn't
11270 // have one.
11271 MOZ_ASSERT(!mMobileViewportManager);
11273 if (mPresContext->IsRootContentDocumentCrossProcess()) {
11274 // Store the resolution so we can restore to this resolution when
11275 // the MVM is destroyed.
11276 mDocument->SetSavedResolutionBeforeMVM(mResolution.valueOr(1.0f));
11278 mMVMContext = new GeckoMVMContext(mDocument, this);
11279 mMobileViewportManager = new MobileViewportManager(mMVMContext, *mvmType);
11280 if (MOZ_UNLIKELY(
11281 MOZ_LOG_TEST(MobileViewportManager::gLog, LogLevel::Debug))) {
11282 nsIURI* uri = mDocument->GetDocumentURI();
11283 MOZ_LOG(MobileViewportManager::gLog, LogLevel::Debug,
11284 ("Created MVM %p (type %d) for URI %s",
11285 mMobileViewportManager.get(), (int)*mvmType,
11286 uri ? uri->GetSpecOrDefault().get() : "(null)"));
11289 if (aAfterInitialization) {
11290 // Setting the initial viewport will trigger a reflow.
11291 mMobileViewportManager->SetInitialViewport();
11297 bool PresShell::UsesMobileViewportSizing() const {
11298 return mMobileViewportManager != nullptr &&
11299 nsLayoutUtils::ShouldHandleMetaViewport(mDocument);
11303 * Determines the current image locking state. Called when one of the
11304 * dependent factors changes.
11306 void PresShell::UpdateImageLockingState() {
11307 // We're locked if we're both thawed and active.
11308 const bool locked = !mFrozen && mIsActive;
11309 auto* tracker = mDocument->ImageTracker();
11310 if (locked == tracker->GetLockingState()) {
11311 return;
11314 tracker->SetLockingState(locked);
11315 if (locked) {
11316 // Request decodes for visible image frames; we want to start decoding as
11317 // quickly as possible when we get foregrounded to minimize flashing.
11318 for (const auto& key : mApproximatelyVisibleFrames) {
11319 if (nsImageFrame* imageFrame = do_QueryFrame(key)) {
11320 imageFrame->MaybeDecodeForPredictedSize();
11326 PresShell* PresShell::GetRootPresShell() const {
11327 if (mPresContext) {
11328 nsPresContext* rootPresContext = mPresContext->GetRootPresContext();
11329 if (rootPresContext) {
11330 return rootPresContext->PresShell();
11333 return nullptr;
11336 void PresShell::AddSizeOfIncludingThis(nsWindowSizes& aSizes) const {
11337 MallocSizeOf mallocSizeOf = aSizes.mState.mMallocSizeOf;
11338 mFrameArena.AddSizeOfExcludingThis(aSizes, Arena::ArenaKind::PresShell);
11339 aSizes.mLayoutPresShellSize += mallocSizeOf(this);
11340 if (mCaret) {
11341 aSizes.mLayoutPresShellSize += mCaret->SizeOfIncludingThis(mallocSizeOf);
11343 aSizes.mLayoutPresShellSize +=
11344 mApproximatelyVisibleFrames.ShallowSizeOfExcludingThis(mallocSizeOf) +
11345 mFramesToDirty.ShallowSizeOfExcludingThis(mallocSizeOf) +
11346 mPendingScrollAnchorSelection.ShallowSizeOfExcludingThis(mallocSizeOf) +
11347 mPendingScrollAnchorAdjustment.ShallowSizeOfExcludingThis(mallocSizeOf);
11349 aSizes.mLayoutTextRunsSize += SizeOfTextRuns(mallocSizeOf);
11351 aSizes.mLayoutPresContextSize +=
11352 mPresContext->SizeOfIncludingThis(mallocSizeOf);
11354 mFrameConstructor->AddSizeOfIncludingThis(aSizes);
11357 size_t PresShell::SizeOfTextRuns(MallocSizeOf aMallocSizeOf) const {
11358 nsIFrame* rootFrame = mFrameConstructor->GetRootFrame();
11359 if (!rootFrame) {
11360 return 0;
11363 // clear the TEXT_RUN_MEMORY_ACCOUNTED flags
11364 nsLayoutUtils::SizeOfTextRunsForFrames(rootFrame, nullptr,
11365 /* clear = */ true);
11367 // collect the total memory in use for textruns
11368 return nsLayoutUtils::SizeOfTextRunsForFrames(rootFrame, aMallocSizeOf,
11369 /* clear = */ false);
11372 void PresShell::MarkFixedFramesForReflow(IntrinsicDirty aIntrinsicDirty) {
11373 nsIFrame* rootFrame = mFrameConstructor->GetRootFrame();
11374 if (rootFrame) {
11375 const nsFrameList& childList =
11376 rootFrame->GetChildList(FrameChildListID::Fixed);
11377 for (nsIFrame* childFrame : childList) {
11378 FrameNeedsReflow(childFrame, aIntrinsicDirty, NS_FRAME_IS_DIRTY);
11383 static void AppendSubtree(nsIDocShell* aDocShell,
11384 nsTArray<nsCOMPtr<nsIDocumentViewer>>& aArray) {
11385 if (nsCOMPtr<nsIDocumentViewer> viewer = aDocShell->GetDocViewer()) {
11386 aArray.AppendElement(viewer);
11389 int32_t n = aDocShell->GetInProcessChildCount();
11390 for (int32_t i = 0; i < n; i++) {
11391 nsCOMPtr<nsIDocShellTreeItem> childItem;
11392 aDocShell->GetInProcessChildAt(i, getter_AddRefs(childItem));
11393 if (childItem) {
11394 nsCOMPtr<nsIDocShell> child(do_QueryInterface(childItem));
11395 AppendSubtree(child, aArray);
11400 void PresShell::MaybeReflowForInflationScreenSizeChange() {
11401 nsPresContext* pc = GetPresContext();
11402 const bool fontInflationWasEnabled = FontSizeInflationEnabled();
11403 RecomputeFontSizeInflationEnabled();
11404 bool changed = false;
11405 if (FontSizeInflationEnabled() && FontSizeInflationMinTwips() != 0) {
11406 pc->ScreenSizeInchesForFontInflation(&changed);
11409 changed = changed || fontInflationWasEnabled != FontSizeInflationEnabled();
11410 if (!changed) {
11411 return;
11413 if (nsCOMPtr<nsIDocShell> docShell = pc->GetDocShell()) {
11414 nsTArray<nsCOMPtr<nsIDocumentViewer>> array;
11415 AppendSubtree(docShell, array);
11416 for (uint32_t i = 0, iEnd = array.Length(); i < iEnd; ++i) {
11417 nsCOMPtr<nsIDocumentViewer> viewer = array[i];
11418 if (RefPtr<PresShell> descendantPresShell = viewer->GetPresShell()) {
11419 nsIFrame* rootFrame = descendantPresShell->GetRootFrame();
11420 if (rootFrame) {
11421 descendantPresShell->FrameNeedsReflow(
11422 rootFrame, IntrinsicDirty::FrameAncestorsAndDescendants,
11423 NS_FRAME_IS_DIRTY);
11430 void PresShell::CompleteChangeToVisualViewportSize() {
11431 // This can get called during reflow, if the caller wants to get the latest
11432 // visual viewport size after scrollbars have been added/removed. In such a
11433 // case, we don't need to mark things as dirty because the things that we
11434 // would mark dirty either just got updated (the root scrollframe's
11435 // scrollbars), or will be laid out later during this reflow cycle (fixed-pos
11436 // items). Callers that update the visual viewport during a reflow are
11437 // responsible for maintaining these invariants.
11438 if (!mIsReflowing) {
11439 if (nsIScrollableFrame* rootScrollFrame =
11440 GetRootScrollFrameAsScrollable()) {
11441 rootScrollFrame->MarkScrollbarsDirtyForReflow();
11443 MarkFixedFramesForReflow(IntrinsicDirty::None);
11446 MaybeReflowForInflationScreenSizeChange();
11448 if (auto* window = nsGlobalWindowInner::Cast(mDocument->GetInnerWindow())) {
11449 window->VisualViewport()->PostResizeEvent();
11453 void PresShell::SetVisualViewportSize(nscoord aWidth, nscoord aHeight) {
11454 MOZ_ASSERT(aWidth >= 0.0 && aHeight >= 0.0);
11456 if (!mVisualViewportSizeSet || mVisualViewportSize.width != aWidth ||
11457 mVisualViewportSize.height != aHeight) {
11458 mVisualViewportSizeSet = true;
11459 mVisualViewportSize.width = aWidth;
11460 mVisualViewportSize.height = aHeight;
11462 CompleteChangeToVisualViewportSize();
11466 void PresShell::ResetVisualViewportSize() {
11467 if (mVisualViewportSizeSet) {
11468 mVisualViewportSizeSet = false;
11469 mVisualViewportSize.width = 0;
11470 mVisualViewportSize.height = 0;
11472 CompleteChangeToVisualViewportSize();
11476 bool PresShell::SetVisualViewportOffset(const nsPoint& aScrollOffset,
11477 const nsPoint& aPrevLayoutScrollPos) {
11478 nsPoint newOffset = aScrollOffset;
11479 nsIScrollableFrame* rootScrollFrame = GetRootScrollFrameAsScrollable();
11480 if (rootScrollFrame) {
11481 // See the comment in nsHTMLScrollFrame::Reflow above the call to
11482 // SetVisualViewportOffset for why we need to do this.
11483 nsRect scrollRange = rootScrollFrame->GetScrollRangeForUserInputEvents();
11484 if (!scrollRange.Contains(newOffset)) {
11485 newOffset.x = std::min(newOffset.x, scrollRange.XMost());
11486 newOffset.x = std::max(newOffset.x, scrollRange.x);
11487 newOffset.y = std::min(newOffset.y, scrollRange.YMost());
11488 newOffset.y = std::max(newOffset.y, scrollRange.y);
11492 // Careful here not to call GetVisualViewportOffset to get the previous visual
11493 // viewport offset because if mVisualViewportOffset is nothing then we'll get
11494 // the layout scroll position directly from the scroll frame and it has likely
11495 // already been updated.
11496 nsPoint prevOffset = aPrevLayoutScrollPos;
11497 if (mVisualViewportOffset.isSome()) {
11498 prevOffset = *mVisualViewportOffset;
11500 if (prevOffset == newOffset) {
11501 return false;
11504 mVisualViewportOffset = Some(newOffset);
11506 if (auto* window = nsGlobalWindowInner::Cast(mDocument->GetInnerWindow())) {
11507 window->VisualViewport()->PostScrollEvent(prevOffset, aPrevLayoutScrollPos);
11510 if (IsVisualViewportSizeSet() && rootScrollFrame) {
11511 rootScrollFrame->Anchor()->UserScrolled();
11514 if (gfxPlatform::UseDesktopZoomingScrollbars()) {
11515 if (nsIScrollableFrame* rootScrollFrame =
11516 GetRootScrollFrameAsScrollable()) {
11517 rootScrollFrame->UpdateScrollbarPosition();
11521 return true;
11524 void PresShell::ResetVisualViewportOffset() { mVisualViewportOffset.reset(); }
11526 void PresShell::ScrollToVisual(const nsPoint& aVisualViewportOffset,
11527 FrameMetrics::ScrollOffsetUpdateType aUpdateType,
11528 ScrollMode aMode) {
11529 MOZ_ASSERT(aMode == ScrollMode::Instant || aMode == ScrollMode::SmoothMsd);
11531 if (aMode == ScrollMode::SmoothMsd) {
11532 if (nsIScrollableFrame* sf = GetRootScrollFrameAsScrollable()) {
11533 if (sf->SmoothScrollVisual(aVisualViewportOffset, aUpdateType)) {
11534 return;
11539 // If the caller asked for instant scroll, or if we failed
11540 // to do a smooth scroll, do an instant scroll.
11541 SetPendingVisualScrollUpdate(aVisualViewportOffset, aUpdateType);
11544 void PresShell::SetPendingVisualScrollUpdate(
11545 const nsPoint& aVisualViewportOffset,
11546 FrameMetrics::ScrollOffsetUpdateType aUpdateType) {
11547 mPendingVisualScrollUpdate =
11548 Some(VisualScrollUpdate{aVisualViewportOffset, aUpdateType});
11550 // The pending update is picked up during the next paint.
11551 // Schedule a paint to make sure one will happen.
11552 if (nsIFrame* rootFrame = GetRootFrame()) {
11553 rootFrame->SchedulePaint();
11557 void PresShell::ClearPendingVisualScrollUpdate() {
11558 if (mPendingVisualScrollUpdate && mPendingVisualScrollUpdate->mAcknowledged) {
11559 mPendingVisualScrollUpdate = mozilla::Nothing();
11563 void PresShell::AcknowledgePendingVisualScrollUpdate() {
11564 MOZ_ASSERT(mPendingVisualScrollUpdate);
11565 mPendingVisualScrollUpdate->mAcknowledged = true;
11568 nsPoint PresShell::GetVisualViewportOffsetRelativeToLayoutViewport() const {
11569 return GetVisualViewportOffset() - GetLayoutViewportOffset();
11572 nsPoint PresShell::GetLayoutViewportOffset() const {
11573 nsPoint result;
11574 if (nsIScrollableFrame* sf = GetRootScrollFrameAsScrollable()) {
11575 result = sf->GetScrollPosition();
11577 return result;
11580 nsSize PresShell::GetLayoutViewportSize() const {
11581 nsSize result;
11582 if (nsIScrollableFrame* sf = GetRootScrollFrameAsScrollable()) {
11583 result = sf->GetScrollPortRect().Size();
11585 return result;
11588 nsSize PresShell::GetVisualViewportSizeUpdatedByDynamicToolbar() const {
11589 NS_ASSERTION(mVisualViewportSizeSet,
11590 "asking for visual viewport size when its not set?");
11591 if (!mMobileViewportManager) {
11592 return mVisualViewportSize;
11595 MOZ_ASSERT(GetDynamicToolbarState() == DynamicToolbarState::InTransition ||
11596 GetDynamicToolbarState() == DynamicToolbarState::Collapsed);
11598 nsSize sizeUpdatedByDynamicToolbar =
11599 mMobileViewportManager->GetVisualViewportSizeUpdatedByDynamicToolbar();
11600 return sizeUpdatedByDynamicToolbar == nsSize() ? mVisualViewportSize
11601 : sizeUpdatedByDynamicToolbar;
11604 void PresShell::RecomputeFontSizeInflationEnabled() {
11605 mFontSizeInflationEnabled = DetermineFontSizeInflationState();
11608 bool PresShell::DetermineFontSizeInflationState() {
11609 MOZ_ASSERT(mPresContext, "our pres context should not be null");
11610 if (mPresContext->IsChrome()) {
11611 return false;
11614 if (FontSizeInflationEmPerLine() == 0 && FontSizeInflationMinTwips() == 0) {
11615 return false;
11618 // Force-enabling font inflation always trumps the heuristics here.
11619 if (!FontSizeInflationForceEnabled()) {
11620 if (BrowserChild* tab = BrowserChild::GetFrom(this)) {
11621 // We're in a child process. Cancel inflation if we're not
11622 // async-pan zoomed.
11623 if (!tab->AsyncPanZoomEnabled()) {
11624 return false;
11626 } else if (XRE_IsParentProcess()) {
11627 // We're in the master process. Cancel inflation if it's been
11628 // explicitly disabled.
11629 if (FontSizeInflationDisabledInMasterProcess()) {
11630 return false;
11635 Maybe<LayoutDeviceIntSize> displaySize;
11636 // The MVM already caches the top-level content viewer size and is therefore
11637 // the fastest way of getting that data.
11638 if (mPresContext->IsRootContentDocumentCrossProcess()) {
11639 if (mMobileViewportManager) {
11640 displaySize = Some(mMobileViewportManager->DisplaySize());
11642 } else if (PresShell* rootPresShell = GetRootPresShell()) {
11643 // With any luck, we can get at the root content document without any cross-
11644 // process shenanigans.
11645 if (auto mvm = rootPresShell->GetMobileViewportManager()) {
11646 displaySize = Some(mvm->DisplaySize());
11650 if (!displaySize) {
11651 // Unfortunately, it looks like the root content document lives in a
11652 // different process. For consistency's sake it would be best to always use
11653 // the content viewer size of the root content document, but it's not worth
11654 // the effort, because this only makes a difference in the case of pages
11655 // with an explicitly sized viewport (neither "width=device-width" nor a
11656 // completely missing viewport tag) being loaded within a frame, which is
11657 // hopefully a relatively exotic case.
11658 // More to the point, these viewport size and zoom-based calculations don't
11659 // really make sense for frames anyway, so instead of creating a way to
11660 // access the content viewer size of the top level document cross-process,
11661 // we probably rather want frames to simply inherit the font inflation state
11662 // of their top-level parent and should therefore invest any time spent on
11663 // getting things to work cross-process into that (bug 1724311).
11665 // Until we get around to that though, we just use the content viewer size
11666 // of however high we can get within the same process.
11668 // (This also serves as a fallback code path if the MVM isn't available,
11669 // e.g. when debugging in non-e10s mode on Desktop.)
11670 nsPresContext* topContext =
11671 mPresContext->GetInProcessRootContentDocumentPresContext();
11672 LayoutDeviceIntSize result;
11673 if (!nsLayoutUtils::GetDocumentViewerSize(topContext, result)) {
11674 return false;
11676 displaySize = Some(result);
11679 ScreenIntSize screenSize = ViewAs<ScreenPixel>(
11680 displaySize.value(),
11681 PixelCastJustification::LayoutDeviceIsScreenForBounds);
11682 nsViewportInfo vInf = GetDocument()->GetViewportInfo(screenSize);
11684 CSSToScreenScale defaultScale =
11685 mPresContext->CSSToDevPixelScale() * LayoutDeviceToScreenScale(1.0);
11687 if (vInf.GetDefaultZoom() >= defaultScale || vInf.IsAutoSizeEnabled()) {
11688 return false;
11691 return true;
11694 static nsIWidget* GetPresContextContainerWidget(nsPresContext* aPresContext) {
11695 nsCOMPtr<nsISupports> container = aPresContext->Document()->GetContainer();
11696 nsCOMPtr<nsIBaseWindow> baseWindow = do_QueryInterface(container);
11697 if (!baseWindow) {
11698 return nullptr;
11701 nsCOMPtr<nsIWidget> mainWidget;
11702 baseWindow->GetMainWidget(getter_AddRefs(mainWidget));
11703 return mainWidget;
11706 static bool IsTopLevelWidget(nsIWidget* aWidget) {
11707 using WindowType = mozilla::widget::WindowType;
11709 auto windowType = aWidget->GetWindowType();
11710 return windowType == WindowType::TopLevel ||
11711 windowType == WindowType::Dialog || windowType == WindowType::Popup ||
11712 windowType == WindowType::Sheet;
11715 PresShell::WindowSizeConstraints PresShell::GetWindowSizeConstraints() {
11716 nsSize minSize(0, 0);
11717 nsSize maxSize(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
11718 nsIFrame* rootFrame = FrameConstructor()->GetRootElementStyleFrame();
11719 if (!rootFrame || !mPresContext) {
11720 return {minSize, maxSize};
11722 const auto* pos = rootFrame->StylePosition();
11723 if (pos->mMinWidth.ConvertsToLength()) {
11724 minSize.width = pos->mMinWidth.ToLength();
11726 if (pos->mMinHeight.ConvertsToLength()) {
11727 minSize.height = pos->mMinHeight.ToLength();
11729 if (pos->mMaxWidth.ConvertsToLength()) {
11730 maxSize.width = pos->mMaxWidth.ToLength();
11732 if (pos->mMaxHeight.ConvertsToLength()) {
11733 maxSize.height = pos->mMaxHeight.ToLength();
11735 return {minSize, maxSize};
11738 void PresShell::SyncWindowProperties(bool aSync) {
11739 nsView* view = mViewManager->GetRootView();
11740 if (!view || !view->HasWidget()) {
11741 return;
11743 RefPtr pc = mPresContext;
11744 if (!pc) {
11745 return;
11748 nsCOMPtr<nsIWidget> windowWidget = GetPresContextContainerWidget(pc);
11749 if (!windowWidget || !IsTopLevelWidget(windowWidget)) {
11750 return;
11753 nsIFrame* rootFrame = FrameConstructor()->GetRootElementStyleFrame();
11754 if (!rootFrame) {
11755 return;
11758 if (!aSync) {
11759 view->SetNeedsWindowPropertiesSync();
11760 return;
11763 AutoWeakFrame weak(rootFrame);
11764 if (!GetRootScrollFrame()) {
11765 // Scrollframes use native widgets which don't work well with
11766 // translucent windows, at least in Windows XP. So if the document
11767 // has a root scrollrame it's useless to try to make it transparent,
11768 // we'll just get something broken.
11769 // We can change this to allow translucent toplevel HTML documents
11770 // (e.g. to do something like Dashboard widgets), once we
11771 // have broad support for translucent scrolled documents, but be
11772 // careful because apparently some Firefox extensions expect
11773 // openDialog("something.html") to produce an opaque window
11774 // even if the HTML doesn't have a background-color set.
11775 auto* canvas = GetCanvasFrame();
11776 widget::TransparencyMode mode = nsLayoutUtils::GetFrameTransparency(
11777 canvas ? canvas : rootFrame, rootFrame);
11778 windowWidget->SetTransparencyMode(mode);
11780 // For macOS, apply color scheme overrides to the top level window widget.
11781 if (auto scheme = pc->GetOverriddenOrEmbedderColorScheme()) {
11782 windowWidget->SetColorScheme(scheme);
11786 if (!weak.IsAlive()) {
11787 return;
11790 const auto& constraints = GetWindowSizeConstraints();
11791 nsContainerFrame::SetSizeConstraints(pc, windowWidget, constraints.mMinSize,
11792 constraints.mMaxSize);
11795 nsresult PresShell::HasRuleProcessorUsedByMultipleStyleSets(uint32_t aSheetType,
11796 bool* aRetVal) {
11797 *aRetVal = false;
11798 return NS_OK;
11801 void PresShell::NotifyStyleSheetServiceSheetAdded(StyleSheet* aSheet,
11802 uint32_t aSheetType) {
11803 switch (aSheetType) {
11804 case nsIStyleSheetService::AGENT_SHEET:
11805 AddAgentSheet(aSheet);
11806 break;
11807 case nsIStyleSheetService::USER_SHEET:
11808 AddUserSheet(aSheet);
11809 break;
11810 case nsIStyleSheetService::AUTHOR_SHEET:
11811 AddAuthorSheet(aSheet);
11812 break;
11813 default:
11814 MOZ_ASSERT_UNREACHABLE("unexpected aSheetType value");
11815 break;
11819 void PresShell::NotifyStyleSheetServiceSheetRemoved(StyleSheet* aSheet,
11820 uint32_t aSheetType) {
11821 StyleSet()->RemoveStyleSheet(*aSheet);
11822 mDocument->ApplicableStylesChanged();
11825 nsIContent* PresShell::EventHandler::GetOverrideClickTarget(
11826 WidgetGUIEvent* aGUIEvent, nsIFrame* aFrame) {
11827 if (aGUIEvent->mMessage != eMouseUp) {
11828 return nullptr;
11831 MOZ_ASSERT(aGUIEvent->mClass == eMouseEventClass);
11832 WidgetMouseEvent* mouseEvent = aGUIEvent->AsMouseEvent();
11834 uint32_t flags = 0;
11835 RelativeTo relativeTo{aFrame};
11836 nsPoint eventPoint =
11837 nsLayoutUtils::GetEventCoordinatesRelativeTo(aGUIEvent, relativeTo);
11838 if (mouseEvent->mIgnoreRootScrollFrame) {
11839 flags |= INPUT_IGNORE_ROOT_SCROLL_FRAME;
11842 nsIFrame* target =
11843 FindFrameTargetedByInputEvent(aGUIEvent, relativeTo, eventPoint, flags);
11844 if (!target) {
11845 return nullptr;
11848 nsIContent* overrideClickTarget = target->GetContent();
11849 while (overrideClickTarget && !overrideClickTarget->IsElement()) {
11850 overrideClickTarget = overrideClickTarget->GetFlattenedTreeParent();
11852 return overrideClickTarget;
11855 /******************************************************************************
11856 * PresShell::EventHandler::EventTargetData
11857 ******************************************************************************/
11859 void PresShell::EventHandler::EventTargetData::SetFrameAndComputePresShell(
11860 nsIFrame* aFrameToHandleEvent) {
11861 if (aFrameToHandleEvent) {
11862 mFrame = aFrameToHandleEvent;
11863 mPresShell = aFrameToHandleEvent->PresShell();
11864 } else {
11865 mFrame = nullptr;
11866 mPresShell = nullptr;
11870 void PresShell::EventHandler::EventTargetData::
11871 SetFrameAndComputePresShellAndContent(nsIFrame* aFrameToHandleEvent,
11872 WidgetGUIEvent* aGUIEvent) {
11873 MOZ_ASSERT(aFrameToHandleEvent);
11874 MOZ_ASSERT(aGUIEvent);
11876 SetFrameAndComputePresShell(aFrameToHandleEvent);
11877 SetContentForEventFromFrame(aGUIEvent);
11880 void PresShell::EventHandler::EventTargetData::SetContentForEventFromFrame(
11881 WidgetGUIEvent* aGUIEvent) {
11882 MOZ_ASSERT(mFrame);
11883 mContent = nullptr;
11884 mFrame->GetContentForEvent(aGUIEvent, getter_AddRefs(mContent));
11885 AssertIfEventTargetContentAndFrameContentMismatch(aGUIEvent);
11888 nsIContent* PresShell::EventHandler::EventTargetData::GetFrameContent() const {
11889 return mFrame ? mFrame->GetContent() : nullptr;
11892 void PresShell::EventHandler::EventTargetData::
11893 AssertIfEventTargetContentAndFrameContentMismatch(
11894 const WidgetGUIEvent* aGUIEvent) const {
11895 #ifdef DEBUG
11896 if (!mContent || !mFrame || !mFrame->GetContent()) {
11897 return;
11900 // If we know the event, we can compute the target correctly.
11901 if (aGUIEvent) {
11902 nsCOMPtr<nsIContent> content;
11903 mFrame->GetContentForEvent(aGUIEvent, getter_AddRefs(content));
11904 MOZ_ASSERT(mContent == content);
11905 return;
11908 // Otherwise, we can check only whether mContent is an inclusive ancestor
11909 // element or not.
11910 if (!mContent->IsElement()) {
11911 MOZ_ASSERT(mContent == mFrame->GetContent());
11912 return;
11914 const Element* const closestInclusiveAncestorElement =
11915 [&]() -> const Element* {
11916 for (const nsIContent* const content :
11917 mFrame->GetContent()->InclusiveFlatTreeAncestorsOfType<nsIContent>()) {
11918 if (content->IsElement()) {
11919 return content->AsElement();
11922 return nullptr;
11923 }();
11924 if (closestInclusiveAncestorElement == mContent) {
11925 return;
11927 if (closestInclusiveAncestorElement->IsInNativeAnonymousSubtree() &&
11928 (mContent == closestInclusiveAncestorElement
11929 ->FindFirstNonChromeOnlyAccessContent())) {
11930 return;
11932 NS_WARNING(nsPrintfCString("mContent=%s", ToString(*mContent).c_str()).get());
11933 NS_WARNING(nsPrintfCString("mFrame->GetContent()=%s",
11934 ToString(*mFrame->GetContent()).c_str())
11935 .get());
11936 MOZ_ASSERT(mContent == mFrame->GetContent());
11937 #endif // #ifdef DEBUG
11940 bool PresShell::EventHandler::EventTargetData::MaybeRetargetToActiveDocument(
11941 WidgetGUIEvent* aGUIEvent) {
11942 MOZ_ASSERT(aGUIEvent);
11943 MOZ_ASSERT(mFrame);
11944 MOZ_ASSERT(mPresShell);
11945 MOZ_ASSERT(!mContent, "Doesn't support to retarget the content");
11947 EventStateManager* activeESM =
11948 EventStateManager::GetActiveEventStateManager();
11949 if (!activeESM) {
11950 return false;
11953 if (aGUIEvent->mClass != ePointerEventClass &&
11954 !aGUIEvent->HasMouseEventMessage()) {
11955 return false;
11958 if (activeESM == GetEventStateManager()) {
11959 return false;
11962 nsPresContext* activePresContext = activeESM->GetPresContext();
11963 if (!activePresContext) {
11964 return false;
11967 PresShell* activePresShell = activePresContext->GetPresShell();
11968 if (!activePresShell) {
11969 return false;
11972 // Note, currently for backwards compatibility we don't forward mouse events
11973 // to the active document when mouse is over some subdocument.
11974 if (!nsContentUtils::ContentIsCrossDocDescendantOf(
11975 activePresShell->GetDocument(), GetDocument())) {
11976 return false;
11979 SetFrameAndComputePresShell(activePresShell->GetRootFrame());
11980 return true;
11983 bool PresShell::EventHandler::EventTargetData::ComputeElementFromFrame(
11984 WidgetGUIEvent* aGUIEvent) {
11985 MOZ_ASSERT(aGUIEvent);
11986 MOZ_ASSERT(aGUIEvent->IsUsingCoordinates());
11987 MOZ_ASSERT(mPresShell);
11988 MOZ_ASSERT(mFrame);
11990 SetContentForEventFromFrame(aGUIEvent);
11992 // If there is no content for this frame, target it anyway. Some frames can
11993 // be targeted but do not have content, particularly windows with scrolling
11994 // off.
11995 if (!mContent) {
11996 return true;
11999 // Bug 103055, bug 185889: mouse events apply to *elements*, not all nodes.
12000 // Thus we get the nearest element parent here.
12001 // XXX we leave the frame the same even if we find an element parent, so that
12002 // the text frame will receive the event (selection and friends are the ones
12003 // who care about that anyway)
12005 // We use weak pointers because during this tight loop, the node
12006 // will *not* go away. And this happens on every mousemove.
12007 nsIContent* content = mContent;
12008 while (content && !content->IsElement()) {
12009 content = content->GetFlattenedTreeParent();
12011 mContent = content;
12013 // If we found an element, target it. Otherwise, target *nothing*.
12014 return !!mContent;
12017 void PresShell::EventHandler::EventTargetData::UpdateWheelEventTarget(
12018 WidgetGUIEvent* aGUIEvent) {
12019 MOZ_ASSERT(aGUIEvent);
12021 if (aGUIEvent->mMessage != eWheel) {
12022 return;
12025 // If dom.event.wheel-event-groups.enabled is not set or the stored
12026 // event target is removed, we will not get a event target frame from the
12027 // wheel transaction here.
12028 nsIFrame* groupFrame = WheelTransaction::GetEventTargetFrame();
12029 if (!groupFrame) {
12030 return;
12033 // If the browsing context is no longer the same as the context of the
12034 // current wheel transaction, do not override the event target.
12035 if (!groupFrame->PresContext() || !groupFrame->PresShell() ||
12036 groupFrame->PresContext() != GetPresContext()) {
12037 return;
12040 // If dom.event.wheel-event-groups.enabled is set and whe have a stored
12041 // event target from the wheel transaction, override the event target.
12042 SetFrameAndComputePresShellAndContent(groupFrame, aGUIEvent);
12045 void PresShell::EventHandler::EventTargetData::UpdateTouchEventTarget(
12046 WidgetGUIEvent* aGUIEvent) {
12047 MOZ_ASSERT(aGUIEvent);
12049 if (aGUIEvent->mClass != eTouchEventClass) {
12050 return;
12053 if (aGUIEvent->mMessage == eTouchStart) {
12054 WidgetTouchEvent* touchEvent = aGUIEvent->AsTouchEvent();
12055 nsIFrame* newFrame =
12056 TouchManager::SuppressInvalidPointsAndGetTargetedFrame(touchEvent);
12057 if (!newFrame) {
12058 return;
12060 SetFrameAndComputePresShellAndContent(newFrame, aGUIEvent);
12061 return;
12064 PresShell* newPresShell = PresShell::GetShellForTouchEvent(aGUIEvent);
12065 if (!newPresShell) {
12066 return; // XXX Why don't we stop handling the event in this case?
12069 // Touch events (except touchstart) are dispatching to the captured
12070 // element. Get correct shell from it.
12071 mPresShell = newPresShell;
12074 /******************************************************************************
12075 * PresShell::EventHandler::HandlingTimeAccumulator
12076 ******************************************************************************/
12078 PresShell::EventHandler::HandlingTimeAccumulator::HandlingTimeAccumulator(
12079 const PresShell::EventHandler& aEventHandler, const WidgetEvent* aEvent)
12080 : mEventHandler(aEventHandler),
12081 mEvent(aEvent),
12082 mHandlingStartTime(TimeStamp::Now()) {
12083 MOZ_ASSERT(mEvent);
12084 MOZ_ASSERT(mEvent->IsTrusted());
12087 PresShell::EventHandler::HandlingTimeAccumulator::~HandlingTimeAccumulator() {
12088 if (mEvent->mTimeStamp <= mEventHandler.mPresShell->mLastOSWake) {
12089 return;
12092 switch (mEvent->mMessage) {
12093 case eKeyPress:
12094 case eKeyDown:
12095 case eKeyUp:
12096 Telemetry::AccumulateTimeDelta(Telemetry::INPUT_EVENT_HANDLED_KEYBOARD_MS,
12097 mHandlingStartTime);
12098 return;
12099 case eMouseDown:
12100 Telemetry::AccumulateTimeDelta(
12101 Telemetry::INPUT_EVENT_HANDLED_MOUSE_DOWN_MS, mHandlingStartTime);
12102 return;
12103 case eMouseUp:
12104 Telemetry::AccumulateTimeDelta(Telemetry::INPUT_EVENT_HANDLED_MOUSE_UP_MS,
12105 mHandlingStartTime);
12106 return;
12107 case eMouseMove:
12108 if (mEvent->mFlags.mHandledByAPZ) {
12109 Telemetry::AccumulateTimeDelta(
12110 Telemetry::INPUT_EVENT_HANDLED_APZ_MOUSE_MOVE_MS,
12111 mHandlingStartTime);
12113 return;
12114 case eWheel:
12115 if (mEvent->mFlags.mHandledByAPZ) {
12116 Telemetry::AccumulateTimeDelta(
12117 Telemetry::INPUT_EVENT_HANDLED_APZ_WHEEL_MS, mHandlingStartTime);
12119 return;
12120 case eTouchMove:
12121 if (mEvent->mFlags.mHandledByAPZ) {
12122 Telemetry::AccumulateTimeDelta(
12123 Telemetry::INPUT_EVENT_HANDLED_APZ_TOUCH_MOVE_MS,
12124 mHandlingStartTime);
12126 return;
12127 default:
12128 return;
12132 void PresShell::EndPaint() {
12133 ClearPendingVisualScrollUpdate();
12135 if (mDocument) {
12136 mDocument->EnumerateSubDocuments([](Document& aSubDoc) {
12137 if (PresShell* presShell = aSubDoc.GetPresShell()) {
12138 presShell->EndPaint();
12140 return CallState::Continue;
12143 if (nsPresContext* presContext = GetPresContext()) {
12144 if (PerformanceMainThread* perf =
12145 presContext->GetPerformanceMainThread()) {
12146 perf->FinalizeLCPEntriesForText();
12152 void PresShell::PingPerTickTelemetry(FlushType aFlushType) {
12153 mLayoutTelemetry.PingPerTickTelemetry(aFlushType);
12156 bool PresShell::GetZoomableByAPZ() const {
12157 return mZoomConstraintsClient && mZoomConstraintsClient->GetAllowZoom();
12160 bool PresShell::ReflowForHiddenContentIfNeeded() {
12161 if (mHiddenContentInForcedLayout.IsEmpty()) {
12162 return false;
12164 mDocument->FlushPendingNotifications(FlushType::Layout);
12165 mHiddenContentInForcedLayout.Clear();
12166 return true;
12169 void PresShell::UpdateHiddenContentInForcedLayout(nsIFrame* aFrame) {
12170 if (!aFrame || !aFrame->IsSubtreeDirty() ||
12171 !StaticPrefs::layout_css_content_visibility_enabled()) {
12172 return;
12175 nsIFrame* topmostFrameWithContentHidden = nullptr;
12176 for (nsIFrame* cur = aFrame->GetInFlowParent(); cur;
12177 cur = cur->GetInFlowParent()) {
12178 if (cur->HidesContent()) {
12179 topmostFrameWithContentHidden = cur;
12180 mHiddenContentInForcedLayout.Insert(cur->GetContent());
12184 if (mHiddenContentInForcedLayout.IsEmpty()) {
12185 return;
12188 // Queue and immediately flush a reflow for this node.
12189 MOZ_ASSERT(topmostFrameWithContentHidden);
12190 FrameNeedsReflow(topmostFrameWithContentHidden, IntrinsicDirty::None,
12191 NS_FRAME_IS_DIRTY);
12194 void PresShell::EnsureReflowIfFrameHasHiddenContent(nsIFrame* aFrame) {
12195 MOZ_ASSERT(mHiddenContentInForcedLayout.IsEmpty());
12197 UpdateHiddenContentInForcedLayout(aFrame);
12198 ReflowForHiddenContentIfNeeded();
12201 bool PresShell::IsForcingLayoutForHiddenContent(const nsIFrame* aFrame) const {
12202 return mHiddenContentInForcedLayout.Contains(aFrame->GetContent());
12205 void PresShell::UpdateRelevancyOfContentVisibilityAutoFrames() {
12206 if (mContentVisibilityRelevancyToUpdate.isEmpty()) {
12207 return;
12210 for (nsIFrame* frame : mContentVisibilityAutoFrames) {
12211 frame->UpdateIsRelevantContent(mContentVisibilityRelevancyToUpdate);
12214 if (nsPresContext* presContext = GetPresContext()) {
12215 presContext->UpdateHiddenByContentVisibilityForAnimationsIfNeeded();
12218 mContentVisibilityRelevancyToUpdate.clear();
12221 void PresShell::ScheduleContentRelevancyUpdate(ContentRelevancyReason aReason) {
12222 if (MOZ_UNLIKELY(mIsDestroying)) {
12223 return;
12226 mContentVisibilityRelevancyToUpdate += aReason;
12228 SetNeedLayoutFlush();
12229 if (nsPresContext* presContext = GetPresContext()) {
12230 presContext->RefreshDriver()->EnsureContentRelevancyUpdateHappens();
12234 PresShell::ProximityToViewportResult PresShell::DetermineProximityToViewport() {
12235 ProximityToViewportResult result;
12236 if (mContentVisibilityAutoFrames.IsEmpty()) {
12237 return result;
12240 auto margin = LengthPercentage::FromPercentage(
12241 StaticPrefs::layout_css_content_visibility_relevant_content_margin() /
12242 100.0f);
12244 auto rootMargin = StyleRect<LengthPercentage>::WithAllSides(margin);
12246 auto input = DOMIntersectionObserver::ComputeInput(
12247 *mDocument, /* aRoot = */ nullptr, &rootMargin);
12249 for (nsIFrame* frame : mContentVisibilityAutoFrames) {
12250 auto* element = frame->GetContent()->AsElement();
12251 result.mAnyScrollIntoViewFlag |=
12252 element->TemporarilyVisibleForScrolledIntoViewDescendant();
12254 // 14.2.3.1
12255 Maybe<bool> oldVisibility = element->GetVisibleForContentVisibility();
12256 bool checkForInitialDetermination =
12257 oldVisibility.isNothing() &&
12258 (element->GetContentRelevancy().isNothing() ||
12259 element->GetContentRelevancy()->isEmpty());
12261 // 14.2.3.2
12262 bool intersects =
12263 DOMIntersectionObserver::Intersect(
12264 input, *element,
12265 DOMIntersectionObserver::IsForProximityToViewport::Yes)
12266 .Intersects();
12267 element->SetVisibleForContentVisibility(intersects);
12268 if (oldVisibility.isNothing() || *oldVisibility != intersects) {
12269 frame->UpdateIsRelevantContent(ContentRelevancyReason::Visible);
12272 // 14.2.3.3
12273 if (checkForInitialDetermination && intersects) {
12274 result.mHadInitialDetermination = true;
12277 if (nsPresContext* presContext = GetPresContext()) {
12278 presContext->UpdateHiddenByContentVisibilityForAnimationsIfNeeded();
12281 return result;
12284 void PresShell::ClearTemporarilyVisibleForScrolledIntoViewDescendantFlags()
12285 const {
12286 for (nsIFrame* frame : mContentVisibilityAutoFrames) {
12287 frame->GetContent()
12288 ->AsElement()
12289 ->SetTemporarilyVisibleForScrolledIntoViewDescendant(false);
12293 void PresShell::UpdateContentRelevancyImmediately(
12294 ContentRelevancyReason aReason) {
12295 if (MOZ_UNLIKELY(mIsDestroying)) {
12296 return;
12299 mContentVisibilityRelevancyToUpdate += aReason;
12301 SetNeedLayoutFlush();
12302 UpdateRelevancyOfContentVisibilityAutoFrames();