PresShell.cpp (475539B)
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/. */ 6 7 /* a presentation of a document, part 2 */ 8 9 #include "PresShell.h" 10 11 #include <algorithm> 12 13 #include "AnchorPositioningUtils.h" 14 #include "AutoProfilerStyleMarker.h" 15 #include "ChildIterator.h" 16 #include "MobileViewportManager.h" 17 #include "OverflowChangedTracker.h" 18 #include "PLDHashTable.h" 19 #include "PositionedEventTargeting.h" 20 #include "PresShellWidgetListener.h" 21 #include "ScrollSnap.h" 22 #include "StickyScrollContainer.h" 23 #include "Units.h" 24 #include "VisualViewport.h" 25 #include "XULTreeElement.h" 26 #include "ZoomConstraintsClient.h" 27 #include "gfxContext.h" 28 #include "gfxPlatform.h" 29 #include "gfxUserFontSet.h" 30 #include "gfxUtils.h" 31 #include "js/GCAPI.h" 32 #include "mozilla/AccessibleCaretEventHub.h" 33 #include "mozilla/AnimationEventDispatcher.h" 34 #include "mozilla/Assertions.h" 35 #include "mozilla/Attributes.h" 36 #include "mozilla/CaretAssociationHint.h" 37 #include "mozilla/ConnectedAncestorTracker.h" 38 #include "mozilla/ContentIterator.h" 39 #include "mozilla/DisplayPortUtils.h" 40 #include "mozilla/EditorBase.h" 41 #include "mozilla/ErrorResult.h" 42 #include "mozilla/EventDispatcher.h" 43 #include "mozilla/EventForwards.h" 44 #include "mozilla/EventStateManager.h" 45 #include "mozilla/GeckoMVMContext.h" 46 #include "mozilla/GlobalStyleSheetCache.h" 47 #include "mozilla/IMEStateManager.h" 48 #include "mozilla/InputTaskManager.h" 49 #include "mozilla/IntegerRange.h" 50 #include "mozilla/Likely.h" 51 #include "mozilla/Logging.h" 52 #include "mozilla/MemoryReporting.h" 53 #include "mozilla/MouseEvents.h" 54 #include "mozilla/PerfStats.h" 55 #include "mozilla/PointerLockManager.h" 56 #include "mozilla/Preferences.h" 57 #include "mozilla/PresShellInlines.h" 58 #include "mozilla/ProfilerLabels.h" 59 #include "mozilla/ProfilerMarkers.h" 60 #include "mozilla/RangeUtils.h" 61 #include "mozilla/RefPtr.h" 62 #include "mozilla/RestyleManager.h" 63 #include "mozilla/SMILAnimationController.h" 64 #include "mozilla/SVGFragmentIdentifier.h" 65 #include "mozilla/SVGObserverUtils.h" 66 #include "mozilla/ScopeExit.h" 67 #include "mozilla/ScrollContainerFrame.h" 68 #include "mozilla/ScrollTimelineAnimationTracker.h" 69 #include "mozilla/ScrollTypes.h" 70 #include "mozilla/ServoBindings.h" 71 #include "mozilla/ServoStyleSet.h" 72 #include "mozilla/Sprintf.h" 73 #include "mozilla/StartupTimeline.h" 74 #include "mozilla/StaticAnalysisFunctions.h" 75 #include "mozilla/StaticPrefs_apz.h" 76 #include "mozilla/StaticPrefs_dom.h" 77 #include "mozilla/StaticPrefs_font.h" 78 #include "mozilla/StaticPrefs_image.h" 79 #include "mozilla/StaticPrefs_layout.h" 80 #include "mozilla/StaticPrefs_test.h" 81 #include "mozilla/StaticPrefs_toolkit.h" 82 #include "mozilla/StyleSheet.h" 83 #include "mozilla/StyleSheetInlines.h" 84 #include "mozilla/Telemetry.h" 85 #include "mozilla/TextComposition.h" 86 #include "mozilla/TextEvents.h" 87 #include "mozilla/TimeStamp.h" 88 #include "mozilla/TouchEvents.h" 89 #include "mozilla/Try.h" 90 #include "mozilla/UniquePtr.h" 91 #include "mozilla/ViewportFrame.h" 92 #include "mozilla/ViewportUtils.h" 93 #include "mozilla/css/ImageLoader.h" 94 #include "mozilla/dom/AncestorIterator.h" 95 #include "mozilla/dom/AnimationTimelinesController.h" 96 #include "mozilla/dom/BrowserBridgeChild.h" 97 #include "mozilla/dom/BrowserChild.h" 98 #include "mozilla/dom/BrowsingContext.h" 99 #include "mozilla/dom/CanonicalBrowsingContext.h" 100 #include "mozilla/dom/ContentChild.h" 101 #include "mozilla/dom/ContentParent.h" 102 #include "mozilla/dom/DOMIntersectionObserver.h" 103 #include "mozilla/dom/Document.h" 104 #include "mozilla/dom/DocumentInlines.h" 105 #include "mozilla/dom/DocumentTimeline.h" 106 #include "mozilla/dom/Element.h" 107 #include "mozilla/dom/ElementBinding.h" 108 #include "mozilla/dom/ElementInlines.h" 109 #include "mozilla/dom/FontFaceSet.h" 110 #include "mozilla/dom/FragmentDirective.h" 111 #include "mozilla/dom/HTMLAreaElement.h" 112 #include "mozilla/dom/LargestContentfulPaint.h" 113 #include "mozilla/dom/MouseEventBinding.h" 114 #include "mozilla/dom/Performance.h" 115 #include "mozilla/dom/PerformanceMainThread.h" 116 #include "mozilla/dom/PointerEventBinding.h" 117 #include "mozilla/dom/PointerEventHandler.h" 118 #include "mozilla/dom/PopupBlocker.h" 119 #include "mozilla/dom/SVGAnimationElement.h" 120 #include "mozilla/dom/ScriptSettings.h" 121 #include "mozilla/dom/Selection.h" 122 #include "mozilla/dom/ShadowIncludingTreeIterator.h" 123 #include "mozilla/dom/Touch.h" 124 #include "mozilla/dom/TouchEvent.h" 125 #include "mozilla/dom/UserActivation.h" 126 #include "mozilla/gfx/2D.h" 127 #include "mozilla/gfx/Types.h" 128 #include "mozilla/glean/GfxMetrics.h" 129 #include "mozilla/glean/LayoutMetrics.h" 130 #include "mozilla/glue/Debug.h" 131 #include "mozilla/layers/APZPublicUtils.h" 132 #include "mozilla/layers/CompositorBridgeChild.h" 133 #include "mozilla/layers/FocusTarget.h" 134 #include "mozilla/layers/InputAPZContext.h" 135 #include "mozilla/layers/ScrollingInteractionContext.h" 136 #include "mozilla/layers/WebRenderLayerManager.h" 137 #include "mozilla/layers/WebRenderUserData.h" 138 #include "mozilla/layout/ScrollAnchorContainer.h" 139 #include "nsAnimationManager.h" 140 #include "nsAutoLayoutPhase.h" 141 #include "nsCOMArray.h" 142 #include "nsCOMPtr.h" 143 #include "nsCRTGlue.h" 144 #include "nsCSSFrameConstructor.h" 145 #include "nsCSSRendering.h" 146 #include "nsCanvasFrame.h" 147 #include "nsCaret.h" 148 #include "nsClassHashtable.h" 149 #include "nsContainerFrame.h" 150 #include "nsContentList.h" 151 #include "nsDOMNavigationTiming.h" 152 #include "nsDisplayList.h" 153 #include "nsDocShell.h" // for reflow observation 154 #include "nsError.h" 155 #include "nsFlexContainerFrame.h" 156 #include "nsFocusManager.h" 157 #include "nsFrameSelection.h" 158 #include "nsGkAtoms.h" 159 #include "nsGlobalWindowOuter.h" 160 #include "nsHashKeys.h" 161 #include "nsIBaseWindow.h" 162 #include "nsIContent.h" 163 #include "nsIDOMXULMenuListElement.h" 164 #include "nsIDOMXULMultSelectCntrlEl.h" 165 #include "nsIDOMXULSelectCntrlItemEl.h" 166 #include "nsIDocShellTreeItem.h" 167 #include "nsIDocShellTreeOwner.h" 168 #include "nsIDragSession.h" 169 #include "nsIFrame.h" 170 #include "nsIFrameInlines.h" 171 #include "nsILayoutHistoryState.h" 172 #include "nsILineIterator.h" // for ScrollContentIntoView 173 #include "nsIMutationObserver.h" 174 #include "nsIObserverService.h" 175 #include "nsIReflowCallback.h" 176 #include "nsIScreen.h" 177 #include "nsIScreenManager.h" 178 #include "nsITimer.h" 179 #include "nsIURI.h" 180 #include "nsImageFrame.h" 181 #include "nsLayoutUtils.h" 182 #include "nsMenuPopupFrame.h" 183 #include "nsNameSpaceManager.h" // for Pref-related rule management (bugs 22963,20760,31816) 184 #include "nsNetUtil.h" 185 #include "nsPIDOMWindow.h" 186 #include "nsPageSequenceFrame.h" 187 #include "nsPlaceholderFrame.h" 188 #include "nsPresContext.h" 189 #include "nsQueryObject.h" 190 #include "nsRange.h" 191 #include "nsReadableUtils.h" 192 #include "nsRefreshDriver.h" 193 #include "nsRegion.h" 194 #include "nsStyleChangeList.h" 195 #include "nsStyleSheetService.h" 196 #include "nsSubDocumentFrame.h" 197 #include "nsTArray.h" 198 #include "nsThreadUtils.h" 199 #include "nsTransitionManager.h" 200 #include "nsTreeBodyFrame.h" 201 #include "nsTreeColumns.h" 202 #include "nsViewportInfo.h" 203 #include "nsWindowSizes.h" 204 #include "nsXPCOM.h" 205 #include "nsXULElement.h" 206 #include "prenv.h" 207 #include "prinrval.h" 208 209 #ifdef XP_WIN 210 # include "winuser.h" 211 #endif 212 213 #ifdef MOZ_REFLOW_PERF 214 # include "nsFontMetrics.h" 215 #endif 216 217 #ifdef ACCESSIBILITY 218 # include "mozilla/a11y/DocAccessible.h" 219 # ifdef DEBUG 220 # include "mozilla/a11y/Logging.h" 221 # endif 222 #endif 223 224 // define the scalfactor of drag and drop images 225 // relative to the max screen height/width 226 #define RELATIVE_SCALEFACTOR 0.0925f 227 228 using namespace mozilla; 229 using namespace mozilla::css; 230 using namespace mozilla::dom; 231 using namespace mozilla::gfx; 232 using namespace mozilla::layers; 233 using namespace mozilla::gfx; 234 using namespace mozilla::layout; 235 using PaintFrameFlags = nsLayoutUtils::PaintFrameFlags; 236 typedef ScrollableLayerGuid::ViewID ViewID; 237 238 constinit PresShell::CapturingContentInfo PresShell::sCapturingContentInfo; 239 240 // RangePaintInfo is used to paint ranges to offscreen buffers 241 struct RangePaintInfo { 242 nsDisplayListBuilder mBuilder; 243 nsDisplayList mList; 244 245 // offset of builder's reference frame to the root frame 246 nsPoint mRootOffset; 247 248 // Resolution at which the items are normally painted. So if we're painting 249 // these items in a range separately from the "full display list", we may want 250 // to paint them at this resolution. 251 float mResolution = 1.0; 252 253 explicit RangePaintInfo(nsIFrame* aFrame) 254 : mBuilder(aFrame, nsDisplayListBuilderMode::Painting, false), 255 mList(&mBuilder) { 256 MOZ_COUNT_CTOR(RangePaintInfo); 257 mBuilder.BeginFrame(); 258 } 259 260 ~RangePaintInfo() { 261 mList.DeleteAll(&mBuilder); 262 mBuilder.EndFrame(); 263 MOZ_COUNT_DTOR(RangePaintInfo); 264 } 265 }; 266 267 #undef NOISY 268 269 #ifdef MOZ_REFLOW_PERF 270 class ReflowCountMgr; 271 272 static const char kGrandTotalsStr[] = "Grand Totals"; 273 274 // Counting Class 275 class ReflowCounter { 276 public: 277 explicit ReflowCounter(ReflowCountMgr* aMgr = nullptr); 278 ~ReflowCounter(); 279 280 void ClearTotals(); 281 void DisplayTotals(const char* aStr); 282 void DisplayDiffTotals(const char* aStr); 283 void DisplayHTMLTotals(const char* aStr); 284 285 void Add() { mTotal++; } 286 void Add(uint32_t aTotal) { mTotal += aTotal; } 287 288 void CalcDiffInTotals(); 289 void SetTotalsCache(); 290 291 void SetMgr(ReflowCountMgr* aMgr) { mMgr = aMgr; } 292 293 uint32_t GetTotal() { return mTotal; } 294 295 protected: 296 void DisplayTotals(uint32_t aTotal, const char* aTitle); 297 void DisplayHTMLTotals(uint32_t aTotal, const char* aTitle); 298 299 uint32_t mTotal; 300 uint32_t mCacheTotal; 301 302 ReflowCountMgr* mMgr; // weak reference (don't delete) 303 }; 304 305 // Counting Class 306 class IndiReflowCounter { 307 public: 308 explicit IndiReflowCounter(ReflowCountMgr* aMgr = nullptr) 309 : mFrame(nullptr), 310 mCount(0), 311 mMgr(aMgr), 312 mCounter(aMgr), 313 mHasBeenOutput(false) {} 314 virtual ~IndiReflowCounter() = default; 315 316 nsAutoString mName; 317 nsIFrame* mFrame; // weak reference (don't delete) 318 int32_t mCount; 319 320 ReflowCountMgr* mMgr; // weak reference (don't delete) 321 322 ReflowCounter mCounter; 323 bool mHasBeenOutput; 324 }; 325 326 //-------------------- 327 // Manager Class 328 //-------------------- 329 class ReflowCountMgr { 330 public: 331 ReflowCountMgr(); 332 virtual ~ReflowCountMgr(); 333 334 void ClearTotals(); 335 void ClearGrandTotals(); 336 void DisplayTotals(const char* aStr); 337 void DisplayHTMLTotals(const char* aStr); 338 void DisplayDiffsInTotals(); 339 340 void Add(const char* aName, nsIFrame* aFrame); 341 ReflowCounter* LookUp(const char* aName); 342 343 void PaintCount(const char* aName, gfxContext* aRenderingContext, 344 nsPresContext* aPresContext, nsIFrame* aFrame, 345 const nsPoint& aOffset, uint32_t aColor); 346 347 FILE* GetOutFile() { return mFD; } 348 349 void SetPresContext(nsPresContext* aPresContext) { 350 mPresContext = aPresContext; // weak reference 351 } 352 void SetPresShell(PresShell* aPresShell) { 353 mPresShell = aPresShell; // weak reference 354 } 355 356 void SetDumpFrameCounts(bool aVal) { mDumpFrameCounts = aVal; } 357 void SetDumpFrameByFrameCounts(bool aVal) { mDumpFrameByFrameCounts = aVal; } 358 void SetPaintFrameCounts(bool aVal) { mPaintFrameByFrameCounts = aVal; } 359 360 bool IsPaintingFrameCounts() { return mPaintFrameByFrameCounts; } 361 362 protected: 363 void DisplayTotals(uint32_t aTotal, uint32_t* aDupArray, char* aTitle); 364 void DisplayHTMLTotals(uint32_t aTotal, uint32_t* aDupArray, char* aTitle); 365 366 void DoGrandTotals(); 367 void DoIndiTotalsTree(); 368 369 // HTML Output Methods 370 void DoGrandHTMLTotals(); 371 372 nsClassHashtable<nsCharPtrHashKey, ReflowCounter> mCounts; 373 nsClassHashtable<nsCharPtrHashKey, IndiReflowCounter> mIndiFrameCounts; 374 FILE* mFD; 375 376 bool mDumpFrameCounts; 377 bool mDumpFrameByFrameCounts; 378 bool mPaintFrameByFrameCounts; 379 380 bool mCycledOnce; 381 382 // Root Frame for Individual Tracking 383 nsPresContext* mPresContext; 384 PresShell* mPresShell; 385 386 // ReflowCountMgr gReflowCountMgr; 387 }; 388 #endif 389 //======================================================================== 390 391 // comment out to hide caret 392 #define SHOW_CARET 393 394 // The upper bound on the amount of time to spend reflowing, in 395 // microseconds. When this bound is exceeded and reflow commands are 396 // still queued up, a reflow event is posted. The idea is for reflow 397 // to not hog the processor beyond the time specifed in 398 // gMaxRCProcessingTime. This data member is initialized from the 399 // layout.reflow.timeslice pref. 400 #define NS_MAX_REFLOW_TIME 1000000 401 static int32_t gMaxRCProcessingTime = -1; 402 403 struct nsCallbackEventRequest { 404 nsIReflowCallback* callback; 405 nsCallbackEventRequest* next; 406 }; 407 408 // ---------------------------------------------------------------------------- 409 410 class nsAutoCauseReflowNotifier { 411 public: 412 MOZ_CAN_RUN_SCRIPT explicit nsAutoCauseReflowNotifier(PresShell* aPresShell) 413 : mPresShell(aPresShell) { 414 mPresShell->WillCauseReflow(); 415 } 416 MOZ_CAN_RUN_SCRIPT ~nsAutoCauseReflowNotifier() { 417 // This check should not be needed. Currently the only place that seem 418 // to need it is the code that deals with bug 337586. 419 if (!mPresShell->mHaveShutDown) { 420 RefPtr<PresShell> presShell(mPresShell); 421 presShell->DidCauseReflow(); 422 } else { 423 nsContentUtils::RemoveScriptBlocker(); 424 } 425 } 426 427 PresShell* mPresShell; 428 }; 429 430 class MOZ_STACK_CLASS nsPresShellEventCB : public EventDispatchingCallback { 431 public: 432 explicit nsPresShellEventCB(PresShell* aPresShell) : mPresShell(aPresShell) {} 433 434 MOZ_CAN_RUN_SCRIPT 435 virtual void HandleEvent(EventChainPostVisitor& aVisitor) override { 436 if (aVisitor.mPresContext && aVisitor.mEvent->mClass != eBasicEventClass) { 437 if (aVisitor.mEvent->mMessage == eMouseDown || 438 aVisitor.mEvent->mMessage == eMouseUp) { 439 // Mouse-up and mouse-down events call nsIFrame::HandlePress/Release 440 // which call GetContentOffsetsFromPoint which requires up-to-date 441 // layout. Bring layout up-to-date now so that GetCurrentEventFrame() 442 // below will return a real frame and we don't have to worry about 443 // destroying it by flushing later. 444 MOZ_KnownLive(mPresShell)->FlushPendingNotifications(FlushType::Layout); 445 } else if (aVisitor.mEvent->mMessage == eWheel && 446 aVisitor.mEventStatus != nsEventStatus_eConsumeNoDefault) { 447 nsIFrame* frame = mPresShell->GetCurrentEventFrame(); 448 if (frame) { 449 // chrome (including addons) should be able to know if content 450 // handles both D3E "wheel" event and legacy mouse scroll events. 451 // We should dispatch legacy mouse events before dispatching the 452 // "wheel" event into system group. 453 RefPtr<EventStateManager> esm = 454 aVisitor.mPresContext->EventStateManager(); 455 esm->DispatchLegacyMouseScrollEvents( 456 frame, aVisitor.mEvent->AsWheelEvent(), &aVisitor.mEventStatus); 457 } 458 } 459 nsIFrame* frame = mPresShell->GetCurrentEventFrame(); 460 if (!frame && (aVisitor.mEvent->mMessage == eMouseUp || 461 aVisitor.mEvent->mMessage == eTouchEnd)) { 462 // Redirect BUTTON_UP and TOUCH_END events to the root frame to ensure 463 // that capturing is released. 464 frame = mPresShell->GetRootFrame(); 465 } 466 if (frame) { 467 frame->HandleEvent(aVisitor.mPresContext, aVisitor.mEvent->AsGUIEvent(), 468 &aVisitor.mEventStatus); 469 } 470 } 471 } 472 473 RefPtr<PresShell> mPresShell; 474 }; 475 476 class nsBeforeFirstPaintDispatcher : public Runnable { 477 public: 478 explicit nsBeforeFirstPaintDispatcher(Document* aDocument) 479 : mozilla::Runnable("nsBeforeFirstPaintDispatcher"), 480 mDocument(aDocument) {} 481 482 // Fires the "before-first-paint" event so that interested parties (right now, 483 // the mobile browser) are aware of it. 484 NS_IMETHOD Run() override { 485 nsCOMPtr<nsIObserverService> observerService = 486 mozilla::services::GetObserverService(); 487 if (observerService) { 488 observerService->NotifyObservers(ToSupports(mDocument), 489 "before-first-paint", nullptr); 490 } 491 return NS_OK; 492 } 493 494 private: 495 RefPtr<Document> mDocument; 496 }; 497 498 // This is a helper class to track whether the targeted frame is destroyed after 499 // dispatching pointer events. In that case, we need the original targeted 500 // content so that we can dispatch the mouse events to it. 501 class MOZ_RAII AutoPointerEventTargetUpdater final { 502 public: 503 AutoPointerEventTargetUpdater(PresShell* aShell, WidgetEvent* aEvent, 504 nsIFrame* aFrame, nsIContent* aTargetContent, 505 nsIContent** aOutTargetContent) { 506 MOZ_ASSERT(aEvent); 507 if (!aOutTargetContent || aEvent->mClass != ePointerEventClass) { 508 // Make the destructor happy. 509 mOutTargetContent = nullptr; 510 return; 511 } 512 MOZ_ASSERT(aShell); 513 MOZ_ASSERT_IF(aFrame && aFrame->GetContent(), 514 aShell->GetDocument() == aFrame->GetContent()->OwnerDoc()); 515 516 mShell = aShell; 517 mWeakFrame = aFrame; 518 mOutTargetContent = aOutTargetContent; 519 mFromTouch = aEvent->AsPointerEvent()->mFromTouchEvent; 520 // Touch event target may have no frame, e.g., removed from the DOM 521 MOZ_ASSERT_IF(!mFromTouch, aFrame); 522 // The frame may be a text frame, but the event target should be an element 523 // node. Therefore, refer aTargetContent first, then, if we have only a 524 // frame, we should use inclusive ancestor of the content. 525 mOriginalPointerEventTarget = [&]() -> nsIContent* { 526 nsIContent* const target = 527 aTargetContent ? aTargetContent 528 : (aFrame ? aFrame->GetContent() : nullptr); 529 if (MOZ_UNLIKELY(!target)) { 530 return nullptr; 531 } 532 if (target->IsElement() || 533 !IsForbiddenDispatchingToNonElementContent(aEvent->mMessage)) { 534 return target; 535 } 536 return target->GetInclusiveFlattenedTreeAncestorElement(); 537 }(); 538 if (mOriginalPointerEventTarget && 539 mOriginalPointerEventTarget->IsInComposedDoc()) { 540 mPointerEventTargetTracker.emplace(*mOriginalPointerEventTarget); 541 } 542 } 543 544 ~AutoPointerEventTargetUpdater() { 545 if (!mOutTargetContent || !mShell || mWeakFrame.IsAlive()) { 546 return; 547 } 548 if (mFromTouch) { 549 // If the source event is a touch event, the touch event target should 550 // always be same target as preceding ePointerDown. Therefore, we should 551 // always set it back to the original event target. 552 mOriginalPointerEventTarget.swap(*mOutTargetContent); 553 } else { 554 // If the source event is not a touch event (must be a mouse event in 555 // this case), the event should be fired on the closest inclusive ancestor 556 // of the pointer event target which is still connected. The mutations 557 // are tracked by PresShell::ContentRemoved. Therefore, we should set it. 558 if (!mPointerEventTargetTracker || 559 !mPointerEventTargetTracker->ContentWasRemoved()) { 560 mOriginalPointerEventTarget.swap(*mOutTargetContent); 561 } else { 562 nsCOMPtr<nsIContent> connectedAncestor = 563 mPointerEventTargetTracker->GetConnectedContent(); 564 connectedAncestor.swap(*mOutTargetContent); 565 } 566 } 567 } 568 569 private: 570 RefPtr<PresShell> mShell; 571 nsCOMPtr<nsIContent> mOriginalPointerEventTarget; 572 AutoWeakFrame mWeakFrame; 573 Maybe<AutoConnectedAncestorTracker> mPointerEventTargetTracker; 574 nsIContent** mOutTargetContent; 575 bool mFromTouch = false; 576 }; 577 578 bool PresShell::sDisableNonTestMouseEvents = false; 579 580 LazyLogModule PresShell::gLog("PresShell"); 581 582 TimeStamp PresShell::EventHandler::sLastInputCreated; 583 TimeStamp PresShell::EventHandler::sLastInputProcessed; 584 StaticRefPtr<Element> PresShell::EventHandler::sLastKeyDownEventTargetElement; 585 586 bool PresShell::sProcessInteractable = false; 587 588 Modifiers PresShell::sCurrentModifiers = MODIFIER_NONE; 589 590 void PresShell::AddAutoWeakFrame(AutoWeakFrame* aWeakFrame) { 591 if (aWeakFrame->GetFrame()) { 592 aWeakFrame->GetFrame()->AddStateBits(NS_FRAME_EXTERNAL_REFERENCE); 593 } 594 aWeakFrame->SetPreviousWeakFrame(mAutoWeakFrames); 595 mAutoWeakFrames = aWeakFrame; 596 } 597 598 void PresShell::AddWeakFrame(WeakFrame* aWeakFrame) { 599 if (aWeakFrame->GetFrame()) { 600 aWeakFrame->GetFrame()->AddStateBits(NS_FRAME_EXTERNAL_REFERENCE); 601 } 602 MOZ_ASSERT(!mWeakFrames.Contains(aWeakFrame)); 603 mWeakFrames.Insert(aWeakFrame); 604 } 605 606 void PresShell::AddConnectedAncestorTracker( 607 AutoConnectedAncestorTracker& aTracker) { 608 aTracker.mPreviousTracker = mLastConnectedAncestorTracker; 609 mLastConnectedAncestorTracker = &aTracker; 610 } 611 612 void PresShell::RemoveAutoWeakFrame(AutoWeakFrame* aWeakFrame) { 613 if (mAutoWeakFrames == aWeakFrame) { 614 mAutoWeakFrames = aWeakFrame->GetPreviousWeakFrame(); 615 return; 616 } 617 AutoWeakFrame* nextWeak = mAutoWeakFrames; 618 while (nextWeak && nextWeak->GetPreviousWeakFrame() != aWeakFrame) { 619 nextWeak = nextWeak->GetPreviousWeakFrame(); 620 } 621 if (nextWeak) { 622 nextWeak->SetPreviousWeakFrame(aWeakFrame->GetPreviousWeakFrame()); 623 } 624 } 625 626 void PresShell::RemoveWeakFrame(WeakFrame* aWeakFrame) { 627 MOZ_ASSERT(mWeakFrames.Contains(aWeakFrame)); 628 mWeakFrames.Remove(aWeakFrame); 629 } 630 631 void PresShell::RemoveConnectedAncestorTracker( 632 const AutoConnectedAncestorTracker& aTracker) { 633 if (mLastConnectedAncestorTracker == &aTracker) { 634 mLastConnectedAncestorTracker = aTracker.mPreviousTracker; 635 return; 636 } 637 AutoConnectedAncestorTracker* nextTracker = mLastConnectedAncestorTracker; 638 while (nextTracker && nextTracker->mPreviousTracker != &aTracker) { 639 nextTracker = nextTracker->mPreviousTracker; 640 } 641 if (nextTracker) { 642 nextTracker->mPreviousTracker = aTracker.mPreviousTracker; 643 } 644 } 645 646 already_AddRefed<nsFrameSelection> PresShell::FrameSelection() { 647 RefPtr<nsFrameSelection> ret = mSelection; 648 return ret.forget(); 649 } 650 651 //---------------------------------------------------------------------- 652 653 static uint32_t sNextPresShellId = 0; 654 655 /* static */ 656 bool PresShell::AccessibleCaretEnabled(nsIDocShell* aDocShell) { 657 // If the pref forces it on, then enable it. 658 if (StaticPrefs::layout_accessiblecaret_enabled()) { 659 return true; 660 } 661 // If the touch pref is on, and touch events are enabled (this depends 662 // on the specific device running), then enable it. 663 if (StaticPrefs::layout_accessiblecaret_enabled_on_touch() && 664 dom::TouchEvent::PrefEnabled(aDocShell)) { 665 return true; 666 } 667 // Otherwise, disabled. 668 return false; 669 } 670 671 PresShell::PresShell(Document* aDocument) 672 : mDocument(aDocument), 673 mLastSelectionForToString(nullptr), 674 #ifdef ACCESSIBILITY 675 mDocAccessible(nullptr), 676 #endif // ACCESSIBILITY 677 mLastResolutionChangeOrigin(ResolutionChangeOrigin::Apz), 678 mPaintCount(0), 679 mAPZFocusSequenceNumber(0), 680 mActiveSuppressDisplayport(0), 681 mPresShellId(++sNextPresShellId), 682 mFontSizeInflationEmPerLine(0), 683 mFontSizeInflationMinTwips(0), 684 mFontSizeInflationLineThreshold(0), 685 mSelectionFlags(nsISelectionDisplay::DISPLAY_TEXT | 686 nsISelectionDisplay::DISPLAY_IMAGES), 687 mChangeNestCount(0), 688 mRenderingStateFlags(RenderingStateFlags::None), 689 mCaretEnabled(false), 690 mNeedLayoutFlush(true), 691 mNeedStyleFlush(true), 692 mNeedThrottledAnimationFlush(true), 693 mVisualViewportSizeSet(false), 694 mDidInitialize(false), 695 mIsDestroying(false), 696 mIsReflowing(false), 697 mIsObservingDocument(false), 698 mForbiddenToFlush(false), 699 mIsDocumentGone(false), 700 mHaveShutDown(false), 701 mPaintingSuppressed(false), 702 mShouldUnsuppressPainting(false), 703 mIgnoreFrameDestruction(false), 704 mIsActive(true), 705 mFrozen(false), 706 mIsFirstPaint(true), 707 mObservesMutationsForPrint(false), 708 mWasLastReflowInterrupted(false), 709 mResizeEventPending(false), 710 mVisualViewportResizeEventPending(false), 711 mFontSizeInflationForceEnabled(false), 712 mFontSizeInflationEnabled(false), 713 mIsNeverPainting(false), 714 mResolutionUpdated(false), 715 mResolutionUpdatedByApz(false), 716 mUnderHiddenEmbedderElement(false), 717 mDocumentLoading(false), 718 mNoDelayedMouseEvents(false), 719 mNoDelayedKeyEvents(false), 720 mNoDelayedSingleTap(false), 721 mApproximateFrameVisibilityVisited(false), 722 mIsLastChromeOnlyEscapeKeyConsumed(false), 723 mHasReceivedPaintMessage(false), 724 mIsLastKeyDownCanceled(false), 725 mHasHandledUserInput(false), 726 mForceDispatchKeyPressEventsForNonPrintableKeys(false), 727 mForceUseLegacyKeyCodeAndCharCodeValues(false), 728 mInitializedWithKeyPressEventDispatchingBlacklist(false), 729 mHasTriedFastUnsuppress(false), 730 mProcessingReflowCommands(false), 731 mPendingDidDoReflow(false), 732 mHasSeenAnchorPos(false) { 733 MOZ_LOG(gLog, LogLevel::Debug, ("PresShell::PresShell this=%p", this)); 734 MOZ_ASSERT(aDocument); 735 736 #ifdef MOZ_REFLOW_PERF 737 mReflowCountMgr = MakeUnique<ReflowCountMgr>(); 738 mReflowCountMgr->SetPresContext(mPresContext); 739 mReflowCountMgr->SetPresShell(this); 740 #endif 741 mLastOSWake = mLoadBegin = TimeStamp::Now(); 742 } 743 744 NS_INTERFACE_TABLE_HEAD(PresShell) 745 NS_INTERFACE_TABLE_BEGIN 746 // In most cases, PresShell should be treated as concrete class, but need to 747 // QI for weak reference. Therefore, the case needed by do_QueryReferent() 748 // should be tested first. 749 NS_INTERFACE_TABLE_ENTRY(PresShell, PresShell) 750 NS_INTERFACE_TABLE_ENTRY(PresShell, nsIDocumentObserver) 751 NS_INTERFACE_TABLE_ENTRY(PresShell, nsISelectionController) 752 NS_INTERFACE_TABLE_ENTRY(PresShell, nsISelectionDisplay) 753 NS_INTERFACE_TABLE_ENTRY(PresShell, nsIObserver) 754 NS_INTERFACE_TABLE_ENTRY(PresShell, nsISupportsWeakReference) 755 NS_INTERFACE_TABLE_ENTRY(PresShell, nsIMutationObserver) 756 NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(PresShell, nsISupports, nsIObserver) 757 NS_INTERFACE_TABLE_END 758 NS_INTERFACE_TABLE_TO_MAP_SEGUE 759 NS_INTERFACE_MAP_END 760 761 NS_IMPL_ADDREF(PresShell) 762 NS_IMPL_RELEASE(PresShell) 763 764 PresShell::~PresShell() { 765 MOZ_RELEASE_ASSERT(!mForbiddenToFlush, 766 "Flag should only be set temporarily, while doing things " 767 "that shouldn't cause destruction"); 768 MOZ_LOG(gLog, LogLevel::Debug, ("PresShell::~PresShell this=%p", this)); 769 770 if (!mHaveShutDown) { 771 MOZ_ASSERT_UNREACHABLE("Someone did not call PresShell::Destroy()"); 772 Destroy(); 773 } 774 775 NS_ASSERTION(mCurrentEventTargetStack.IsEmpty(), 776 "Huh, event content left on the stack in pres shell dtor!"); 777 NS_ASSERTION(mFirstCallbackEventRequest == nullptr && 778 mLastCallbackEventRequest == nullptr, 779 "post-reflow queues not empty. This means we're leaking"); 780 781 MOZ_ASSERT(!mAllocatedPointers || mAllocatedPointers->IsEmpty(), 782 "Some pres arena objects were not freed"); 783 784 mFrameConstructor = nullptr; 785 } 786 787 /** 788 * Initialize the presentation shell. 789 * Note this can't be merged into our constructor because caret initialization 790 * calls AddRef() on us. 791 */ 792 void PresShell::Init(nsPresContext* aPresContext) { 793 MOZ_ASSERT(mDocument); 794 MOZ_ASSERT(aPresContext); 795 MOZ_ASSERT(!mWidgetListener, "Already initialized"); 796 797 mWidgetListener = MakeUnique<PresShellWidgetListener>(this); 798 799 // mDocument is now set. It might have a display document whose "need layout/ 800 // style" flush flags are not set, but ours will be set. To keep these 801 // consistent, call the flag setting functions to propagate those flags up 802 // to the display document. 803 SetNeedLayoutFlush(); 804 SetNeedStyleFlush(); 805 806 // Create our frame constructor. 807 mFrameConstructor = MakeUnique<nsCSSFrameConstructor>(mDocument, this); 808 809 // Bind the context to the presentation shell. 810 // FYI: We cannot initialize mPresContext in the constructor because we 811 // cannot call AttachPresShell() in it and once we initialize 812 // mPresContext, other objects may refer refresh driver or restyle 813 // manager via mPresContext and that causes hitting MOZ_ASSERT in some 814 // places. Therefore, we should initialize mPresContext here with 815 // const_cast hack since we want to guarantee that mPresContext lives 816 // as long as the PresShell. 817 const_cast<RefPtr<nsPresContext>&>(mPresContext) = aPresContext; 818 mPresContext->AttachPresShell(this); 819 820 mPresContext->InitFontCache(); 821 822 // FIXME(emilio, bug 1544185): Some Android code somehow depends on the shell 823 // being eagerly registered as a style flush observer. This shouldn't be 824 // needed otherwise. 825 EnsureStyleFlush(); 826 827 const bool accessibleCaretEnabled = 828 AccessibleCaretEnabled(mDocument->GetDocShell()); 829 if (accessibleCaretEnabled) { 830 // Need to happen before nsFrameSelection has been set up. 831 mAccessibleCaretEventHub = new AccessibleCaretEventHub(this); 832 mAccessibleCaretEventHub->Init(); 833 } 834 835 mSelection = new nsFrameSelection(this, accessibleCaretEnabled); 836 837 // Important: this has to happen after the selection has been set up 838 #ifdef SHOW_CARET 839 // make the caret 840 mCaret = new nsCaret(); 841 mCaret->Init(this); 842 mOriginalCaret = mCaret; 843 844 // SetCaretEnabled(true); // make it show in browser windows 845 #endif 846 // set up selection to be displayed in document 847 // Don't enable selection for print media 848 nsPresContext::nsPresContextType type = mPresContext->Type(); 849 if (type != nsPresContext::eContext_PrintPreview && 850 type != nsPresContext::eContext_Print) { 851 SetDisplaySelection(nsISelectionController::SELECTION_DISABLED); 852 } 853 854 if (gMaxRCProcessingTime == -1) { 855 gMaxRCProcessingTime = 856 Preferences::GetInt("layout.reflow.timeslice", NS_MAX_REFLOW_TIME); 857 } 858 859 if (nsStyleSheetService* ss = nsStyleSheetService::GetInstance()) { 860 ss->RegisterPresShell(this); 861 } 862 863 { 864 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); 865 if (os) { 866 os->AddObserver(this, "memory-pressure", false); 867 os->AddObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC, false); 868 if (XRE_IsParentProcess() && !sProcessInteractable) { 869 os->AddObserver(this, "sessionstore-one-or-no-tab-restored", false); 870 } 871 os->AddObserver(this, "font-info-updated", false); 872 os->AddObserver(this, "internal-look-and-feel-changed", false); 873 } 874 } 875 876 #ifdef MOZ_REFLOW_PERF 877 if (mReflowCountMgr) { 878 bool paintFrameCounts = 879 Preferences::GetBool("layout.reflow.showframecounts"); 880 881 bool dumpFrameCounts = 882 Preferences::GetBool("layout.reflow.dumpframecounts"); 883 884 bool dumpFrameByFrameCounts = 885 Preferences::GetBool("layout.reflow.dumpframebyframecounts"); 886 887 mReflowCountMgr->SetDumpFrameCounts(dumpFrameCounts); 888 mReflowCountMgr->SetDumpFrameByFrameCounts(dumpFrameByFrameCounts); 889 mReflowCountMgr->SetPaintFrameCounts(paintFrameCounts); 890 } 891 #endif 892 893 mDocument->TimelinesController().UpdateLastRefreshDriverTime(); 894 895 // Get our activeness from the docShell. 896 ActivenessMaybeChanged(); 897 898 // Setup our font inflation preferences. 899 mFontSizeInflationEmPerLine = StaticPrefs::font_size_inflation_emPerLine(); 900 mFontSizeInflationMinTwips = StaticPrefs::font_size_inflation_minTwips(); 901 mFontSizeInflationLineThreshold = 902 StaticPrefs::font_size_inflation_lineThreshold(); 903 mFontSizeInflationForceEnabled = 904 StaticPrefs::font_size_inflation_forceEnabled(); 905 // We'll compute the font size inflation state in Initialize(), when we know 906 // the document type. 907 908 mTouchManager.Init(this, mDocument); 909 910 if (mPresContext->IsRootContentDocumentCrossProcess()) { 911 mZoomConstraintsClient = new ZoomConstraintsClient(); 912 mZoomConstraintsClient->Init(this, mDocument); 913 914 // We call this to create mMobileViewportManager, if it is needed. 915 MaybeRecreateMobileViewportManager(false); 916 } 917 918 if (nsCOMPtr<nsIDocShell> docShell = mPresContext->GetDocShell()) { 919 if (BrowsingContext* bc = docShell->GetBrowsingContext()) { 920 mUnderHiddenEmbedderElement = bc->IsUnderHiddenEmbedderElement(); 921 } 922 } 923 } 924 925 enum TextPerfLogType { eLog_reflow, eLog_loaddone, eLog_totals }; 926 927 static void LogTextPerfStats(gfxTextPerfMetrics* aTextPerf, 928 PresShell* aPresShell, 929 const gfxTextPerfMetrics::TextCounts& aCounts, 930 float aTime, TextPerfLogType aLogType, 931 const char* aURL) { 932 LogModule* tpLog = gfxPlatform::GetLog(eGfxLog_textperf); 933 934 // ignore XUL contexts unless at debug level 935 mozilla::LogLevel logLevel = LogLevel::Warning; 936 if (aCounts.numContentTextRuns == 0) { 937 logLevel = LogLevel::Debug; 938 } 939 940 if (!MOZ_LOG_TEST(tpLog, logLevel)) { 941 return; 942 } 943 944 char prefix[256]; 945 946 switch (aLogType) { 947 case eLog_reflow: 948 SprintfLiteral(prefix, "(textperf-reflow) %p time-ms: %7.0f", aPresShell, 949 aTime); 950 break; 951 case eLog_loaddone: 952 SprintfLiteral(prefix, "(textperf-loaddone) %p time-ms: %7.0f", 953 aPresShell, aTime); 954 break; 955 default: 956 MOZ_ASSERT(aLogType == eLog_totals, "unknown textperf log type"); 957 SprintfLiteral(prefix, "(textperf-totals) %p", aPresShell); 958 } 959 960 double hitRatio = 0.0; 961 uint32_t lookups = aCounts.wordCacheHit + aCounts.wordCacheMiss; 962 if (lookups) { 963 hitRatio = double(aCounts.wordCacheHit) / double(lookups); 964 } 965 966 if (aLogType == eLog_loaddone) { 967 MOZ_LOG( 968 tpLog, logLevel, 969 ("%s reflow: %d chars: %d " 970 "[%s] " 971 "content-textruns: %d chrome-textruns: %d " 972 "max-textrun-len: %d " 973 "word-cache-lookups: %d word-cache-hit-ratio: %4.3f " 974 "word-cache-space: %d word-cache-long: %d " 975 "pref-fallbacks: %d system-fallbacks: %d " 976 "textruns-const: %d textruns-destr: %d " 977 "generic-lookups: %d " 978 "cumulative-textruns-destr: %d\n", 979 prefix, aTextPerf->reflowCount, aCounts.numChars, (aURL ? aURL : ""), 980 aCounts.numContentTextRuns, aCounts.numChromeTextRuns, 981 aCounts.maxTextRunLen, lookups, hitRatio, aCounts.wordCacheSpaceRules, 982 aCounts.wordCacheLong, aCounts.fallbackPrefs, aCounts.fallbackSystem, 983 aCounts.textrunConst, aCounts.textrunDestr, aCounts.genericLookups, 984 aTextPerf->cumulative.textrunDestr)); 985 } else { 986 MOZ_LOG( 987 tpLog, logLevel, 988 ("%s reflow: %d chars: %d " 989 "content-textruns: %d chrome-textruns: %d " 990 "max-textrun-len: %d " 991 "word-cache-lookups: %d word-cache-hit-ratio: %4.3f " 992 "word-cache-space: %d word-cache-long: %d " 993 "pref-fallbacks: %d system-fallbacks: %d " 994 "textruns-const: %d textruns-destr: %d " 995 "generic-lookups: %d " 996 "cumulative-textruns-destr: %d\n", 997 prefix, aTextPerf->reflowCount, aCounts.numChars, 998 aCounts.numContentTextRuns, aCounts.numChromeTextRuns, 999 aCounts.maxTextRunLen, lookups, hitRatio, aCounts.wordCacheSpaceRules, 1000 aCounts.wordCacheLong, aCounts.fallbackPrefs, aCounts.fallbackSystem, 1001 aCounts.textrunConst, aCounts.textrunDestr, aCounts.genericLookups, 1002 aTextPerf->cumulative.textrunDestr)); 1003 } 1004 } 1005 1006 bool PresShell::InRDMPane() { 1007 if (Document* doc = GetDocument()) { 1008 if (BrowsingContext* bc = doc->GetBrowsingContext()) { 1009 return bc->InRDMPane(); 1010 } 1011 } 1012 return false; 1013 } 1014 1015 #if defined(MOZ_WIDGET_ANDROID) 1016 void PresShell::MaybeNotifyShowDynamicToolbar() { 1017 const DynamicToolbarState dynToolbarState = GetDynamicToolbarState(); 1018 if ((dynToolbarState == DynamicToolbarState::Collapsed || 1019 dynToolbarState == DynamicToolbarState::InTransition)) { 1020 MOZ_ASSERT(mPresContext && 1021 mPresContext->IsRootContentDocumentCrossProcess()); 1022 if (BrowserChild* browserChild = BrowserChild::GetFrom(this)) { 1023 browserChild->SendShowDynamicToolbar(); 1024 } 1025 } 1026 } 1027 #endif // defined(MOZ_WIDGET_ANDROID) 1028 1029 void PresShell::Destroy() { 1030 // Do not add code before this line please! 1031 if (mHaveShutDown) { 1032 return; 1033 } 1034 1035 NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(), 1036 "destroy called on presshell while scripts not blocked"); 1037 1038 nsIURI* uri = mDocument->GetDocumentURI(); 1039 AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING_RELEVANT_FOR_JS( 1040 "Layout tree destruction", LAYOUT_Destroy, 1041 uri ? uri->GetSpecOrDefault() : "N/A"_ns); 1042 1043 // Try to determine if the page is the user had a meaningful opportunity to 1044 // zoom this page. This is not 100% accurate but should be "good enough" for 1045 // telemetry purposes. 1046 auto isUserZoomablePage = [&]() -> bool { 1047 if (mIsFirstPaint) { 1048 // Page was never painted, so it wasn't zoomable by the user. We get a 1049 // handful of these "transient" presShells. 1050 return false; 1051 } 1052 if (!mPresContext->IsRootContentDocumentCrossProcess()) { 1053 // Not a root content document, so APZ doesn't support zooming it. 1054 return false; 1055 } 1056 if (InRDMPane()) { 1057 // Responsive design mode is a special case that we want to ignore here. 1058 return false; 1059 } 1060 if (mDocument && mDocument->IsInitialDocument()) { 1061 // Ignore initial about:blank page loads 1062 return false; 1063 } 1064 if (XRE_IsContentProcess() && 1065 IsExtensionRemoteType(ContentChild::GetSingleton()->GetRemoteType())) { 1066 // Also omit presShells from the extension process because they sometimes 1067 // can't be zoomed by the user. 1068 return false; 1069 } 1070 // Otherwise assume the page is user-zoomable. 1071 return true; 1072 }; 1073 if (isUserZoomablePage()) { 1074 glean::apz_zoom::activity 1075 .EnumGet(static_cast<glean::apz_zoom::ActivityLabel>( 1076 IsResolutionUpdatedByApz())) 1077 .Add(); 1078 } 1079 1080 // dump out cumulative text perf metrics 1081 gfxTextPerfMetrics* tp; 1082 if (mPresContext && (tp = mPresContext->GetTextPerfMetrics())) { 1083 tp->Accumulate(); 1084 if (tp->cumulative.numChars > 0) { 1085 LogTextPerfStats(tp, this, tp->cumulative, 0.0, eLog_totals, nullptr); 1086 } 1087 } 1088 if (mPresContext) { 1089 if (gfxUserFontSet* fs = mPresContext->GetUserFontSet()) { 1090 uint32_t fontCount; 1091 uint64_t fontSize; 1092 fs->GetLoadStatistics(fontCount, fontSize); 1093 glean::webfont::per_page.Add(fontCount); 1094 glean::webfont::size_per_page.Accumulate(uint32_t(fontSize / 1024)); 1095 } else { 1096 glean::webfont::per_page.Add(0); 1097 glean::webfont::size_per_page.Accumulate(0); 1098 } 1099 } 1100 1101 #ifdef MOZ_REFLOW_PERF 1102 DumpReflows(); 1103 mReflowCountMgr = nullptr; 1104 #endif 1105 1106 if (mZoomConstraintsClient) { 1107 mZoomConstraintsClient->Destroy(); 1108 mZoomConstraintsClient = nullptr; 1109 } 1110 if (mMobileViewportManager) { 1111 mMobileViewportManager->Destroy(); 1112 mMobileViewportManager = nullptr; 1113 mMVMContext = nullptr; 1114 } 1115 1116 MaybeReleaseCapturingContent(); 1117 1118 EventHandler::OnPresShellDestroy(mDocument); 1119 1120 if (mContentToScrollTo) { 1121 mContentToScrollTo->RemoveProperty(nsGkAtoms::scrolling); 1122 mContentToScrollTo = nullptr; 1123 } 1124 1125 if (mPresContext) { 1126 // We need to notify the destroying the nsPresContext to ESM for 1127 // suppressing to use from ESM. 1128 mPresContext->EventStateManager()->NotifyDestroyPresContext(mPresContext); 1129 } 1130 1131 if (nsStyleSheetService* ss = nsStyleSheetService::GetInstance()) { 1132 ss->UnregisterPresShell(this); 1133 } 1134 1135 { 1136 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); 1137 if (os) { 1138 os->RemoveObserver(this, "memory-pressure"); 1139 os->RemoveObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC); 1140 if (XRE_IsParentProcess()) { 1141 os->RemoveObserver(this, "sessionstore-one-or-no-tab-restored"); 1142 } 1143 os->RemoveObserver(this, "font-info-updated"); 1144 os->RemoveObserver(this, "internal-look-and-feel-changed"); 1145 } 1146 } 1147 1148 // If our paint suppression timer is still active, kill it. 1149 CancelPaintSuppressionTimer(); 1150 1151 mSynthMouseMoveEvent.Revoke(); 1152 1153 mUpdateApproximateFrameVisibilityEvent.Revoke(); 1154 1155 ClearApproximatelyVisibleFramesList(Some(OnNonvisible::DiscardImages)); 1156 1157 if (mOriginalCaret) { 1158 mOriginalCaret->Terminate(); 1159 } 1160 if (mCaret && mCaret != mOriginalCaret) { 1161 mCaret->Terminate(); 1162 } 1163 mCaret = mOriginalCaret = nullptr; 1164 1165 mFocusedFrameSelection = nullptr; 1166 1167 if (mSelection) { 1168 RefPtr<nsFrameSelection> frameSelection = mSelection; 1169 frameSelection->DisconnectFromPresShell(); 1170 } 1171 1172 mIsDestroying = true; 1173 1174 #ifdef ACCESSIBILITY 1175 if (mDocAccessible) { 1176 # ifdef DEBUG 1177 if (a11y::logging::IsEnabled(a11y::logging::eDocDestroy)) 1178 a11y::logging::DocDestroy("presshell destroyed", mDocument); 1179 # endif 1180 1181 mDocAccessible->Shutdown(); 1182 mDocAccessible = nullptr; 1183 } 1184 #endif // ACCESSIBILITY 1185 1186 // We can't release all the event content in 1187 // mCurrentEventContentStack here since there might be code on the 1188 // stack that will release the event content too. Double release 1189 // bad! 1190 1191 // The frames will be torn down, so remove them from the current 1192 // event frame stack (since they'd be dangling references if we'd 1193 // leave them in) and null out the mCurrentEventFrame pointer as 1194 // well. 1195 1196 mCurrentEventTarget.ClearFrame(); 1197 1198 for (EventTargetInfo& eventTargetInfo : mCurrentEventTargetStack) { 1199 eventTargetInfo.ClearFrame(); 1200 } 1201 1202 mFramesToDirty.Clear(); 1203 mPendingScrollAnchorSelection.Clear(); 1204 mPendingScrollAnchorAdjustment.Clear(); 1205 mPendingScrollResnap.Clear(); 1206 1207 // This shell must be removed from the document before the frame 1208 // hierarchy is torn down to avoid finding deleted frames through 1209 // this presshell while the frames are being torn down 1210 if (mDocument) { 1211 NS_ASSERTION(mDocument->GetPresShell() == this, "Wrong shell?"); 1212 mDocument->ClearServoRestyleRoot(); 1213 mDocument->DeletePresShell(); 1214 } 1215 1216 if (mPresContext) { 1217 mPresContext->AnimationEventDispatcher()->ClearEventQueue(); 1218 } 1219 1220 // Revoke any pending events. We need to do this and cancel pending reflows 1221 // before we destroy the frame constructor, since apparently frame destruction 1222 // sometimes spins the event queue when plug-ins are involved(!). 1223 // XXXmats is this still needed now that plugins are gone? 1224 CancelAllPendingReflows(); 1225 CancelPostedReflowCallbacks(); 1226 1227 // Destroy the frame constructor. This will destroy the frame hierarchy 1228 mFrameConstructor->WillDestroyFrameTree(); 1229 1230 NS_WARNING_ASSERTION(!mAutoWeakFrames && mWeakFrames.IsEmpty(), 1231 "Weak frames alive after destroying FrameManager"); 1232 while (mAutoWeakFrames) { 1233 mAutoWeakFrames->Clear(this); 1234 } 1235 const nsTArray<WeakFrame*> weakFrames = ToArray(mWeakFrames); 1236 for (WeakFrame* weakFrame : weakFrames) { 1237 weakFrame->Clear(this); 1238 } 1239 1240 // Clear the embedding frame only after tearing down the frame tree, since we 1241 // rely on reaching the display root frame from frame destruction. 1242 if (nsSubDocumentFrame* f = GetInProcessEmbedderFrame()) { 1243 f->RemoveEmbeddingPresShell(this); 1244 } 1245 mEmbedderFrame = nullptr; 1246 1247 // Terminate AccessibleCaretEventHub after tearing down the frame tree so that 1248 // we don't need to remove caret element's frame in 1249 // AccessibleCaret::RemoveCaretElement(). 1250 if (mAccessibleCaretEventHub) { 1251 mAccessibleCaretEventHub->Terminate(); 1252 mAccessibleCaretEventHub = nullptr; 1253 } 1254 1255 mWidgetListener = nullptr; 1256 1257 if (mPresContext) { 1258 // We hold a reference to the pres context, and it holds a weak link back 1259 // to us. To avoid the pres context having a dangling reference, set its 1260 // pres shell to nullptr 1261 mPresContext->DetachPresShell(); 1262 } 1263 1264 mHaveShutDown = true; 1265 1266 mTouchManager.Destroy(); 1267 } 1268 1269 void PresShell::StartObservingRefreshDriver() { 1270 nsRefreshDriver* rd = mPresContext->RefreshDriver(); 1271 if (mResizeEventPending || mVisualViewportResizeEventPending) { 1272 rd->ScheduleRenderingPhase(mozilla::RenderingPhase::ResizeSteps); 1273 } 1274 if (mNeedLayoutFlush || mNeedStyleFlush) { 1275 rd->ScheduleRenderingPhase(mozilla::RenderingPhase::Layout); 1276 } 1277 } 1278 1279 nsRefreshDriver* PresShell::GetRefreshDriver() const { 1280 return mPresContext ? mPresContext->RefreshDriver() : nullptr; 1281 } 1282 1283 // NOTE(emilio): It'd be ideal if instead of this explicit tracking we could 1284 // rely on mDocument->GetEmbedderElement()->GetPrimaryFrame() + relevant 1285 // null-checks. However, given how things are set up now, the embedder element 1286 // in BrowsingContext / Window get cleared before tearing down the pres shell, 1287 // and RDL relies on getting ahold of it to get the display root. 1288 void PresShell::SetInProcessEmbedderFrame(nsSubDocumentFrame* aFrame) { 1289 mEmbedderFrame = aFrame; 1290 } 1291 1292 void PresShell::SetAuthorStyleDisabled(bool aStyleDisabled) { 1293 if (aStyleDisabled != StyleSet()->GetAuthorStyleDisabled()) { 1294 StyleSet()->SetAuthorStyleDisabled(aStyleDisabled); 1295 mDocument->ApplicableStylesChanged(); 1296 1297 nsCOMPtr<nsIObserverService> observerService = 1298 mozilla::services::GetObserverService(); 1299 if (observerService) { 1300 observerService->NotifyObservers( 1301 ToSupports(mDocument), "author-style-disabled-changed", nullptr); 1302 } 1303 } 1304 } 1305 1306 bool PresShell::GetAuthorStyleDisabled() const { 1307 return StyleSet()->GetAuthorStyleDisabled(); 1308 } 1309 1310 void PresShell::AddUserSheet(StyleSheet* aSheet) { 1311 // Make sure this does what nsDocumentViewer::CreateStyleSet does wrt 1312 // ordering. We want this new sheet to come after all the existing stylesheet 1313 // service sheets (which are at the start), but before other user sheets; see 1314 // nsIStyleSheetService.idl for the ordering. 1315 1316 nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance(); 1317 nsTArray<RefPtr<StyleSheet>>& userSheets = *sheetService->UserStyleSheets(); 1318 1319 // Search for the place to insert the new user sheet. Since all of the 1320 // stylesheet service provided user sheets should be at the start of the style 1321 // set's list, and aSheet should be at the end of userSheets. Given that, we 1322 // can find the right place to insert the new sheet based on the length of 1323 // userSheets. 1324 MOZ_ASSERT(aSheet); 1325 MOZ_ASSERT(userSheets.LastElement() == aSheet); 1326 1327 size_t index = userSheets.Length() - 1; 1328 1329 // Assert that all of userSheets (except for the last, new element) matches up 1330 // with what's in the style set. 1331 for (size_t i = 0; i < index; ++i) { 1332 MOZ_ASSERT(StyleSet()->SheetAt(StyleOrigin::User, i) == userSheets[i]); 1333 } 1334 1335 if (index == static_cast<size_t>(StyleSet()->SheetCount(StyleOrigin::User))) { 1336 StyleSet()->AppendStyleSheet(*aSheet); 1337 } else { 1338 StyleSheet* ref = StyleSet()->SheetAt(StyleOrigin::User, index); 1339 StyleSet()->InsertStyleSheetBefore(*aSheet, *ref); 1340 } 1341 1342 mDocument->ApplicableStylesChanged(); 1343 } 1344 1345 void PresShell::AddAgentSheet(StyleSheet* aSheet) { 1346 // Make sure this does what nsDocumentViewer::CreateStyleSet does 1347 // wrt ordering. 1348 StyleSet()->AppendStyleSheet(*aSheet); 1349 mDocument->ApplicableStylesChanged(); 1350 } 1351 1352 void PresShell::AddAuthorSheet(StyleSheet* aSheet) { 1353 // Document specific "additional" Author sheets should be stronger than the 1354 // ones added with the StyleSheetService. 1355 StyleSheet* firstAuthorSheet = mDocument->GetFirstAdditionalAuthorSheet(); 1356 if (firstAuthorSheet) { 1357 StyleSet()->InsertStyleSheetBefore(*aSheet, *firstAuthorSheet); 1358 } else { 1359 StyleSet()->AppendStyleSheet(*aSheet); 1360 } 1361 1362 mDocument->ApplicableStylesChanged(); 1363 } 1364 1365 bool PresShell::NeedsFocusFixUp() const { 1366 if (NS_WARN_IF(!mDocument)) { 1367 return false; 1368 } 1369 1370 nsIContent* currentFocus = mDocument->GetUnretargetedFocusedContent( 1371 Document::IncludeChromeOnly::Yes); 1372 if (!currentFocus) { 1373 return false; 1374 } 1375 1376 // If focus target is an area element with one or more shapes that are 1377 // focusable areas. 1378 if (auto* area = HTMLAreaElement::FromNode(currentFocus)) { 1379 if (nsFocusManager::IsAreaElementFocusable(*area)) { 1380 return false; 1381 } 1382 } 1383 1384 nsIFrame* f = currentFocus->GetPrimaryFrame(); 1385 if (f && f->IsFocusable()) { 1386 return false; 1387 } 1388 1389 if (currentFocus == mDocument->GetBody() || 1390 currentFocus == mDocument->GetRootElement()) { 1391 return false; 1392 } 1393 1394 return true; 1395 } 1396 1397 bool PresShell::FixUpFocus() { 1398 if (!NeedsFocusFixUp()) { 1399 return false; 1400 } 1401 RefPtr fm = nsFocusManager::GetFocusManager(); 1402 nsCOMPtr<nsPIDOMWindowOuter> window = mDocument->GetWindow(); 1403 if (NS_WARN_IF(!window)) { 1404 return false; 1405 } 1406 fm->ClearFocus(window); 1407 return true; 1408 } 1409 1410 void PresShell::SelectionWillTakeFocus() { 1411 if (mSelection) { 1412 FrameSelectionWillTakeFocus(*mSelection, 1413 CanMoveLastSelectionForToString::No); 1414 } 1415 } 1416 1417 void PresShell::SelectionWillLoseFocus() { 1418 // Do nothing, the main selection is the default focused selection. 1419 } 1420 1421 // Selection repainting code relies on selection offsets being properly 1422 // adjusted (see bug 1626291), so we need to wait until the DOM is finished 1423 // notifying. 1424 static void RepaintNormalSelectionWhenSafe(nsFrameSelection& aFrameSelection) { 1425 if (nsContentUtils::IsSafeToRunScript()) { 1426 aFrameSelection.RepaintSelection(SelectionType::eNormal); 1427 return; 1428 } 1429 1430 // Note that importantly we don't defer changing the DisplaySelection. That'd 1431 // be potentially racy with other code that may change it. 1432 nsContentUtils::AddScriptRunner(NS_NewRunnableFunction( 1433 "RepaintNormalSelectionWhenSafe", 1434 [sel = RefPtr<nsFrameSelection>(&aFrameSelection)] { 1435 sel->RepaintSelection(SelectionType::eNormal); 1436 })); 1437 } 1438 1439 void PresShell::FrameSelectionWillLoseFocus(nsFrameSelection& aFrameSelection) { 1440 if (mFocusedFrameSelection != &aFrameSelection) { 1441 return; 1442 } 1443 1444 // Do nothing, the main selection is the default focused selection. 1445 if (&aFrameSelection == mSelection) { 1446 return; 1447 } 1448 1449 RefPtr<nsFrameSelection> old = std::move(mFocusedFrameSelection); 1450 MOZ_ASSERT(!mFocusedFrameSelection); 1451 1452 if (old->GetDisplaySelection() != nsISelectionController::SELECTION_HIDDEN) { 1453 old->SetDisplaySelection(nsISelectionController::SELECTION_HIDDEN); 1454 RepaintNormalSelectionWhenSafe(*old); 1455 } 1456 1457 if (mSelection) { 1458 FrameSelectionWillTakeFocus(*mSelection, 1459 CanMoveLastSelectionForToString::No); 1460 } 1461 } 1462 1463 void PresShell::FrameSelectionWillTakeFocus( 1464 nsFrameSelection& aFrameSelection, 1465 CanMoveLastSelectionForToString aCanMoveLastSelectionForToString) { 1466 if (StaticPrefs::dom_selection_mimic_chrome_tostring_enabled()) { 1467 if (aCanMoveLastSelectionForToString == 1468 CanMoveLastSelectionForToString::Yes) { 1469 UpdateLastSelectionForToString(&aFrameSelection); 1470 } 1471 } 1472 if (mFocusedFrameSelection == &aFrameSelection) { 1473 #ifdef XP_MACOSX 1474 // FIXME: Mac needs to update the global selection cache, even if the 1475 // document's focused selection doesn't change, and this is currently done 1476 // from RepaintSelection. Maybe we should move part of the global selection 1477 // handling here, or something of that sort, unclear. 1478 RepaintNormalSelectionWhenSafe(aFrameSelection); 1479 #endif 1480 return; 1481 } 1482 1483 RefPtr<nsFrameSelection> old = std::move(mFocusedFrameSelection); 1484 mFocusedFrameSelection = &aFrameSelection; 1485 1486 if (old && 1487 old->GetDisplaySelection() != nsISelectionController::SELECTION_HIDDEN) { 1488 old->SetDisplaySelection(nsISelectionController::SELECTION_HIDDEN); 1489 RepaintNormalSelectionWhenSafe(*old); 1490 } 1491 1492 if (aFrameSelection.GetDisplaySelection() != 1493 nsISelectionController::SELECTION_ON) { 1494 aFrameSelection.SetDisplaySelection(nsISelectionController::SELECTION_ON); 1495 RepaintNormalSelectionWhenSafe(aFrameSelection); 1496 } 1497 } 1498 1499 void PresShell::UpdateLastSelectionForToString( 1500 const nsFrameSelection* aFrameSelection) { 1501 if (mLastSelectionForToString != aFrameSelection) { 1502 mLastSelectionForToString = aFrameSelection; 1503 } 1504 } 1505 1506 NS_IMETHODIMP 1507 PresShell::SetDisplaySelection(int16_t aToggle) { 1508 mSelection->SetDisplaySelection(aToggle); 1509 return NS_OK; 1510 } 1511 1512 NS_IMETHODIMP 1513 PresShell::GetDisplaySelection(int16_t* aToggle) { 1514 *aToggle = mSelection->GetDisplaySelection(); 1515 return NS_OK; 1516 } 1517 1518 NS_IMETHODIMP 1519 PresShell::GetSelectionFromScript(RawSelectionType aRawSelectionType, 1520 Selection** aSelection) { 1521 if (!aSelection || !mSelection) { 1522 return NS_ERROR_NULL_POINTER; 1523 } 1524 1525 RefPtr<Selection> selection = 1526 mSelection->GetSelection(ToSelectionType(aRawSelectionType)); 1527 1528 if (!selection) { 1529 return NS_ERROR_INVALID_ARG; 1530 } 1531 1532 selection.forget(aSelection); 1533 return NS_OK; 1534 } 1535 1536 Selection* PresShell::GetSelection(RawSelectionType aRawSelectionType) { 1537 if (!mSelection) { 1538 return nullptr; 1539 } 1540 1541 return mSelection->GetSelection(ToSelectionType(aRawSelectionType)); 1542 } 1543 1544 Selection* PresShell::GetCurrentSelection(SelectionType aSelectionType) { 1545 if (!mSelection) { 1546 return nullptr; 1547 } 1548 1549 return mSelection->GetSelection(aSelectionType); 1550 } 1551 1552 nsFrameSelection* PresShell::GetLastFocusedFrameSelection() { 1553 return mFocusedFrameSelection ? mFocusedFrameSelection : mSelection; 1554 } 1555 1556 NS_IMETHODIMP 1557 PresShell::ScrollSelectionIntoView(RawSelectionType aRawSelectionType, 1558 SelectionRegion aRegion, 1559 ControllerScrollFlags aFlags) { 1560 if (!mSelection) { 1561 return NS_ERROR_NULL_POINTER; 1562 } 1563 1564 RefPtr<nsFrameSelection> frameSelection = mSelection; 1565 return frameSelection->ScrollSelectionIntoView( 1566 ToSelectionType(aRawSelectionType), aRegion, aFlags); 1567 } 1568 1569 NS_IMETHODIMP 1570 PresShell::RepaintSelection(RawSelectionType aRawSelectionType) { 1571 if (!mSelection) { 1572 return NS_ERROR_NULL_POINTER; 1573 } 1574 1575 if (MOZ_UNLIKELY(mIsDestroying)) { 1576 return NS_OK; 1577 } 1578 1579 RefPtr<nsFrameSelection> frameSelection = mSelection; 1580 return frameSelection->RepaintSelection(ToSelectionType(aRawSelectionType)); 1581 } 1582 1583 // Make shell be a document observer 1584 void PresShell::BeginObservingDocument() { 1585 if (mDocument && !mIsDestroying) { 1586 mIsObservingDocument = true; 1587 if (mIsDocumentGone) { 1588 NS_WARNING( 1589 "Adding a presshell that was disconnected from the document " 1590 "as a document observer? Sounds wrong..."); 1591 mIsDocumentGone = false; 1592 } 1593 } 1594 } 1595 1596 // Make shell stop being a document observer 1597 void PresShell::EndObservingDocument() { 1598 // XXXbz do we need to tell the frame constructor that the document 1599 // is gone, perhaps? Except for printing it's NOT gone, sometimes. 1600 mIsDocumentGone = true; 1601 mIsObservingDocument = false; 1602 } 1603 1604 void PresShell::InitPaintSuppressionTimer() { 1605 // Default to PAINTLOCK_EVENT_DELAY if we can't get the pref value. 1606 Document* doc = mDocument->GetDisplayDocument() 1607 ? mDocument->GetDisplayDocument() 1608 : mDocument.get(); 1609 const bool inProcess = !doc->GetBrowsingContext() || 1610 doc->GetBrowsingContext()->Top()->IsInProcess(); 1611 int32_t delay = inProcess 1612 ? StaticPrefs::nglayout_initialpaint_delay() 1613 : StaticPrefs::nglayout_initialpaint_delay_in_oopif(); 1614 mPaintSuppressionTimer->InitWithNamedFuncCallback( 1615 [](nsITimer* aTimer, void* aPresShell) { 1616 RefPtr<PresShell> self = static_cast<PresShell*>(aPresShell); 1617 self->UnsuppressPainting(); 1618 }, 1619 this, delay, nsITimer::TYPE_ONE_SHOT, 1620 "PresShell::sPaintSuppressionCallback"_ns); 1621 } 1622 1623 nsresult PresShell::Initialize() { 1624 if (mIsDestroying) { 1625 return NS_OK; 1626 } 1627 1628 if (!mDocument) { 1629 // Nothing to do 1630 return NS_OK; 1631 } 1632 1633 MOZ_LOG(gLog, LogLevel::Debug, ("PresShell::Initialize this=%p", this)); 1634 1635 NS_ASSERTION(!mDidInitialize, "Why are we being called?"); 1636 1637 RefPtr<PresShell> kungFuDeathGrip(this); 1638 1639 RecomputeFontSizeInflationEnabled(); 1640 MOZ_DIAGNOSTIC_ASSERT(!mIsDestroying); 1641 1642 // Ensure the pres context doesn't think it has changed, since we haven't even 1643 // started layout. This avoids spurious restyles / reflows afterwards. 1644 // 1645 // Note that this is very intentionally before setting mDidInitialize so it 1646 // doesn't notify the document, or run media query change events. 1647 mPresContext->FlushPendingMediaFeatureValuesChanged(); 1648 MOZ_DIAGNOSTIC_ASSERT(!mIsDestroying); 1649 1650 mDidInitialize = true; 1651 1652 // Get the root frame from the frame constructor. 1653 // XXXbz it would be nice to move this somewhere else... like frame manager 1654 // Init(), say. But we need to make sure our views are all set up by the 1655 // time we do this! 1656 MOZ_ASSERT(!mFrameConstructor->GetRootFrame(), 1657 "How did that happen, exactly?"); 1658 ViewportFrame* rootFrame; 1659 { 1660 nsAutoScriptBlocker scriptBlocker; 1661 rootFrame = mFrameConstructor->ConstructRootFrame(); 1662 mFrameConstructor->SetRootFrame(rootFrame); 1663 } 1664 1665 NS_ENSURE_STATE(!mHaveShutDown); 1666 1667 if (!rootFrame) { 1668 return NS_ERROR_OUT_OF_MEMORY; 1669 } 1670 1671 if (Element* root = mDocument->GetRootElement()) { 1672 { 1673 nsAutoCauseReflowNotifier reflowNotifier(this); 1674 // Have the style sheet processor construct frame for the root 1675 // content object down 1676 mFrameConstructor->ContentInserted( 1677 root, nsCSSFrameConstructor::InsertionKind::Sync); 1678 } 1679 // Something in mFrameConstructor->ContentInserted may have caused 1680 // Destroy() to get called, bug 337586. Or, nsAutoCauseReflowNotifier 1681 // (which sets up a script blocker) going out of scope may have killed us 1682 // too 1683 NS_ENSURE_STATE(!mHaveShutDown); 1684 } 1685 1686 mDocument->MaybeScheduleRendering(); 1687 1688 NS_ASSERTION(rootFrame, "How did that happen?"); 1689 1690 // Note: when the frame was created above it had the NS_FRAME_IS_DIRTY bit 1691 // set, but XBL processing could have caused a reflow which clears it. 1692 if (MOZ_LIKELY(rootFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY))) { 1693 // Unset the DIRTY bits so that FrameNeedsReflow() will work right. 1694 rootFrame->RemoveStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN); 1695 NS_ASSERTION(!mDirtyRoots.Contains(rootFrame), 1696 "Why is the root in mDirtyRoots already?"); 1697 FrameNeedsReflow(rootFrame, IntrinsicDirty::None, NS_FRAME_IS_DIRTY); 1698 NS_ASSERTION(mDirtyRoots.Contains(rootFrame), 1699 "Should be in mDirtyRoots now"); 1700 NS_ASSERTION(mNeedStyleFlush || mNeedLayoutFlush, 1701 "Why no reflow scheduled?"); 1702 } 1703 1704 // Restore our root scroll position now if we're getting here after EndLoad 1705 // got called, since this is our one chance to do it. Note that we need not 1706 // have reflowed for this to work; when the scrollframe is finally reflowed 1707 // it'll pick up the position we store in it here. 1708 if (!mDocumentLoading) { 1709 RestoreRootScrollPosition(); 1710 } 1711 1712 // For printing, we just immediately unsuppress. 1713 if (!mPresContext->IsPaginated()) { 1714 // Kick off a one-shot timer based off our pref value. When this timer 1715 // fires, if painting is still locked down, then we will go ahead and 1716 // trigger a full invalidate and allow painting to proceed normally. 1717 mPaintingSuppressed = true; 1718 // Don't suppress painting if the document isn't loading. However, 1719 // the initial about:blank appears not to be loading, but we still 1720 // want to suppress painting. 1721 nsIDocShell* docShell = mDocument->GetDocShell(); 1722 if ((docShell && 1723 !nsDocShell::Cast(docShell) 1724 ->HasStartedLoadingOtherThanInitialBlankURI() && 1725 mDocument->IsInitialDocument()) || 1726 mDocument->GetReadyStateEnum() != Document::READYSTATE_COMPLETE) { 1727 mPaintSuppressionTimer = NS_NewTimer(); 1728 } 1729 if (!mPaintSuppressionTimer) { 1730 mPaintingSuppressed = false; 1731 } else { 1732 // Initialize the timer. 1733 mPaintSuppressionTimer->SetTarget(GetMainThreadSerialEventTarget()); 1734 InitPaintSuppressionTimer(); 1735 if (mHasTriedFastUnsuppress) { 1736 // Someone tried to unsuppress painting before Initialize was called so 1737 // unsuppress painting rather soon. 1738 mHasTriedFastUnsuppress = false; 1739 TryUnsuppressPaintingSoon(); 1740 MOZ_ASSERT(mHasTriedFastUnsuppress); 1741 } 1742 } 1743 } 1744 1745 // If we get here and painting is not suppressed, we still want to run the 1746 // unsuppression logic, so set mShouldUnsuppressPainting to true. 1747 if (!mPaintingSuppressed) { 1748 mShouldUnsuppressPainting = true; 1749 } 1750 1751 return NS_OK; // XXX this needs to be real. MMP 1752 } 1753 1754 void PresShell::TryUnsuppressPaintingSoon() { 1755 if (mHasTriedFastUnsuppress) { 1756 return; 1757 } 1758 mHasTriedFastUnsuppress = true; 1759 1760 if (!mDidInitialize || !IsPaintingSuppressed() || !XRE_IsContentProcess()) { 1761 return; 1762 } 1763 1764 if (!mDocument->IsInitialDocument() && 1765 mDocument->DidHitCompleteSheetCache() && 1766 mPresContext->IsRootContentDocumentCrossProcess()) { 1767 // Try to unsuppress faster on a top level page if it uses stylesheet 1768 // cache, since that hints that many resources can be painted sooner than 1769 // in a cold page load case. 1770 NS_DispatchToCurrentThreadQueue( 1771 NS_NewRunnableFunction("PresShell::TryUnsuppressPaintingSoon", 1772 [self = RefPtr{this}]() -> void { 1773 if (self->IsPaintingSuppressed()) { 1774 PROFILER_MARKER_UNTYPED( 1775 "Fast paint unsuppression", GRAPHICS); 1776 self->UnsuppressPainting(); 1777 } 1778 }), 1779 EventQueuePriority::Control); 1780 } 1781 } 1782 1783 void PresShell::RefreshZoomConstraintsForScreenSizeChange() { 1784 if (mZoomConstraintsClient) { 1785 mZoomConstraintsClient->ScreenSizeChanged(); 1786 } 1787 } 1788 1789 nsSize PresShell::MaybePendingLayoutViewportSize() const { 1790 if (mPendingLayoutViewportSize) { 1791 return *mPendingLayoutViewportSize; 1792 } 1793 return mPresContext ? mPresContext->GetVisibleArea().Size() : nsSize(); 1794 } 1795 1796 bool PresShell::ShouldDelayResize() const { 1797 if (!IsVisible()) { 1798 return true; 1799 } 1800 nsRefreshDriver* rd = GetRefreshDriver(); 1801 return rd && rd->IsResizeSuppressed(); 1802 } 1803 1804 void PresShell::FlushDelayedResize() { 1805 if (!mPendingLayoutViewportSize) { 1806 return; 1807 } 1808 auto size = mPendingLayoutViewportSize.extract(); 1809 if (!mPresContext || size == mPresContext->GetVisibleArea().Size()) { 1810 return; 1811 } 1812 ResizeReflow(size); 1813 } 1814 1815 void PresShell::SetLayoutViewportSize(const nsSize& aSize, bool aDelay) { 1816 mPendingLayoutViewportSize = Some(aSize); 1817 if (aDelay || ShouldDelayResize()) { 1818 SetNeedStyleFlush(); 1819 SetNeedLayoutFlush(); 1820 return; 1821 } 1822 FlushDelayedResize(); 1823 } 1824 1825 void PresShell::ForceResizeReflowWithCurrentDimensions() { 1826 ResizeReflow(MaybePendingLayoutViewportSize()); 1827 } 1828 1829 void PresShell::ResizeReflow(const nsSize& aSize, 1830 ResizeReflowOptions aOptions) { 1831 if (mZoomConstraintsClient) { 1832 // If we have a ZoomConstraintsClient and the available screen area 1833 // changed, then we might need to disable double-tap-to-zoom, so notify 1834 // the ZCC to update itself. 1835 mZoomConstraintsClient->ScreenSizeChanged(); 1836 } 1837 if (UsesMobileViewportSizing()) { 1838 // If we are using mobile viewport sizing, request a reflow from the MVM. 1839 // It can recompute the final CSS viewport and trigger a call to 1840 // ResizeReflowIgnoreOverride if it changed. We don't force adjusting 1841 // of resolution, because that is only necessary when we are destroying 1842 // the MVM. 1843 MOZ_ASSERT(mMobileViewportManager); 1844 mMobileViewportManager->RequestReflow(false); 1845 return; 1846 } 1847 ResizeReflowIgnoreOverride(aSize, aOptions); 1848 } 1849 1850 bool PresShell::SimpleResizeReflow(const nsSize& aSize) { 1851 MOZ_ASSERT(aSize.width != NS_UNCONSTRAINEDSIZE); 1852 MOZ_ASSERT(aSize.height != NS_UNCONSTRAINEDSIZE); 1853 nsSize oldSize = mPresContext->GetVisibleArea().Size(); 1854 mPresContext->SetVisibleArea(nsRect(nsPoint(), aSize)); 1855 nsIFrame* rootFrame = GetRootFrame(); 1856 if (!rootFrame) { 1857 return false; 1858 } 1859 WritingMode wm = rootFrame->GetWritingMode(); 1860 bool isBSizeChanging = wm.IsVertical() ? oldSize.width != aSize.width 1861 : oldSize.height != aSize.height; 1862 if (isBSizeChanging) { 1863 nsLayoutUtils::MarkIntrinsicISizesDirtyIfDependentOnBSize(rootFrame); 1864 rootFrame->SetHasBSizeChange(true); 1865 } 1866 FrameNeedsReflow(rootFrame, IntrinsicDirty::None, 1867 NS_FRAME_HAS_DIRTY_CHILDREN); 1868 1869 if (mMobileViewportManager) { 1870 mMobileViewportManager->UpdateSizesBeforeReflow(); 1871 } 1872 return true; 1873 } 1874 1875 bool PresShell::CanHandleUserInputEvents(WidgetGUIEvent* aGUIEvent) { 1876 if (XRE_IsParentProcess()) { 1877 return true; 1878 } 1879 1880 if (aGUIEvent->mFlags.mIsSynthesizedForTests && 1881 !StaticPrefs::dom_input_events_security_isUserInputHandlingDelayTest()) { 1882 return true; 1883 } 1884 1885 if (!aGUIEvent->IsUserAction()) { 1886 return true; 1887 } 1888 1889 if (nsPresContext* rootPresContext = mPresContext->GetRootPresContext()) { 1890 return rootPresContext->UserInputEventsAllowed(); 1891 } 1892 1893 return true; 1894 } 1895 1896 void PresShell::PostScrollEvent(Runnable* aEvent) { 1897 MOZ_ASSERT(aEvent); 1898 mPendingScrollEvents.AppendElement(aEvent); 1899 1900 // If we (or any descendant docs) have any content visibility: auto elements, 1901 // we also need to run its proximity to the viewport on scroll. Same for 1902 // intersection observers. 1903 // 1904 // We don't need to mark ourselves as needing a layout flush. We don't need to 1905 // get flushed, we just need the viewport relevancy / content-visibility: auto 1906 // viewport proximity phases to run. 1907 mPresContext->RefreshDriver()->ScheduleRenderingPhases( 1908 {RenderingPhase::ScrollSteps, RenderingPhase::Layout, 1909 RenderingPhase::UpdateIntersectionObservations}); 1910 } 1911 1912 void PresShell::ScheduleResizeEventIfNeeded(ResizeEventKind aKind) { 1913 if (mIsDestroying) { 1914 return; 1915 } 1916 if (MOZ_UNLIKELY(mDocument->GetBFCacheEntry())) { 1917 return; 1918 } 1919 if (aKind == ResizeEventKind::Regular) { 1920 mResizeEventPending = true; 1921 } else { 1922 MOZ_ASSERT(aKind == ResizeEventKind::Visual); 1923 mVisualViewportResizeEventPending = true; 1924 } 1925 mPresContext->RefreshDriver()->ScheduleRenderingPhase( 1926 RenderingPhase::ResizeSteps); 1927 } 1928 1929 bool PresShell::ResizeReflowIgnoreOverride(const nsSize& aSize, 1930 ResizeReflowOptions aOptions) { 1931 MOZ_ASSERT(!mIsReflowing, "Shouldn't be in reflow here!"); 1932 1933 // Historically we never fired resize events if there was no root frame by the 1934 // time this function got called. 1935 const bool initialized = mDidInitialize; 1936 RefPtr<PresShell> kungFuDeathGrip(this); 1937 1938 auto postResizeEventIfNeeded = [this, initialized]() { 1939 if (initialized) { 1940 ScheduleResizeEventIfNeeded(ResizeEventKind::Regular); 1941 } 1942 }; 1943 1944 // If there are orthogonal flows that were dependent on the ICB size, mark 1945 // them as dirty to ensure they will be reflowed. 1946 for (auto* frame : mOrthogonalFlows) { 1947 FrameNeedsReflow(frame, IntrinsicDirty::None, NS_FRAME_HAS_DIRTY_CHILDREN); 1948 } 1949 mOrthogonalFlows.Clear(); 1950 1951 if (!(aOptions & ResizeReflowOptions::BSizeLimit)) { 1952 nsSize oldSize = mPresContext->GetVisibleArea().Size(); 1953 if (oldSize == aSize) { 1954 return false; 1955 } 1956 1957 bool changed = SimpleResizeReflow(aSize); 1958 postResizeEventIfNeeded(); 1959 return changed; 1960 } 1961 1962 // Make sure that style is flushed before setting the pres context 1963 // VisibleArea. 1964 // 1965 // Otherwise we may end up with bogus viewport units resolved against the 1966 // unconstrained bsize, or restyling the whole document resolving viewport 1967 // units against targetWidth, which may end up doing wasteful work. 1968 mDocument->FlushPendingNotifications(FlushType::Frames); 1969 1970 nsIFrame* rootFrame = GetRootFrame(); 1971 if (mIsDestroying || !rootFrame) { 1972 // If we don't have a root frame yet, that means we haven't had our initial 1973 // reflow... If that's the case, and aWidth or aHeight is unconstrained, 1974 // ignore them altogether. 1975 if (aSize.height == NS_UNCONSTRAINEDSIZE || 1976 aSize.width == NS_UNCONSTRAINEDSIZE) { 1977 // We can't do the work needed for SizeToContent without a root 1978 // frame, and we want to return before setting the visible area. 1979 return false; 1980 } 1981 1982 mPresContext->SetVisibleArea(nsRect(nsPoint(), aSize)); 1983 // There isn't anything useful we can do if the initial reflow hasn't 1984 // happened. 1985 return true; 1986 } 1987 1988 WritingMode wm = rootFrame->GetWritingMode(); 1989 MOZ_ASSERT( 1990 (wm.IsVertical() ? aSize.height : aSize.width) != NS_UNCONSTRAINEDSIZE, 1991 "unconstrained isize not allowed"); 1992 1993 nsSize targetSize = aSize; 1994 if (wm.IsVertical()) { 1995 targetSize.width = NS_UNCONSTRAINEDSIZE; 1996 } else { 1997 targetSize.height = NS_UNCONSTRAINEDSIZE; 1998 } 1999 2000 mPresContext->SetVisibleArea(nsRect(nsPoint(), targetSize)); 2001 // XXX Do a full invalidate at the beginning so that invalidates along 2002 // the way don't have region accumulation issues? 2003 2004 // For height:auto BSizes (i.e. layout-controlled), descendant 2005 // intrinsic sizes can't depend on them. So the only other case is 2006 // viewport-controlled BSizes which we handle here. 2007 nsLayoutUtils::MarkIntrinsicISizesDirtyIfDependentOnBSize(rootFrame); 2008 rootFrame->SetHasBSizeChange(true); 2009 FrameNeedsReflow(rootFrame, IntrinsicDirty::None, 2010 NS_FRAME_HAS_DIRTY_CHILDREN); 2011 2012 { 2013 nsAutoCauseReflowNotifier crNotifier(this); 2014 WillDoReflow(); 2015 2016 // Kick off a top-down reflow 2017 AUTO_LAYOUT_PHASE_ENTRY_POINT(GetPresContext(), Reflow); 2018 2019 mDirtyRoots.Remove(rootFrame); 2020 DoReflow(rootFrame, true, nullptr); 2021 2022 const bool reflowAgain = 2023 wm.IsVertical() ? mPresContext->GetVisibleArea().width > aSize.width 2024 : mPresContext->GetVisibleArea().height > aSize.height; 2025 2026 if (reflowAgain) { 2027 mPresContext->SetVisibleArea(nsRect(nsPoint(), aSize)); 2028 rootFrame->SetHasBSizeChange(true); 2029 DoReflow(rootFrame, true, nullptr); 2030 } 2031 } 2032 2033 // Now, we may have been destroyed by the destructor of 2034 // `nsAutoCauseReflowNotifier`. 2035 2036 mPendingDidDoReflow = true; 2037 DidDoReflow(true); 2038 2039 // the reflow above should've set our bsize if it was NS_UNCONSTRAINEDSIZE, 2040 // and the isize shouldn't be NS_UNCONSTRAINEDSIZE anyway. 2041 MOZ_DIAGNOSTIC_ASSERT( 2042 mPresContext->GetVisibleArea().width != NS_UNCONSTRAINEDSIZE, 2043 "width should not be NS_UNCONSTRAINEDSIZE after reflow"); 2044 MOZ_DIAGNOSTIC_ASSERT( 2045 mPresContext->GetVisibleArea().height != NS_UNCONSTRAINEDSIZE, 2046 "height should not be NS_UNCONSTRAINEDSIZE after reflow"); 2047 2048 postResizeEventIfNeeded(); 2049 return true; 2050 } 2051 2052 // https://drafts.csswg.org/cssom-view/#document-run-the-resize-steps 2053 void PresShell::RunResizeSteps() { 2054 if (!mResizeEventPending && !mVisualViewportResizeEventPending) { 2055 return; 2056 } 2057 if (mIsDocumentGone) { 2058 return; 2059 } 2060 2061 RefPtr window = nsGlobalWindowInner::Cast(mDocument->GetInnerWindow()); 2062 if (!window) { 2063 return; 2064 } 2065 2066 if (mResizeEventPending) { 2067 // Clear it before firing, just in case the event triggers another resize 2068 // event. Such event will fire next tick. 2069 mResizeEventPending = false; 2070 WidgetEvent event(true, mozilla::eResize); 2071 nsEventStatus status = nsEventStatus_eIgnore; 2072 2073 if (RefPtr<nsPIDOMWindowOuter> outer = window->GetOuterWindow()) { 2074 // MOZ_KnownLive due to bug 1506441 2075 EventDispatcher::Dispatch(MOZ_KnownLive(nsGlobalWindowOuter::Cast(outer)), 2076 mPresContext, &event, nullptr, &status); 2077 } 2078 } 2079 2080 if (mVisualViewportResizeEventPending) { 2081 mVisualViewportResizeEventPending = false; 2082 RefPtr vv = window->VisualViewport(); 2083 vv->FireResizeEvent(); 2084 } 2085 } 2086 2087 // https://drafts.csswg.org/cssom-view/#document-run-the-scroll-steps 2088 // But note: https://github.com/w3c/csswg-drafts/issues/11164 2089 void PresShell::RunScrollSteps() { 2090 // Scroll events are one-shot, so after running them we can drop them. 2091 // However, dispatching a scroll event can potentially cause more scroll 2092 // events to be posted, so we move the initial set into a temporary array 2093 // first. (Newly posted scroll events will be dispatched on the next tick.) 2094 auto events = std::move(mPendingScrollEvents); 2095 for (auto& event : events) { 2096 event->Run(); 2097 } 2098 } 2099 2100 static nsIContent* GetNativeAnonymousSubtreeRoot(nsIContent* aContent) { 2101 if (!aContent) { 2102 return nullptr; 2103 } 2104 return aContent->GetClosestNativeAnonymousSubtreeRoot(); 2105 } 2106 2107 void PresShell::NativeAnonymousContentWillBeRemoved(nsIContent* aAnonContent) { 2108 MOZ_ASSERT(aAnonContent->IsRootOfNativeAnonymousSubtree()); 2109 mPresContext->EventStateManager()->NativeAnonymousContentRemoved( 2110 aAnonContent); 2111 #ifdef ACCESSIBILITY 2112 if (nsAccessibilityService* accService = GetAccService()) { 2113 accService->ContentRemoved(this, aAnonContent); 2114 } 2115 #endif 2116 if (mDocument->DevToolsAnonymousAndShadowEventsEnabled()) { 2117 aAnonContent->QueueDevtoolsAnonymousEvent(/* aIsRemove = */ true); 2118 } 2119 if (nsIContent* root = 2120 GetNativeAnonymousSubtreeRoot(mCurrentEventTarget.mContent)) { 2121 if (aAnonContent == root) { 2122 mCurrentEventTarget.UpdateFrameAndContent( 2123 nullptr, aAnonContent->GetFlattenedTreeParent()); 2124 } 2125 } 2126 2127 for (EventTargetInfo& eventTargetInfo : mCurrentEventTargetStack) { 2128 nsIContent* anon = GetNativeAnonymousSubtreeRoot(eventTargetInfo.mContent); 2129 if (aAnonContent == anon) { 2130 eventTargetInfo.UpdateFrameAndContent( 2131 nullptr, aAnonContent->GetFlattenedTreeParent()); 2132 } 2133 } 2134 } 2135 2136 void PresShell::SetIgnoreFrameDestruction(bool aIgnore) { 2137 if (mDocument) { 2138 // We need to tell the ImageLoader to drop all its references to frames 2139 // because they're about to go away and it won't get notifications of that. 2140 mDocument->EnsureStyleImageLoader().ClearFrames(mPresContext); 2141 } 2142 mIgnoreFrameDestruction = aIgnore; 2143 } 2144 2145 void PresShell::NotifyDestroyingFrame(nsIFrame* aFrame) { 2146 // We must remove these from FrameLayerBuilder::DisplayItemData::mFrameList 2147 // here, otherwise the DisplayItemData destructor will use the destroyed frame 2148 // when it tries to remove it from the (array) value of this property. 2149 aFrame->RemoveDisplayItemDataForDeletion(); 2150 2151 if (!mIgnoreFrameDestruction) { 2152 if (aFrame->HasImageRequest()) { 2153 mDocument->EnsureStyleImageLoader().DropRequestsForFrame(aFrame); 2154 } 2155 2156 mFrameConstructor->NotifyDestroyingFrame(aFrame); 2157 2158 mDirtyRoots.Remove(aFrame); 2159 2160 // Remove frame properties 2161 aFrame->RemoveAllProperties(); 2162 2163 const auto ComputeTargetContent = 2164 [&aFrame](const EventTargetInfo& aEventTargetInfo) -> nsIContent* { 2165 if (!IsForbiddenDispatchingToNonElementContent( 2166 aEventTargetInfo.mEventMessage)) { 2167 return aFrame->GetContent(); 2168 } 2169 return aFrame->GetContent() 2170 ? aFrame->GetContent() 2171 ->GetInclusiveFlattenedTreeAncestorElement() 2172 : nullptr; 2173 }; 2174 2175 if (aFrame == mCurrentEventTarget.mFrame) { 2176 mCurrentEventTarget.UpdateFrameAndContent( 2177 nullptr, ComputeTargetContent(mCurrentEventTarget)); 2178 } 2179 2180 for (EventTargetInfo& eventTargetInfo : mCurrentEventTargetStack) { 2181 if (aFrame == eventTargetInfo.mFrame) { 2182 // One of our stack frames was deleted. Get its content so that when we 2183 // pop it we can still get its new frame from its content 2184 eventTargetInfo.UpdateFrameAndContent( 2185 nullptr, ComputeTargetContent(eventTargetInfo)); 2186 } 2187 } 2188 2189 mFramesToDirty.Remove(aFrame); 2190 mOrthogonalFlows.Remove(aFrame); 2191 2192 if (ScrollContainerFrame* scrollContainerFrame = do_QueryFrame(aFrame)) { 2193 mPendingScrollAnchorSelection.Remove(scrollContainerFrame); 2194 mPendingScrollAnchorAdjustment.Remove(scrollContainerFrame); 2195 mPendingScrollResnap.Remove(scrollContainerFrame); 2196 } 2197 } 2198 } 2199 2200 already_AddRefed<nsCaret> PresShell::GetCaret() const { 2201 RefPtr<nsCaret> caret = mCaret; 2202 return caret.forget(); 2203 } 2204 2205 already_AddRefed<AccessibleCaretEventHub> 2206 PresShell::GetAccessibleCaretEventHub() const { 2207 RefPtr<AccessibleCaretEventHub> eventHub = mAccessibleCaretEventHub; 2208 return eventHub.forget(); 2209 } 2210 2211 void PresShell::SetCaret(nsCaret* aNewCaret) { 2212 if (mCaret == aNewCaret) { 2213 return; 2214 } 2215 if (mCaret) { 2216 mCaret->SchedulePaint(); 2217 } 2218 mCaret = aNewCaret; 2219 if (aNewCaret) { 2220 aNewCaret->SchedulePaint(); 2221 } 2222 } 2223 2224 void PresShell::RestoreCaret() { SetCaret(mOriginalCaret); } 2225 2226 NS_IMETHODIMP PresShell::SetCaretEnabled(bool aInEnable) { 2227 bool oldEnabled = mCaretEnabled; 2228 2229 mCaretEnabled = aInEnable; 2230 2231 if (mCaretEnabled != oldEnabled) { 2232 MOZ_ASSERT(mCaret); 2233 if (mCaret) { 2234 mCaret->SetVisible(mCaretEnabled); 2235 } 2236 } 2237 2238 return NS_OK; 2239 } 2240 2241 NS_IMETHODIMP PresShell::SetCaretReadOnly(bool aReadOnly) { 2242 if (mCaret) { 2243 mCaret->SetCaretReadOnly(aReadOnly); 2244 } 2245 return NS_OK; 2246 } 2247 2248 NS_IMETHODIMP PresShell::GetCaretEnabled(bool* aOutEnabled) { 2249 NS_ENSURE_ARG_POINTER(aOutEnabled); 2250 *aOutEnabled = mCaretEnabled; 2251 return NS_OK; 2252 } 2253 2254 NS_IMETHODIMP PresShell::SetCaretVisibilityDuringSelection(bool aVisibility) { 2255 if (mCaret) { 2256 mCaret->SetVisibilityDuringSelection(aVisibility); 2257 } 2258 return NS_OK; 2259 } 2260 2261 NS_IMETHODIMP PresShell::GetCaretVisible(bool* aOutIsVisible) { 2262 *aOutIsVisible = false; 2263 if (mCaret) { 2264 *aOutIsVisible = mCaret->IsVisible(); 2265 } 2266 return NS_OK; 2267 } 2268 2269 NS_IMETHODIMP PresShell::SetSelectionFlags(int16_t aFlags) { 2270 mSelectionFlags = aFlags; 2271 return NS_OK; 2272 } 2273 2274 NS_IMETHODIMP PresShell::GetSelectionFlags(int16_t* aFlags) { 2275 if (!aFlags) { 2276 return NS_ERROR_INVALID_ARG; 2277 } 2278 2279 *aFlags = mSelectionFlags; 2280 return NS_OK; 2281 } 2282 2283 // implementation of nsISelectionController 2284 2285 NS_IMETHODIMP 2286 PresShell::PhysicalMove(int16_t aDirection, int16_t aAmount, bool aExtend) { 2287 RefPtr<nsFrameSelection> frameSelection = mSelection; 2288 return frameSelection->PhysicalMove(aDirection, aAmount, aExtend); 2289 } 2290 2291 NS_IMETHODIMP 2292 PresShell::CharacterMove(bool aForward, bool aExtend) { 2293 RefPtr<nsFrameSelection> frameSelection = mSelection; 2294 return frameSelection->CharacterMove(aForward, aExtend); 2295 } 2296 2297 NS_IMETHODIMP 2298 PresShell::WordMove(bool aForward, bool aExtend) { 2299 RefPtr<nsFrameSelection> frameSelection = mSelection; 2300 nsresult result = frameSelection->WordMove(aForward, aExtend); 2301 // if we can't go down/up any more we must then move caret completely to 2302 // end/beginning respectively. 2303 if (NS_FAILED(result)) { 2304 result = CompleteMove(aForward, aExtend); 2305 } 2306 return result; 2307 } 2308 2309 NS_IMETHODIMP 2310 PresShell::LineMove(bool aForward, bool aExtend) { 2311 RefPtr<nsFrameSelection> frameSelection = mSelection; 2312 nsresult result = frameSelection->LineMove(aForward, aExtend); 2313 // if we can't go down/up any more we must then move caret completely to 2314 // end/beginning respectively. 2315 if (NS_FAILED(result)) { 2316 result = CompleteMove(aForward, aExtend); 2317 } 2318 return result; 2319 } 2320 2321 NS_IMETHODIMP 2322 PresShell::IntraLineMove(bool aForward, bool aExtend) { 2323 RefPtr<nsFrameSelection> frameSelection = mSelection; 2324 return frameSelection->IntraLineMove(aForward, aExtend); 2325 } 2326 2327 NS_IMETHODIMP 2328 PresShell::PageMove(bool aForward, bool aExtend) { 2329 nsIFrame* frame = nullptr; 2330 if (!aExtend) { 2331 frame = GetScrollContainerFrameToScroll(VerticalScrollDirection); 2332 // If there is no scrollable frame, get the frame to move caret instead. 2333 } 2334 if (!frame || frame->PresContext() != mPresContext) { 2335 frame = mSelection->GetFrameToPageSelect(); 2336 if (!frame) { 2337 return NS_OK; 2338 } 2339 } 2340 // We may scroll parent scrollable element of current selection limiter. 2341 // In such case, we don't want to scroll selection into view unless 2342 // selection is changed. 2343 RefPtr<nsFrameSelection> frameSelection = mSelection; 2344 return frameSelection->PageMove( 2345 aForward, aExtend, frame, nsFrameSelection::SelectionIntoView::IfChanged); 2346 } 2347 2348 NS_IMETHODIMP 2349 PresShell::ScrollPage(bool aForward) { 2350 ScrollContainerFrame* scrollContainerFrame = 2351 GetScrollContainerFrameToScroll(VerticalScrollDirection); 2352 ScrollMode scrollMode = apz::GetScrollModeForOrigin(ScrollOrigin::Pages); 2353 if (scrollContainerFrame) { 2354 scrollContainerFrame->ScrollBy(nsIntPoint(0, aForward ? 1 : -1), 2355 ScrollUnit::PAGES, scrollMode, nullptr, 2356 mozilla::ScrollOrigin::NotSpecified, 2357 ScrollContainerFrame::NOT_MOMENTUM, 2358 ScrollSnapFlags::IntendedDirection | 2359 ScrollSnapFlags::IntendedEndPosition); 2360 } 2361 return NS_OK; 2362 } 2363 2364 NS_IMETHODIMP 2365 PresShell::ScrollLine(bool aForward) { 2366 ScrollContainerFrame* scrollContainerFrame = 2367 GetScrollContainerFrameToScroll(VerticalScrollDirection); 2368 ScrollMode scrollMode = apz::GetScrollModeForOrigin(ScrollOrigin::Lines); 2369 if (scrollContainerFrame) { 2370 nsRect scrollPort = scrollContainerFrame->GetScrollPortRect(); 2371 nsSize lineSize = scrollContainerFrame->GetLineScrollAmount(); 2372 int32_t lineCount = StaticPrefs::toolkit_scrollbox_verticalScrollDistance(); 2373 if (lineCount * lineSize.height > scrollPort.Height()) { 2374 return ScrollPage(aForward); 2375 } 2376 scrollContainerFrame->ScrollBy( 2377 nsIntPoint(0, aForward ? lineCount : -lineCount), ScrollUnit::LINES, 2378 scrollMode, nullptr, mozilla::ScrollOrigin::NotSpecified, 2379 ScrollContainerFrame::NOT_MOMENTUM, ScrollSnapFlags::IntendedDirection); 2380 } 2381 return NS_OK; 2382 } 2383 2384 NS_IMETHODIMP 2385 PresShell::ScrollCharacter(bool aRight) { 2386 ScrollContainerFrame* scrollContainerFrame = 2387 GetScrollContainerFrameToScroll(HorizontalScrollDirection); 2388 ScrollMode scrollMode = apz::GetScrollModeForOrigin(ScrollOrigin::Lines); 2389 if (scrollContainerFrame) { 2390 int32_t h = StaticPrefs::toolkit_scrollbox_horizontalScrollDistance(); 2391 scrollContainerFrame->ScrollBy( 2392 nsIntPoint(aRight ? h : -h, 0), ScrollUnit::LINES, scrollMode, nullptr, 2393 mozilla::ScrollOrigin::NotSpecified, ScrollContainerFrame::NOT_MOMENTUM, 2394 ScrollSnapFlags::IntendedDirection); 2395 } 2396 return NS_OK; 2397 } 2398 2399 NS_IMETHODIMP 2400 PresShell::CompleteScroll(bool aForward) { 2401 ScrollContainerFrame* scrollContainerFrame = 2402 GetScrollContainerFrameToScroll(VerticalScrollDirection); 2403 ScrollMode scrollMode = apz::GetScrollModeForOrigin(ScrollOrigin::Other); 2404 if (scrollContainerFrame) { 2405 scrollContainerFrame->ScrollBy(nsIntPoint(0, aForward ? 1 : -1), 2406 ScrollUnit::WHOLE, scrollMode, nullptr, 2407 mozilla::ScrollOrigin::NotSpecified, 2408 ScrollContainerFrame::NOT_MOMENTUM, 2409 ScrollSnapFlags::IntendedEndPosition); 2410 } 2411 return NS_OK; 2412 } 2413 2414 NS_IMETHODIMP 2415 PresShell::CompleteMove(bool aForward, bool aExtend) { 2416 // Beware! This may flush notifications via synchronous 2417 // ScrollSelectionIntoView. 2418 RefPtr<nsFrameSelection> frameSelection = mSelection; 2419 Element* const limiter = frameSelection->GetAncestorLimiter(); 2420 nsIFrame* frame = limiter ? limiter->GetPrimaryFrame() 2421 : FrameConstructor()->GetRootElementFrame(); 2422 if (!frame) { 2423 return NS_ERROR_FAILURE; 2424 } 2425 nsIFrame::CaretPosition pos = frame->GetExtremeCaretPosition(!aForward); 2426 2427 const nsFrameSelection::FocusMode focusMode = 2428 aExtend ? nsFrameSelection::FocusMode::kExtendSelection 2429 : nsFrameSelection::FocusMode::kCollapseToNewPoint; 2430 frameSelection->HandleClick( 2431 MOZ_KnownLive(pos.mResultContent) /* bug 1636889 */, pos.mContentOffset, 2432 pos.mContentOffset, focusMode, 2433 aForward ? CaretAssociationHint::After : CaretAssociationHint::Before); 2434 if (limiter) { 2435 // HandleClick resets ancestorLimiter, so set it again. 2436 frameSelection->SetAncestorLimiter(limiter); 2437 } 2438 2439 // After ScrollSelectionIntoView(), the pending notifications might be 2440 // flushed and PresShell/PresContext/Frames may be dead. See bug 418470. 2441 return ScrollSelectionIntoView(SelectionType::eNormal, 2442 nsISelectionController::SELECTION_FOCUS_REGION, 2443 SelectionScrollMode::SyncFlush); 2444 } 2445 2446 // end implementations nsISelectionController 2447 2448 ScrollContainerFrame* PresShell::GetRootScrollContainerFrame() const { 2449 if (!mFrameConstructor) { 2450 return nullptr; 2451 } 2452 nsIFrame* rootFrame = mFrameConstructor->GetRootFrame(); 2453 if (!rootFrame) { 2454 return nullptr; 2455 } 2456 nsIFrame* theFrame = rootFrame->PrincipalChildList().FirstChild(); 2457 if (!theFrame || !theFrame->IsScrollContainerFrame()) { 2458 return nullptr; 2459 } 2460 return static_cast<ScrollContainerFrame*>(theFrame); 2461 } 2462 2463 nsPageSequenceFrame* PresShell::GetPageSequenceFrame() const { 2464 return mFrameConstructor->GetPageSequenceFrame(); 2465 } 2466 2467 nsCanvasFrame* PresShell::GetCanvasFrame() const { 2468 return mFrameConstructor->GetCanvasFrame(); 2469 } 2470 2471 void PresShell::RestoreRootScrollPosition() { 2472 if (ScrollContainerFrame* sf = GetRootScrollContainerFrame()) { 2473 sf->ScrollToRestoredPosition(); 2474 } 2475 } 2476 2477 void PresShell::MaybeReleaseCapturingContent() { 2478 RefPtr<nsFrameSelection> frameSelection = FrameSelection(); 2479 if (frameSelection) { 2480 frameSelection->SetDragState(false); 2481 } 2482 if (sCapturingContentInfo.mContent && 2483 sCapturingContentInfo.mContent->OwnerDoc() == mDocument) { 2484 PresShell::ReleaseCapturingContent(); 2485 } 2486 } 2487 2488 void PresShell::BeginLoad(Document* aDocument) { 2489 mDocumentLoading = true; 2490 2491 SuppressDisplayport(true); 2492 2493 gfxTextPerfMetrics* tp = nullptr; 2494 if (mPresContext) { 2495 tp = mPresContext->GetTextPerfMetrics(); 2496 } 2497 2498 bool shouldLog = MOZ_LOG_TEST(gLog, LogLevel::Debug); 2499 if (shouldLog || tp) { 2500 mLoadBegin = TimeStamp::Now(); 2501 } 2502 2503 if (shouldLog) { 2504 nsIURI* uri = mDocument->GetDocumentURI(); 2505 MOZ_LOG(gLog, LogLevel::Debug, 2506 ("(presshell) %p load begin [%s]\n", this, 2507 uri ? uri->GetSpecOrDefault().get() : "")); 2508 } 2509 } 2510 2511 void PresShell::EndLoad(Document* aDocument) { 2512 MOZ_ASSERT(aDocument == mDocument, "Wrong document"); 2513 2514 SuppressDisplayport(false); 2515 RestoreRootScrollPosition(); 2516 2517 mDocumentLoading = false; 2518 } 2519 2520 void PresShell::LoadComplete() { 2521 gfxTextPerfMetrics* tp = nullptr; 2522 if (mPresContext) { 2523 tp = mPresContext->GetTextPerfMetrics(); 2524 } 2525 2526 // log load 2527 bool shouldLog = MOZ_LOG_TEST(gLog, LogLevel::Debug); 2528 if (shouldLog || tp) { 2529 TimeDuration loadTime = TimeStamp::Now() - mLoadBegin; 2530 nsIURI* uri = mDocument->GetDocumentURI(); 2531 nsAutoCString spec; 2532 if (uri) { 2533 spec = uri->GetSpecOrDefault(); 2534 } 2535 if (shouldLog) { 2536 MOZ_LOG(gLog, LogLevel::Debug, 2537 ("(presshell) %p load done time-ms: %9.2f [%s]\n", this, 2538 loadTime.ToMilliseconds(), spec.get())); 2539 } 2540 if (tp) { 2541 tp->Accumulate(); 2542 if (tp->cumulative.numChars > 0) { 2543 LogTextPerfStats(tp, this, tp->cumulative, loadTime.ToMilliseconds(), 2544 eLog_loaddone, spec.get()); 2545 } 2546 } 2547 } 2548 } 2549 2550 #ifdef DEBUG 2551 void PresShell::VerifyHasDirtyRootAncestor(nsIFrame* aFrame) { 2552 // XXXbz due to bug 372769, can't actually assert anything here... 2553 // XXX Since bug 372769 is now fixed, the assertion is being enabled in bug 2554 // 1758104. 2555 # if 0 2556 // XXXbz shouldn't need this part; remove it once FrameNeedsReflow 2557 // handles the root frame correctly. 2558 if (!aFrame->GetParent()) { 2559 return; 2560 } 2561 2562 // Make sure that there is a reflow root ancestor of |aFrame| that's 2563 // in mDirtyRoots already. 2564 while (aFrame && aFrame->HasAnyStateBits(NS_FRAME_HAS_DIRTY_CHILDREN)) { 2565 if ((aFrame->HasAnyStateBits(NS_FRAME_REFLOW_ROOT | 2566 NS_FRAME_DYNAMIC_REFLOW_ROOT) || 2567 !aFrame->GetParent()) && 2568 mDirtyRoots.Contains(aFrame)) { 2569 return; 2570 } 2571 2572 aFrame = aFrame->GetParent(); 2573 } 2574 2575 MOZ_ASSERT_UNREACHABLE( 2576 "Frame has dirty bits set but isn't scheduled to be " 2577 "reflowed?"); 2578 # endif 2579 } 2580 #endif 2581 2582 void PresShell::PostPendingScrollAnchorSelection( 2583 mozilla::layout::ScrollAnchorContainer* aContainer) { 2584 mPendingScrollAnchorSelection.Insert(aContainer->ScrollContainer()); 2585 } 2586 2587 void PresShell::FlushPendingScrollAnchorSelections() { 2588 for (ScrollContainerFrame* scroll : mPendingScrollAnchorSelection) { 2589 scroll->Anchor()->SelectAnchor(); 2590 } 2591 mPendingScrollAnchorSelection.Clear(); 2592 } 2593 2594 void PresShell::PostPendingScrollAnchorAdjustment( 2595 ScrollAnchorContainer* aContainer) { 2596 mPendingScrollAnchorAdjustment.Insert(aContainer->ScrollContainer()); 2597 } 2598 2599 void PresShell::FlushPendingScrollAnchorAdjustments() { 2600 for (ScrollContainerFrame* scroll : mPendingScrollAnchorAdjustment) { 2601 scroll->Anchor()->ApplyAdjustments(); 2602 } 2603 mPendingScrollAnchorAdjustment.Clear(); 2604 } 2605 2606 void PresShell::PostPendingScrollResnap( 2607 ScrollContainerFrame* aScrollContainerFrame) { 2608 mPendingScrollResnap.Insert(aScrollContainerFrame); 2609 } 2610 2611 void PresShell::FlushPendingScrollResnap() { 2612 for (ScrollContainerFrame* scrollContainerFrame : mPendingScrollResnap) { 2613 scrollContainerFrame->TryResnap(); 2614 } 2615 mPendingScrollResnap.Clear(); 2616 } 2617 2618 void PresShell::FrameNeedsReflow(nsIFrame* aFrame, 2619 IntrinsicDirty aIntrinsicDirty, 2620 nsFrameState aBitToAdd, 2621 ReflowRootHandling aRootHandling) { 2622 MOZ_ASSERT(aBitToAdd == NS_FRAME_IS_DIRTY || 2623 aBitToAdd == NS_FRAME_HAS_DIRTY_CHILDREN || !aBitToAdd, 2624 "Unexpected bits being added"); 2625 2626 // FIXME bug 478135 2627 NS_ASSERTION( 2628 aIntrinsicDirty != IntrinsicDirty::FrameAncestorsAndDescendants || 2629 aBitToAdd != NS_FRAME_HAS_DIRTY_CHILDREN, 2630 "bits don't correspond to style change reason"); 2631 2632 // FIXME bug 457400 2633 NS_ASSERTION(!mIsReflowing, "can't mark frame dirty during reflow"); 2634 2635 // If we've not yet done the initial reflow, then don't bother 2636 // enqueuing a reflow command yet. 2637 if (!mDidInitialize) { 2638 return; 2639 } 2640 2641 // If we're already destroying, don't bother with this either. 2642 if (mIsDestroying) { 2643 return; 2644 } 2645 2646 AutoTArray<nsIFrame*, 4> subtrees; 2647 subtrees.AppendElement(aFrame); 2648 2649 do { 2650 nsIFrame* subtreeRoot = subtrees.PopLastElement(); 2651 2652 // Grab |wasDirty| now so we can go ahead and update the bits on 2653 // subtreeRoot. 2654 bool wasDirty = subtreeRoot->IsSubtreeDirty(); 2655 subtreeRoot->AddStateBits(aBitToAdd); 2656 2657 // Determine whether we need to keep looking for the next ancestor 2658 // reflow root if subtreeRoot itself is a reflow root. 2659 bool targetNeedsReflowFromParent; 2660 switch (aRootHandling) { 2661 case ReflowRootHandling::PositionOrSizeChange: 2662 targetNeedsReflowFromParent = true; 2663 break; 2664 case ReflowRootHandling::NoPositionOrSizeChange: 2665 targetNeedsReflowFromParent = false; 2666 break; 2667 case ReflowRootHandling::InferFromBitToAdd: 2668 targetNeedsReflowFromParent = (aBitToAdd == NS_FRAME_IS_DIRTY); 2669 break; 2670 } 2671 2672 auto FrameIsReflowRoot = [](const nsIFrame* aFrame) { 2673 return aFrame->HasAnyStateBits(NS_FRAME_REFLOW_ROOT | 2674 NS_FRAME_DYNAMIC_REFLOW_ROOT); 2675 }; 2676 2677 auto CanStopClearingAncestorIntrinsics = [&](const nsIFrame* aFrame) { 2678 return FrameIsReflowRoot(aFrame) && aFrame != subtreeRoot; 2679 }; 2680 2681 auto IsReflowBoundary = [&](const nsIFrame* aFrame) { 2682 return FrameIsReflowRoot(aFrame) && 2683 (aFrame != subtreeRoot || !targetNeedsReflowFromParent); 2684 }; 2685 2686 // Mark the intrinsic widths as dirty on the frame, all of its ancestors, 2687 // and all of its descendants, if needed: 2688 2689 if (aIntrinsicDirty != IntrinsicDirty::None) { 2690 // Mark argument and all ancestors dirty. (Unless we hit a reflow root 2691 // that should contain the reflow. 2692 for (nsIFrame* a = subtreeRoot; 2693 a && !CanStopClearingAncestorIntrinsics(a); a = a->GetParent()) { 2694 a->MarkIntrinsicISizesDirty(); 2695 if (a->IsAbsolutelyPositioned()) { 2696 // If we get here, 'a' is abspos, so its subtree's intrinsic sizing 2697 // has no effect on its ancestors' intrinsic sizing. So, don't loop 2698 // upwards any further. 2699 break; 2700 } 2701 } 2702 } 2703 2704 const bool frameAncestorAndDescendantISizesDirty = 2705 (aIntrinsicDirty == IntrinsicDirty::FrameAncestorsAndDescendants); 2706 const bool dirty = (aBitToAdd == NS_FRAME_IS_DIRTY); 2707 if (frameAncestorAndDescendantISizesDirty || dirty) { 2708 // Mark all descendants dirty (using an nsTArray stack rather than 2709 // recursion). 2710 // Note that ReflowInput::InitResizeFlags has some similar 2711 // code; see comments there for how and why it differs. 2712 AutoTArray<nsIFrame*, 32> stack; 2713 stack.AppendElement(subtreeRoot); 2714 2715 do { 2716 nsIFrame* f = stack.PopLastElement(); 2717 2718 if (frameAncestorAndDescendantISizesDirty && f->IsPlaceholderFrame()) { 2719 // Call `GetOutOfFlowFrame` directly because we can get here from 2720 // frame destruction and the placeholder might be already torn down. 2721 if (nsIFrame* oof = 2722 static_cast<nsPlaceholderFrame*>(f)->GetOutOfFlowFrame()) { 2723 if (!nsLayoutUtils::IsProperAncestorFrame(subtreeRoot, oof)) { 2724 // We have another distinct subtree we need to mark. 2725 subtrees.AppendElement(oof); 2726 } 2727 } 2728 } 2729 2730 for (const auto& childList : f->ChildLists()) { 2731 for (nsIFrame* kid : childList.mList) { 2732 if (frameAncestorAndDescendantISizesDirty) { 2733 kid->MarkIntrinsicISizesDirty(); 2734 } 2735 if (dirty) { 2736 kid->AddStateBits(NS_FRAME_IS_DIRTY); 2737 } 2738 stack.AppendElement(kid); 2739 } 2740 } 2741 } while (stack.Length() != 0); 2742 } 2743 2744 // Skip setting dirty bits up the tree if we weren't given a bit to add. 2745 if (!aBitToAdd) { 2746 continue; 2747 } 2748 2749 // Set NS_FRAME_HAS_DIRTY_CHILDREN bits (via nsIFrame::ChildIsDirty) 2750 // up the tree until we reach either a frame that's already dirty or 2751 // a reflow root. 2752 nsIFrame* f = subtreeRoot; 2753 for (;;) { 2754 if (IsReflowBoundary(f) || !f->GetParent()) { 2755 // we've hit a reflow root or the root frame 2756 if (!wasDirty) { 2757 mDirtyRoots.Add(f); 2758 SetNeedLayoutFlush(); 2759 } 2760 #ifdef DEBUG 2761 else { 2762 VerifyHasDirtyRootAncestor(f); 2763 } 2764 #endif 2765 2766 break; 2767 } 2768 2769 nsIFrame* child = f; 2770 f = f->GetParent(); 2771 wasDirty = f->IsSubtreeDirty(); 2772 f->ChildIsDirty(child); 2773 NS_ASSERTION(f->HasAnyStateBits(NS_FRAME_HAS_DIRTY_CHILDREN), 2774 "ChildIsDirty didn't do its job"); 2775 if (wasDirty) { 2776 // This frame was already marked dirty. 2777 #ifdef DEBUG 2778 VerifyHasDirtyRootAncestor(f); 2779 #endif 2780 break; 2781 } 2782 } 2783 } while (subtrees.Length() != 0); 2784 2785 EnsureLayoutFlush(); 2786 } 2787 2788 void PresShell::FrameNeedsToContinueReflow(nsIFrame* aFrame) { 2789 NS_ASSERTION(mIsReflowing, "Must be in reflow when marking path dirty."); 2790 MOZ_ASSERT(mCurrentReflowRoot, "Must have a current reflow root here"); 2791 NS_ASSERTION( 2792 aFrame == mCurrentReflowRoot || 2793 nsLayoutUtils::IsProperAncestorFrame(mCurrentReflowRoot, aFrame), 2794 "Frame passed in is not the descendant of mCurrentReflowRoot"); 2795 NS_ASSERTION(aFrame->HasAnyStateBits(NS_FRAME_IN_REFLOW), 2796 "Frame passed in not in reflow?"); 2797 2798 mFramesToDirty.Insert(aFrame); 2799 } 2800 2801 already_AddRefed<nsIContent> PresShell::GetContentForScrolling() const { 2802 if (nsCOMPtr<nsIContent> focused = GetFocusedContentInOurWindow()) { 2803 return focused.forget(); 2804 } 2805 return GetSelectedContentForScrolling(); 2806 } 2807 2808 already_AddRefed<nsIContent> PresShell::GetSelectedContentForScrolling() const { 2809 nsCOMPtr<nsIContent> selectedContent; 2810 if (mSelection) { 2811 Selection& domSelection = mSelection->NormalSelection(); 2812 selectedContent = nsIContent::FromNodeOrNull(domSelection.GetFocusNode()); 2813 } 2814 return selectedContent.forget(); 2815 } 2816 2817 ScrollContainerFrame* PresShell::GetScrollContainerFrameToScrollForContent( 2818 nsIContent* aContent, ScrollDirections aDirections) { 2819 ScrollContainerFrame* scrollContainerFrame = nullptr; 2820 if (aContent) { 2821 nsIFrame* startFrame = aContent->GetPrimaryFrame(); 2822 if (startFrame) { 2823 scrollContainerFrame = startFrame->GetScrollTargetFrame(); 2824 if (scrollContainerFrame) { 2825 startFrame = scrollContainerFrame->GetScrolledFrame(); 2826 } 2827 scrollContainerFrame = 2828 nsLayoutUtils::GetNearestScrollableFrameForDirection(startFrame, 2829 aDirections); 2830 } 2831 } 2832 if (!scrollContainerFrame) { 2833 scrollContainerFrame = GetRootScrollContainerFrame(); 2834 if (!scrollContainerFrame || !scrollContainerFrame->GetScrolledFrame()) { 2835 return nullptr; 2836 } 2837 scrollContainerFrame = nsLayoutUtils::GetNearestScrollableFrameForDirection( 2838 scrollContainerFrame->GetScrolledFrame(), aDirections); 2839 } 2840 return scrollContainerFrame; 2841 } 2842 2843 ScrollContainerFrame* PresShell::GetScrollContainerFrameToScroll( 2844 ScrollDirections aDirections) { 2845 nsCOMPtr<nsIContent> content = GetContentForScrolling(); 2846 return GetScrollContainerFrameToScrollForContent(content.get(), aDirections); 2847 } 2848 2849 void PresShell::CancelAllPendingReflows() { mDirtyRoots.Clear(); } 2850 2851 static bool DestroyFramesAndStyleDataFor( 2852 Element* aElement, nsPresContext& aPresContext, 2853 RestyleManager::IncludeRoot aIncludeRoot) { 2854 bool didReconstruct = 2855 aPresContext.FrameConstructor()->DestroyFramesFor(aElement); 2856 RestyleManager::ClearServoDataFromSubtree(aElement, aIncludeRoot); 2857 return didReconstruct; 2858 } 2859 2860 void PresShell::SlotAssignmentWillChange(Element& aElement, 2861 HTMLSlotElement* aOldSlot, 2862 HTMLSlotElement* aNewSlot) { 2863 MOZ_ASSERT(aOldSlot != aNewSlot); 2864 2865 if (MOZ_UNLIKELY(!mDidInitialize)) { 2866 return; 2867 } 2868 2869 // If the old slot is about to become empty and show fallback, let layout know 2870 // that it needs to do work. 2871 if (aOldSlot && aOldSlot->AssignedNodes().Length() == 1 && 2872 aOldSlot->HasChildren()) { 2873 DestroyFramesForAndRestyle(aOldSlot); 2874 } 2875 2876 // Ensure the new element starts off clean. 2877 DestroyFramesAndStyleDataFor(&aElement, *mPresContext, 2878 RestyleManager::IncludeRoot::Yes); 2879 2880 if (aNewSlot) { 2881 // If the new slot will stop showing fallback content, we need to reframe it 2882 // altogether. 2883 if (aNewSlot->AssignedNodes().IsEmpty() && aNewSlot->HasChildren()) { 2884 DestroyFramesForAndRestyle(aNewSlot); 2885 // Otherwise we just care about the element, but we need to ensure that 2886 // something takes care of traversing to the relevant slot, if needed. 2887 } else if (aNewSlot->HasServoData() && 2888 !Servo_Element_IsDisplayNone(aNewSlot)) { 2889 // Set the reframe bits... 2890 aNewSlot->NoteDescendantsNeedFramesForServo(); 2891 aElement.SetFlags(NODE_NEEDS_FRAME); 2892 // Now the style dirty bits. Note that we can't just do 2893 // aElement.NoteDirtyForServo(), because the new slot is not setup yet. 2894 aNewSlot->SetHasDirtyDescendantsForServo(); 2895 aNewSlot->NoteDirtySubtreeForServo(); 2896 } 2897 } 2898 } 2899 2900 #ifdef DEBUG 2901 static void AssertNoFramesOrStyleDataInDescendants(Element& aElement) { 2902 for (nsINode* node : ShadowIncludingTreeIterator(aElement)) { 2903 nsIContent* c = nsIContent::FromNode(node); 2904 if (c == &aElement) { 2905 continue; 2906 } 2907 // FIXME(emilio): The <area> check is needed because of bug 135040. 2908 MOZ_ASSERT(!c->GetPrimaryFrame() || c->IsHTMLElement(nsGkAtoms::area)); 2909 MOZ_ASSERT(!c->IsElement() || !c->AsElement()->HasServoData()); 2910 } 2911 } 2912 #endif 2913 2914 void PresShell::DestroyFramesForAndRestyle(Element* aElement) { 2915 #ifdef DEBUG 2916 auto postCondition = MakeScopeExit([&]() { 2917 MOZ_ASSERT(!aElement->GetPrimaryFrame()); 2918 AssertNoFramesOrStyleDataInDescendants(*aElement); 2919 }); 2920 #endif 2921 2922 MOZ_ASSERT(aElement); 2923 if (!aElement->HasServoData()) { 2924 // Nothing to do here, the element already is out of the flat tree or is not 2925 // styled. 2926 return; 2927 } 2928 2929 // Mark ourselves as not safe to flush while we're doing frame destruction. 2930 nsAutoScriptBlocker scriptBlocker; 2931 ++mChangeNestCount; 2932 2933 const bool didReconstruct = FrameConstructor()->DestroyFramesFor(aElement); 2934 // Clear the style data from all the flattened tree descendants, but _not_ 2935 // from us, since otherwise we wouldn't see the reframe. 2936 RestyleManager::ClearServoDataFromSubtree(aElement, 2937 RestyleManager::IncludeRoot::No); 2938 auto changeHint = 2939 didReconstruct ? nsChangeHint(0) : nsChangeHint_ReconstructFrame; 2940 mPresContext->RestyleManager()->PostRestyleEvent( 2941 aElement, RestyleHint::RestyleSubtree(), changeHint); 2942 2943 --mChangeNestCount; 2944 } 2945 2946 void PresShell::ShadowRootWillBeAttached(Element& aElement) { 2947 #ifdef DEBUG 2948 auto postCondition = MakeScopeExit( 2949 [&]() { AssertNoFramesOrStyleDataInDescendants(aElement); }); 2950 #endif 2951 2952 if (!aElement.HasServoData()) { 2953 // Nothing to do here, the element already is out of the flat tree or is not 2954 // styled. 2955 return; 2956 } 2957 2958 if (!aElement.HasChildren()) { 2959 // The element has no children, just avoid the work. 2960 return; 2961 } 2962 2963 // Mark ourselves as not safe to flush while we're doing frame destruction. 2964 nsAutoScriptBlocker scriptBlocker; 2965 ++mChangeNestCount; 2966 2967 // NOTE(emilio): We use FlattenedChildIterator intentionally here (rather than 2968 // StyleChildrenIterator), since we don't want to remove ::before / ::after 2969 // content. 2970 FlattenedChildIterator iter(&aElement); 2971 nsCSSFrameConstructor* fc = FrameConstructor(); 2972 for (nsIContent* c = iter.GetNextChild(); c; c = iter.GetNextChild()) { 2973 fc->DestroyFramesFor(c); 2974 if (c->IsElement()) { 2975 RestyleManager::ClearServoDataFromSubtree(c->AsElement()); 2976 } 2977 } 2978 2979 #ifdef ACCESSIBILITY 2980 if (nsAccessibilityService* accService = GetAccService()) { 2981 accService->ScheduleAccessibilitySubtreeUpdate(this, &aElement); 2982 } 2983 #endif 2984 2985 --mChangeNestCount; 2986 } 2987 2988 void PresShell::PostRecreateFramesFor(Element* aElement) { 2989 if (MOZ_UNLIKELY(!mDidInitialize)) { 2990 // Nothing to do here. In fact, if we proceed and aElement is the root, we 2991 // will crash. 2992 return; 2993 } 2994 2995 mPresContext->RestyleManager()->PostRestyleEvent( 2996 aElement, RestyleHint{0}, nsChangeHint_ReconstructFrame); 2997 } 2998 2999 void PresShell::RestyleForAnimation(Element* aElement, RestyleHint aHint) { 3000 // Now that we no longer have separate non-animation and animation 3001 // restyles, this method having a distinct identity is less important, 3002 // but it still seems useful to offer as a "more public" API and as a 3003 // checkpoint for these restyles to go through. 3004 mPresContext->RestyleManager()->PostRestyleEvent(aElement, aHint, 3005 nsChangeHint(0)); 3006 } 3007 3008 void PresShell::SetForwardingContainer(const WeakPtr<nsDocShell>& aContainer) { 3009 mForwardingContainer = aContainer; 3010 } 3011 3012 void PresShell::ClearFrameRefs(nsIFrame* aFrame) { 3013 mPresContext->EventStateManager()->ClearFrameRefs(aFrame); 3014 3015 AutoWeakFrame* weakFrame = mAutoWeakFrames; 3016 while (weakFrame) { 3017 AutoWeakFrame* prev = weakFrame->GetPreviousWeakFrame(); 3018 if (weakFrame->GetFrame() == aFrame) { 3019 // This removes weakFrame from mAutoWeakFrames. 3020 weakFrame->Clear(this); 3021 } 3022 weakFrame = prev; 3023 } 3024 3025 AutoTArray<WeakFrame*, 4> toRemove; 3026 for (WeakFrame* weakFrame : mWeakFrames) { 3027 if (weakFrame->GetFrame() == aFrame) { 3028 toRemove.AppendElement(weakFrame); 3029 } 3030 } 3031 for (WeakFrame* weakFrame : toRemove) { 3032 weakFrame->Clear(this); 3033 } 3034 } 3035 3036 UniquePtr<gfxContext> PresShell::CreateReferenceRenderingContext() { 3037 if (mPresContext->IsScreen()) { 3038 return gfxContext::CreateOrNull( 3039 gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget().get()); 3040 } 3041 3042 // We assume the devCtx has positive width and height for this call. 3043 // However, width and height, may be outside of the reasonable range 3044 // so rc may still be null. 3045 nsDeviceContext* devCtx = mPresContext->DeviceContext(); 3046 return devCtx->CreateReferenceRenderingContext(); 3047 } 3048 3049 // https://html.spec.whatwg.org/#scroll-to-the-fragment-identifier 3050 nsresult PresShell::GoToAnchor(const nsAString& aAnchorName, 3051 const nsRange* aFirstTextDirective, bool aScroll, 3052 ScrollFlags aAdditionalScrollFlags) { 3053 if (!mDocument) { 3054 return NS_ERROR_FAILURE; 3055 } 3056 3057 if (mDocument->GetSVGRootElement()) { 3058 // We need to execute this even if there is an empty anchor name 3059 // so that any existing SVG fragment identifier effect is removed 3060 if (SVGFragmentIdentifier::ProcessFragmentIdentifier(mDocument, 3061 aAnchorName)) { 3062 return NS_OK; 3063 } 3064 } 3065 3066 // Hold a reference to the ESM in case event dispatch tears us down. 3067 RefPtr<EventStateManager> esm = mPresContext->EventStateManager(); 3068 3069 // https://wicg.github.io/scroll-to-text-fragment/#invoking-text-directives 3070 // From "Monkeypatching HTML § 7.4.6.3 Scrolling to a fragment:" 3071 // 3.4. If target is a range, then: 3072 // 3.4.1 Set target to be the first common ancestor of target's start node and 3073 // end node. 3074 // 3.4.2 While target is non-null and is not an element, set target to 3075 // target's parent. 3076 // ------ 3077 // Common closest ancestor is not suitable here, as it can scroll to positions 3078 // where no text directive is visible. Instead, scroll to the start container 3079 // of the text directive. 3080 // see https://bugzil.la/1906895 and 3081 // https://github.com/WICG/scroll-to-text-fragment/issues/259 3082 Element* textFragmentTargetElement = [&aFirstTextDirective]() -> Element* { 3083 nsINode* node = aFirstTextDirective 3084 ? aFirstTextDirective->GetStartContainer() 3085 : nullptr; 3086 while (node && !node->IsElement()) { 3087 node = node->GetParent(); 3088 } 3089 return Element::FromNodeOrNull(node); 3090 }(); 3091 const bool thereIsATextFragment = !!textFragmentTargetElement; 3092 3093 // 1. If there is no indicated part of the document, set the Document's target 3094 // element to null. 3095 // 3096 // FIXME(emilio): Per spec empty fragment string should take the same 3097 // code-path as "top"! 3098 if (aAnchorName.IsEmpty() && !thereIsATextFragment) { 3099 NS_ASSERTION(!aScroll, "can't scroll to empty anchor name"); 3100 esm->SetContentState(nullptr, ElementState::URLTARGET); 3101 return NS_OK; 3102 } 3103 3104 // 2. If the indicated part of the document is the top of the document, 3105 // then: 3106 // (handled below when `target` is null, and anchor is `top`) 3107 3108 // 3.1. Let target be element that is the indicated part of the document. 3109 // 3110 // https://html.spec.whatwg.org/#target-element 3111 // https://html.spec.whatwg.org/#find-a-potential-indicated-element 3112 RefPtr<Element> target = textFragmentTargetElement; 3113 if (!target) { 3114 target = nsContentUtils::GetTargetElement(mDocument, aAnchorName); 3115 } 3116 3117 // 1. If there is no indicated part of the document, set the Document's 3118 // target element to null. 3119 // 2.1. Set the Document's target element to null. 3120 // 3.2. Set the Document's target element to target. 3121 esm->SetContentState(target, ElementState::URLTARGET); 3122 3123 // TODO: Spec probably needs a section to account for this. 3124 if (ScrollContainerFrame* rootScroll = GetRootScrollContainerFrame()) { 3125 if (rootScroll->DidHistoryRestore()) { 3126 // Scroll position restored from history trumps scrolling to anchor. 3127 aScroll = false; 3128 rootScroll->ClearDidHistoryRestore(); 3129 } 3130 } 3131 3132 if (target) { 3133 // 3.4 Run the ancestor revealing algorithm on target. 3134 ErrorResult rv; 3135 target->AncestorRevealingAlgorithm(rv); 3136 if (MOZ_UNLIKELY(rv.Failed())) { 3137 return rv.StealNSResult(); 3138 } 3139 3140 if (aScroll) { 3141 // https://wicg.github.io/scroll-to-text-fragment/#invoking-text-directives 3142 // From "Monkeypatching HTML § 7.4.6.3 Scrolling to a fragment:" 3143 // 3.9 Let blockPosition be "center" if scrollTarget is a range, "start" 3144 // otherwise. 3145 // Implementation note: Use `ScrollSelectionIntoView` for text fragment, 3146 // since the text fragment is stored as a `eTargetText` selection. 3147 // 3148 // 3.4. Scroll target into view, with behavior set to "auto", block set to 3149 // "start", and inline set to "nearest". 3150 // FIXME(emilio): Not all callers pass ScrollSmoothAuto (but we use auto 3151 // smooth scroll for `top` regardless below, so maybe they should!). 3152 ScrollingInteractionContext scrollToAnchorContext(true); 3153 if (thereIsATextFragment) { 3154 MOZ_TRY(ScrollSelectionIntoView( 3155 SelectionType::eTargetText, 3156 nsISelectionController::SELECTION_ANCHOR_REGION, 3157 ScrollAxis(WhereToScroll::Center, WhenToScroll::Always), 3158 ScrollAxis(), 3159 ScrollFlags::AnchorScrollFlags | aAdditionalScrollFlags, 3160 SelectionScrollMode::SyncFlush)); 3161 } else { 3162 MOZ_TRY(ScrollContentIntoView( 3163 target, ScrollAxis(WhereToScroll::Start, WhenToScroll::Always), 3164 ScrollAxis(), 3165 ScrollFlags::AnchorScrollFlags | aAdditionalScrollFlags)); 3166 } 3167 if (ScrollContainerFrame* rootScroll = GetRootScrollContainerFrame()) { 3168 mLastAnchorScrolledTo = target; 3169 mLastAnchorScrollPositionY = rootScroll->GetScrollPosition().y; 3170 mLastAnchorScrollType = thereIsATextFragment 3171 ? AnchorScrollType::TextDirective 3172 : AnchorScrollType::Anchor; 3173 } 3174 } 3175 3176 { 3177 // 3.6. Move the sequential focus navigation starting point to target. 3178 // 3179 // Move the caret to the anchor. That way tabbing will start from the new 3180 // location. 3181 // 3182 // TODO(emilio): Do we want to do this even if aScroll is false? 3183 // 3184 // NOTE: Intentionally out of order for now with the focus steps, see 3185 // https://github.com/whatwg/html/issues/7759 3186 RefPtr<nsRange> jumpToRange = nsRange::Create(mDocument); 3187 nsCOMPtr<nsIContent> nodeToSelect = target.get(); 3188 while (nodeToSelect->GetFirstChild()) { 3189 nodeToSelect = nodeToSelect->GetFirstChild(); 3190 } 3191 jumpToRange->SelectNodeContents(*nodeToSelect, IgnoreErrors()); 3192 RefPtr sel = &mSelection->NormalSelection(); 3193 MOZ_ASSERT(sel); 3194 sel->RemoveAllRanges(IgnoreErrors()); 3195 sel->AddRangeAndSelectFramesAndNotifyListeners(*jumpToRange, 3196 IgnoreErrors()); 3197 if (!StaticPrefs::layout_selectanchor()) { 3198 // Use a caret (collapsed selection) at the start of the anchor. 3199 sel->CollapseToStart(IgnoreErrors()); 3200 } 3201 } 3202 3203 // 3.5. Run the focusing steps for target, with the Document's viewport as 3204 // the fallback target. 3205 // 3206 // Note that ScrollContentIntoView flushes, so we don't need to do that 3207 // again here. We also don't need to scroll again either. 3208 // 3209 // We intentionally focus the target only when aScroll is true, we need to 3210 // sort out if the spec needs to differentiate these cases. When aScroll is 3211 // false we still clear the focus unconditionally, that's legacy behavior, 3212 // maybe we shouldn't do it. 3213 // 3214 // TODO(emilio): Do we really want to clear the focus even if aScroll is 3215 // false? 3216 const bool shouldFocusTarget = [&] { 3217 if (!aScroll || thereIsATextFragment) { 3218 return false; 3219 } 3220 nsIFrame* targetFrame = target->GetPrimaryFrame(); 3221 return targetFrame && targetFrame->IsFocusable(); 3222 }(); 3223 3224 if (shouldFocusTarget) { 3225 FocusOptions options; 3226 options.mPreventScroll = true; 3227 target->Focus(options, CallerType::NonSystem, IgnoreErrors()); 3228 } else if (RefPtr<nsIFocusManager> fm = nsFocusManager::GetFocusManager()) { 3229 if (nsPIDOMWindowOuter* win = mDocument->GetWindow()) { 3230 // Now focus the document itself if focus is on an element within it. 3231 nsCOMPtr<mozIDOMWindowProxy> focusedWindow; 3232 fm->GetFocusedWindow(getter_AddRefs(focusedWindow)); 3233 if (SameCOMIdentity(win, focusedWindow)) { 3234 fm->ClearFocus(focusedWindow); 3235 } 3236 } 3237 } 3238 3239 // If the target is an animation element, activate the animation 3240 if (auto* animationElement = SVGAnimationElement::FromNode(target.get())) { 3241 animationElement->ActivateByHyperlink(); 3242 } 3243 3244 #ifdef ACCESSIBILITY 3245 if (nsAccessibilityService* accService = GetAccService()) { 3246 nsIContent* a11yTarget = target; 3247 if (thereIsATextFragment) { 3248 // A text fragment starts in a text leaf node. `target` is the element 3249 // parent, but there may be many other children of that element before 3250 // the start of the text fragment. Explicitly use the start leaf node 3251 // here to get a11y clients as close as possible to the fragment (on 3252 // platforms which support this). 3253 a11yTarget = nsIContent::FromNodeOrNull( 3254 aFirstTextDirective->GetStartContainer()); 3255 if (!a11yTarget) { 3256 a11yTarget = target; 3257 } 3258 } 3259 accService->NotifyOfAnchorJumpTo(a11yTarget); 3260 } 3261 #endif 3262 } else if (nsContentUtils::EqualsIgnoreASCIICase(aAnchorName, u"top"_ns)) { 3263 // 2.2. Scroll to the beginning of the document for the Document. 3264 ScrollContainerFrame* sf = GetRootScrollContainerFrame(); 3265 // Check |aScroll| after setting |rv| so we set |rv| to the same 3266 // thing whether or not |aScroll| is true. 3267 if (aScroll && sf) { 3268 ScrollMode scrollMode = sf->ScrollModeForScrollBehavior(); 3269 // Scroll to the top of the page 3270 sf->ScrollTo(nsPoint(0, 0), scrollMode); 3271 } 3272 } else { 3273 return NS_ERROR_FAILURE; 3274 } 3275 3276 return NS_OK; 3277 } 3278 3279 nsresult PresShell::ScrollToAnchor() { 3280 NS_ASSERTION(mDidInitialize, "should have done initial reflow by now"); 3281 if (mLastAnchorScrollType == AnchorScrollType::Anchor) { 3282 nsCOMPtr<nsIContent> lastAnchor = std::move(mLastAnchorScrolledTo); 3283 if (!lastAnchor) { 3284 return NS_OK; 3285 } 3286 3287 ScrollContainerFrame* rootScroll = GetRootScrollContainerFrame(); 3288 if (!rootScroll || 3289 mLastAnchorScrollPositionY != rootScroll->GetScrollPosition().y) { 3290 return NS_OK; 3291 } 3292 return ScrollContentIntoView( 3293 lastAnchor, ScrollAxis(WhereToScroll::Start, WhenToScroll::Always), 3294 ScrollAxis(), ScrollFlags::AnchorScrollFlags); 3295 } 3296 3297 return ScrollSelectionIntoView( 3298 SelectionType::eTargetText, 3299 nsISelectionController::SELECTION_ANCHOR_REGION, 3300 ScrollAxis(WhereToScroll::Center, WhenToScroll::Always), ScrollAxis(), 3301 ScrollFlags::AnchorScrollFlags, SelectionScrollMode::SyncFlush); 3302 } 3303 3304 /* 3305 * Helper (per-continuation) for ScrollContentIntoView. 3306 * 3307 * @param aContainerFrame [in] the frame which aRect is relative to 3308 * @param aFrame [in] Frame whose bounds should be unioned 3309 * @param aUseWholeLineHeightForInlines [in] if true, then for inline frames 3310 * we should include the top of the line in the added rectangle 3311 * @param aRect [inout] rect into which its bounds should be unioned 3312 * @param aHaveRect [inout] whether aRect contains data yet 3313 * @param aPrevBlock [inout] the block aLines is a line iterator for 3314 * @param aLines [inout] the line iterator we're using 3315 * @param aCurLine [inout] the line to start looking from in this iterator 3316 */ 3317 static void AccumulateFrameBounds(nsIFrame* aContainerFrame, nsIFrame* aFrame, 3318 bool aUseWholeLineHeightForInlines, 3319 nsRect& aRect, bool& aHaveRect, 3320 nsIFrame*& aPrevBlock, 3321 nsILineIterator*& aLines, int32_t& aCurLine) { 3322 nsIFrame* frame = aFrame; 3323 nsRect frameBounds = nsRect(nsPoint(0, 0), aFrame->GetSize()); 3324 3325 // If this is an inline frame and either the bounds height is 0 (quirks 3326 // layout model) or aUseWholeLineHeightForInlines is set, we need to 3327 // change the top of the bounds to include the whole line. 3328 if (frameBounds.height == 0 || aUseWholeLineHeightForInlines) { 3329 nsIFrame* prevFrame = aFrame; 3330 nsIFrame* f = aFrame; 3331 3332 while (f && f->IsLineParticipant() && !f->IsTransformed() && 3333 !f->IsAbsPosContainingBlock()) { 3334 prevFrame = f; 3335 f = prevFrame->GetParent(); 3336 } 3337 3338 if (f != aFrame && f && f->IsBlockFrame()) { 3339 // find the line containing aFrame and increase the top of |offset|. 3340 if (f != aPrevBlock) { 3341 aLines = f->GetLineIterator(); 3342 aPrevBlock = f; 3343 aCurLine = 0; 3344 } 3345 if (aLines) { 3346 int32_t index = aLines->FindLineContaining(prevFrame, aCurLine); 3347 if (index >= 0) { 3348 auto line = aLines->GetLine(index).unwrap(); 3349 frameBounds += frame->GetOffsetTo(f); 3350 frame = f; 3351 if (line.mLineBounds.y < frameBounds.y) { 3352 frameBounds.height = frameBounds.YMost() - line.mLineBounds.y; 3353 frameBounds.y = line.mLineBounds.y; 3354 } 3355 } 3356 } 3357 } 3358 } 3359 3360 nsRect transformedBounds = nsLayoutUtils::TransformFrameRectToAncestor( 3361 frame, frameBounds, aContainerFrame); 3362 3363 if (aHaveRect) { 3364 // We can't use nsRect::UnionRect since it drops empty rects on 3365 // the floor, and we need to include them. (Thus we need 3366 // aHaveRect to know when to drop the initial value on the floor.) 3367 aRect = aRect.UnionEdges(transformedBounds); 3368 } else { 3369 aHaveRect = true; 3370 aRect = transformedBounds; 3371 } 3372 } 3373 3374 static bool ComputeNeedToScroll(WhenToScroll aWhenToScroll, nscoord aLineSize, 3375 nscoord aRectMin, nscoord aRectMax, 3376 nscoord aViewMin, nscoord aViewMax) { 3377 // See how the rect should be positioned in a given axis. 3378 switch (aWhenToScroll) { 3379 case WhenToScroll::Always: 3380 // The caller wants the frame as visible as possible 3381 return true; 3382 case WhenToScroll::IfNotVisible: 3383 if (aLineSize > (aRectMax - aRectMin)) { 3384 // If the line size is greater than the size of the rect 3385 // to scroll into view, do not use the line size to determine 3386 // if we need to scroll. 3387 aLineSize = 0; 3388 } 3389 3390 // Scroll only if no part of the frame is visible in this view. 3391 return aRectMax - aLineSize <= aViewMin || 3392 aRectMin + aLineSize >= aViewMax; 3393 case WhenToScroll::IfNotFullyVisible: 3394 // Scroll only if part of the frame is hidden and more can fit in view 3395 return !(aRectMin >= aViewMin && aRectMax <= aViewMax) && 3396 std::min(aViewMax, aRectMax) - std::max(aRectMin, aViewMin) < 3397 aViewMax - aViewMin; 3398 } 3399 return false; 3400 } 3401 3402 static nscoord ComputeWhereToScroll(WhereToScroll aWhereToScroll, 3403 nscoord aOriginalCoord, nscoord aRectMin, 3404 nscoord aRectMax, nscoord aViewMin, 3405 nscoord aViewMax, nscoord* aRangeMin, 3406 nscoord* aRangeMax) { 3407 nscoord resultCoord = aOriginalCoord; 3408 nscoord scrollPortLength = aViewMax - aViewMin; 3409 if (!aWhereToScroll.mPercentage) { 3410 // Scroll the minimum amount necessary to show as much as possible of the 3411 // frame. If the frame is too large, don't hide any initially visible part 3412 // of it. 3413 nscoord min = std::min(aRectMin, aRectMax - scrollPortLength); 3414 nscoord max = std::max(aRectMin, aRectMax - scrollPortLength); 3415 resultCoord = std::clamp(aOriginalCoord, min, max); 3416 } else { 3417 float percent = aWhereToScroll.mPercentage.value() / 100.0f; 3418 nscoord frameAlignCoord = 3419 NSToCoordRound(aRectMin + (aRectMax - aRectMin) * percent); 3420 resultCoord = NSToCoordRound(frameAlignCoord - scrollPortLength * percent); 3421 } 3422 // Force the scroll range to extend to include resultCoord. 3423 *aRangeMin = std::min(resultCoord, aRectMax - scrollPortLength); 3424 *aRangeMax = std::max(resultCoord, aRectMin); 3425 return resultCoord; 3426 } 3427 3428 static WhereToScroll GetApplicableWhereToScroll( 3429 const ScrollContainerFrame* aScrollContainerFrame, 3430 const nsIFrame* aScrollableFrame, const nsIFrame* aTarget, 3431 ScrollDirection aScrollDirection, WhereToScroll aOriginal) { 3432 MOZ_ASSERT(do_QueryFrame(aScrollContainerFrame) == aScrollableFrame); 3433 if (aTarget == aScrollableFrame) { 3434 return aOriginal; 3435 } 3436 3437 StyleScrollSnapAlignKeyword align = 3438 aScrollDirection == ScrollDirection::eHorizontal 3439 ? aScrollContainerFrame->GetScrollSnapAlignFor(aTarget).first 3440 : aScrollContainerFrame->GetScrollSnapAlignFor(aTarget).second; 3441 3442 switch (align) { 3443 case StyleScrollSnapAlignKeyword::None: 3444 return aOriginal; 3445 case StyleScrollSnapAlignKeyword::Start: 3446 return WhereToScroll::Start; 3447 case StyleScrollSnapAlignKeyword::Center: 3448 return WhereToScroll::Center; 3449 case StyleScrollSnapAlignKeyword::End: 3450 return WhereToScroll::End; 3451 } 3452 return aOriginal; 3453 } 3454 3455 static ScrollMode GetScrollModeForScrollIntoView( 3456 const ScrollContainerFrame* aScrollContainerFrame, 3457 ScrollFlags aScrollFlags) { 3458 // Default to an instant scroll, but if the scroll behavior given is "auto" 3459 // or "smooth", use that as the specified behavior. If the user has disabled 3460 // smooth scrolls, a given mode of "auto" or "smooth" should not result in 3461 // a smooth scroll. 3462 ScrollBehavior behavior = ScrollBehavior::Instant; 3463 if (aScrollFlags & ScrollFlags::ScrollSmooth) { 3464 behavior = ScrollBehavior::Smooth; 3465 } else if (aScrollFlags & ScrollFlags::ScrollSmoothAuto) { 3466 behavior = ScrollBehavior::Auto; 3467 } 3468 return aScrollContainerFrame->ScrollModeForScrollBehavior(behavior); 3469 } 3470 3471 /** 3472 * This function takes a scroll container frame, a rect in the coordinate system 3473 * of the scrolled frame, and a desired percentage-based scroll 3474 * position and attempts to scroll the rect to that position in the 3475 * visual viewport. 3476 * 3477 * This needs to work even if aRect has a width or height of zero. 3478 */ 3479 static Maybe<nsPoint> ScrollToShowRect( 3480 ScrollContainerFrame* aScrollContainerFrame, 3481 const nsIFrame* aScrollableFrame, const nsIFrame* aTarget, 3482 const nsRect& aRect, const Sides aScrollPaddingSkipSides, 3483 const nsMargin& aMargin, ScrollAxis aVertical, ScrollAxis aHorizontal, 3484 ScrollFlags aScrollFlags) { 3485 nsPoint scrollPt = aScrollContainerFrame->GetVisualViewportOffset(); 3486 const nsPoint originalScrollPt = scrollPt; 3487 const nsRect visibleRect(scrollPt, 3488 aScrollContainerFrame->GetVisualViewportSize()); 3489 3490 const nsMargin padding = [&] { 3491 nsMargin p = aScrollContainerFrame->GetScrollPadding(); 3492 p.ApplySkipSides(aScrollPaddingSkipSides); 3493 return p + aMargin; 3494 }(); 3495 3496 const nsRect rectToScrollIntoView = [&] { 3497 nsRect r(aRect); 3498 r.Inflate(padding); 3499 return r.Intersect(aScrollContainerFrame->GetScrolledRect()); 3500 }(); 3501 3502 nsSize lineSize; 3503 // Don't call GetLineScrollAmount unless we actually need it. Not only 3504 // does this save time, but it's not safe to call GetLineScrollAmount 3505 // during reflow (because it depends on font size inflation and doesn't 3506 // use the in-reflow-safe font-size inflation path). If we did call it, 3507 // it would assert and possible give the wrong result. 3508 if (aVertical.mWhenToScroll == WhenToScroll::IfNotVisible || 3509 aHorizontal.mWhenToScroll == WhenToScroll::IfNotVisible) { 3510 lineSize = aScrollContainerFrame->GetLineScrollAmount(); 3511 } 3512 ScrollStyles ss = aScrollContainerFrame->GetScrollStyles(); 3513 nsRect allowedRange(scrollPt, nsSize(0, 0)); 3514 3515 if ((aScrollFlags & ScrollFlags::ScrollOverflowHidden) || 3516 ss.mVertical != StyleOverflow::Hidden) { 3517 if (ComputeNeedToScroll(aVertical.mWhenToScroll, lineSize.height, aRect.y, 3518 aRect.YMost(), visibleRect.y + padding.top, 3519 visibleRect.YMost() - padding.bottom)) { 3520 // If the scroll-snap-align on the frame is valid, we need to respect it. 3521 WhereToScroll whereToScroll = GetApplicableWhereToScroll( 3522 aScrollContainerFrame, aScrollableFrame, aTarget, 3523 ScrollDirection::eVertical, aVertical.mWhereToScroll); 3524 3525 nscoord maxHeight; 3526 scrollPt.y = ComputeWhereToScroll( 3527 whereToScroll, scrollPt.y, rectToScrollIntoView.y, 3528 rectToScrollIntoView.YMost(), visibleRect.y, visibleRect.YMost(), 3529 &allowedRange.y, &maxHeight); 3530 allowedRange.height = maxHeight - allowedRange.y; 3531 } 3532 } 3533 3534 if ((aScrollFlags & ScrollFlags::ScrollOverflowHidden) || 3535 ss.mHorizontal != StyleOverflow::Hidden) { 3536 if (ComputeNeedToScroll(aHorizontal.mWhenToScroll, lineSize.width, aRect.x, 3537 aRect.XMost(), visibleRect.x + padding.left, 3538 visibleRect.XMost() - padding.right)) { 3539 // If the scroll-snap-align on the frame is valid, we need to respect it. 3540 WhereToScroll whereToScroll = GetApplicableWhereToScroll( 3541 aScrollContainerFrame, aScrollableFrame, aTarget, 3542 ScrollDirection::eHorizontal, aHorizontal.mWhereToScroll); 3543 3544 nscoord maxWidth; 3545 scrollPt.x = ComputeWhereToScroll( 3546 whereToScroll, scrollPt.x, rectToScrollIntoView.x, 3547 rectToScrollIntoView.XMost(), visibleRect.x, visibleRect.XMost(), 3548 &allowedRange.x, &maxWidth); 3549 allowedRange.width = maxWidth - allowedRange.x; 3550 } 3551 } 3552 3553 // If we don't need to scroll, then don't try since it might cancel 3554 // a current smooth scroll operation. 3555 if (scrollPt == originalScrollPt) { 3556 return Nothing(); 3557 } 3558 3559 ScrollMode scrollMode = 3560 GetScrollModeForScrollIntoView(aScrollContainerFrame, aScrollFlags); 3561 nsIFrame* frame = do_QueryFrame(aScrollContainerFrame); 3562 AutoWeakFrame weakFrame(frame); 3563 aScrollContainerFrame->ScrollTo(scrollPt, scrollMode, &allowedRange, 3564 ScrollSnapFlags::IntendedEndPosition, 3565 aScrollFlags & ScrollFlags::TriggeredByScript 3566 ? ScrollTriggeredByScript::Yes 3567 : ScrollTriggeredByScript::No); 3568 return Some(scrollPt); 3569 } 3570 3571 nsresult PresShell::ScrollContentIntoView(nsIContent* aContent, 3572 ScrollAxis aVertical, 3573 ScrollAxis aHorizontal, 3574 ScrollFlags aScrollFlags) { 3575 NS_ENSURE_TRUE(aContent, NS_ERROR_NULL_POINTER); 3576 RefPtr<Document> composedDoc = aContent->GetComposedDoc(); 3577 NS_ENSURE_STATE(composedDoc); 3578 3579 NS_ASSERTION(mDidInitialize, "should have done initial reflow by now"); 3580 3581 if (mContentToScrollTo) { 3582 mContentToScrollTo->RemoveProperty(nsGkAtoms::scrolling); 3583 } 3584 mContentToScrollTo = aContent; 3585 ScrollIntoViewData* data = new ScrollIntoViewData(); 3586 data->mContentScrollVAxis = aVertical; 3587 data->mContentScrollHAxis = aHorizontal; 3588 data->mContentToScrollToFlags = aScrollFlags; 3589 if (NS_FAILED(mContentToScrollTo->SetProperty( 3590 nsGkAtoms::scrolling, data, 3591 nsINode::DeleteProperty<PresShell::ScrollIntoViewData>))) { 3592 mContentToScrollTo = nullptr; 3593 } 3594 3595 // If the target frame has an ancestor of a `content-visibility: auto` 3596 // element ensure that it is laid out, so that the boundary rectangle is 3597 // correct. 3598 // Additionally, ensure that all ancestor elements with 'content-visibility: 3599 // auto' are set to 'visible'. so that they are laid out as visible before 3600 // scrolling, improving the accuracy of the scroll position, especially when 3601 // the scroll target is within the overflow area. And here invoking 3602 // 'SetTemporarilyVisibleForScrolledIntoViewDescendant' would make the 3603 // intersection observer knows that it should generate entries for these 3604 // c-v:auto ancestors, so that the content relevancy could be checked again 3605 // after scrolling. https://drafts.csswg.org/css-contain-2/#cv-notes 3606 bool reflowedForHiddenContent = false; 3607 if (mContentToScrollTo) { 3608 if (nsIFrame* frame = mContentToScrollTo->GetPrimaryFrame()) { 3609 bool hasContentVisibilityAutoAncestor = false; 3610 auto* ancestor = frame->GetClosestContentVisibilityAncestor( 3611 nsIFrame::IncludeContentVisibility::Auto); 3612 while (ancestor) { 3613 if (auto* element = Element::FromNodeOrNull(ancestor->GetContent())) { 3614 hasContentVisibilityAutoAncestor = true; 3615 element->SetTemporarilyVisibleForScrolledIntoViewDescendant(true); 3616 element->SetVisibleForContentVisibility(true); 3617 } 3618 ancestor = ancestor->GetClosestContentVisibilityAncestor( 3619 nsIFrame::IncludeContentVisibility::Auto); 3620 } 3621 if (hasContentVisibilityAutoAncestor) { 3622 UpdateHiddenContentInForcedLayout(frame); 3623 // TODO: There might be the other already scheduled relevancy updates, 3624 // other than caused be scrollIntoView. 3625 UpdateContentRelevancyImmediately(ContentRelevancyReason::Visible); 3626 reflowedForHiddenContent = ReflowForHiddenContentIfNeeded(); 3627 } 3628 } 3629 } 3630 3631 if (!reflowedForHiddenContent) { 3632 // Flush layout and attempt to scroll in the process. 3633 if (PresShell* presShell = composedDoc->GetPresShell()) { 3634 presShell->SetNeedLayoutFlush(); 3635 } 3636 composedDoc->FlushPendingNotifications(FlushType::InterruptibleLayout); 3637 } 3638 3639 // If mContentToScrollTo is non-null, that means we interrupted the reflow 3640 // (or suppressed it altogether because we're suppressing interruptible 3641 // flushes right now) and won't necessarily get the position correct, but do 3642 // a best-effort scroll here. The other option would be to do this inside 3643 // FlushPendingNotifications, but I'm not sure the repeated scrolling that 3644 // could trigger if reflows keep getting interrupted would be more desirable 3645 // than a single best-effort scroll followed by one final scroll on the first 3646 // completed reflow. 3647 if (mContentToScrollTo) { 3648 DoScrollContentIntoView(); 3649 } 3650 return NS_OK; 3651 } 3652 3653 static nsMargin GetScrollMargin(const nsIFrame* aFrame) { 3654 MOZ_ASSERT(aFrame); 3655 // If we're focusing something that can't be targeted by content, allow 3656 // content to customize the margin. 3657 // 3658 // TODO: This is also a bit of an issue for delegated focus, see 3659 // https://github.com/whatwg/html/issues/7033. 3660 if (aFrame->GetContent() && aFrame->GetContent()->ChromeOnlyAccess()) { 3661 // XXX Should we use nsIContent::FindFirstNonChromeOnlyAccessContent() 3662 // instead of nsINode::GetClosestNativeAnonymousSubtreeRootParentOrHost()? 3663 if (const nsIContent* userContent = 3664 aFrame->GetContent() 3665 ->GetClosestNativeAnonymousSubtreeRootParentOrHost()) { 3666 if (const nsIFrame* frame = userContent->GetPrimaryFrame()) { 3667 return frame->StyleMargin()->GetScrollMargin(); 3668 } 3669 } 3670 } 3671 return aFrame->StyleMargin()->GetScrollMargin(); 3672 } 3673 3674 void PresShell::DoScrollContentIntoView() { 3675 NS_ASSERTION(mDidInitialize, "should have done initial reflow by now"); 3676 3677 nsIFrame* frame = mContentToScrollTo->GetPrimaryFrame(); 3678 3679 if (!frame || frame->IsHiddenByContentVisibilityOnAnyAncestor( 3680 nsIFrame::IncludeContentVisibility::Hidden)) { 3681 mContentToScrollTo->RemoveProperty(nsGkAtoms::scrolling); 3682 mContentToScrollTo = nullptr; 3683 return; 3684 } 3685 3686 if (frame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) { 3687 // The reflow flush before this scroll got interrupted, and this frame's 3688 // coords and size are all zero, and it has no content showing anyway. 3689 // Don't bother scrolling to it. We'll try again when we finish up layout. 3690 return; 3691 } 3692 3693 auto* data = static_cast<ScrollIntoViewData*>( 3694 mContentToScrollTo->GetProperty(nsGkAtoms::scrolling)); 3695 if (MOZ_UNLIKELY(!data)) { 3696 mContentToScrollTo = nullptr; 3697 return; 3698 } 3699 3700 ScrollFrameIntoView(frame, Nothing(), data->mContentScrollVAxis, 3701 data->mContentScrollHAxis, data->mContentToScrollToFlags); 3702 } 3703 3704 static bool NeedToVisuallyScroll(const nsSize& aLayoutViewportSize, 3705 const nsRect& aPositionFixedRect) { 3706 // position:fixed elements are fixed to the layout viewport, thus the 3707 // coordinate system is (0, 0) origin. 3708 // (and the maximum visible position is the layout viewport size, elements 3709 // outside of the size will never be laid out) 3710 const nsRect layoutViewport = nsRect(nsPoint(), aLayoutViewportSize); 3711 3712 // `BaseRect::Intersects(const Sub& aRect)` does return false if `aRect` is 3713 // empty, but we do want to visually scroll to empty position:fixed elements 3714 // if the elements are inside the layout viewport. 3715 if (aPositionFixedRect.IsEmpty()) { 3716 if (aPositionFixedRect.x > layoutViewport.XMost() || 3717 aPositionFixedRect.XMost() < layoutViewport.x || 3718 aPositionFixedRect.y > layoutViewport.YMost() || 3719 aPositionFixedRect.YMost() < layoutViewport.y) { 3720 return false; 3721 } 3722 return true; 3723 } 3724 3725 if (!layoutViewport.Intersects(aPositionFixedRect)) { 3726 return false; 3727 } 3728 return true; 3729 } 3730 3731 void PresShell::ScrollFrameIntoVisualViewport( 3732 Maybe<nsPoint>& aDestination, const nsRect& aPositionFixedRect, 3733 const nsIFrame* aPositionFixedFrame, ScrollAxis aVertical, 3734 ScrollAxis aHorizontal, ScrollFlags aScrollFlags) { 3735 PresShell* root = GetRootPresShell(); 3736 if (!root) { 3737 return; 3738 } 3739 3740 if (!root->GetPresContext()->IsRootContentDocumentCrossProcess()) { 3741 return; 3742 } 3743 3744 ScrollContainerFrame* rootScrollContainer = 3745 root->GetRootScrollContainerFrame(); 3746 if (!rootScrollContainer) { 3747 return; 3748 } 3749 3750 if (!aDestination) { 3751 MOZ_ASSERT(aPositionFixedFrame); 3752 // If we have in the top level content document but we didn't reach to 3753 // the root scroll container in the frame tree walking up loop in 3754 // ScrollFrameIntoView, it means the target element is inside a 3755 // position:fixed subtree. 3756 if (!StaticPrefs::layout_scroll_fixed_content_into_view_visually()) { 3757 return; 3758 } 3759 3760 const nsSize visualViewportSize = 3761 rootScrollContainer->GetVisualViewportSize(); 3762 3763 const nsSize layoutViewportSize = root->GetLayoutViewportSize(); 3764 const nsRect layoutViewport = nsRect(nsPoint(), layoutViewportSize); 3765 // `positon:fixed` element are attached/fixed to the ViewportFrame, which is 3766 // the parent of the root scroll container frame, thus what we need here is 3767 // the visible area of the position:fixed element inside the root scroll 3768 // container frame. 3769 // For example, if the top left position of the fixed element is (-100, 3770 // -100), it's outside of the scrollable range either in the layout viewport 3771 // or the visual viewport. Likewise, if the right bottom position of the 3772 // fixed element is (110vw, 110vh), it's also outside of the scrollable 3773 // range. 3774 const nsRect clampedPositionFixedRect = 3775 aPositionFixedRect.MoveInsideAndClamp(layoutViewport); 3776 // If the position:fixed element is already inside the visual viewport, we 3777 // don't need to scroll visually. 3778 if (clampedPositionFixedRect.y >= 0 && 3779 clampedPositionFixedRect.YMost() <= visualViewportSize.height && 3780 clampedPositionFixedRect.x >= 0 && 3781 clampedPositionFixedRect.XMost() <= visualViewportSize.width) { 3782 return; 3783 } 3784 3785 // If the position:fixed element is totally outside of the the layout 3786 // viewport, it will never be in the viewport. 3787 if (!NeedToVisuallyScroll(layoutViewportSize, aPositionFixedRect)) { 3788 return; 3789 } 3790 // Offset the position:fixed element position by the layout scroll 3791 // position because the position:fixed origin (0, 0) is the layout scroll 3792 // position. Otherwise if we've already scrolled, this scrollIntoView 3793 // operation will jump back to near (0, 0) position. 3794 nsPoint layoutOffset = rootScrollContainer->GetScrollPosition(); 3795 3796 const nsRect visibleRect(layoutOffset, visualViewportSize); 3797 nscoord unusedRangeMinOutparam; 3798 nscoord unusedRangeMaxOutparam; 3799 nscoord x = ComputeWhereToScroll( 3800 aScrollFlags & ScrollFlags::ForZoomToFocusedInput 3801 ? WhereToScroll::Nearest 3802 : aHorizontal.mWhereToScroll, 3803 layoutOffset.x, aPositionFixedRect.x, aPositionFixedRect.XMost(), 3804 visibleRect.x, visibleRect.XMost(), &unusedRangeMinOutparam, 3805 &unusedRangeMaxOutparam); 3806 nscoord y = ComputeWhereToScroll( 3807 aScrollFlags & ScrollFlags::ForZoomToFocusedInput 3808 ? WhereToScroll::Nearest 3809 : aVertical.mWhereToScroll, 3810 layoutOffset.y, aPositionFixedRect.y, aPositionFixedRect.YMost(), 3811 visibleRect.y, visibleRect.YMost(), &unusedRangeMinOutparam, 3812 &unusedRangeMaxOutparam); 3813 3814 layoutOffset.x += x; 3815 layoutOffset.y += y; 3816 aDestination = Some(layoutOffset); 3817 } 3818 3819 // NOTE: It seems chrome doesn't respect the root element's 3820 // scroll-behavior for visual scrolling. 3821 ScrollMode scrollMode = 3822 GetScrollModeForScrollIntoView(rootScrollContainer, aScrollFlags); 3823 root->ScrollToVisual(*aDestination, FrameMetrics::eMainThread, scrollMode); 3824 } 3825 3826 bool PresShell::ScrollFrameIntoView( 3827 nsIFrame* aTargetFrame, const Maybe<nsRect>& aKnownRectRelativeToTarget, 3828 ScrollAxis aVertical, ScrollAxis aHorizontal, ScrollFlags aScrollFlags) { 3829 // If the AxesAreLogical flag is set, the aVertical and aHorizontal params 3830 // actually refer to block and inline axes respectively, so we resolve them 3831 // to physical axes/directions here. 3832 // XXX Maybe we should convert more of the following code to logical axes, 3833 // if it's convenient for more callers to work that way? 3834 if (aScrollFlags & ScrollFlags::AxesAreLogical) { 3835 // The aVertical parameter actually refers to the element's block axis, 3836 // and aHorizontal to its inline axis. Potentially reverse/swap them, 3837 // according to its writing mode and directionality. 3838 WritingMode wm = aTargetFrame->GetWritingMode(); 3839 if (wm.IsVerticalRL()) { 3840 // Reverse the block-axis percentage. 3841 if (aVertical.mWhereToScroll.mPercentage) { 3842 aVertical.mWhereToScroll.mPercentage = 3843 Some(100 - aVertical.mWhereToScroll.mPercentage.value()); 3844 } 3845 } 3846 if (wm.IsInlineReversed()) { 3847 // Reverse the inline-axis percentage. 3848 if (aHorizontal.mWhereToScroll.mPercentage) { 3849 aHorizontal.mWhereToScroll.mPercentage = 3850 Some(100 - aHorizontal.mWhereToScroll.mPercentage.value()); 3851 } 3852 } 3853 if (wm.IsVertical()) { 3854 std::swap(aVertical, aHorizontal); 3855 } 3856 // Remove the AxesAreLogical flag, to make it clear that methods we call 3857 // always get physical axes from here on. 3858 aScrollFlags &= ~ScrollFlags::AxesAreLogical; 3859 } 3860 3861 // The scroll margin only applies to the whole bounds of the element, so don't 3862 // apply it if we get an arbitrary rect / point to scroll to. 3863 const nsMargin scrollMargin = 3864 aKnownRectRelativeToTarget ? nsMargin() : GetScrollMargin(aTargetFrame); 3865 3866 Sides skipPaddingSides; 3867 const auto MaybeSkipPaddingSides = [&](nsIFrame* aFrame) { 3868 if (!aFrame->IsStickyPositioned()) { 3869 return; 3870 } 3871 const nsPoint pos = aFrame->GetPosition(); 3872 const nsPoint normalPos = aFrame->GetNormalPosition(); 3873 if (pos == normalPos) { 3874 return; // Frame is not stuck. 3875 } 3876 // If we're targetting a sticky element, make sure not to apply 3877 // scroll-padding on the direction we're stuck. 3878 const auto* stylePosition = aFrame->StylePosition(); 3879 const auto anchorResolutionParams = 3880 AnchorPosOffsetResolutionParams::UseCBFrameSize( 3881 AnchorPosResolutionParams::From(aFrame)); 3882 for (auto side : AllPhysicalSides()) { 3883 if (stylePosition->GetAnchorResolvedInset(side, anchorResolutionParams) 3884 ->IsAuto()) { 3885 continue; 3886 } 3887 // See if this axis is stuck. 3888 const bool yAxis = side == eSideTop || side == eSideBottom; 3889 const bool stuck = yAxis ? pos.y != normalPos.y : pos.x != normalPos.x; 3890 if (!stuck) { 3891 continue; 3892 } 3893 skipPaddingSides |= SideToSideBit(side); 3894 } 3895 }; 3896 3897 nsIFrame* container = aTargetFrame; 3898 3899 const nsIFrame* positionFixedFrame = nullptr; 3900 auto isPositionFixed = [&](const nsIFrame* aFrame) -> bool { 3901 return aFrame->StyleDisplay()->mPosition == StylePositionProperty::Fixed && 3902 nsLayoutUtils::IsReallyFixedPos(aFrame); 3903 }; 3904 // This function needs to work even if rect has a width or height of 0. 3905 nsRect rect = [&] { 3906 if (aKnownRectRelativeToTarget) { 3907 return *aKnownRectRelativeToTarget; 3908 } 3909 MaybeSkipPaddingSides(aTargetFrame); 3910 while (nsIFrame* parent = container->GetParent()) { 3911 if (isPositionFixed(container)) { 3912 positionFixedFrame = container; 3913 } 3914 container = parent; 3915 if (container->IsScrollContainerOrSubclass()) { 3916 // We really just need a non-fragmented frame so that we can accumulate 3917 // the bounds of all our continuations relative to it. We shouldn't jump 3918 // out of our nearest scrollable frame, and that's an ok reference 3919 // frame, so try to use that, or the root frame if there's nothing to 3920 // scroll in this document. 3921 break; 3922 } 3923 MaybeSkipPaddingSides(container); 3924 } 3925 MOZ_DIAGNOSTIC_ASSERT(container); 3926 3927 nsRect targetFrameBounds; 3928 { 3929 bool haveRect = false; 3930 const bool useWholeLineHeightForInlines = 3931 aVertical.mWhenToScroll != WhenToScroll::IfNotFullyVisible; 3932 AutoAssertNoDomMutations 3933 guard; // Ensure use of nsILineIterators is safe. 3934 nsIFrame* prevBlock = nullptr; 3935 // Reuse the same line iterator across calls to AccumulateFrameBounds. 3936 // We set it every time we detect a new block (stored in prevBlock). 3937 nsILineIterator* lines = nullptr; 3938 // The last line we found a continuation on in |lines|. We assume that 3939 // later continuations cannot come on earlier lines. 3940 int32_t curLine = 0; 3941 nsIFrame* frame = aTargetFrame; 3942 do { 3943 AccumulateFrameBounds(container, frame, useWholeLineHeightForInlines, 3944 targetFrameBounds, haveRect, prevBlock, lines, 3945 curLine); 3946 } while ((frame = frame->GetNextContinuation())); 3947 } 3948 3949 return targetFrameBounds; 3950 }(); 3951 bool didScroll = false; 3952 const nsIFrame* target = aTargetFrame; 3953 Maybe<nsPoint> rootScrollDestination; 3954 // Walk up the frame hierarchy scrolling the rect into view and 3955 // keeping rect relative to container 3956 do { 3957 if (isPositionFixed(container)) { 3958 positionFixedFrame = container; 3959 } 3960 3961 if (ScrollContainerFrame* sf = do_QueryFrame(container)) { 3962 nsPoint oldPosition = sf->GetScrollPosition(); 3963 nsRect targetRect = rect - sf->GetScrolledFrame()->GetPosition(); 3964 3965 { 3966 AutoWeakFrame wf(container); 3967 Maybe<nsPoint> destination = ScrollToShowRect( 3968 sf, container, target, targetRect, skipPaddingSides, scrollMargin, 3969 aVertical, aHorizontal, aScrollFlags); 3970 if (!wf.IsAlive()) { 3971 return didScroll; 3972 } 3973 3974 if (sf->IsRootScrollFrameOfDocument() && 3975 sf->PresContext()->IsRootContentDocumentCrossProcess()) { 3976 rootScrollDestination = destination; 3977 } 3978 } 3979 3980 nsPoint newPosition = sf->LastScrollDestination(); 3981 // If the scroll position increased, that means our content moved up, 3982 // so our rect's offset should decrease 3983 rect += oldPosition - newPosition; 3984 3985 if (oldPosition != newPosition) { 3986 didScroll = true; 3987 } 3988 3989 // only scroll one container when this flag is set 3990 if (aScrollFlags & ScrollFlags::ScrollFirstAncestorOnly) { 3991 break; 3992 } 3993 3994 // This scroll container will be the next target element in the nearest 3995 // ancestor scroll container. 3996 target = container; 3997 // We found a sticky scroll container, we shouldn't skip that side 3998 // anymore. 3999 skipPaddingSides = {}; 4000 } 4001 4002 MaybeSkipPaddingSides(container); 4003 4004 nsIFrame* parent = container->GetParent(); 4005 NS_ASSERTION(parent || !container->IsTransformed(), 4006 "viewport shouldnt be transformed"); 4007 if (parent && container->IsTransformed()) { 4008 rect = 4009 nsLayoutUtils::TransformFrameRectToAncestor(container, rect, parent); 4010 } else { 4011 rect += container->GetPosition(); 4012 } 4013 if (!parent && !(aScrollFlags & ScrollFlags::ScrollNoParentFrames)) { 4014 nsPoint extraOffset(0, 0); 4015 int32_t APD = container->PresContext()->AppUnitsPerDevPixel(); 4016 parent = nsLayoutUtils::GetCrossDocParentFrameInProcess(container, 4017 &extraOffset); 4018 if (parent) { 4019 int32_t parentAPD = parent->PresContext()->AppUnitsPerDevPixel(); 4020 rect = rect.ScaleToOtherAppUnitsRoundOut(APD, parentAPD); 4021 rect += extraOffset; 4022 } else { 4023 nsCOMPtr<nsIDocShell> docShell = 4024 container->PresContext()->GetDocShell(); 4025 if (BrowserChild* browserChild = BrowserChild::GetFrom(docShell)) { 4026 // Defer to the parent document if this is an out-of-process iframe. 4027 (void)browserChild->SendScrollRectIntoView( 4028 rect, aVertical, aHorizontal, aScrollFlags, APD); 4029 } 4030 } 4031 } 4032 container = parent; 4033 } while (container); 4034 4035 // If this is inside the top level content document process (and a direct 4036 // descendant of it), also call ScrollToVisual() since we want to 4037 // scroll the rect into view visually, and that may require scrolling 4038 // the visual viewport in scenarios where there is not enough layout 4039 // scroll range. 4040 if (!rootScrollDestination && !positionFixedFrame) { 4041 return didScroll; 4042 } 4043 4044 ScrollFrameIntoVisualViewport(rootScrollDestination, rect, positionFixedFrame, 4045 aVertical, aHorizontal, aScrollFlags); 4046 4047 return didScroll; 4048 } 4049 4050 void PresShell::SchedulePaint() { 4051 if (MOZ_UNLIKELY(mIsDestroying)) { 4052 return; 4053 } 4054 if (nsPresContext* presContext = GetPresContext()) { 4055 presContext->RefreshDriver()->SchedulePaint(); 4056 } 4057 } 4058 4059 void PresShell::ClearMouseCapture() { 4060 ReleaseCapturingContent(); 4061 AllowMouseCapture(false); 4062 } 4063 4064 void PresShell::ClearMouseCapture(nsIFrame* aFrame) { 4065 MOZ_ASSERT(aFrame); 4066 4067 nsIContent* capturingContent = GetCapturingContent(); 4068 if (!capturingContent) { 4069 return; 4070 } 4071 4072 nsIFrame* capturingFrame = capturingContent->GetPrimaryFrame(); 4073 const bool shouldClear = 4074 !capturingFrame || 4075 nsLayoutUtils::IsAncestorFrameCrossDocInProcess(aFrame, capturingFrame); 4076 if (shouldClear) { 4077 ClearMouseCapture(); 4078 } 4079 } 4080 4081 nsresult PresShell::CaptureHistoryState(nsILayoutHistoryState** aState) { 4082 MOZ_ASSERT(nullptr != aState, "null state pointer"); 4083 4084 // We actually have to mess with the docshell here, since we want to 4085 // store the state back in it. 4086 // XXXbz this isn't really right, since this is being called in the 4087 // content viewer's Hide() method... by that point the docshell's 4088 // state could be wrong. We should sort out a better ownership 4089 // model for the layout history state. 4090 nsCOMPtr<nsIDocShell> docShell(mPresContext->GetDocShell()); 4091 if (!docShell) { 4092 return NS_ERROR_FAILURE; 4093 } 4094 4095 nsCOMPtr<nsILayoutHistoryState> historyState; 4096 docShell->GetLayoutHistoryState(getter_AddRefs(historyState)); 4097 if (!historyState) { 4098 // Create the document state object 4099 historyState = NS_NewLayoutHistoryState(); 4100 docShell->SetLayoutHistoryState(historyState); 4101 } 4102 4103 *aState = historyState; 4104 NS_IF_ADDREF(*aState); 4105 4106 // Capture frame state for the entire frame hierarchy 4107 nsIFrame* rootFrame = mFrameConstructor->GetRootFrame(); 4108 if (!rootFrame) { 4109 return NS_OK; 4110 } 4111 4112 mFrameConstructor->CaptureFrameState(rootFrame, historyState); 4113 4114 return NS_OK; 4115 } 4116 4117 void PresShell::ScheduleBeforeFirstPaint() { 4118 if (!mDocument->IsResourceDoc()) { 4119 // Notify observers that a new page is about to be drawn. Execute this 4120 // as soon as it is safe to run JS, which is guaranteed to be before we 4121 // go back to the event loop and actually draw the page. 4122 MOZ_LOG(gLog, LogLevel::Debug, 4123 ("PresShell::ScheduleBeforeFirstPaint this=%p", this)); 4124 4125 nsContentUtils::AddScriptRunner( 4126 new nsBeforeFirstPaintDispatcher(mDocument)); 4127 } 4128 } 4129 4130 void PresShell::UnsuppressAndInvalidate() { 4131 // Note: We ignore the EnsureVisible check for resource documents, because 4132 // they won't have a docshell, so they'll always fail EnsureVisible. 4133 if ((!mDocument->IsResourceDoc() && !mPresContext->EnsureVisible()) || 4134 mHaveShutDown) { 4135 // No point; we're about to be torn down anyway. 4136 return; 4137 } 4138 4139 ScheduleBeforeFirstPaint(); 4140 4141 PROFILER_MARKER_UNTYPED("UnsuppressAndInvalidate", GRAPHICS); 4142 4143 mPaintingSuppressed = false; 4144 if (nsIFrame* rootFrame = mFrameConstructor->GetRootFrame()) { 4145 // let's assume that outline on a root frame is not supported 4146 rootFrame->InvalidateFrame(); 4147 } 4148 4149 if (mPresContext->IsRootContentDocumentCrossProcess()) { 4150 if (auto* bc = BrowserChild::GetFrom(mDocument->GetDocShell())) { 4151 if (mDocument->IsInitialDocument()) { 4152 bc->SendDidUnsuppressPaintingNormalPriority(); 4153 } else { 4154 bc->SendDidUnsuppressPainting(); 4155 } 4156 } 4157 } 4158 4159 // now that painting is unsuppressed, focus may be set on the document 4160 if (nsPIDOMWindowOuter* win = mDocument->GetWindow()) { 4161 win->SetReadyForFocus(); 4162 } 4163 4164 if (!mHaveShutDown) { 4165 SynthesizeMouseMove(false); 4166 ScheduleApproximateFrameVisibilityUpdateNow(); 4167 } 4168 } 4169 4170 void PresShell::CancelPaintSuppressionTimer() { 4171 if (mPaintSuppressionTimer) { 4172 mPaintSuppressionTimer->Cancel(); 4173 mPaintSuppressionTimer = nullptr; 4174 } 4175 } 4176 4177 void PresShell::UnsuppressPainting() { 4178 CancelPaintSuppressionTimer(); 4179 4180 if (mIsDocumentGone || !mPaintingSuppressed) { 4181 return; 4182 } 4183 4184 // If we have reflows pending, just wait until we process 4185 // the reflows and get all the frames where we want them 4186 // before actually unlocking the painting. Otherwise 4187 // go ahead and unlock now. 4188 if (!mDirtyRoots.IsEmpty()) { 4189 mShouldUnsuppressPainting = true; 4190 } else { 4191 UnsuppressAndInvalidate(); 4192 } 4193 } 4194 4195 // Post a request to handle an arbitrary callback after reflow has finished. 4196 nsresult PresShell::PostReflowCallback(nsIReflowCallback* aCallback) { 4197 void* result = AllocateByObjectID(eArenaObjectID_nsCallbackEventRequest, 4198 sizeof(nsCallbackEventRequest)); 4199 nsCallbackEventRequest* request = (nsCallbackEventRequest*)result; 4200 4201 request->callback = aCallback; 4202 request->next = nullptr; 4203 4204 if (mLastCallbackEventRequest) { 4205 mLastCallbackEventRequest = mLastCallbackEventRequest->next = request; 4206 } else { 4207 mFirstCallbackEventRequest = request; 4208 mLastCallbackEventRequest = request; 4209 } 4210 4211 return NS_OK; 4212 } 4213 4214 void PresShell::CancelReflowCallback(nsIReflowCallback* aCallback) { 4215 nsCallbackEventRequest* before = nullptr; 4216 nsCallbackEventRequest* node = mFirstCallbackEventRequest; 4217 while (node) { 4218 nsIReflowCallback* callback = node->callback; 4219 4220 if (callback == aCallback) { 4221 nsCallbackEventRequest* toFree = node; 4222 if (node == mFirstCallbackEventRequest) { 4223 node = node->next; 4224 mFirstCallbackEventRequest = node; 4225 NS_ASSERTION(before == nullptr, "impossible"); 4226 } else { 4227 node = node->next; 4228 before->next = node; 4229 } 4230 4231 if (toFree == mLastCallbackEventRequest) { 4232 mLastCallbackEventRequest = before; 4233 } 4234 4235 FreeByObjectID(eArenaObjectID_nsCallbackEventRequest, toFree); 4236 } else { 4237 before = node; 4238 node = node->next; 4239 } 4240 } 4241 } 4242 4243 void PresShell::CancelPostedReflowCallbacks() { 4244 while (mFirstCallbackEventRequest) { 4245 nsCallbackEventRequest* node = mFirstCallbackEventRequest; 4246 mFirstCallbackEventRequest = node->next; 4247 if (!mFirstCallbackEventRequest) { 4248 mLastCallbackEventRequest = nullptr; 4249 } 4250 nsIReflowCallback* callback = node->callback; 4251 FreeByObjectID(eArenaObjectID_nsCallbackEventRequest, node); 4252 if (callback) { 4253 callback->ReflowCallbackCanceled(); 4254 } 4255 } 4256 } 4257 4258 void PresShell::HandlePostedReflowCallbacks(bool aInterruptible) { 4259 while (true) { 4260 // Call all our callbacks, tell us if we need to flush again. 4261 bool shouldFlush = false; 4262 while (mFirstCallbackEventRequest) { 4263 nsCallbackEventRequest* node = mFirstCallbackEventRequest; 4264 mFirstCallbackEventRequest = node->next; 4265 if (!mFirstCallbackEventRequest) { 4266 mLastCallbackEventRequest = nullptr; 4267 } 4268 nsIReflowCallback* callback = node->callback; 4269 FreeByObjectID(eArenaObjectID_nsCallbackEventRequest, node); 4270 if (callback && callback->ReflowFinished()) { 4271 shouldFlush = true; 4272 } 4273 } 4274 4275 if (!shouldFlush || mIsDestroying) { 4276 return; 4277 } 4278 4279 // The flush might cause us to have more callbacks. 4280 const auto flushType = 4281 aInterruptible ? FlushType::InterruptibleLayout : FlushType::Layout; 4282 FlushPendingNotifications(flushType); 4283 } 4284 } 4285 4286 bool PresShell::IsSafeToFlush() const { 4287 // Not safe if we are getting torn down, reflowing, or in the middle of frame 4288 // construction. 4289 if (mIsReflowing || mChangeNestCount || mIsDestroying) { 4290 return false; 4291 } 4292 // Not safe either if we're painting. 4293 // TODO(emilio): What could call us while painting now? 4294 return !mIsPainting; 4295 } 4296 4297 void PresShell::NotifyFontFaceSetOnRefresh() { 4298 if (FontFaceSet* set = mDocument->GetFonts()) { 4299 set->DidRefresh(); 4300 } 4301 } 4302 4303 void PresShell::DoFlushPendingNotifications(FlushType aType) { 4304 // by default, flush animations if aType >= FlushType::Style 4305 mozilla::ChangesToFlush flush(aType, aType >= FlushType::Style, 4306 aType >= FlushType::Layout); 4307 FlushPendingNotifications(flush); 4308 } 4309 4310 #ifdef DEBUG 4311 static void AssertFrameSubtreeIsSane(const nsIFrame& aRoot) { 4312 if (const nsIContent* content = aRoot.GetContent()) { 4313 MOZ_ASSERT(content->GetFlattenedTreeParentNodeForStyle(), 4314 "Node not in the flattened tree still has a frame?"); 4315 } 4316 4317 for (const auto& childList : aRoot.ChildLists()) { 4318 for (const nsIFrame* child : childList.mList) { 4319 AssertFrameSubtreeIsSane(*child); 4320 } 4321 } 4322 } 4323 #endif 4324 4325 static inline void AssertFrameTreeIsSane(const PresShell& aPresShell) { 4326 #ifdef DEBUG 4327 if (const nsIFrame* root = aPresShell.GetRootFrame()) { 4328 AssertFrameSubtreeIsSane(*root); 4329 } 4330 #endif 4331 } 4332 4333 static void TriggerPendingScrollTimelineAnimations(Document* aDocument) { 4334 auto* tracker = aDocument->GetScrollTimelineAnimationTracker(); 4335 if (!tracker || !tracker->HasPendingAnimations()) { 4336 return; 4337 } 4338 tracker->TriggerPendingAnimations(); 4339 } 4340 4341 void PresShell::DoFlushPendingNotifications(mozilla::ChangesToFlush aFlush) { 4342 // FIXME(emilio, bug 1530177): Turn into a release assert when bug 1530188 and 4343 // bug 1530190 are fixed. 4344 MOZ_DIAGNOSTIC_ASSERT(!mForbiddenToFlush, "This is bad!"); 4345 4346 // Per our API contract, hold a strong ref to ourselves until we return. 4347 RefPtr<PresShell> kungFuDeathGrip = this; 4348 4349 /** 4350 * VERY IMPORTANT: If you add some sort of new flushing to this 4351 * method, make sure to add the relevant SetNeedLayoutFlush or 4352 * SetNeedStyleFlush calls on the shell. 4353 */ 4354 FlushType flushType = aFlush.mFlushType; 4355 4356 if (aFlush.mUpdateRelevancy) { 4357 // If needed, first update the relevancy of any content of elements with 4358 // `content-visibility: auto` so that the values returned from e.g. script 4359 // queries are up-to-date. 4360 UpdateRelevancyOfContentVisibilityAutoFrames(); 4361 } 4362 4363 MOZ_ASSERT(NeedFlush(flushType), "Why did we get called?"); 4364 4365 AUTO_PROFILER_MARKER_TEXT( 4366 "DoFlushPendingNotifications", LAYOUT, 4367 MarkerOptions(MarkerStack::Capture(), MarkerInnerWindowIdFromDocShell( 4368 mPresContext->GetDocShell())), 4369 nsDependentCString(kFlushTypeNames[flushType])); 4370 AUTO_PROFILER_LABEL_DYNAMIC_CSTR_NONSENSITIVE( 4371 "PresShell::DoFlushPendingNotifications", LAYOUT, 4372 kFlushTypeNames[flushType]); 4373 4374 #ifdef ACCESSIBILITY 4375 # ifdef DEBUG 4376 if (nsAccessibilityService* accService = GetAccService()) { 4377 NS_ASSERTION(!accService->IsProcessingRefreshDriverNotification(), 4378 "Flush during accessible tree update!"); 4379 } 4380 # endif 4381 #endif 4382 4383 NS_ASSERTION(flushType >= FlushType::Style, "Why did we get called?"); 4384 4385 bool isSafeToFlush = IsSafeToFlush(); 4386 4387 // If layout could possibly trigger scripts, then it's only safe to flush if 4388 // it's safe to run script. 4389 bool hasHadScriptObject; 4390 if (mDocument->GetScriptHandlingObject(hasHadScriptObject) || 4391 hasHadScriptObject) { 4392 isSafeToFlush = isSafeToFlush && nsContentUtils::IsSafeToRunScript(); 4393 } 4394 4395 // Don't flush if the doc is already in the bfcache. 4396 if (MOZ_UNLIKELY(mDocument->GetPresShell() != this)) { 4397 MOZ_DIAGNOSTIC_ASSERT(!mDocument->GetPresShell(), 4398 "Where did this shell come from?"); 4399 isSafeToFlush = false; 4400 } 4401 4402 MOZ_DIAGNOSTIC_ASSERT(!mIsDestroying || !isSafeToFlush); 4403 MOZ_DIAGNOSTIC_ASSERT(mIsDestroying || mWidgetListener); 4404 MOZ_DIAGNOSTIC_ASSERT(mIsDestroying || mDocument->HasShellOrBFCacheEntry()); 4405 4406 if (!isSafeToFlush) { 4407 return; 4408 } 4409 4410 // We need to make sure external resource documents are flushed too (for 4411 // example, svg filters that reference a filter in an external document 4412 // need the frames in the external document to be constructed for the 4413 // filter to work). We only need external resources to be flushed when the 4414 // main document is flushing >= FlushType::Frames, so we flush external 4415 // resources here instead of Document::FlushPendingNotifications. 4416 mDocument->FlushExternalResources(flushType); 4417 4418 // Force flushing of any pending content notifications that might have 4419 // queued up while our event was pending. That will ensure that we don't 4420 // construct frames for content right now that's still waiting to be 4421 // notified on, 4422 mDocument->FlushPendingNotifications(FlushType::ContentAndNotify); 4423 4424 mDocument->UpdateSVGUseElementShadowTrees(); 4425 4426 // Process pending restyles, since any flush of the presshell wants 4427 // up-to-date style data. 4428 if (MOZ_LIKELY(!mIsDestroying)) { 4429 FlushDelayedResize(); 4430 mPresContext->FlushPendingMediaFeatureValuesChanged(); 4431 } 4432 4433 if (MOZ_LIKELY(!mIsDestroying)) { 4434 // Now that we have flushed media queries, update the rules before looking 4435 // up @font-face / @counter-style / @font-feature-values rules. 4436 StyleSet()->UpdateStylistIfNeeded(); 4437 4438 // Flush any pending update of the user font set, since that could 4439 // cause style changes (for updating ex/ch units, and to cause a 4440 // reflow). 4441 mDocument->FlushUserFontSet(); 4442 4443 mPresContext->FlushCounterStyles(); 4444 4445 mPresContext->FlushFontFeatureValues(); 4446 4447 mPresContext->FlushFontPaletteValues(); 4448 4449 // Flush any requested SMIL samples. 4450 if (mDocument->HasAnimationController()) { 4451 mDocument->GetAnimationController()->FlushResampleRequests(); 4452 } 4453 } 4454 4455 // The FlushResampleRequests() above might have flushed style changes. 4456 if (MOZ_LIKELY(!mIsDestroying)) { 4457 if (aFlush.mFlushAnimations) { 4458 mPresContext->EffectCompositor()->PostRestyleForThrottledAnimations(); 4459 mNeedThrottledAnimationFlush = false; 4460 } 4461 4462 nsAutoScriptBlocker scriptBlocker; 4463 Maybe<uint64_t> innerWindowID; 4464 if (auto* window = mDocument->GetInnerWindow()) { 4465 innerWindowID = Some(window->WindowID()); 4466 } 4467 AutoProfilerStyleMarker tracingStyleFlush(std::move(mStyleCause), 4468 innerWindowID); 4469 PerfStats::AutoMetricRecording<PerfStats::Metric::Styling> autoRecording; 4470 4471 mPresContext->RestyleManager()->ProcessPendingRestyles(); 4472 mNeedStyleFlush = false; 4473 } 4474 4475 AssertFrameTreeIsSane(*this); 4476 4477 if (flushType >= (SuppressInterruptibleReflows() 4478 ? FlushType::Layout 4479 : FlushType::InterruptibleLayout) && 4480 !mIsDestroying) { 4481 if (DoFlushLayout(/* aInterruptible = */ flushType < FlushType::Layout)) { 4482 if (mContentToScrollTo) { 4483 DoScrollContentIntoView(); 4484 if (mContentToScrollTo) { 4485 mContentToScrollTo->RemoveProperty(nsGkAtoms::scrolling); 4486 mContentToScrollTo = nullptr; 4487 } 4488 } 4489 } 4490 // FIXME(emilio): Maybe we should assert here but it's not 100% sure it'd 4491 // hold right now, UnsuppressAndInvalidate and so on can run script... 4492 if (MOZ_LIKELY(mDirtyRoots.IsEmpty())) { 4493 mNeedLayoutFlush = false; 4494 } 4495 } 4496 4497 FlushPendingScrollResnap(); 4498 4499 if (MOZ_LIKELY(!mIsDestroying)) { 4500 // Try to trigger pending scroll-driven animations after we flush 4501 // style and layout (if any). If we try to trigger them after flushing 4502 // style but the frame tree is not ready, we will check them again after 4503 // we flush layout because the requirement to trigger scroll-driven 4504 // animations is that the associated scroll containers are ready (i.e. the 4505 // scroll-timeline is active), and this depends on the readiness of the 4506 // scrollable frame and the primary frame of the scroll container. 4507 TriggerPendingScrollTimelineAnimations(mDocument); 4508 } 4509 } 4510 4511 MOZ_CAN_RUN_SCRIPT_BOUNDARY void PresShell::CharacterDataChanged( 4512 nsIContent* aContent, const CharacterDataChangeInfo& aInfo) { 4513 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript()); 4514 MOZ_ASSERT(!mIsDocumentGone, "Unexpected CharacterDataChanged"); 4515 MOZ_ASSERT(aContent->OwnerDoc() == mDocument, "Unexpected document"); 4516 4517 nsAutoCauseReflowNotifier crNotifier(this); 4518 4519 mPresContext->RestyleManager()->CharacterDataChanged(aContent, aInfo); 4520 mFrameConstructor->CharacterDataChanged(aContent, aInfo); 4521 } 4522 4523 MOZ_CAN_RUN_SCRIPT_BOUNDARY void PresShell::ElementStateChanged( 4524 Document* aDocument, Element* aElement, ElementState aStateMask) { 4525 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript()); 4526 MOZ_ASSERT(!mIsDocumentGone, "Unexpected ContentStateChanged"); 4527 MOZ_ASSERT(aDocument == mDocument, "Unexpected aDocument"); 4528 4529 if (mDidInitialize) { 4530 nsAutoCauseReflowNotifier crNotifier(this); 4531 mPresContext->RestyleManager()->ElementStateChanged(aElement, aStateMask); 4532 } 4533 } 4534 4535 MOZ_CAN_RUN_SCRIPT_BOUNDARY void PresShell::CustomStatesWillChange( 4536 Element& aElement) { 4537 if (MOZ_UNLIKELY(!mDidInitialize)) { 4538 return; 4539 } 4540 4541 mPresContext->RestyleManager()->CustomStatesWillChange(aElement); 4542 } 4543 4544 MOZ_CAN_RUN_SCRIPT_BOUNDARY void PresShell::CustomStateChanged( 4545 Element& aElement, nsAtom* aState) { 4546 MOZ_ASSERT(!mIsDocumentGone, "Unexpected CustomStateChanged"); 4547 MOZ_ASSERT(aState, "Unexpected empty state"); 4548 4549 if (mDidInitialize) { 4550 nsAutoCauseReflowNotifier crNotifier(this); 4551 mPresContext->RestyleManager()->CustomStateChanged(aElement, aState); 4552 } 4553 } 4554 4555 void PresShell::DocumentStatesChanged(DocumentState aStateMask) { 4556 MOZ_ASSERT(!mIsDocumentGone, "Unexpected DocumentStatesChanged"); 4557 MOZ_ASSERT(mDocument); 4558 MOZ_ASSERT(!aStateMask.IsEmpty()); 4559 4560 if (mDidInitialize) { 4561 StyleSet()->InvalidateStyleForDocumentStateChanges(aStateMask); 4562 } 4563 4564 if (aStateMask.HasState(DocumentState::WINDOW_INACTIVE)) { 4565 if (nsIFrame* root = mFrameConstructor->GetRootFrame()) { 4566 root->SchedulePaint(); 4567 } 4568 } 4569 } 4570 4571 MOZ_CAN_RUN_SCRIPT_BOUNDARY void PresShell::AttributeWillChange( 4572 Element* aElement, int32_t aNameSpaceID, nsAtom* aAttribute, 4573 AttrModType aModType) { 4574 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript()); 4575 MOZ_ASSERT(!mIsDocumentGone, "Unexpected AttributeWillChange"); 4576 MOZ_ASSERT(aElement->OwnerDoc() == mDocument, "Unexpected document"); 4577 4578 // XXXwaterson it might be more elegant to wait until after the 4579 // initial reflow to begin observing the document. That would 4580 // squelch any other inappropriate notifications as well. 4581 if (mDidInitialize) { 4582 nsAutoCauseReflowNotifier crNotifier(this); 4583 mPresContext->RestyleManager()->AttributeWillChange(aElement, aNameSpaceID, 4584 aAttribute, aModType); 4585 } 4586 } 4587 4588 MOZ_CAN_RUN_SCRIPT_BOUNDARY void PresShell::AttributeChanged( 4589 Element* aElement, int32_t aNameSpaceID, nsAtom* aAttribute, 4590 AttrModType aModType, const nsAttrValue* aOldValue) { 4591 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript()); 4592 MOZ_ASSERT(!mIsDocumentGone, "Unexpected AttributeChanged"); 4593 MOZ_ASSERT(aElement->OwnerDoc() == mDocument, "Unexpected document"); 4594 4595 // XXXwaterson it might be more elegant to wait until after the 4596 // initial reflow to begin observing the document. That would 4597 // squelch any other inappropriate notifications as well. 4598 if (mDidInitialize) { 4599 nsAutoCauseReflowNotifier crNotifier(this); 4600 mPresContext->RestyleManager()->AttributeChanged( 4601 aElement, aNameSpaceID, aAttribute, aModType, aOldValue); 4602 } 4603 } 4604 4605 static void MaybeDestroyFramesAndStyles(nsIContent* aContent, 4606 nsPresContext& aPresContext) { 4607 if (!aContent->IsElement()) { 4608 return; 4609 } 4610 4611 Element* element = aContent->AsElement(); 4612 if (!element->HasServoData()) { 4613 return; 4614 } 4615 4616 Element* parent = 4617 Element::FromNodeOrNull(element->GetFlattenedTreeParentNode()); 4618 if (!parent || !parent->HasServoData() || 4619 Servo_Element_IsDisplayNone(parent)) { 4620 DestroyFramesAndStyleDataFor(element, aPresContext, 4621 RestyleManager::IncludeRoot::Yes); 4622 } 4623 } 4624 4625 MOZ_CAN_RUN_SCRIPT_BOUNDARY void PresShell::ContentAppended( 4626 nsIContent* aFirstNewContent, const ContentAppendInfo& aInfo) { 4627 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript()); 4628 MOZ_ASSERT(!mIsDocumentGone, "Unexpected ContentAppended"); 4629 MOZ_ASSERT(aFirstNewContent->OwnerDoc() == mDocument, "Unexpected document"); 4630 4631 // We never call ContentAppended with a document as the container, so we can 4632 // assert that we have an nsIContent parent. 4633 MOZ_ASSERT(aFirstNewContent->GetParent()); 4634 MOZ_ASSERT(aFirstNewContent->GetParent()->IsElement() || 4635 aFirstNewContent->GetParent()->IsShadowRoot()); 4636 4637 if (!mDidInitialize) { 4638 return; 4639 } 4640 4641 mPresContext->EventStateManager()->ContentAppended(aFirstNewContent, aInfo); 4642 4643 if (aInfo.mOldParent) { 4644 MaybeDestroyFramesAndStyles(aFirstNewContent, *mPresContext); 4645 } 4646 4647 nsAutoCauseReflowNotifier crNotifier(this); 4648 4649 // Call this here so it only happens for real content mutations and 4650 // not cases when the frame constructor calls its own methods to force 4651 // frame reconstruction. 4652 mPresContext->RestyleManager()->ContentAppended(aFirstNewContent); 4653 4654 mFrameConstructor->ContentAppended( 4655 aFirstNewContent, nsCSSFrameConstructor::InsertionKind::Async); 4656 } 4657 4658 MOZ_CAN_RUN_SCRIPT_BOUNDARY void PresShell::ContentInserted( 4659 nsIContent* aChild, const ContentInsertInfo& aInfo) { 4660 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript()); 4661 MOZ_ASSERT(!mIsDocumentGone, "Unexpected ContentInserted"); 4662 MOZ_ASSERT(aChild->OwnerDoc() == mDocument, "Unexpected document"); 4663 4664 if (!mDidInitialize) { 4665 return; 4666 } 4667 4668 mPresContext->EventStateManager()->ContentInserted(aChild, aInfo); 4669 4670 if (aInfo.mOldParent) { 4671 MaybeDestroyFramesAndStyles(aChild, *mPresContext); 4672 } 4673 4674 nsAutoCauseReflowNotifier crNotifier(this); 4675 4676 // Call this here so it only happens for real content mutations and 4677 // not cases when the frame constructor calls its own methods to force 4678 // frame reconstruction. 4679 mPresContext->RestyleManager()->ContentInserted(aChild); 4680 4681 mFrameConstructor->ContentInserted( 4682 aChild, nsCSSFrameConstructor::InsertionKind::Async); 4683 } 4684 4685 MOZ_CAN_RUN_SCRIPT_BOUNDARY void PresShell::ContentWillBeRemoved( 4686 nsIContent* aChild, const ContentRemoveInfo& aInfo) { 4687 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript()); 4688 MOZ_ASSERT(!mIsDocumentGone, "Unexpected ContentRemoved"); 4689 MOZ_ASSERT(aChild->OwnerDoc() == mDocument, "Unexpected document"); 4690 // Notify the ESM that the content has been removed, so that 4691 // it can clean up any state related to the content. 4692 4693 mPresContext->EventStateManager()->ContentRemoved(mDocument, aChild, aInfo); 4694 4695 nsAutoCauseReflowNotifier crNotifier(this); 4696 4697 for (AutoConnectedAncestorTracker* tracker = mLastConnectedAncestorTracker; 4698 tracker; tracker = tracker->mPreviousTracker) { 4699 if (tracker->ConnectedNode().IsInclusiveFlatTreeDescendantOf(aChild)) { 4700 tracker->mConnectedAncestor = aChild->GetFlattenedTreeParentElement(); 4701 } 4702 } 4703 4704 if (aInfo.mNewParent && aChild->IsElement()) { 4705 if (aInfo.mNewParent->IsElement() && 4706 aInfo.mNewParent->AsElement()->HasServoData() && 4707 !Servo_Element_IsDisplayNone(aInfo.mNewParent->AsElement())) { 4708 DestroyFramesForAndRestyle(aChild->AsElement()); 4709 return; 4710 } 4711 } 4712 4713 mFrameConstructor->ContentWillBeRemoved( 4714 aChild, nsCSSFrameConstructor::RemovalKind::Dom); 4715 4716 // NOTE(emilio): It's important that this goes after the frame constructor 4717 // stuff, otherwise the frame constructor can't see elements which are 4718 // display: contents / display: none, because we'd have cleared all the style 4719 // data from there. 4720 mPresContext->RestyleManager()->ContentWillBeRemoved(aChild); 4721 } 4722 4723 void PresShell::NotifyCounterStylesAreDirty() { 4724 // TODO: Looks like that nsFrameConstructor::NotifyCounterStylesAreDirty() 4725 // does not run script. If so, we don't need to block script with 4726 // nsAutoCauseReflowNotifier here. Instead, there should be methods 4727 // and stack only class which manages only mChangeNestCount for 4728 // avoiding unnecessary `MOZ_CAN_RUN_SCRIPT` marking. 4729 nsAutoCauseReflowNotifier reflowNotifier(this); 4730 mFrameConstructor->NotifyCounterStylesAreDirty(); 4731 } 4732 4733 bool PresShell::FrameIsAncestorOfDirtyRoot(nsIFrame* aFrame) const { 4734 return mDirtyRoots.FrameIsAncestorOfAnyElement(aFrame); 4735 } 4736 4737 void PresShell::ReconstructFrames() { 4738 MOZ_ASSERT(!mFrameConstructor->GetRootFrame() || mDidInitialize, 4739 "Must not have root frame before initial reflow"); 4740 if (!mDidInitialize || mIsDestroying) { 4741 // Nothing to do here 4742 return; 4743 } 4744 4745 if (Element* root = mDocument->GetRootElement()) { 4746 PostRecreateFramesFor(root); 4747 } 4748 4749 mDocument->FlushPendingNotifications(FlushType::Frames); 4750 } 4751 4752 nsresult PresShell::RenderDocument(const nsRect& aRect, 4753 RenderDocumentFlags aFlags, 4754 nscolor aBackgroundColor, 4755 gfxContext* aThebesContext) { 4756 NS_ENSURE_TRUE(!(aFlags & RenderDocumentFlags::IsUntrusted), 4757 NS_ERROR_NOT_IMPLEMENTED); 4758 4759 nsRootPresContext* rootPresContext = mPresContext->GetRootPresContext(); 4760 if (rootPresContext) { 4761 rootPresContext->FlushWillPaintObservers(); 4762 if (mIsDestroying) { 4763 return NS_OK; 4764 } 4765 } 4766 4767 nsAutoScriptBlocker blockScripts; 4768 4769 // Set up the rectangle as the path in aThebesContext 4770 gfxRect r(0, 0, nsPresContext::AppUnitsToFloatCSSPixels(aRect.width), 4771 nsPresContext::AppUnitsToFloatCSSPixels(aRect.height)); 4772 aThebesContext->NewPath(); 4773 #ifdef MOZ_GFX_OPTIMIZE_MOBILE 4774 aThebesContext->SnappedRectangle(r); 4775 #else 4776 aThebesContext->Rectangle(r); 4777 #endif 4778 4779 nsIFrame* rootFrame = mFrameConstructor->GetRootFrame(); 4780 if (!rootFrame) { 4781 // Nothing to paint, just fill the rect 4782 aThebesContext->SetColor(sRGBColor::FromABGR(aBackgroundColor)); 4783 aThebesContext->Fill(); 4784 return NS_OK; 4785 } 4786 4787 gfxContextAutoSaveRestore save(aThebesContext); 4788 4789 MOZ_ASSERT(aThebesContext->CurrentOp() == CompositionOp::OP_OVER); 4790 4791 aThebesContext->Clip(); 4792 4793 nsDeviceContext* devCtx = mPresContext->DeviceContext(); 4794 4795 gfxPoint offset(-nsPresContext::AppUnitsToFloatCSSPixels(aRect.x), 4796 -nsPresContext::AppUnitsToFloatCSSPixels(aRect.y)); 4797 gfxFloat scale = 4798 gfxFloat(devCtx->AppUnitsPerDevPixel()) / AppUnitsPerCSSPixel(); 4799 4800 // Since canvas APIs use floats to set up their matrices, we may have some 4801 // slight rounding errors here. We use NudgeToIntegers() here to adjust 4802 // matrix components that are integers up to the accuracy of floats to be 4803 // those integers. 4804 gfxMatrix newTM = aThebesContext->CurrentMatrixDouble() 4805 .PreTranslate(offset) 4806 .PreScale(scale, scale) 4807 .NudgeToIntegers(); 4808 aThebesContext->SetMatrixDouble(newTM); 4809 4810 AutoSaveRestoreRenderingState _(this); 4811 4812 bool wouldFlushRetainedLayers = false; 4813 PaintFrameFlags flags = PaintFrameFlags::IgnoreSuppression; 4814 if (aThebesContext->CurrentMatrix().HasNonIntegerTranslation()) { 4815 flags |= PaintFrameFlags::InTransform; 4816 } 4817 if (!(aFlags & RenderDocumentFlags::AsyncDecodeImages)) { 4818 flags |= PaintFrameFlags::SyncDecodeImages; 4819 } 4820 if (aFlags & RenderDocumentFlags::UseHighQualityScaling) { 4821 flags |= PaintFrameFlags::UseHighQualityScaling; 4822 } 4823 if (aFlags & RenderDocumentFlags::UseWidgetLayers) { 4824 // We only support using widget layers on display root's with widgets. 4825 nsIWidget* widget = rootFrame->GetOwnWidget(); 4826 if (widget && nsLayoutUtils::GetDisplayRootFrame(rootFrame) == rootFrame) { 4827 WindowRenderer* renderer = widget->GetWindowRenderer(); 4828 // WebRenderLayerManagers in content processes 4829 // don't support taking snapshots. 4830 if (renderer && 4831 (!renderer->AsKnowsCompositor() || XRE_IsParentProcess())) { 4832 flags |= PaintFrameFlags::WidgetLayers; 4833 } 4834 } 4835 } 4836 if (!(aFlags & RenderDocumentFlags::DrawCaret)) { 4837 wouldFlushRetainedLayers = true; 4838 flags |= PaintFrameFlags::HideCaret; 4839 } 4840 if (aFlags & RenderDocumentFlags::IgnoreViewportScrolling) { 4841 wouldFlushRetainedLayers = !IgnoringViewportScrolling(); 4842 mRenderingStateFlags |= RenderingStateFlags::IgnoringViewportScrolling; 4843 } 4844 if (aFlags & RenderDocumentFlags::ResetViewportScrolling) { 4845 wouldFlushRetainedLayers = true; 4846 flags |= PaintFrameFlags::ResetViewportScrolling; 4847 } 4848 if (aFlags & RenderDocumentFlags::DrawWindowNotFlushing) { 4849 mRenderingStateFlags |= RenderingStateFlags::DrawWindowNotFlushing; 4850 } 4851 if (aFlags & RenderDocumentFlags::DocumentRelative) { 4852 // XXX be smarter about this ... drawWindow might want a rect 4853 // that's "pretty close" to what our retained layer tree covers. 4854 // In that case, it wouldn't disturb normal rendering too much, 4855 // and we should allow it. 4856 wouldFlushRetainedLayers = true; 4857 flags |= PaintFrameFlags::DocumentRelative; 4858 } 4859 4860 // Don't let drawWindow blow away our retained layer tree 4861 if ((flags & PaintFrameFlags::WidgetLayers) && wouldFlushRetainedLayers) { 4862 flags &= ~PaintFrameFlags::WidgetLayers; 4863 } 4864 4865 nsLayoutUtils::PaintFrame(aThebesContext, rootFrame, nsRegion(aRect), 4866 aBackgroundColor, 4867 nsDisplayListBuilderMode::Painting, flags); 4868 4869 return NS_OK; 4870 } 4871 4872 /* 4873 * Clip the display list aList to a range. Returns the clipped 4874 * rectangle surrounding the range. 4875 */ 4876 nsRect PresShell::ClipListToRange(nsDisplayListBuilder* aBuilder, 4877 nsDisplayList* aList, nsRange* aRange) { 4878 // iterate though the display items and add up the bounding boxes of each. 4879 // This will allow the total area of the frames within the range to be 4880 // determined. To do this, remove an item from the bottom of the list, check 4881 // whether it should be part of the range, and if so, append it to the top 4882 // of the temporary list tmpList. If the item is a text frame at the end of 4883 // the selection range, clip it to the portion of the text frame that is 4884 // part of the selection. Then, append the wrapper to the top of the list. 4885 // Otherwise, just delete the item and don't append it. 4886 nsRect surfaceRect; 4887 4888 for (nsDisplayItem* i : aList->TakeItems()) { 4889 if (i->GetType() == DisplayItemType::TYPE_CONTAINER) { 4890 aList->AppendToTop(i); 4891 surfaceRect.UnionRect( 4892 surfaceRect, ClipListToRange(aBuilder, i->GetChildren(), aRange)); 4893 continue; 4894 } 4895 4896 // itemToInsert indiciates the item that should be inserted into the 4897 // temporary list. If null, no item should be inserted. 4898 nsDisplayItem* itemToInsert = nullptr; 4899 nsIFrame* frame = i->Frame(); 4900 nsIContent* content = frame->GetContent(); 4901 if (content) { 4902 bool atStart = 4903 content == aRange->GetMayCrossShadowBoundaryStartContainer(); 4904 bool atEnd = content == aRange->GetMayCrossShadowBoundaryEndContainer(); 4905 if ((atStart || atEnd) && frame->IsTextFrame()) { 4906 auto [frameStartOffset, frameEndOffset] = frame->GetOffsets(); 4907 4908 int32_t highlightStart = 4909 atStart ? std::max(static_cast<int32_t>( 4910 aRange->MayCrossShadowBoundaryStartOffset()), 4911 frameStartOffset) 4912 : frameStartOffset; 4913 int32_t highlightEnd = 4914 atEnd ? std::min(static_cast<int32_t>( 4915 aRange->MayCrossShadowBoundaryEndOffset()), 4916 frameEndOffset) 4917 : frameEndOffset; 4918 if (highlightStart < highlightEnd) { 4919 // determine the location of the start and end edges of the range. 4920 nsPoint startPoint, endPoint; 4921 frame->GetPointFromOffset(highlightStart, &startPoint); 4922 frame->GetPointFromOffset(highlightEnd, &endPoint); 4923 4924 // The clip rectangle is determined by taking the the start and 4925 // end points of the range, offset from the reference frame. 4926 // Because of rtl, the end point may be to the left of (or above, 4927 // in vertical mode) the start point, so x (or y) is set to the 4928 // lower of the values. 4929 nsRect textRect(aBuilder->ToReferenceFrame(frame), frame->GetSize()); 4930 if (frame->GetWritingMode().IsVertical()) { 4931 nscoord y = std::min(startPoint.y, endPoint.y); 4932 textRect.y += y; 4933 textRect.height = std::max(startPoint.y, endPoint.y) - y; 4934 } else { 4935 nscoord x = std::min(startPoint.x, endPoint.x); 4936 textRect.x += x; 4937 textRect.width = std::max(startPoint.x, endPoint.x) - x; 4938 } 4939 surfaceRect.UnionRect(surfaceRect, textRect); 4940 4941 const ActiveScrolledRoot* asr = i->GetActiveScrolledRoot(); 4942 4943 DisplayItemClip newClip; 4944 newClip.SetTo(textRect); 4945 4946 const DisplayItemClipChain* newClipChain = 4947 aBuilder->AllocateDisplayItemClipChain(newClip, asr, nullptr); 4948 4949 i->IntersectClip(aBuilder, newClipChain, true); 4950 itemToInsert = i; 4951 } 4952 } 4953 // Don't try to descend into subdocuments. 4954 // If this ever changes we'd need to add handling for subdocuments with 4955 // different zoom levels. 4956 else if (content->GetComposedDoc() == 4957 aRange->GetMayCrossShadowBoundaryStartContainer() 4958 ->GetComposedDoc()) { 4959 // if the node is within the range, append it to the temporary list 4960 bool before, after; 4961 nsresult rv = 4962 RangeUtils::CompareNodeToRange<TreeKind::ShadowIncludingDOM>( 4963 content, aRange, &before, &after); 4964 if (NS_SUCCEEDED(rv) && !before && !after) { 4965 itemToInsert = i; 4966 bool snap; 4967 surfaceRect.UnionRect(surfaceRect, i->GetBounds(aBuilder, &snap)); 4968 } 4969 } 4970 } 4971 4972 // insert the item into the list if necessary. If the item has a child 4973 // list, insert that as well 4974 nsDisplayList* sublist = i->GetSameCoordinateSystemChildren(); 4975 if (itemToInsert || sublist) { 4976 aList->AppendToTop(itemToInsert ? itemToInsert : i); 4977 // if the item is a list, iterate over it as well 4978 if (sublist) { 4979 surfaceRect.UnionRect(surfaceRect, 4980 ClipListToRange(aBuilder, sublist, aRange)); 4981 } 4982 } else { 4983 // otherwise, just delete the item and don't readd it to the list 4984 i->Destroy(aBuilder); 4985 } 4986 } 4987 4988 return surfaceRect; 4989 } 4990 4991 #ifdef DEBUG 4992 # include <stdio.h> 4993 4994 static bool gDumpRangePaintList = false; 4995 #endif 4996 4997 UniquePtr<RangePaintInfo> PresShell::CreateRangePaintInfo( 4998 nsRange* aRange, nsRect& aSurfaceRect, bool aForPrimarySelection) { 4999 nsIFrame* ancestorFrame = nullptr; 5000 nsIFrame* rootFrame = GetRootFrame(); 5001 5002 // If the start or end of the range is the document, just use the root 5003 // frame, otherwise get the common ancestor of the two endpoints of the 5004 // range. 5005 nsINode* startContainer = aRange->GetMayCrossShadowBoundaryStartContainer(); 5006 nsINode* endContainer = aRange->GetMayCrossShadowBoundaryEndContainer(); 5007 Document* doc = startContainer->GetComposedDoc(); 5008 if (startContainer == doc || endContainer == doc) { 5009 ancestorFrame = rootFrame; 5010 } else { 5011 nsINode* ancestor = 5012 StaticPrefs::dom_shadowdom_selection_across_boundary_enabled() 5013 ? nsContentUtils::GetClosestCommonShadowIncludingInclusiveAncestor( 5014 startContainer, endContainer) 5015 : nsContentUtils::GetClosestCommonInclusiveAncestor(startContainer, 5016 endContainer); 5017 NS_ASSERTION(!ancestor || ancestor->IsContent(), 5018 "common ancestor is not content"); 5019 5020 while (ancestor && ancestor->IsContent()) { 5021 ancestorFrame = ancestor->AsContent()->GetPrimaryFrame(); 5022 if (ancestorFrame) { 5023 break; 5024 } 5025 5026 ancestor = ancestor->GetParentOrShadowHostNode(); 5027 } 5028 5029 // use the nearest ancestor frame that includes all continuations as the 5030 // root for building the display list 5031 while (ancestorFrame && 5032 nsLayoutUtils::GetNextContinuationOrIBSplitSibling(ancestorFrame)) { 5033 ancestorFrame = ancestorFrame->GetParent(); 5034 } 5035 } 5036 5037 if (!ancestorFrame) { 5038 return nullptr; 5039 } 5040 5041 // get a display list containing the range 5042 auto info = MakeUnique<RangePaintInfo>(ancestorFrame); 5043 info->mBuilder.SetIncludeAllOutOfFlows(); 5044 if (aForPrimarySelection) { 5045 info->mBuilder.SetSelectedFramesOnly(); 5046 } 5047 info->mBuilder.EnterPresShell(ancestorFrame); 5048 5049 ContentSubtreeIterator subtreeIter; 5050 nsresult rv = StaticPrefs::dom_shadowdom_selection_across_boundary_enabled() 5051 ? subtreeIter.InitWithAllowCrossShadowBoundary(aRange) 5052 : subtreeIter.Init(aRange); 5053 if (NS_FAILED(rv)) { 5054 return nullptr; 5055 } 5056 5057 auto BuildDisplayListForNode = [&](nsINode* aNode) { 5058 if (MOZ_UNLIKELY(!aNode->IsContent())) { 5059 return; 5060 } 5061 nsIFrame* frame = aNode->AsContent()->GetPrimaryFrame(); 5062 // XXX deal with frame being null due to display:contents 5063 for (; frame; 5064 frame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(frame)) { 5065 info->mBuilder.SetVisibleRect(frame->InkOverflowRect()); 5066 info->mBuilder.SetDirtyRect(frame->InkOverflowRect()); 5067 frame->BuildDisplayListForStackingContext(&info->mBuilder, &info->mList); 5068 } 5069 }; 5070 if (startContainer->NodeType() == nsINode::TEXT_NODE) { 5071 BuildDisplayListForNode(startContainer); 5072 } 5073 for (; !subtreeIter.IsDone(); subtreeIter.Next()) { 5074 nsCOMPtr<nsINode> node = subtreeIter.GetCurrentNode(); 5075 BuildDisplayListForNode(node); 5076 } 5077 if (endContainer != startContainer && 5078 endContainer->NodeType() == nsINode::TEXT_NODE) { 5079 BuildDisplayListForNode(endContainer); 5080 } 5081 5082 // If one of the ancestor presShells (including this one) has a resolution 5083 // set, we may have some APZ zoom applied. That means we may want to rasterize 5084 // the nodes at that zoom level. Populate `info` with the relevant information 5085 // so that the caller can decide what to do. Also wrap the display list in 5086 // appropriate nsDisplayAsyncZoom display items. This code handles the general 5087 // case with nested async zooms (even though that never actually happens), 5088 // because it fell out of the implementation for free. 5089 // 5090 // TODO: Do we need to do the same for ancestor transforms? 5091 for (nsPresContext* ctx = GetPresContext(); ctx; 5092 ctx = ctx->GetParentPresContext()) { 5093 PresShell* shell = ctx->PresShell(); 5094 float resolution = shell->GetResolution(); 5095 5096 // If we are at the root document in the process, try to see if documents 5097 // in enclosing processes have a resolution and include that as well. 5098 if (!ctx->GetParentPresContext()) { 5099 // xScale is an arbitrary choice. Outside of edge cases involving CSS 5100 // transforms, xScale == yScale so it doesn't matter. 5101 resolution *= ViewportUtils::TryInferEnclosingResolution(shell).xScale; 5102 } 5103 5104 if (resolution == 1.0) { 5105 continue; 5106 } 5107 5108 info->mResolution *= resolution; 5109 nsIFrame* rootScrollContainerFrame = shell->GetRootScrollContainerFrame(); 5110 ViewID zoomedId = nsLayoutUtils::FindOrCreateIDFor( 5111 rootScrollContainerFrame->GetContent()); 5112 5113 nsDisplayList wrapped(&info->mBuilder); 5114 wrapped.AppendNewToTop<nsDisplayAsyncZoom>( 5115 &info->mBuilder, rootScrollContainerFrame, &info->mList, nullptr, 5116 nsDisplayItem::ContainerASRType::Constant, zoomedId); 5117 info->mList.AppendToTop(&wrapped); 5118 } 5119 5120 #ifdef DEBUG 5121 if (gDumpRangePaintList) { 5122 fprintf(stderr, "CreateRangePaintInfo --- before ClipListToRange:\n"); 5123 nsIFrame::PrintDisplayList(&(info->mBuilder), info->mList); 5124 } 5125 #endif 5126 5127 nsRect rangeRect = ClipListToRange(&info->mBuilder, &info->mList, aRange); 5128 5129 info->mBuilder.LeavePresShell(ancestorFrame, &info->mList); 5130 5131 #ifdef DEBUG 5132 if (gDumpRangePaintList) { 5133 fprintf(stderr, "CreateRangePaintInfo --- after ClipListToRange:\n"); 5134 nsIFrame::PrintDisplayList(&(info->mBuilder), info->mList); 5135 } 5136 #endif 5137 5138 // determine the offset of the reference frame for the display list 5139 // to the root frame. This will allow the coordinates used when painting 5140 // to all be offset from the same point 5141 info->mRootOffset = ancestorFrame->GetBoundingClientRect().TopLeft(); 5142 rangeRect.MoveBy(info->mRootOffset); 5143 aSurfaceRect.UnionRect(aSurfaceRect, rangeRect); 5144 5145 return info; 5146 } 5147 5148 already_AddRefed<SourceSurface> PresShell::PaintRangePaintInfo( 5149 const nsTArray<UniquePtr<RangePaintInfo>>& aItems, Selection* aSelection, 5150 const Maybe<CSSIntRegion>& aRegion, nsRect aArea, 5151 const LayoutDeviceIntPoint aPoint, LayoutDeviceIntRect* aScreenRect, 5152 RenderImageFlags aFlags) { 5153 nsPresContext* pc = GetPresContext(); 5154 if (!pc || aArea.width == 0 || aArea.height == 0) { 5155 return nullptr; 5156 } 5157 5158 // use the rectangle to create the surface 5159 LayoutDeviceIntRect pixelArea = LayoutDeviceIntRect::FromAppUnitsToOutside( 5160 aArea, pc->AppUnitsPerDevPixel()); 5161 5162 // if the image should not be resized, scale must be 1 5163 float scale = 1.0; 5164 5165 const nsRect maxSize = pc->DeviceContext()->GetClientRect(); 5166 5167 // check if the image should be resized 5168 bool resize = !!(aFlags & RenderImageFlags::AutoScale); 5169 5170 if (resize) { 5171 // check if image-resizing-algorithm should be used 5172 if (aFlags & RenderImageFlags::IsImage) { 5173 // get max screensize 5174 int32_t maxWidth = pc->AppUnitsToDevPixels(maxSize.width); 5175 int32_t maxHeight = pc->AppUnitsToDevPixels(maxSize.height); 5176 // resize image relative to the screensize 5177 // get best height/width relative to screensize 5178 float bestHeight = float(maxHeight) * RELATIVE_SCALEFACTOR; 5179 float bestWidth = float(maxWidth) * RELATIVE_SCALEFACTOR; 5180 // calculate scale for bestWidth 5181 float adjustedScale = bestWidth / float(pixelArea.width); 5182 // get the worst height (height when width is perfect) 5183 float worstHeight = float(pixelArea.height) * adjustedScale; 5184 // get the difference of best and worst height 5185 float difference = bestHeight - worstHeight; 5186 // halve the difference and add it to worstHeight to get 5187 // the best compromise between bestHeight and bestWidth, 5188 // then calculate the corresponding scale factor 5189 adjustedScale = (worstHeight + difference / 2) / float(pixelArea.height); 5190 // prevent upscaling 5191 scale = std::min(scale, adjustedScale); 5192 } else { 5193 // get half of max screensize 5194 int32_t maxWidth = pc->AppUnitsToDevPixels(maxSize.width >> 1); 5195 int32_t maxHeight = pc->AppUnitsToDevPixels(maxSize.height >> 1); 5196 if (pixelArea.width > maxWidth || pixelArea.height > maxHeight) { 5197 // divide the maximum size by the image size in both directions. 5198 // Whichever direction produces the smallest result determines how much 5199 // should be scaled. 5200 if (pixelArea.width > maxWidth) { 5201 scale = std::min(scale, float(maxWidth) / pixelArea.width); 5202 } 5203 if (pixelArea.height > maxHeight) { 5204 scale = std::min(scale, float(maxHeight) / pixelArea.height); 5205 } 5206 } 5207 } 5208 5209 // Pick a resolution scale factor that is the highest we need for any of 5210 // the items. This means some items may get rendered at a higher-than-needed 5211 // resolution but at least nothing will be avoidably blurry. 5212 float resolutionScale = 1.0; 5213 for (const UniquePtr<RangePaintInfo>& rangeInfo : aItems) { 5214 resolutionScale = std::max(resolutionScale, rangeInfo->mResolution); 5215 } 5216 float unclampedResolution = resolutionScale; 5217 // Clamp the resolution scale so that `pixelArea` when scaled by `scale` and 5218 // `resolutionScale` isn't bigger than `maxSize`. This prevents creating 5219 // giant/unbounded images. 5220 resolutionScale = 5221 std::min(resolutionScale, maxSize.width / (scale * pixelArea.width)); 5222 resolutionScale = 5223 std::min(resolutionScale, maxSize.height / (scale * pixelArea.height)); 5224 // The following assert should only get hit if pixelArea scaled by `scale` 5225 // alone would already have been bigger than `maxSize`, which should never 5226 // be the case. For release builds we handle gracefully by reverting 5227 // resolutionScale to 1.0 to avoid unexpected consequences. 5228 MOZ_ASSERT(resolutionScale >= 1.0); 5229 resolutionScale = std::max(1.0f, resolutionScale); 5230 5231 scale *= resolutionScale; 5232 5233 // Now we need adjust the output screen position of the surface based on the 5234 // scaling factor and any APZ zoom that may be in effect. The goal is here 5235 // to set `aScreenRect`'s top-left corner (in screen-relative LD pixels) 5236 // such that the scaling effect on the surface appears anchored at `aPoint` 5237 // ("anchor" here is like "transform-origin"). When this code is e.g. used 5238 // to generate a drag image for dragging operations, `aPoint` refers to the 5239 // position of the mouse cursor (also in screen-relative LD pixels), and the 5240 // user-visible effect of doing this is that the point at which the user 5241 // clicked to start the drag remains under the mouse during the drag. 5242 5243 // In order to do this we first compute the top-left corner of the 5244 // pixelArea is screen-relative LD pixels. 5245 LayoutDevicePoint visualPoint = ViewportUtils::ToScreenRelativeVisual( 5246 LayoutDevicePoint(pixelArea.TopLeft()), pc); 5247 // And then adjust the output screen position based on that, which we can do 5248 // since everything here is screen-relative LD pixels. Note that the scale 5249 // factor we use here is the effective "transform" scale applied to the 5250 // content we're painting, relative to the scale at which it would normally 5251 // get painted at as part of page rendering (`unclampedResolution`). 5252 float scaleRelativeToNormalContent = scale / unclampedResolution; 5253 aScreenRect->x = 5254 NSToIntFloor(aPoint.x - float(aPoint.x.value - visualPoint.x.value) * 5255 scaleRelativeToNormalContent); 5256 aScreenRect->y = 5257 NSToIntFloor(aPoint.y - float(aPoint.y.value - visualPoint.y.value) * 5258 scaleRelativeToNormalContent); 5259 5260 pixelArea.width = NSToIntFloor(float(pixelArea.width) * scale); 5261 pixelArea.height = NSToIntFloor(float(pixelArea.height) * scale); 5262 if (!pixelArea.width || !pixelArea.height) { 5263 return nullptr; 5264 } 5265 } else { 5266 // move aScreenRect to the position of the surface in screen coordinates 5267 LayoutDevicePoint visualPoint = ViewportUtils::ToScreenRelativeVisual( 5268 LayoutDevicePoint(pixelArea.TopLeft()), pc); 5269 aScreenRect->MoveTo(RoundedToInt(visualPoint)); 5270 } 5271 aScreenRect->width = pixelArea.width; 5272 aScreenRect->height = pixelArea.height; 5273 5274 RefPtr<DrawTarget> dt = 5275 gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget( 5276 IntSize(pixelArea.width, pixelArea.height), SurfaceFormat::B8G8R8A8); 5277 if (!dt || !dt->IsValid()) { 5278 return nullptr; 5279 } 5280 5281 gfxContext ctx(dt); 5282 5283 if (aRegion) { 5284 RefPtr<PathBuilder> builder = dt->CreatePathBuilder(FillRule::FILL_WINDING); 5285 5286 // Convert aRegion from CSS pixels to dev pixels 5287 nsIntRegion region = aRegion->ToAppUnits(AppUnitsPerCSSPixel()) 5288 .ToOutsidePixels(pc->AppUnitsPerDevPixel()); 5289 for (auto iter = region.RectIter(); !iter.Done(); iter.Next()) { 5290 const IntRect& rect = iter.Get(); 5291 5292 builder->MoveTo(rect.TopLeft()); 5293 builder->LineTo(rect.TopRight()); 5294 builder->LineTo(rect.BottomRight()); 5295 builder->LineTo(rect.BottomLeft()); 5296 builder->LineTo(rect.TopLeft()); 5297 } 5298 5299 RefPtr<Path> path = builder->Finish(); 5300 ctx.Clip(path); 5301 } 5302 5303 gfxMatrix initialTM = ctx.CurrentMatrixDouble(); 5304 5305 if (resize) { 5306 initialTM.PreScale(scale, scale); 5307 } 5308 5309 // translate so that points are relative to the surface area 5310 gfxPoint surfaceOffset = nsLayoutUtils::PointToGfxPoint( 5311 -aArea.TopLeft(), pc->AppUnitsPerDevPixel()); 5312 initialTM.PreTranslate(surfaceOffset); 5313 5314 // temporarily hide the selection so that text is drawn normally. If a 5315 // selection is being rendered, use that, otherwise use the presshell's 5316 // selection. 5317 RefPtr<nsFrameSelection> frameSelection; 5318 if (aSelection) { 5319 frameSelection = aSelection->GetFrameSelection(); 5320 } else { 5321 frameSelection = FrameSelection(); 5322 } 5323 int16_t oldDisplaySelection = frameSelection->GetDisplaySelection(); 5324 frameSelection->SetDisplaySelection(nsISelectionController::SELECTION_HIDDEN); 5325 5326 // next, paint each range in the selection 5327 for (const UniquePtr<RangePaintInfo>& rangeInfo : aItems) { 5328 // the display lists paint relative to the offset from the reference 5329 // frame, so account for that translation too: 5330 gfxPoint rootOffset = nsLayoutUtils::PointToGfxPoint( 5331 rangeInfo->mRootOffset, pc->AppUnitsPerDevPixel()); 5332 ctx.SetMatrixDouble(initialTM.PreTranslate(rootOffset)); 5333 aArea.MoveBy(-rangeInfo->mRootOffset.x, -rangeInfo->mRootOffset.y); 5334 nsRegion visible(aArea); 5335 rangeInfo->mList.PaintRoot(&rangeInfo->mBuilder, &ctx, 5336 nsDisplayList::PAINT_DEFAULT, Nothing()); 5337 aArea.MoveBy(rangeInfo->mRootOffset.x, rangeInfo->mRootOffset.y); 5338 } 5339 5340 // restore the old selection display state 5341 frameSelection->SetDisplaySelection(oldDisplaySelection); 5342 5343 return dt->Snapshot(); 5344 } 5345 5346 already_AddRefed<SourceSurface> PresShell::RenderNode( 5347 nsINode* aNode, const Maybe<CSSIntRegion>& aRegion, 5348 const LayoutDeviceIntPoint aPoint, LayoutDeviceIntRect* aScreenRect, 5349 RenderImageFlags aFlags) { 5350 // area will hold the size of the surface needed to draw the node, measured 5351 // from the root frame. 5352 nsRect area; 5353 nsTArray<UniquePtr<RangePaintInfo>> rangeItems; 5354 5355 // nothing to draw if the node isn't in a document 5356 if (!aNode->IsInComposedDoc()) { 5357 return nullptr; 5358 } 5359 5360 RefPtr<nsRange> range = nsRange::Create(aNode); 5361 IgnoredErrorResult rv; 5362 range->SelectNode(*aNode, rv); 5363 if (rv.Failed()) { 5364 return nullptr; 5365 } 5366 5367 UniquePtr<RangePaintInfo> info = CreateRangePaintInfo(range, area, false); 5368 if (info) { 5369 // XXX(Bug 1631371) Check if this should use a fallible operation as it 5370 // pretended earlier, or change the return type to void. 5371 rangeItems.AppendElement(std::move(info)); 5372 } 5373 5374 Maybe<CSSIntRegion> region = aRegion; 5375 if (region) { 5376 // combine the area with the supplied region 5377 CSSIntRect rrectPixels = region->GetBounds(); 5378 5379 nsRect rrect = ToAppUnits(rrectPixels, AppUnitsPerCSSPixel()); 5380 area.IntersectRect(area, rrect); 5381 5382 nsPresContext* pc = GetPresContext(); 5383 if (!pc) { 5384 return nullptr; 5385 } 5386 5387 // move the region so that it is offset from the topleft corner of the 5388 // surface 5389 region->MoveBy(-nsPresContext::AppUnitsToIntCSSPixels(area.x), 5390 -nsPresContext::AppUnitsToIntCSSPixels(area.y)); 5391 } 5392 5393 return PaintRangePaintInfo(rangeItems, nullptr, region, area, aPoint, 5394 aScreenRect, aFlags); 5395 } 5396 5397 already_AddRefed<SourceSurface> PresShell::RenderSelection( 5398 Selection* aSelection, const LayoutDeviceIntPoint aPoint, 5399 LayoutDeviceIntRect* aScreenRect, RenderImageFlags aFlags) { 5400 // area will hold the size of the surface needed to draw the selection, 5401 // measured from the root frame. 5402 nsRect area; 5403 nsTArray<UniquePtr<RangePaintInfo>> rangeItems; 5404 5405 // iterate over each range and collect them into the rangeItems array. 5406 // This is done so that the size of selection can be determined so as 5407 // to allocate a surface area 5408 const uint32_t rangeCount = aSelection->RangeCount(); 5409 NS_ASSERTION(rangeCount > 0, "RenderSelection called with no selection"); 5410 for (const uint32_t r : IntegerRange(rangeCount)) { 5411 MOZ_ASSERT(aSelection->RangeCount() == rangeCount); 5412 RefPtr<nsRange> range = aSelection->GetRangeAt(r); 5413 5414 UniquePtr<RangePaintInfo> info = CreateRangePaintInfo(range, area, true); 5415 if (info) { 5416 // XXX(Bug 1631371) Check if this should use a fallible operation as it 5417 // pretended earlier. 5418 rangeItems.AppendElement(std::move(info)); 5419 } 5420 } 5421 5422 return PaintRangePaintInfo(rangeItems, aSelection, Nothing(), area, aPoint, 5423 aScreenRect, aFlags); 5424 } 5425 5426 static void AddDisplayItemToBottom(nsDisplayListBuilder* aBuilder, 5427 nsDisplayList* aList, nsDisplayItem* aItem) { 5428 nsDisplayList list(aBuilder); 5429 list.AppendToTop(aItem); 5430 list.AppendToTop(aList); 5431 aList->AppendToTop(&list); 5432 } 5433 5434 void PresShell::AddCanvasBackgroundColorItem(nsDisplayListBuilder* aBuilder, 5435 nsDisplayList* aList, 5436 nsIFrame* aFrame, 5437 const nsRect& aBounds, 5438 nscolor aBackstopColor) { 5439 if (aBounds.IsEmpty() || !aFrame->IsViewportFrame()) { 5440 // We don't want to add an item for the canvas background color if the frame 5441 // (sub)tree we are painting doesn't include any canvas frames. 5442 return; 5443 } 5444 5445 const SingleCanvasBackground& canvasBg = mCanvasBackground.mViewport; 5446 const nscolor bgcolor = NS_ComposeColors(aBackstopColor, canvasBg.mColor); 5447 if (NS_GET_A(bgcolor) == 0) { 5448 return; 5449 } 5450 5451 // With async scrolling, we'd like to have two instances of the background 5452 // color: one that scrolls with the content and one underneath which does not 5453 // scroll with the content, but which can be shown during checkerboarding and 5454 // overscroll and the dynamic toolbar movement. We can only do that if the 5455 // color is opaque. 5456 // 5457 // We also need to paint the background if CSS hasn't specified it (since 5458 // otherwise nsCanvasFrame might not paint it). Note that non-CSS-specified 5459 // backgrounds shouldn't ever be semi-transparent. 5460 const bool forceUnscrolledItem = 5461 nsLayoutUtils::UsesAsyncScrolling(aFrame) && NS_GET_A(bgcolor) == 255; 5462 if (canvasBg.mCSSSpecified && !forceUnscrolledItem) { 5463 return; 5464 } 5465 5466 MOZ_ASSERT(NS_GET_A(bgcolor) == 255); 5467 const bool isRootContentDocumentCrossProcess = 5468 mPresContext->IsRootContentDocumentCrossProcess(); 5469 MOZ_ASSERT_IF( 5470 !aFrame->GetParent() && isRootContentDocumentCrossProcess && 5471 mPresContext->HasDynamicToolbar(), 5472 aBounds.Size() == 5473 nsLayoutUtils::ExpandHeightForDynamicToolbar( 5474 mPresContext, aFrame->InkOverflowRectRelativeToSelf().Size())); 5475 5476 nsDisplaySolidColor* item = 5477 MakeDisplayItem<nsDisplaySolidColor>(aBuilder, aFrame, aBounds, bgcolor); 5478 if (canvasBg.mCSSSpecified && isRootContentDocumentCrossProcess) { 5479 item->SetIsCheckerboardBackground(); 5480 } 5481 AddDisplayItemToBottom(aBuilder, aList, item); 5482 } 5483 5484 bool PresShell::IsTransparentContainerElement() const { 5485 if (mDocument->IsInitialDocument()) { 5486 switch (StaticPrefs::layout_css_initial_document_transparency()) { 5487 case 3: 5488 return true; 5489 case 2: 5490 if (!mDocument->IsTopLevelContentDocument()) { 5491 return true; 5492 } 5493 [[fallthrough]]; 5494 case 1: 5495 if (mDocument->IsLikelyContentInaccessibleTopLevelAboutBlank()) { 5496 return true; 5497 } 5498 [[fallthrough]]; 5499 default: 5500 break; 5501 } 5502 } 5503 5504 nsPresContext* pc = GetPresContext(); 5505 if (!pc->IsRootContentDocumentCrossProcess()) { 5506 if (mDocument->IsInChromeDocShell()) { 5507 return true; 5508 } 5509 // Frames are transparent except if their used embedder color-scheme is 5510 // mismatched, in which case we use an opaque background to avoid 5511 // black-on-black or white-on-white text, see 5512 // https://github.com/w3c/csswg-drafts/issues/4772 5513 if (BrowsingContext* bc = mDocument->GetBrowsingContext()) { 5514 switch (bc->GetEmbedderColorSchemes().mUsed) { 5515 case dom::PrefersColorSchemeOverride::Light: 5516 return pc->DefaultBackgroundColorScheme() == ColorScheme::Light; 5517 case dom::PrefersColorSchemeOverride::Dark: 5518 return pc->DefaultBackgroundColorScheme() == ColorScheme::Dark; 5519 case dom::PrefersColorSchemeOverride::None: 5520 break; 5521 } 5522 } 5523 return true; 5524 } 5525 5526 nsIDocShell* docShell = pc->GetDocShell(); 5527 if (!docShell) { 5528 return false; 5529 } 5530 nsPIDOMWindowOuter* pwin = docShell->GetWindow(); 5531 if (!pwin) { 5532 return false; 5533 } 5534 if (Element* containerElement = pwin->GetFrameElementInternal()) { 5535 return containerElement->HasAttr(nsGkAtoms::transparent); 5536 } 5537 if (BrowserChild* tab = BrowserChild::GetFrom(docShell)) { 5538 // Check if presShell is the top PresShell. Only the top can influence the 5539 // canvas background color. 5540 return this == tab->GetTopLevelPresShell() && tab->IsTransparent(); 5541 } 5542 return false; 5543 } 5544 5545 nscolor PresShell::GetDefaultBackgroundColorToDraw() const { 5546 if (!mPresContext) { 5547 return NS_RGB(255, 255, 255); 5548 } 5549 return mPresContext->DefaultBackgroundColor(); 5550 } 5551 5552 void PresShell::UpdateCanvasBackground() { 5553 mCanvasBackground = ComputeCanvasBackground(); 5554 } 5555 5556 static SingleCanvasBackground ComputeSingleCanvasBackground(nsIFrame* aCanvas) { 5557 MOZ_ASSERT(aCanvas->IsCanvasFrame()); 5558 const nsIFrame* bgFrame = nsCSSRendering::FindBackgroundFrame(aCanvas); 5559 static constexpr nscolor kTransparent = NS_RGBA(0, 0, 0, 0); 5560 if (bgFrame->IsThemed()) { 5561 // Ignore the CSS background-color if `appearance` is used on the root. 5562 return {kTransparent, false}; 5563 } 5564 bool drawBackgroundImage = false; 5565 bool drawBackgroundColor = false; 5566 nscolor color = nsCSSRendering::DetermineBackgroundColor( 5567 aCanvas->PresContext(), bgFrame->Style(), aCanvas, drawBackgroundImage, 5568 drawBackgroundColor); 5569 if (!drawBackgroundColor) { 5570 // No need to draw the CSS-specified background (or no CSS-specified 5571 // background at all). 5572 return {kTransparent, false}; 5573 } 5574 return {color, true}; 5575 } 5576 5577 PresShell::CanvasBackground PresShell::ComputeCanvasBackground() const { 5578 // If we have a frame tree and it has style information that 5579 // specifies the background color of the canvas, update our local 5580 // cache of that color. 5581 nsIFrame* canvas = GetCanvasFrame(); 5582 if (!canvas) { 5583 nscolor color = GetDefaultBackgroundColorToDraw(); 5584 // If the root element of the document (ie html) has style 'display: none' 5585 // then the document's background color does not get drawn; return the color 5586 // we actually draw. 5587 return {{color, false}, {color, false}}; 5588 } 5589 5590 auto viewportBg = ComputeSingleCanvasBackground(canvas); 5591 if (!IsTransparentContainerElement()) { 5592 viewportBg.mColor = 5593 NS_ComposeColors(GetDefaultBackgroundColorToDraw(), viewportBg.mColor); 5594 } 5595 auto pageBg = viewportBg; 5596 nsCanvasFrame* docElementCb = 5597 mFrameConstructor->GetDocElementContainingBlock(); 5598 if (canvas != docElementCb) { 5599 // We're in paged mode / print / print-preview, and just computed the "root" 5600 // canvas background. Compute the doc element containing block background 5601 // too. 5602 MOZ_ASSERT(mPresContext->IsRootPaginatedDocument()); 5603 pageBg = ComputeSingleCanvasBackground(docElementCb); 5604 } 5605 return {viewportBg, pageBg}; 5606 } 5607 5608 nscolor PresShell::ComputeBackstopColor(nsIFrame* aDisplayRoot) { 5609 nsIWidget* widget = 5610 aDisplayRoot ? aDisplayRoot->GetNearestWidget() : GetNearestWidget(); 5611 if (widget && 5612 (widget->GetTransparencyMode() != widget::TransparencyMode::Opaque || 5613 widget->WidgetPaintsBackground())) { 5614 // Within a transparent widget, so the backstop color must be 5615 // totally transparent. 5616 return NS_RGBA(0, 0, 0, 0); 5617 } 5618 // Within an opaque widget (or no widget at all), so the backstop 5619 // color must be totally opaque. The user's default background 5620 // as reported by the prescontext is guaranteed to be opaque. 5621 return GetDefaultBackgroundColorToDraw(); 5622 } 5623 5624 struct PaintParams { 5625 nscolor mBackgroundColor; 5626 }; 5627 5628 WindowRenderer* PresShell::GetWindowRenderer() { 5629 if (nsIWidget* widget = GetOwnWidget()) { 5630 return widget->GetWindowRenderer(); 5631 } 5632 return nullptr; 5633 } 5634 5635 nsIWidget* PresShell::GetNearestWidget() const { 5636 if (auto* widget = GetOwnWidget()) { 5637 return widget; 5638 } 5639 if (auto* embedder = GetInProcessEmbedderFrame()) { 5640 return embedder->GetNearestWidget(); 5641 } 5642 return GetRootWidget(); 5643 } 5644 5645 nsIWidget* PresShell::GetOwnWidget() const { 5646 return mWidgetListener ? mWidgetListener->GetWidget() : nullptr; 5647 } 5648 5649 bool PresShell::AsyncPanZoomEnabled() { 5650 if (nsIWidget* widget = GetOwnWidget()) { 5651 return widget->AsyncPanZoomEnabled(); 5652 } 5653 return gfxPlatform::AsyncPanZoomEnabled(); 5654 } 5655 5656 nsresult PresShell::SetResolutionAndScaleTo(float aResolution, 5657 ResolutionChangeOrigin aOrigin) { 5658 if (!(aResolution > 0.0)) { 5659 return NS_ERROR_ILLEGAL_VALUE; 5660 } 5661 if (aResolution == mResolution.valueOr(0.0)) { 5662 MOZ_ASSERT(mResolution.isSome()); 5663 return NS_OK; 5664 } 5665 5666 // GetResolution handles mResolution being nothing by returning 1 so this 5667 // is checking that the resolution is actually changing. 5668 bool resolutionUpdated = aResolution != GetResolution(); 5669 5670 mLastResolutionChangeOrigin = aOrigin; 5671 5672 RenderingState state(this); 5673 state.mResolution = Some(aResolution); 5674 SetRenderingState(state); 5675 if (mMobileViewportManager) { 5676 mMobileViewportManager->ResolutionUpdated(aOrigin); 5677 } 5678 // Changing the resolution changes the visual viewport size which may 5679 // make the current visual viewport offset out-of-bounds (if the size 5680 // increased). APZ will reconcile this by sending a clamped visual 5681 // viewport offset on the next repaint, but to avoid main-thread code 5682 // observing an out-of-bounds offset until then, reclamp it here. 5683 if (IsVisualViewportOffsetSet()) { 5684 SetVisualViewportOffset(GetVisualViewportOffset(), 5685 GetLayoutViewportOffset()); 5686 } 5687 if (aOrigin == ResolutionChangeOrigin::Apz) { 5688 mResolutionUpdatedByApz = true; 5689 } else if (resolutionUpdated) { 5690 mResolutionUpdated = true; 5691 } 5692 5693 if (auto* window = nsGlobalWindowInner::Cast(mDocument->GetInnerWindow())) { 5694 window->VisualViewport()->PostResizeEvent(); 5695 } 5696 5697 return NS_OK; 5698 } 5699 5700 float PresShell::GetCumulativeResolution() const { 5701 float resolution = GetResolution(); 5702 nsPresContext* parentCtx = GetPresContext()->GetParentPresContext(); 5703 if (parentCtx) { 5704 resolution *= parentCtx->PresShell()->GetCumulativeResolution(); 5705 } 5706 return resolution; 5707 } 5708 5709 void PresShell::SetRestoreResolution(float aResolution, 5710 LayoutDeviceIntSize aDisplaySize) { 5711 if (mMobileViewportManager) { 5712 mMobileViewportManager->SetRestoreResolution(aResolution, aDisplaySize); 5713 } 5714 } 5715 5716 void PresShell::SetRenderingState(const RenderingState& aState) { 5717 if (GetResolution() != aState.mResolution.valueOr(1.f)) { 5718 if (nsIFrame* frame = GetRootFrame()) { 5719 frame->SchedulePaint(); 5720 } 5721 } 5722 5723 mRenderingStateFlags = aState.mRenderingStateFlags; 5724 mResolution = aState.mResolution; 5725 #ifdef ACCESSIBILITY 5726 if (nsAccessibilityService* accService = GetAccService()) { 5727 accService->NotifyOfResolutionChange(this, GetResolution()); 5728 } 5729 #endif 5730 } 5731 5732 void PresShell::SynthesizeMouseMove(bool aFromScroll) { 5733 if (!StaticPrefs::layout_reflow_synthMouseMove()) { 5734 return; 5735 } 5736 5737 if (mPaintingSuppressed || !mIsActive || !mPresContext) { 5738 return; 5739 } 5740 5741 if (!IsRoot()) { 5742 if (PresShell* rootPresShell = GetRootPresShell()) { 5743 rootPresShell->SynthesizeMouseMove(aFromScroll); 5744 } 5745 return; 5746 } 5747 5748 if (mLastMousePointerId.isNothing() && mPointerIds.IsEmpty()) { 5749 return; 5750 } 5751 5752 if (!mSynthMouseMoveEvent.IsPending()) { 5753 auto ev = MakeRefPtr<nsSynthMouseMoveEvent>(this, aFromScroll); 5754 5755 GetPresContext()->RefreshDriver()->AddRefreshObserver( 5756 ev, FlushType::Display, "Synthetic mouse move event"); 5757 mSynthMouseMoveEvent = std::move(ev); 5758 } 5759 } 5760 5761 static nsMenuPopupFrame* FindPopupFrame(nsPresContext* aRootPresContext, 5762 nsIWidget* aRootWidget, 5763 const LayoutDeviceIntPoint& aPt) { 5764 return nsLayoutUtils::GetPopupFrameForPoint( 5765 aRootPresContext, aRootWidget, aPt, 5766 nsLayoutUtils::GetPopupFrameForPointFlags::OnlyReturnFramesWithWidgets); 5767 } 5768 5769 void PresShell::ProcessSynthMouseMoveEvent(bool aFromScroll) { 5770 auto forgetMouseMove = MakeScopeExit([&]() { 5771 // Must be safe to refer `this` without grabbing it with a RefPtr since this 5772 // method is marked as MOZ_CAN_RUN_SCRIPT, and we don't want to use RefPtr 5773 // here because of a hot path 5774 mSynthMouseMoveEvent.Forget(); 5775 }); 5776 // If drag session has started, we shouldn't synthesize mousemove event. 5777 nsIWidget* widget = GetOwnWidget(); 5778 if (!widget) { 5779 return; 5780 } 5781 nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession(widget); 5782 if (dragSession) { 5783 // Don't forget it. We need to synthesize a mouse move when the drag 5784 // session ends. 5785 forgetMouseMove.release(); 5786 return; 5787 } 5788 5789 if (!mPresContext) { 5790 return; 5791 } 5792 5793 // allow new event to be posted while handling this one only if the 5794 // source of the event is a scroll (to prevent infinite reflow loops) 5795 if (aFromScroll) { 5796 mSynthMouseMoveEvent.Forget(); 5797 forgetMouseMove.release(); 5798 } 5799 5800 NS_ASSERTION(IsRoot(), "Only a root pres shell should be here"); 5801 5802 if (StaticPrefs::dom_event_pointer_boundary_dispatch_when_layout_change()) { 5803 const AutoTArray<uint32_t, 16> pointerIds(mPointerIds.Clone()); 5804 for (const uint32_t pointerId : pointerIds) { 5805 const PointerInfo* const pointerInfo = 5806 PointerEventHandler::GetPointerInfo(pointerId); 5807 if (MOZ_UNLIKELY(!pointerInfo) || !pointerInfo->HasLastState() || 5808 // We shouldn't dispatch pointer boundary events when a layout change 5809 // if the pointer is not a stationary device. 5810 !pointerInfo->InputSourceSupportsHover()) { 5811 continue; 5812 } 5813 // If the pointer is captured, we don't need to dispatch pointer boundary 5814 // events since pointer boundary events should be fired before 5815 // gotpointercapture. 5816 PointerCaptureInfo* const captureInfo = 5817 PointerEventHandler::GetPointerCaptureInfo(pointerId); 5818 if (captureInfo && captureInfo->mOverrideElement) { 5819 continue; 5820 } 5821 ProcessSynthMouseOrPointerMoveEvent(ePointerMove, pointerId, 5822 *pointerInfo); 5823 } 5824 } 5825 5826 if (mLastMousePointerId.isSome()) { 5827 if (const PointerInfo* const lastMouseInfo = 5828 PointerEventHandler::GetLastMouseInfo(this)) { 5829 if (lastMouseInfo->HasLastState()) { 5830 ProcessSynthMouseOrPointerMoveEvent(eMouseMove, *mLastMousePointerId, 5831 *lastMouseInfo); 5832 } 5833 } 5834 } 5835 } 5836 5837 void PresShell::ProcessSynthMouseOrPointerMoveEvent( 5838 EventMessage aMoveMessage, uint32_t aPointerId, 5839 const PointerInfo& aPointerInfo) { 5840 MOZ_ASSERT(aMoveMessage == eMouseMove || aMoveMessage == ePointerMove); 5841 NS_ASSERTION(IsRoot(), "Only a root pres shell should be here"); 5842 5843 #ifdef DEBUG 5844 if (aMoveMessage == eMouseMove || aMoveMessage == ePointerMove) { 5845 MOZ_LOG(aMoveMessage == eMouseMove 5846 ? PointerEventHandler::MouseLocationLogRef() 5847 : PointerEventHandler::PointerLocationLogRef(), 5848 LogLevel::Info, 5849 ("[ps=%p]synthesizing %s to (%d,%d) (pointerId=%u, source=%s)\n", 5850 this, ToChar(aMoveMessage), aPointerInfo.mLastRefPointInRootDoc.x, 5851 aPointerInfo.mLastRefPointInRootDoc.y, aPointerId, 5852 InputSourceToString(aPointerInfo.mInputSource).get())); 5853 } 5854 #endif 5855 5856 int32_t APD = mPresContext->AppUnitsPerDevPixel(); 5857 5858 // mRefPoint will be mMouseLocation relative to the widget of |view|, the 5859 // widget we will put in the event we dispatch, in widgetAPD appunits 5860 nsPoint refpoint(0, 0); 5861 5862 nsIWidget* ownWidget = GetOwnWidget(); 5863 if (!ownWidget) { 5864 return; 5865 } 5866 MOZ_ASSERT(!nsCOMPtr{nsContentUtils::GetDragSession(ownWidget)}); 5867 5868 // We need a widget to put in the event we are going to dispatch so we look 5869 // for a view that has a widget and the mouse location is over. We first look 5870 // for floating views, if there isn't one we use the root view. |view| holds 5871 // that view. 5872 nsCOMPtr<nsIWidget> widget; 5873 // We always dispatch the event to the pres shell that contains the view that 5874 // the mouse is over. pointShell is that. 5875 RefPtr<PresShell> pointShell; 5876 // The appunits per devpixel ratio of |widget|. 5877 int32_t widgetAPD; 5878 // If we're in a child process and the view points to an OOP iframe, this is 5879 // its BrowserBridgeChild. 5880 RefPtr<BrowserBridgeChild> bbc; 5881 5882 // We either dispatch the event to a popup, or a view. 5883 nsMenuPopupFrame* popupFrame = 5884 FindPopupFrame(mPresContext, ownWidget, 5885 LayoutDeviceIntPoint::FromAppUnitsToNearest( 5886 aPointerInfo.mLastRefPointInRootDoc, APD)); 5887 if (popupFrame) { 5888 pointShell = popupFrame->PresShell(); 5889 widget = popupFrame->GetWidget(); 5890 widgetAPD = popupFrame->PresContext()->AppUnitsPerDevPixel(); 5891 refpoint = aPointerInfo.mLastRefPointInRootDoc; 5892 DebugOnly<nsLayoutUtils::TransformResult> result = 5893 nsLayoutUtils::TransformPoint( 5894 RelativeTo{GetRootFrame(), ViewportType::Visual}, 5895 RelativeTo{popupFrame, ViewportType::Layout}, refpoint); 5896 MOZ_ASSERT(result == nsLayoutUtils::TRANSFORM_SUCCEEDED); 5897 } 5898 if (!widget) { 5899 widget = ownWidget; 5900 widgetAPD = APD; 5901 pointShell = this; 5902 refpoint = aPointerInfo.mLastRefPointInRootDoc; 5903 } 5904 NS_ASSERTION(widget, "view should have a widget here"); 5905 Maybe<WidgetMouseEvent> mouseMoveEvent; 5906 Maybe<WidgetPointerEvent> pointerMoveEvent; 5907 if (aMoveMessage == eMouseMove) { 5908 mouseMoveEvent.emplace(true, eMouseMove, widget, 5909 WidgetMouseEvent::eSynthesized); 5910 mouseMoveEvent->mButton = MouseButton::ePrimary; 5911 // We don't want to dispatch preceding pointer event since the caller 5912 // should've already been dispatched it. However, if the target is an OOP 5913 // iframe, we'll set this to true again below. 5914 mouseMoveEvent->convertToPointer = false; 5915 } else { 5916 pointerMoveEvent.emplace(true, ePointerMove, widget); 5917 pointerMoveEvent->mButton = MouseButton::eNotPressed; 5918 pointerMoveEvent->mReason = WidgetMouseEvent::eSynthesized; 5919 } 5920 WidgetMouseEvent& event = 5921 mouseMoveEvent ? mouseMoveEvent.ref() : pointerMoveEvent.ref(); 5922 5923 // If the last cursor location was set by a synthesized mouse event for tests, 5924 // running test should expect a restyle or a DOM mutation under the cursor may 5925 // cause mouse boundary events in a remote process if the cursor is over a 5926 // remote content. Therefore, the events should not be ignored by 5927 // PresShell::HandleEvent in the remote process. So we need to mark the 5928 // synthesized eMouseMove as "synthesized for tests". 5929 event.mFlags.mIsSynthesizedForTests = aPointerInfo.mIsSynthesizedForTests; 5930 5931 event.mRefPoint = 5932 LayoutDeviceIntPoint::FromAppUnitsToNearest(refpoint, widgetAPD); 5933 event.mButtons = aPointerInfo.mLastButtons; 5934 event.mInputSource = aPointerInfo.mInputSource; 5935 event.pointerId = aPointerId; 5936 event.mModifiers = PresShell::GetCurrentModifiers(); 5937 5938 MOZ_ASSERT(pointShell); 5939 // Since this gets run in a refresh tick there isn't an InputAPZContext on 5940 // the stack from the nsIWidget. We need to simulate one with at least 5941 // the correct target guid, so that the correct callback transform gets 5942 // applied if this event goes to a child process. The input block id is set 5943 // to 0 because this is a synthetic event which doesn't really belong to any 5944 // input block. Same for the APZ response field. 5945 InputAPZContext apzContext(aPointerInfo.mLastTargetGuid, 0, 5946 nsEventStatus_eIgnore); 5947 AUTO_PROFILER_MARKER_DOCSHELL("DispatchSynthMouseOrPointerMove", GRAPHICS, 5948 pointShell->GetPresContext()->GetDocShell()); 5949 nsEventStatus status = nsEventStatus_eIgnore; 5950 if (auto* eventFrame = popupFrame ? popupFrame : GetRootFrame()) { 5951 pointShell->HandleEvent(eventFrame, &event, false, &status); 5952 } 5953 } 5954 5955 /* static */ 5956 void PresShell::MarkFramesInListApproximatelyVisible( 5957 const nsDisplayList& aList) { 5958 for (nsDisplayItem* item : aList) { 5959 nsDisplayList* sublist = item->GetChildren(); 5960 if (sublist) { 5961 MarkFramesInListApproximatelyVisible(*sublist); 5962 continue; 5963 } 5964 5965 nsIFrame* frame = item->Frame(); 5966 MOZ_ASSERT(frame); 5967 5968 if (!frame->TrackingVisibility()) { 5969 continue; 5970 } 5971 5972 // Use the presshell containing the frame. 5973 PresShell* presShell = frame->PresShell(); 5974 MOZ_ASSERT(!presShell->AssumeAllFramesVisible()); 5975 if (presShell->mApproximatelyVisibleFrames.EnsureInserted(frame)) { 5976 // The frame was added to mApproximatelyVisibleFrames, so increment its 5977 // visible count. 5978 frame->IncApproximateVisibleCount(); 5979 } 5980 } 5981 } 5982 5983 /* static */ 5984 void PresShell::DecApproximateVisibleCount( 5985 VisibleFrames& aFrames, const Maybe<OnNonvisible>& aNonvisibleAction 5986 /* = Nothing() */) { 5987 for (nsIFrame* frame : aFrames) { 5988 // Decrement the frame's visible count if we're still tracking its 5989 // visibility. (We may not be, if the frame disabled visibility tracking 5990 // after we added it to the visible frames list.) 5991 if (frame->TrackingVisibility()) { 5992 frame->DecApproximateVisibleCount(aNonvisibleAction); 5993 } 5994 } 5995 } 5996 5997 void PresShell::RebuildApproximateFrameVisibilityDisplayList( 5998 const nsDisplayList& aList) { 5999 MOZ_ASSERT(!mApproximateFrameVisibilityVisited, "already visited?"); 6000 mApproximateFrameVisibilityVisited = true; 6001 6002 // Remove the entries of the mApproximatelyVisibleFrames hashtable and put 6003 // them in oldApproxVisibleFrames. 6004 VisibleFrames oldApproximatelyVisibleFrames = 6005 std::move(mApproximatelyVisibleFrames); 6006 6007 MarkFramesInListApproximatelyVisible(aList); 6008 6009 DecApproximateVisibleCount(oldApproximatelyVisibleFrames); 6010 } 6011 6012 void PresShell::ClearApproximateFrameVisibilityVisited() { 6013 if (!mApproximateFrameVisibilityVisited) { 6014 ClearApproximatelyVisibleFramesList(); 6015 } 6016 mApproximateFrameVisibilityVisited = false; 6017 mDocument->EnumerateSubDocuments([](Document& aSubdoc) { 6018 if (auto* ps = aSubdoc.GetPresShell()) { 6019 ps->ClearApproximateFrameVisibilityVisited(); 6020 } 6021 return CallState::Continue; 6022 }); 6023 } 6024 6025 void PresShell::ClearApproximatelyVisibleFramesList( 6026 const Maybe<OnNonvisible>& aNonvisibleAction 6027 /* = Nothing() */) { 6028 DecApproximateVisibleCount(mApproximatelyVisibleFrames, aNonvisibleAction); 6029 mApproximatelyVisibleFrames.Clear(); 6030 } 6031 6032 // aRect is relative to aFrame 6033 // aPreserve3DRect is set upon entering a preserve3d context and it doesn't 6034 // change, it stays relative to the root frame in the preserve3d context. Any 6035 // frame that is in a preserve3d context ignores aRect but takes aPreserve3DRect 6036 // and transforms it from the root of the preserve3d context to itself 6037 // (nsDisplayTransform::UntransformRect does this by default), and passes the 6038 // result down as aRect (leaving aPreserve3DRect untouched). Additionally, we 6039 // descend into every frame inside the preserve3d context (we skip the rect 6040 // intersection test). Any frame that is not in a preserve3d context just uses 6041 // aRect and doesn't need to know about any of this, even if it's parent frame 6042 // is in the preserve3d context. Any frame that is extend3d (ie has preserve3d 6043 // transform style) but not combines3d (ie its either transformed or backface 6044 // visibility hidden and its parent has preserve3d style) forms the root of a 6045 // preserve3d context. And any frame that is combines3d is in a preserve3d 6046 // context. 6047 void PresShell::MarkFramesInSubtreeApproximatelyVisible( 6048 nsIFrame* aFrame, const nsRect& aRect, const nsRect& aPreserve3DRect, 6049 bool aRemoveOnly /* = false */) { 6050 MOZ_DIAGNOSTIC_ASSERT(aFrame, "aFrame arg should be a valid frame pointer"); 6051 MOZ_ASSERT(aFrame->PresShell() == this, "wrong presshell"); 6052 6053 if (aFrame->TrackingVisibility() && aFrame->StyleVisibility()->IsVisible() && 6054 (!aRemoveOnly || 6055 aFrame->GetVisibility() == Visibility::ApproximatelyVisible)) { 6056 MOZ_ASSERT(!AssumeAllFramesVisible()); 6057 if (mApproximatelyVisibleFrames.EnsureInserted(aFrame)) { 6058 // The frame was added to mApproximatelyVisibleFrames, so increment its 6059 // visible count. 6060 aFrame->IncApproximateVisibleCount(); 6061 } 6062 } 6063 6064 nsSubDocumentFrame* subdocFrame = do_QueryFrame(aFrame); 6065 if (subdocFrame) { 6066 PresShell* presShell = subdocFrame->GetSubdocumentPresShellForPainting( 6067 nsSubDocumentFrame::IGNORE_PAINT_SUPPRESSION); 6068 if (presShell && !presShell->AssumeAllFramesVisible()) { 6069 nsRect rect = aRect; 6070 nsIFrame* root = presShell->GetRootFrame(); 6071 if (root) { 6072 rect.MoveBy(aFrame->GetOffsetToCrossDoc(root)); 6073 } else { 6074 rect.MoveBy(-aFrame->GetContentRectRelativeToSelf().TopLeft()); 6075 } 6076 rect = rect.ScaleToOtherAppUnitsRoundOut( 6077 aFrame->PresContext()->AppUnitsPerDevPixel(), 6078 presShell->GetPresContext()->AppUnitsPerDevPixel()); 6079 6080 presShell->RebuildApproximateFrameVisibility(&rect); 6081 } 6082 return; 6083 } 6084 6085 nsRect rect = aRect; 6086 6087 if (ScrollContainerFrame* scrollFrame = do_QueryFrame(aFrame)) { 6088 bool ignoreDisplayPort = false; 6089 if (DisplayPortUtils::IsMissingDisplayPortBaseRect(aFrame->GetContent())) { 6090 // We can properly set the base rect for root scroll frames on top level 6091 // and root content documents. Otherwise the base rect we compute might 6092 // be way too big without the limiting that 6093 // ScrollContainerFrame::DecideScrollableLayer does, so we just ignore the 6094 // displayport in that case. 6095 nsPresContext* pc = aFrame->PresContext(); 6096 if (scrollFrame->IsRootScrollFrameOfDocument() && 6097 (pc->IsRootContentDocumentCrossProcess() || 6098 (pc->IsChrome() && !pc->GetParentPresContext()))) { 6099 nsRect baseRect( 6100 nsPoint(), nsLayoutUtils::CalculateCompositionSizeForFrame(aFrame)); 6101 DisplayPortUtils::SetDisplayPortBase(aFrame->GetContent(), baseRect); 6102 } else { 6103 ignoreDisplayPort = true; 6104 } 6105 } 6106 6107 nsRect displayPort; 6108 bool usingDisplayport = 6109 !ignoreDisplayPort && 6110 DisplayPortUtils::GetDisplayPortForVisibilityTesting( 6111 aFrame->GetContent(), &displayPort); 6112 6113 scrollFrame->NotifyApproximateFrameVisibilityUpdate(!usingDisplayport); 6114 6115 if (usingDisplayport) { 6116 rect = displayPort; 6117 } else { 6118 rect = rect.Intersect(scrollFrame->GetScrollPortRect()); 6119 } 6120 rect = scrollFrame->ExpandRectToNearlyVisible(rect); 6121 } 6122 6123 for (const auto& [list, listID] : aFrame->ChildLists()) { 6124 for (nsIFrame* child : list) { 6125 // Note: This assert should be trivially satisfied, just by virtue of how 6126 // nsFrameList and its iterator works (with nullptr being an end-of-list 6127 // sentinel which should terminate the loop). But we do somehow get 6128 // crash reports inside this loop that suggest `child` is null... 6129 MOZ_DIAGNOSTIC_ASSERT(child, "shouldn't have null values in child lists"); 6130 6131 const bool extend3DContext = child->Extend3DContext(); 6132 const bool combines3DTransformWithAncestors = 6133 (extend3DContext || child->IsTransformed()) && 6134 child->Combines3DTransformWithAncestors(); 6135 6136 nsRect r = rect - child->GetPosition(); 6137 if (!combines3DTransformWithAncestors) { 6138 if (!r.IntersectRect(r, child->InkOverflowRect())) { 6139 continue; 6140 } 6141 } 6142 6143 nsRect newPreserve3DRect = aPreserve3DRect; 6144 if (extend3DContext && !combines3DTransformWithAncestors) { 6145 newPreserve3DRect = r; 6146 } 6147 6148 if (child->IsTransformed()) { 6149 if (combines3DTransformWithAncestors) { 6150 r = newPreserve3DRect; 6151 } 6152 const nsRect overflow = child->InkOverflowRectRelativeToSelf(); 6153 nsRect out; 6154 if (nsDisplayTransform::UntransformRect(r, overflow, child, &out)) { 6155 r = out; 6156 } else { 6157 r.SetEmpty(); 6158 } 6159 } 6160 MarkFramesInSubtreeApproximatelyVisible(child, r, newPreserve3DRect, 6161 aRemoveOnly); 6162 } 6163 } 6164 } 6165 6166 void PresShell::RebuildApproximateFrameVisibility( 6167 nsRect* aRect, bool aRemoveOnly /* = false */) { 6168 MOZ_ASSERT(!mApproximateFrameVisibilityVisited, "already visited?"); 6169 mApproximateFrameVisibilityVisited = true; 6170 6171 nsIFrame* rootFrame = GetRootFrame(); 6172 if (!rootFrame) { 6173 return; 6174 } 6175 6176 // Remove the entries of the mApproximatelyVisibleFrames hashtable and put 6177 // them in oldApproximatelyVisibleFrames. 6178 VisibleFrames oldApproximatelyVisibleFrames = 6179 std::move(mApproximatelyVisibleFrames); 6180 6181 nsRect vis(nsPoint(0, 0), rootFrame->GetSize()); 6182 if (aRect) { 6183 vis = *aRect; 6184 } 6185 6186 // If we are in-process root but not the top level content, we need to take 6187 // the intersection with the iframe visible rect. 6188 if (mPresContext->IsRootContentDocumentInProcess() && 6189 !mPresContext->IsRootContentDocumentCrossProcess()) { 6190 // There are two possibilities that we can't get the iframe's visible 6191 // rect other than the iframe is out side of ancestors' display ports. 6192 // a) the BrowserChild is being torn down 6193 // b) the visible rect hasn't been delivered the BrowserChild 6194 // In both cases we consider the visible rect is empty. 6195 Maybe<nsRect> visibleRect; 6196 if (BrowserChild* browserChild = BrowserChild::GetFrom(this)) { 6197 visibleRect = browserChild->GetVisibleRect(); 6198 } 6199 vis = vis.Intersect(visibleRect.valueOr(nsRect())); 6200 } 6201 6202 MarkFramesInSubtreeApproximatelyVisible(rootFrame, vis, vis, aRemoveOnly); 6203 6204 DecApproximateVisibleCount(oldApproximatelyVisibleFrames); 6205 } 6206 6207 void PresShell::UpdateApproximateFrameVisibility() { 6208 DoUpdateApproximateFrameVisibility(/* aRemoveOnly = */ false); 6209 } 6210 6211 void PresShell::DoUpdateApproximateFrameVisibility(bool aRemoveOnly) { 6212 MOZ_ASSERT( 6213 !mPresContext || mPresContext->IsRootContentDocumentInProcess(), 6214 "Updating approximate frame visibility on a non-root content document?"); 6215 6216 mUpdateApproximateFrameVisibilityEvent.Revoke(); 6217 6218 if (mHaveShutDown || mIsDestroying) { 6219 return; 6220 } 6221 6222 // call update on that frame 6223 nsIFrame* rootFrame = GetRootFrame(); 6224 if (!rootFrame) { 6225 ClearApproximatelyVisibleFramesList(Some(OnNonvisible::DiscardImages)); 6226 return; 6227 } 6228 6229 RebuildApproximateFrameVisibility(/* aRect = */ nullptr, aRemoveOnly); 6230 ClearApproximateFrameVisibilityVisited(); 6231 6232 #ifdef DEBUG_FRAME_VISIBILITY_DISPLAY_LIST 6233 // This can be used to debug the frame walker by comparing beforeFrameList 6234 // and mApproximatelyVisibleFrames in RebuildFrameVisibilityDisplayList to see 6235 // if they produce the same results (mApproximatelyVisibleFrames holds the 6236 // frames the display list thinks are visible, beforeFrameList holds the 6237 // frames the frame walker thinks are visible). 6238 nsDisplayListBuilder builder( 6239 rootFrame, nsDisplayListBuilderMode::FRAME_VISIBILITY, false); 6240 nsRect updateRect(nsPoint(0, 0), rootFrame->GetSize()); 6241 nsIFrame* rootScroll = GetRootScrollFrame(); 6242 if (rootScroll) { 6243 nsIContent* content = rootScroll->GetContent(); 6244 if (content) { 6245 (void)nsLayoutUtils::GetDisplayPortForVisibilityTesting( 6246 content, &updateRect, RelativeTo::ScrollFrame); 6247 } 6248 6249 if (IgnoringViewportScrolling()) { 6250 builder.SetIgnoreScrollFrame(rootScroll); 6251 } 6252 } 6253 builder.IgnorePaintSuppression(); 6254 builder.EnterPresShell(rootFrame); 6255 nsDisplayList list; 6256 rootFrame->BuildDisplayListForStackingContext(&builder, updateRect, &list); 6257 builder.LeavePresShell(rootFrame, &list); 6258 6259 RebuildApproximateFrameVisibilityDisplayList(list); 6260 6261 ClearApproximateFrameVisibilityVisited(); 6262 6263 list.DeleteAll(&builder); 6264 #endif 6265 } 6266 6267 bool PresShell::AssumeAllFramesVisible() { 6268 if (!StaticPrefs::layout_framevisibility_enabled() || !mPresContext || 6269 !mDocument) { 6270 return true; 6271 } 6272 6273 // We assume all frames are visible in print, print preview, chrome, and 6274 // resource docs and don't keep track of them. 6275 if (mPresContext->Type() == nsPresContext::eContext_PrintPreview || 6276 mPresContext->Type() == nsPresContext::eContext_Print || 6277 mPresContext->IsChrome() || mDocument->IsResourceDoc()) { 6278 return true; 6279 } 6280 6281 // If we're assuming all frames are visible in the top level content 6282 // document, we need to in subdocuments as well. Otherwise we can get in a 6283 // situation where things like animations won't work in subdocuments because 6284 // their frames appear not to be visible, since we won't schedule an image 6285 // visibility update if the top level content document is assuming all 6286 // frames are visible. 6287 // 6288 // Note that it's not safe to call IsRootContentDocumentInProcess() if we're 6289 // currently being destroyed, so we have to check that first. 6290 if (!mHaveShutDown && !mIsDestroying && 6291 !mPresContext->IsRootContentDocumentInProcess()) { 6292 nsPresContext* presContext = 6293 mPresContext->GetInProcessRootContentDocumentPresContext(); 6294 if (presContext && presContext->PresShell()->AssumeAllFramesVisible()) { 6295 return true; 6296 } 6297 } 6298 6299 return false; 6300 } 6301 6302 void PresShell::ScheduleApproximateFrameVisibilityUpdateSoon() { 6303 if (AssumeAllFramesVisible()) { 6304 return; 6305 } 6306 6307 if (!mPresContext) { 6308 return; 6309 } 6310 6311 nsRefreshDriver* refreshDriver = mPresContext->RefreshDriver(); 6312 if (!refreshDriver) { 6313 return; 6314 } 6315 6316 // Ask the refresh driver to update frame visibility soon. 6317 refreshDriver->ScheduleFrameVisibilityUpdate(); 6318 } 6319 6320 void PresShell::ScheduleApproximateFrameVisibilityUpdateNow() { 6321 if (AssumeAllFramesVisible()) { 6322 return; 6323 } 6324 6325 if (!mPresContext->IsRootContentDocumentInProcess()) { 6326 nsPresContext* presContext = 6327 mPresContext->GetInProcessRootContentDocumentPresContext(); 6328 if (!presContext) { 6329 return; 6330 } 6331 MOZ_ASSERT(presContext->IsRootContentDocumentInProcess(), 6332 "Didn't get a root prescontext from " 6333 "GetInProcessRootContentDocumentPresContext?"); 6334 presContext->PresShell()->ScheduleApproximateFrameVisibilityUpdateNow(); 6335 return; 6336 } 6337 6338 if (mHaveShutDown || mIsDestroying) { 6339 return; 6340 } 6341 6342 if (mUpdateApproximateFrameVisibilityEvent.IsPending()) { 6343 return; 6344 } 6345 6346 RefPtr<nsRunnableMethod<PresShell>> event = 6347 NewRunnableMethod("PresShell::UpdateApproximateFrameVisibility", this, 6348 &PresShell::UpdateApproximateFrameVisibility); 6349 nsresult rv = mDocument->Dispatch(do_AddRef(event)); 6350 6351 if (NS_SUCCEEDED(rv)) { 6352 mUpdateApproximateFrameVisibilityEvent = std::move(event); 6353 } 6354 } 6355 6356 void PresShell::EnsureFrameInApproximatelyVisibleList(nsIFrame* aFrame) { 6357 if (!aFrame->TrackingVisibility()) { 6358 return; 6359 } 6360 6361 if (AssumeAllFramesVisible()) { 6362 aFrame->IncApproximateVisibleCount(); 6363 return; 6364 } 6365 6366 #ifdef DEBUG 6367 // Make sure it's in this pres shell. 6368 nsCOMPtr<nsIContent> content = aFrame->GetContent(); 6369 if (content) { 6370 PresShell* presShell = content->OwnerDoc()->GetPresShell(); 6371 MOZ_ASSERT(!presShell || presShell == this, "wrong shell"); 6372 } 6373 #endif 6374 6375 if (mApproximatelyVisibleFrames.EnsureInserted(aFrame)) { 6376 // We inserted a new entry. 6377 aFrame->IncApproximateVisibleCount(); 6378 } 6379 } 6380 6381 void PresShell::RemoveFrameFromApproximatelyVisibleList(nsIFrame* aFrame) { 6382 #ifdef DEBUG 6383 // Make sure it's in this pres shell. 6384 nsCOMPtr<nsIContent> content = aFrame->GetContent(); 6385 if (content) { 6386 PresShell* presShell = content->OwnerDoc()->GetPresShell(); 6387 MOZ_ASSERT(!presShell || presShell == this, "wrong shell"); 6388 } 6389 #endif 6390 6391 if (AssumeAllFramesVisible()) { 6392 MOZ_ASSERT(mApproximatelyVisibleFrames.Count() == 0, 6393 "Shouldn't have any frames in the table"); 6394 return; 6395 } 6396 6397 if (mApproximatelyVisibleFrames.EnsureRemoved(aFrame) && 6398 aFrame->TrackingVisibility()) { 6399 // aFrame was in the hashtable, and we're still tracking its visibility, 6400 // so we need to decrement its visible count. 6401 aFrame->DecApproximateVisibleCount(); 6402 } 6403 } 6404 6405 void PresShell::PaintAndRequestComposite(nsIFrame* aFrame, 6406 WindowRenderer* aRenderer, 6407 PaintFlags aFlags) { 6408 if (!mIsActive) { 6409 return; 6410 } 6411 6412 NS_ASSERTION(aRenderer, "Must be in paint event"); 6413 if (aRenderer->AsFallback()) { 6414 // The fallback renderer doesn't do any retaining, so we just need to 6415 // notify the view and widget that we're invalid, and we'll do a 6416 // paint+composite from the PaintWindow callback. 6417 if (nsIWidget* widget = aFrame ? aFrame->GetOwnWidget() : nullptr) { 6418 auto bounds = widget->GetBounds(); 6419 widget->Invalidate(LayoutDeviceIntRect({}, bounds.Size())); 6420 } 6421 return; 6422 } 6423 6424 // Otherwise we're a retained WebRenderLayerManager, so we want to call 6425 // Paint to update with any changes and push those to WR. 6426 PaintInternalFlags flags = PaintInternalFlags::None; 6427 if (aFlags & PaintFlags::PaintSyncDecodeImages) { 6428 flags |= PaintInternalFlags::PaintSyncDecodeImages; 6429 } 6430 if (aFlags & PaintFlags::PaintCompositeOffscreen) { 6431 flags |= PaintInternalFlags::PaintCompositeOffscreen; 6432 } 6433 PaintInternal(aFrame, aRenderer, flags); 6434 } 6435 6436 void PresShell::SyncPaintFallback(nsIFrame* aFrame, WindowRenderer* aRenderer) { 6437 if (!mIsActive) { 6438 return; 6439 } 6440 6441 NS_ASSERTION(aRenderer->AsFallback(), 6442 "Can't do Sync paint for remote renderers"); 6443 if (!aRenderer->AsFallback()) { 6444 return; 6445 } 6446 6447 PaintInternal(aFrame, aRenderer, PaintInternalFlags::PaintComposite); 6448 GetPresContext()->NotifyDidPaintForSubtree(); 6449 } 6450 6451 void PresShell::PaintInternal(nsIFrame* aFrame, WindowRenderer* aRenderer, 6452 PaintInternalFlags aFlags) { 6453 MOZ_ASSERT_IF(aFrame, 6454 aFrame->IsViewportFrame() || aFrame->IsMenuPopupFrame()); 6455 nsCString url; 6456 nsIURI* uri = mDocument->GetDocumentURI(); 6457 Document* contentRoot = GetPrimaryContentDocument(); 6458 if (contentRoot) { 6459 uri = contentRoot->GetDocumentURI(); 6460 } 6461 url = uri ? uri->GetSpecOrDefault() : "N/A"_ns; 6462 AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING_RELEVANT_FOR_JS( 6463 "Paint", GRAPHICS, Substring(url, std::min(size_t(128), url.Length()))); 6464 6465 Maybe<js::AutoAssertNoContentJS> nojs; 6466 6467 // On Android, Flash can call into content JS during painting, so we can't 6468 // assert there. However, we don't rely on this assertion on Android because 6469 // we don't paint while JS is running. 6470 #if !defined(MOZ_WIDGET_ANDROID) 6471 if (!(aFlags & PaintInternalFlags::PaintComposite)) { 6472 // We need to allow content JS when the flag is set since we may trigger 6473 // MozAfterPaint events in content in those cases. 6474 nojs.emplace(dom::danger::GetJSContext()); 6475 } 6476 #endif 6477 6478 NS_ASSERTION(!mIsDestroying, "painting a destroyed PresShell"); 6479 NS_ASSERTION(aRenderer, "null renderer"); 6480 6481 MOZ_ASSERT(!mApproximateFrameVisibilityVisited, "Should have been cleared"); 6482 6483 if (!mIsActive) { 6484 return; 6485 } 6486 6487 FocusTarget focusTarget; 6488 if (StaticPrefs::apz_keyboard_enabled_AtStartup()) { 6489 // Update the focus target for async keyboard scrolling. This will be 6490 // forwarded to APZ by nsDisplayList::PaintRoot. We need to to do this 6491 // before we enter the paint phase because dispatching eVoid events can 6492 // cause layout to happen. 6493 uint64_t focusSequenceNumber = mAPZFocusSequenceNumber; 6494 if (nsMenuPopupFrame* popup = do_QueryFrame(aFrame)) { 6495 focusSequenceNumber = popup->GetAPZFocusSequenceNumber(); 6496 } 6497 focusTarget = FocusTarget(this, focusSequenceNumber); 6498 } 6499 6500 nsPresContext* presContext = GetPresContext(); 6501 AUTO_LAYOUT_PHASE_ENTRY_POINT(presContext, Paint); 6502 6503 WebRenderLayerManager* layerManager = aRenderer->AsWebRender(); 6504 6505 // Whether or not we should set first paint when painting is suppressed 6506 // is debatable. For now we'll do it because B2G relied on first paint 6507 // to configure the viewport and we only want to do that when we have 6508 // real content to paint. See Bug 798245 6509 if (mIsFirstPaint && !mPaintingSuppressed) { 6510 MOZ_LOG(gLog, LogLevel::Debug, 6511 ("PresShell::Paint, first paint, this=%p", this)); 6512 6513 if (layerManager) { 6514 layerManager->SetIsFirstPaint(); 6515 } 6516 mIsFirstPaint = false; 6517 } 6518 6519 const bool offscreen = 6520 bool(aFlags & PaintInternalFlags::PaintCompositeOffscreen); 6521 6522 if (!aRenderer->BeginTransaction(url)) { 6523 return; 6524 } 6525 6526 // Send an updated focus target with this transaction. Be sure to do this 6527 // before we paint in the case this is an empty transaction. 6528 if (layerManager) { 6529 layerManager->SetFocusTarget(focusTarget); 6530 } 6531 6532 if (aFrame) { 6533 if (!(aFlags & PaintInternalFlags::PaintSyncDecodeImages) && 6534 !aFrame->HasAnyStateBits(NS_FRAME_UPDATE_LAYER_TREE)) { 6535 if (layerManager) { 6536 layerManager->SetTransactionIdAllocator(presContext->RefreshDriver()); 6537 } 6538 6539 if (aRenderer->EndEmptyTransaction( 6540 (aFlags & PaintInternalFlags::PaintComposite) 6541 ? WindowRenderer::END_DEFAULT 6542 : WindowRenderer::END_NO_COMPOSITE)) { 6543 return; 6544 } 6545 } 6546 aFrame->RemoveStateBits(NS_FRAME_UPDATE_LAYER_TREE); 6547 } 6548 6549 nscolor bgcolor = ComputeBackstopColor(aFrame); 6550 PaintFrameFlags flags = 6551 PaintFrameFlags::WidgetLayers | PaintFrameFlags::ExistingTransaction; 6552 6553 // We force sync-decode for printing / print-preview (printing already does 6554 // this from nsPageSequenceFrame::PrintNextSheet). 6555 // We also force sync-decoding via pref for reftests. 6556 if (aFlags & PaintInternalFlags::PaintSyncDecodeImages || 6557 mDocument->IsStaticDocument() || 6558 StaticPrefs::image_testing_decode_sync_enabled()) { 6559 flags |= PaintFrameFlags::SyncDecodeImages; 6560 } 6561 if (aFlags & PaintInternalFlags::PaintCompositeOffscreen) { 6562 flags |= PaintFrameFlags::CompositeOffscreen; 6563 } 6564 if (aRenderer->GetBackendType() == layers::LayersBackend::LAYERS_WR) { 6565 flags |= PaintFrameFlags::ForWebRender; 6566 } 6567 6568 if (aFrame) { 6569 // We can paint directly into the widget using its layer manager. 6570 SelectionNodeCache cache(*this); 6571 nsLayoutUtils::PaintFrame(nullptr, aFrame, nsRegion(), bgcolor, 6572 nsDisplayListBuilderMode::Painting, flags); 6573 return; 6574 } 6575 6576 bgcolor = NS_ComposeColors(bgcolor, mCanvasBackground.mViewport.mColor); 6577 6578 if (aRenderer->GetBackendType() == layers::LayersBackend::LAYERS_WR) { 6579 LayoutDeviceRect bounds = LayoutDeviceRect::FromAppUnits( 6580 presContext->GetVisibleArea(), presContext->AppUnitsPerDevPixel()); 6581 WebRenderBackgroundData data(wr::ToLayoutRect(bounds), 6582 wr::ToColorF(ToDeviceColor(bgcolor))); 6583 WrFiltersHolder wrFilters; 6584 6585 layerManager->SetTransactionIdAllocator(presContext->RefreshDriver()); 6586 layerManager->EndTransactionWithoutLayer( 6587 nullptr, nullptr, std::move(wrFilters), &data, 0, offscreen); 6588 return; 6589 } 6590 6591 FallbackRenderer* fallback = aRenderer->AsFallback(); 6592 MOZ_ASSERT(fallback); 6593 6594 if (aFlags & PaintInternalFlags::PaintComposite) { 6595 nsIntRect bounds = presContext->GetVisibleArea().ToOutsidePixels( 6596 presContext->AppUnitsPerDevPixel()); 6597 fallback->EndTransactionWithColor(bounds, ToDeviceColor(bgcolor)); 6598 } 6599 } 6600 6601 // static 6602 void PresShell::SetCapturingContent(nsIContent* aContent, CaptureFlags aFlags, 6603 WidgetEvent* aEvent) { 6604 // If capture was set for pointer lock, don't unlock unless we are coming 6605 // out of pointer lock explicitly. 6606 if (!aContent && sCapturingContentInfo.mPointerLock && 6607 !(aFlags & CaptureFlags::PointerLock)) { 6608 return; 6609 } 6610 6611 sCapturingContentInfo.mContent = nullptr; 6612 sCapturingContentInfo.mRemoteTarget = nullptr; 6613 6614 // only set capturing content if allowed or the 6615 // CaptureFlags::IgnoreAllowedState or CaptureFlags::PointerLock are used. 6616 if ((aFlags & CaptureFlags::IgnoreAllowedState) || 6617 sCapturingContentInfo.mAllowed || (aFlags & CaptureFlags::PointerLock)) { 6618 if (aContent) { 6619 sCapturingContentInfo.mContent = aContent; 6620 } 6621 if (aEvent) { 6622 MOZ_ASSERT(XRE_IsParentProcess()); 6623 MOZ_ASSERT(aEvent->mMessage == eMouseDown); 6624 MOZ_ASSERT(aEvent->HasBeenPostedToRemoteProcess()); 6625 sCapturingContentInfo.mRemoteTarget = 6626 BrowserParent::GetLastMouseRemoteTarget(); 6627 MOZ_ASSERT(sCapturingContentInfo.mRemoteTarget); 6628 } 6629 // CaptureFlags::PointerLock is the same as 6630 // CaptureFlags::RetargetToElement & CaptureFlags::IgnoreAllowedState. 6631 sCapturingContentInfo.mRetargetToElement = 6632 !!(aFlags & CaptureFlags::RetargetToElement) || 6633 !!(aFlags & CaptureFlags::PointerLock); 6634 sCapturingContentInfo.mPreventDrag = 6635 !!(aFlags & CaptureFlags::PreventDragStart); 6636 sCapturingContentInfo.mPointerLock = !!(aFlags & CaptureFlags::PointerLock); 6637 } 6638 } 6639 6640 nsIContent* PresShell::GetCurrentEventContent() { 6641 if (mCurrentEventTarget.mContent && 6642 mCurrentEventTarget.mContent->GetComposedDoc() != mDocument) { 6643 mCurrentEventTarget.Clear(); 6644 } 6645 return mCurrentEventTarget.mContent; 6646 } 6647 6648 nsIFrame* PresShell::GetCurrentEventFrame() { 6649 if (MOZ_UNLIKELY(mIsDestroying)) { 6650 return nullptr; 6651 } 6652 6653 // GetCurrentEventContent() makes sure the content is still in the 6654 // same document that this pres shell belongs to. If not, then the 6655 // frame shouldn't get an event, nor should we even assume its safe 6656 // to try and find the frame. 6657 nsIContent* content = GetCurrentEventContent(); 6658 if (!mCurrentEventTarget.mFrame && content) { 6659 mCurrentEventTarget.mFrame = content->GetPrimaryFrame(); 6660 MOZ_ASSERT_IF( 6661 mCurrentEventTarget.mFrame, 6662 mCurrentEventTarget.mFrame->PresContext()->GetPresShell() == this); 6663 } 6664 return mCurrentEventTarget.mFrame; 6665 } 6666 6667 already_AddRefed<nsIContent> PresShell::GetEventTargetContent( 6668 WidgetEvent* aEvent) { 6669 nsCOMPtr<nsIContent> content = GetCurrentEventContent(); 6670 if (!content) { 6671 if (nsIFrame* currentEventFrame = GetCurrentEventFrame()) { 6672 content = currentEventFrame->GetContentForEvent(aEvent); 6673 NS_ASSERTION(!content || content->GetComposedDoc() == mDocument, 6674 "handing out content from a different doc"); 6675 } 6676 } 6677 return content.forget(); 6678 } 6679 6680 void PresShell::PushCurrentEventInfo(const EventTargetInfo& aInfo) { 6681 if (mCurrentEventTarget.IsSet()) { 6682 // XXX Why do we insert first item instead of append it? This requires to 6683 // move the previous items... 6684 mCurrentEventTargetStack.InsertElementAt(0, std::move(mCurrentEventTarget)); 6685 } 6686 mCurrentEventTarget = aInfo; 6687 } 6688 6689 void PresShell::PushCurrentEventInfo(EventTargetInfo&& aInfo) { 6690 if (mCurrentEventTarget.IsSet()) { 6691 mCurrentEventTargetStack.InsertElementAt(0, std::move(mCurrentEventTarget)); 6692 } 6693 mCurrentEventTarget = std::move(aInfo); 6694 } 6695 6696 void PresShell::PopCurrentEventInfo() { 6697 mCurrentEventTarget.Clear(); 6698 6699 if (!mCurrentEventTargetStack.IsEmpty()) { 6700 mCurrentEventTarget = std::move(mCurrentEventTargetStack[0]); 6701 mCurrentEventTargetStack.RemoveElementAt(0); 6702 6703 // Don't use it if it has moved to a different document. 6704 if (mCurrentEventTarget.mContent && 6705 mCurrentEventTarget.mContent->GetComposedDoc() != mDocument) { 6706 mCurrentEventTarget.Clear(); 6707 } 6708 } 6709 } 6710 6711 // static 6712 bool PresShell::EventHandler::InZombieDocument(nsIContent* aContent) { 6713 // If a content node points to a null document, or the document is not 6714 // attached to a window, then it is possibly in a zombie document, 6715 // about to be replaced by a newly loading document. 6716 // Such documents cannot handle DOM events. 6717 // It might actually be in a node not attached to any document, 6718 // in which case there is not parent presshell to retarget it to. 6719 Document* doc = aContent->GetComposedDoc(); 6720 return !doc || !doc->GetWindow(); 6721 } 6722 6723 already_AddRefed<nsPIDOMWindowOuter> PresShell::GetRootWindow() { 6724 nsCOMPtr<nsPIDOMWindowOuter> window = mDocument->GetWindow(); 6725 if (window) { 6726 nsCOMPtr<nsPIDOMWindowOuter> rootWindow = window->GetPrivateRoot(); 6727 NS_ASSERTION(rootWindow, "nsPIDOMWindow::GetPrivateRoot() returns NULL"); 6728 return rootWindow.forget(); 6729 } 6730 6731 // If we don't have DOM window, we're zombie, we should find the root window 6732 // with our parent shell. 6733 RefPtr<PresShell> parentPresShell = GetParentPresShellForEventHandling(); 6734 NS_ENSURE_TRUE(parentPresShell, nullptr); 6735 return parentPresShell->GetRootWindow(); 6736 } 6737 6738 already_AddRefed<nsPIDOMWindowOuter> 6739 PresShell::GetFocusedDOMWindowInOurWindow() { 6740 nsCOMPtr<nsPIDOMWindowOuter> rootWindow = GetRootWindow(); 6741 NS_ENSURE_TRUE(rootWindow, nullptr); 6742 nsCOMPtr<nsPIDOMWindowOuter> focusedWindow; 6743 nsFocusManager::GetFocusedDescendant(rootWindow, 6744 nsFocusManager::eIncludeAllDescendants, 6745 getter_AddRefs(focusedWindow)); 6746 return focusedWindow.forget(); 6747 } 6748 6749 already_AddRefed<nsIContent> PresShell::GetFocusedContentInOurWindow() const { 6750 nsFocusManager* fm = nsFocusManager::GetFocusManager(); 6751 if (fm && mDocument) { 6752 RefPtr<Element> focusedElement; 6753 fm->GetFocusedElementForWindow(mDocument->GetWindow(), false, nullptr, 6754 getter_AddRefs(focusedElement)); 6755 return focusedElement.forget(); 6756 } 6757 return nullptr; 6758 } 6759 6760 already_AddRefed<PresShell> PresShell::GetParentPresShellForEventHandling() { 6761 if (!mPresContext) { 6762 return nullptr; 6763 } 6764 6765 // Now, find the parent pres shell and send the event there 6766 RefPtr<nsDocShell> docShell = mPresContext->GetDocShell(); 6767 if (!docShell) { 6768 docShell = mForwardingContainer.get(); 6769 } 6770 6771 // Might have gone away, or never been around to start with 6772 if (!docShell) { 6773 return nullptr; 6774 } 6775 6776 BrowsingContext* bc = docShell->GetBrowsingContext(); 6777 if (!bc) { 6778 return nullptr; 6779 } 6780 6781 RefPtr<BrowsingContext> parentBC; 6782 if (XRE_IsParentProcess()) { 6783 parentBC = bc->Canonical()->GetParentCrossChromeBoundary(); 6784 } else { 6785 parentBC = bc->GetParent(); 6786 } 6787 6788 RefPtr<nsIDocShell> parentDocShell = 6789 parentBC ? parentBC->GetDocShell() : nullptr; 6790 if (!parentDocShell) { 6791 return nullptr; 6792 } 6793 6794 RefPtr<PresShell> parentPresShell = parentDocShell->GetPresShell(); 6795 return parentPresShell.forget(); 6796 } 6797 6798 nsresult PresShell::EventHandler::RetargetEventToParent( 6799 WidgetGUIEvent* aGUIEvent, nsEventStatus* aEventStatus) { 6800 // Send this events straight up to the parent pres shell. 6801 // We do this for keystroke events in zombie documents or if either a frame 6802 // or a root content is not present. 6803 // That way at least the UI key bindings can work. 6804 6805 RefPtr<PresShell> parentPresShell = GetParentPresShellForEventHandling(); 6806 NS_ENSURE_TRUE(parentPresShell, NS_ERROR_FAILURE); 6807 6808 // Fake the event as though it's from the parent pres shell's root frame. 6809 return parentPresShell->HandleEvent(parentPresShell->GetRootFrame(), 6810 aGUIEvent, true, aEventStatus); 6811 } 6812 6813 void PresShell::DisableNonTestMouseEvents(bool aDisable) { 6814 sDisableNonTestMouseEvents = aDisable; 6815 } 6816 6817 nsPoint PresShell::GetEventLocation(const WidgetMouseEvent& aEvent) const { 6818 nsIFrame* rootFrame = GetRootFrame(); 6819 if (!rootFrame) { 6820 // Matches old TranslateWidgetToView behavior 6821 return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE); 6822 } 6823 6824 RelativeTo relativeTo{rootFrame}; 6825 if (rootFrame->PresContext()->IsRootContentDocumentCrossProcess()) { 6826 relativeTo.mViewportType = ViewportType::Visual; 6827 } 6828 return nsLayoutUtils::GetEventCoordinatesRelativeTo(&aEvent, relativeTo); 6829 } 6830 6831 void PresShell::RecordPointerLocation(WidgetGUIEvent* aEvent) { 6832 if (!mPresContext) { 6833 return; 6834 } 6835 6836 if (!IsRoot()) { 6837 PresShell* rootPresShell = GetRootPresShell(); 6838 if (rootPresShell) { 6839 rootPresShell->RecordPointerLocation(aEvent); 6840 } 6841 return; 6842 } 6843 6844 const auto StoreMouseLocation = [&](const WidgetMouseEvent& aMouseEvent) { 6845 if (aMouseEvent.mMessage == eMouseMove && aMouseEvent.IsSynthesized()) { 6846 return false; 6847 } 6848 PointerEventHandler::RecordMouseState(*this, aMouseEvent); 6849 mLastMousePointerId = Some(aMouseEvent.pointerId); 6850 return true; 6851 }; 6852 6853 const auto ClearMouseLocation = [&](const WidgetMouseEvent& aMouseEvent) { 6854 PointerEventHandler::ClearMouseState(*this, aMouseEvent); 6855 mLastMousePointerId.reset(); 6856 }; 6857 6858 const auto ClearMouseLocationIfSetByTouch = 6859 [&](const WidgetPointerEvent& aPointerEvent) { 6860 const PointerInfo* lastMouseInfo = 6861 PointerEventHandler::GetLastMouseInfo(this); 6862 if (lastMouseInfo && lastMouseInfo->HasLastState() && 6863 lastMouseInfo->mInputSource == 6864 MouseEvent_Binding::MOZ_SOURCE_TOUCH && 6865 aPointerEvent.mInputSource == 6866 MouseEvent_Binding::MOZ_SOURCE_TOUCH) { 6867 ClearMouseLocation(aPointerEvent); 6868 } 6869 }; 6870 6871 const auto StorePointerLocation = 6872 [&](const WidgetMouseEvent& aMouseOrPointerEvent) { 6873 if (!mPointerIds.Contains(aMouseOrPointerEvent.pointerId)) { 6874 mPointerIds.AppendElement(aMouseOrPointerEvent.pointerId); 6875 } 6876 PointerEventHandler::RecordPointerState( 6877 GetEventLocation(aMouseOrPointerEvent), aMouseOrPointerEvent); 6878 }; 6879 6880 const auto ClearPointerLocation = 6881 [&](const WidgetMouseEvent& aMouseOrPointerEvent) { 6882 mPointerIds.RemoveElement(aMouseOrPointerEvent.pointerId); 6883 PointerEventHandler::RecordPointerState( 6884 nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE), 6885 aMouseOrPointerEvent); 6886 }; 6887 6888 const auto StoreLastPointerEventLocation = 6889 [&](const WidgetMouseEvent& aMouseOrPointerEvent) { 6890 // TODO: instead, encapsulate mMouseLocation and 6891 // `mLastOverWindowPointerLocation` in a struct. 6892 mLastOverWindowPointerLocation = GetEventLocation(aMouseOrPointerEvent); 6893 }; 6894 6895 switch (aEvent->mMessage) { 6896 case eMouseMove: 6897 case eMouseEnterIntoWidget: 6898 case eMouseDown: 6899 case eMouseUp: 6900 case eDragEnter: 6901 case eDragStart: 6902 case eDragOver: 6903 case eDrop: { 6904 const WidgetMouseEvent& mouseEvent = *aEvent->AsMouseEvent(); 6905 if (StoreMouseLocation(mouseEvent) && 6906 (aEvent->mMessage == eMouseEnterIntoWidget || 6907 aEvent->mClass == eDragEventClass)) { 6908 SynthesizeMouseMove(false); 6909 } 6910 // In a drag session, we won't dispatch pointer events except 6911 // ePointerCancel immediately after eDragStart. However, once the drag 6912 // session ends, we want to synthesize ePointerMove at the dropped point. 6913 // Therefore, we should update the last state of the pointer when we start 6914 // handling a drag event. 6915 if (aEvent->mClass == eDragEventClass) { 6916 StorePointerLocation(mouseEvent); 6917 } 6918 break; 6919 } 6920 case eDragExit: { 6921 const WidgetMouseEvent& mouseEvent = *aEvent->AsMouseEvent(); 6922 if (aEvent->mRelatedTarget) { 6923 // not exit from the widget 6924 break; 6925 } 6926 ClearMouseLocation(mouseEvent); 6927 ClearPointerLocation(mouseEvent); 6928 break; 6929 } 6930 case eMouseExitFromWidget: { 6931 const WidgetMouseEvent& mouseEvent = *aEvent->AsMouseEvent(); 6932 // Although we only care about the mouse moving into an area for which 6933 // this pres shell doesn't receive mouse move events, we don't check which 6934 // widget the mouse exit was for since this seems to vary by platform. 6935 // Hopefully this won't matter at all since we'll get the mouse move or 6936 // enter after the mouse exit when the mouse moves from one of our widgets 6937 // into another. 6938 ClearMouseLocation(mouseEvent); 6939 ClearPointerLocation(mouseEvent); 6940 break; 6941 } 6942 case ePointerMove: 6943 case ePointerRawUpdate: 6944 case eMouseRawUpdate: { 6945 const WidgetMouseEvent& mouseEvent = *aEvent->AsMouseEvent(); 6946 if (!mouseEvent.IsReal()) { 6947 break; 6948 } 6949 StoreLastPointerEventLocation(mouseEvent); 6950 if (const WidgetPointerEvent* const pointerEvent = 6951 mouseEvent.AsPointerEvent()) { 6952 StorePointerLocation(*pointerEvent); 6953 } 6954 break; 6955 } 6956 case ePointerDown: { 6957 const WidgetPointerEvent& pointerEvent = *aEvent->AsPointerEvent(); 6958 StoreLastPointerEventLocation(pointerEvent); 6959 StorePointerLocation(pointerEvent); 6960 break; 6961 } 6962 case ePointerUp: { 6963 const WidgetPointerEvent& pointerEvent = *aEvent->AsPointerEvent(); 6964 StoreLastPointerEventLocation(pointerEvent); 6965 // If the pointer supports hover, we need to keep storing the last 6966 // position to synthesize ePointerMove after layout changes. 6967 if (pointerEvent.InputSourceSupportsHover()) { 6968 StorePointerLocation(pointerEvent); 6969 } 6970 // If the pointer does not support hover, we won't synthesize ePointerMove 6971 // for that. So, we can clear the pointer location. 6972 else { 6973 ClearPointerLocation(pointerEvent); 6974 } 6975 // If the pointer is for a touch, we need to forget the last state of 6976 // mMouseLocation if it was set by a touch because the touch is being 6977 // removed from the active pointers. 6978 ClearMouseLocationIfSetByTouch(pointerEvent); 6979 break; 6980 } 6981 case ePointerCancel: { 6982 // If a touch is canceled, it means that the touch input is tracked by a 6983 // gesture like swipe to scroll, pinch to zoom or DnD. So, it means that 6984 // a normal touch sequence finished. Then, we shouldn't give `:hover` 6985 // state to the element underneath the last touch point anymore. For 6986 // example, it's odd that new element which comes underneath the first 6987 // touch position gets `:hover` style even though the scroll is caused 6988 // by swipe (i.e., has moved the touch position). 6989 ClearMouseLocationIfSetByTouch(*aEvent->AsPointerEvent()); 6990 break; 6991 } 6992 default: 6993 break; 6994 } 6995 } 6996 6997 // static 6998 void PresShell::RecordModifiers(WidgetGUIEvent* aEvent) { 6999 switch (aEvent->mMessage) { 7000 case eKeyPress: 7001 case eKeyUp: 7002 case eKeyDown: 7003 case eMouseMove: 7004 case eMouseUp: 7005 case eMouseDown: 7006 case eMouseEnterIntoWidget: 7007 case eMouseExitFromWidget: 7008 case eMouseActivate: 7009 case eMouseTouchDrag: 7010 case eMouseLongTap: 7011 case eMouseRawUpdate: 7012 case eMouseExploreByTouch: 7013 case ePointerCancel: 7014 case eContextMenu: 7015 case eTouchStart: 7016 case eTouchMove: 7017 case eTouchEnd: 7018 case eTouchCancel: 7019 case eTouchPointerCancel: 7020 case eTouchRawUpdate: 7021 case eWheel: 7022 sCurrentModifiers = aEvent->AsInputEvent()->mModifiers; 7023 break; 7024 default: 7025 break; 7026 } 7027 } 7028 7029 void PresShell::nsSynthMouseMoveEvent::Revoke() { 7030 if (mPresShell) { 7031 mPresShell->GetPresContext()->RefreshDriver()->RemoveRefreshObserver( 7032 this, FlushType::Display); 7033 mPresShell = nullptr; 7034 } 7035 } 7036 7037 static CallState FlushThrottledStyles(Document& aDocument) { 7038 PresShell* presShell = aDocument.GetPresShell(); 7039 if (presShell && presShell->IsVisible()) { 7040 if (nsPresContext* presContext = presShell->GetPresContext()) { 7041 presContext->RestyleManager()->UpdateOnlyAnimationStyles(); 7042 } 7043 } 7044 7045 aDocument.EnumerateSubDocuments(FlushThrottledStyles); 7046 return CallState::Continue; 7047 } 7048 7049 bool PresShell::CanDispatchEvent(const WidgetGUIEvent* aEvent) const { 7050 bool rv = 7051 mPresContext && !mHaveShutDown && nsContentUtils::IsSafeToRunScript(); 7052 if (aEvent) { 7053 rv &= (aEvent && aEvent->mWidget && !aEvent->mWidget->Destroyed()); 7054 } 7055 return rv; 7056 } 7057 7058 /* static */ 7059 PresShell* PresShell::GetShellForEventTarget(nsIFrame* aFrame, 7060 nsIContent* aContent) { 7061 if (aFrame) { 7062 return aFrame->PresShell(); 7063 } 7064 if (aContent) { 7065 Document* doc = aContent->GetComposedDoc(); 7066 if (!doc) { 7067 return nullptr; 7068 } 7069 return doc->GetPresShell(); 7070 } 7071 return nullptr; 7072 } 7073 7074 /* static */ 7075 PresShell* PresShell::GetShellForTouchEvent(WidgetGUIEvent* aEvent) { 7076 switch (aEvent->mMessage) { 7077 case eTouchMove: 7078 case eTouchRawUpdate: 7079 case eTouchCancel: 7080 case eTouchEnd: { 7081 // get the correct shell to dispatch to 7082 WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent(); 7083 for (dom::Touch* touch : touchEvent->mTouches) { 7084 if (!touch) { 7085 return nullptr; 7086 } 7087 7088 RefPtr<dom::Touch> oldTouch = 7089 TouchManager::GetCapturedTouch(touch->Identifier()); 7090 if (!oldTouch) { 7091 return nullptr; 7092 } 7093 7094 nsIContent* const content = 7095 nsIContent::FromEventTargetOrNull(oldTouch->GetTarget()); 7096 if (!content) { 7097 return nullptr; 7098 } 7099 7100 if (PresShell* const presShell = content->OwnerDoc()->GetPresShell()) { 7101 return presShell; 7102 } 7103 } 7104 return nullptr; 7105 } 7106 default: 7107 return nullptr; 7108 } 7109 } 7110 7111 nsresult PresShell::HandleEvent(nsIFrame* aFrameForPresShell, 7112 WidgetGUIEvent* aGUIEvent, 7113 bool aDontRetargetEvents, 7114 nsEventStatus* aEventStatus) { 7115 MOZ_ASSERT(aGUIEvent); 7116 7117 RecordModifiers(aGUIEvent); 7118 7119 AutoWeakFrame weakFrameForPresShell(aFrameForPresShell); 7120 7121 // Running tests must not expect that some mouse boundary events are fired 7122 // when something occurs in the parent process, e.g., when a popup is 7123 // opened/closed at the last mouse cursor position in the parent process (the 7124 // position may be different from the position which stored in this process). 7125 // Therefore, let's ignore synthesized mouse events coming form another 7126 // process if and only if they are not caused by the API. 7127 if (aGUIEvent->CameFromAnotherProcess() && XRE_IsContentProcess() && 7128 !aGUIEvent->mFlags.mIsSynthesizedForTests) { 7129 const PointerInfo* const lastMouseInfo = 7130 PointerEventHandler::GetLastMouseInfo(); 7131 if (lastMouseInfo && lastMouseInfo->mIsSynthesizedForTests) { 7132 switch (aGUIEvent->mMessage) { 7133 // Synthesized eMouseMove will case mouse boundary events like 7134 // mouseover, mouseout, and :hover state is changed at dispatching the 7135 // events. 7136 case eMouseMove: 7137 // eMouseExitFromWidget comes from the parent process if the cursor 7138 // crosses a puppet widget boundary. Then, the event will be handled as 7139 // a synthesized eMouseMove in this process and may cause unexpected 7140 // `mouseout` and `mouseleave`. 7141 case eMouseExitFromWidget: 7142 // eMouseEnterIntoWidget causes updating the hover state under the event 7143 // position which may be different from the last cursor position 7144 // synthesized in this process. 7145 case eMouseEnterIntoWidget: 7146 if (!aGUIEvent->AsMouseEvent()->IsReal()) { 7147 return NS_OK; 7148 } 7149 break; 7150 default: 7151 break; 7152 } 7153 } 7154 } 7155 7156 // Here we are granting some delays to ensure that user input events are 7157 // created while the page content may not be visible to the user are not 7158 // processed. 7159 // The main purpose of this is to avoid user inputs are handled in the 7160 // new document where as the user inputs were originally targeting some 7161 // content in the old document. 7162 if (!CanHandleUserInputEvents(aGUIEvent)) { 7163 return NS_OK; 7164 } 7165 7166 // If there is a composition and we got a pointing device events which may not 7167 // impossible to continue the composition, we should notify the editor of the 7168 // event before dispatching it. Then, composition will be commited before 7169 // the editor loses focus. This behavior is compatible with Chrome. 7170 // FIXME: Perhaps, we should do same thing before dispatching touch events. 7171 switch (aGUIEvent->mMessage) { 7172 case eMouseDown: 7173 case eMouseUp: { 7174 nsPIDOMWindowOuter* const focusedWindow = 7175 nsFocusManager::GetFocusedWindowStatic(); 7176 if (!focusedWindow) { 7177 break; 7178 } 7179 Document* const focusedDocument = focusedWindow->GetExtantDoc(); 7180 if (!focusedDocument) { 7181 break; 7182 } 7183 nsPresContext* const focusedPresContext = 7184 focusedDocument->GetPresContext(); 7185 if (!focusedPresContext) { 7186 break; 7187 } 7188 const RefPtr<TextComposition> textComposition = 7189 IMEStateManager::GetTextCompositionFor(focusedPresContext); 7190 if (!textComposition) { 7191 break; 7192 } 7193 // If there is a composition and it's managed by an editor, let's notify 7194 // the editor of mouse button event. The editor commits the composition 7195 // unless IME consumes the event. 7196 if (RefPtr<EditorBase> editorBase = textComposition->GetEditorBase()) { 7197 MOZ_ASSERT(aGUIEvent->AsMouseEvent()); 7198 editorBase->WillHandleMouseButtonEvent(*aGUIEvent->AsMouseEvent()); 7199 } 7200 // Otherwise, we should commit the orphan composition instead. 7201 else if (nsCOMPtr<nsIWidget> widget = textComposition->GetWidget()) { 7202 textComposition->RequestToCommit(widget, false); 7203 } 7204 if (!CanHandleUserInputEvents(aGUIEvent)) { 7205 return NS_OK; 7206 } 7207 // Before bug 1945988, we dispatched the mouse button event without 7208 // committing composition and the editor will commit composition. 7209 // However, for compatibility with the other browsers, we started 7210 // committing composition before dispatching the mouse event. To keep 7211 // the traditional behavior, we should compute aFrameForPresShell if it's 7212 // reframed by dispatching the composition events (and input events) 7213 // above. Otherwise, we stop dispatching the mouse button events newly in 7214 // such case. 7215 if (MOZ_UNLIKELY(!weakFrameForPresShell.IsAlive())) { 7216 FlushPendingNotifications(FlushType::Layout); 7217 if (MOZ_UNLIKELY(IsDestroying())) { 7218 return NS_OK; 7219 } 7220 nsIFrame* const newFrameForPresShell = GetRootFrame(); 7221 if (MOZ_UNLIKELY(!newFrameForPresShell)) { 7222 return NS_OK; 7223 } 7224 weakFrameForPresShell = newFrameForPresShell; 7225 } 7226 break; 7227 } 7228 default: 7229 break; 7230 } 7231 7232 if (mPresContext) { 7233 switch (aGUIEvent->mMessage) { 7234 case eMouseMove: 7235 case eMouseRawUpdate: 7236 if (!aGUIEvent->AsMouseEvent()->IsReal()) { 7237 break; 7238 } 7239 [[fallthrough]]; 7240 case eMouseDown: 7241 case eMouseUp: { 7242 // We should flush pending mousemove event now because some mouse 7243 // boundary events which should've already been dispatched before a user 7244 // input may have not been dispatched. E.g., if a mousedown event 7245 // listener removed or appended an element under the cursor and mouseup 7246 // event comes immediately after that, mouseover or mouseout may have 7247 // not been dispatched on the new element yet. 7248 // XXX If eMouseMove is not propery dispatched before eMouseDown and 7249 // a `mousedown` event listener removes the event target or its 7250 // ancestor, eMouseOver will be dispatched between eMouseDown and 7251 // eMouseUp. That could cause unexpected behavior if a `mouseover` 7252 // event listener assumes it's always disptached before `mousedown`. 7253 // However, we're not sure whether it could happen with users' input. 7254 // FIXME: Perhaps, we need to do this for all events which are directly 7255 // caused by user input, e.g., eKeyDown, etc. 7256 const RefPtr<PresShell> rootPresShell = 7257 IsRoot() ? this : GetRootPresShell(); 7258 if (rootPresShell && rootPresShell->mSynthMouseMoveEvent.IsPending()) { 7259 RefPtr<nsSynthMouseMoveEvent> synthMouseMoveEvent = 7260 rootPresShell->mSynthMouseMoveEvent.get(); 7261 synthMouseMoveEvent->Run(); 7262 if (IsDestroying()) { 7263 return NS_OK; 7264 } 7265 // XXX If the frame or "this" is reframed, it might be better to 7266 // recompute the frame. However, it could treat the user input on 7267 // unexpected element. Therefore, we should not do that until we'd 7268 // get a bug report caused by that. 7269 if (MOZ_UNLIKELY(!weakFrameForPresShell.IsAlive())) { 7270 return NS_OK; 7271 } 7272 } 7273 break; 7274 } 7275 default: 7276 break; 7277 } 7278 } 7279 7280 // If the event may cause ePointerMove, we need to dispatch ePointerRawUpdate 7281 // before that if and only if there are some `pointerrawupdate` event 7282 // listeners. Note that if a `pointerrawupdate` event listener destroys its 7283 // document/window, we need to dispatch the following pointer event (e.g., 7284 // ePointerMove) in the parent document/window with the parent PresShell. 7285 // Therefore, we need to consider the target PresShell for each event 7286 // (ePointerRawUpdate and the following pointer event) in 7287 // EventHandler::HandleEvent(). Thus, we need to dispatch the internal event 7288 // for ePointerRawUpdate before calling EventHandler::HandleEvent() below. 7289 if (!aDontRetargetEvents && 7290 StaticPrefs::dom_event_pointer_rawupdate_enabled()) { 7291 nsresult rv = EnsurePrecedingPointerRawUpdate( 7292 weakFrameForPresShell, *aGUIEvent, aDontRetargetEvents); 7293 if (NS_FAILED(rv)) { 7294 return rv; 7295 } 7296 if (!CanHandleUserInputEvents(aGUIEvent)) { 7297 return NS_OK; 7298 } 7299 } 7300 7301 EventHandler eventHandler(*this); 7302 return eventHandler.HandleEvent(weakFrameForPresShell, aGUIEvent, 7303 aDontRetargetEvents, aEventStatus); 7304 } 7305 7306 nsresult PresShell::EnsurePrecedingPointerRawUpdate( 7307 AutoWeakFrame& aWeakFrameForPresShell, const WidgetGUIEvent& aSourceEvent, 7308 bool aDontRetargetEvents) { 7309 MOZ_ASSERT(StaticPrefs::dom_event_pointer_rawupdate_enabled()); 7310 if (PointerEventHandler::ToPointerEventMessage(&aSourceEvent) != 7311 ePointerMove) { 7312 return NS_OK; 7313 } 7314 7315 // We should not dispatch ePointerRawUpdate directly because dispatching 7316 // it requires some steps which are defined by "fire a pointer event" section 7317 // in the spec. https://w3c.github.io/pointerevents/#dfn-fire-a-pointer-event 7318 // We handle the steps when we call DispatchPrecedingPointerEvent(). 7319 // Therefore, this method dispatches eMouseRawUpdate or eTouchRawUpdate event 7320 // if the event should follow a ePointerRawUpdate. Then, 7321 // HandleEventUsingCoordinates() will stop handling the internal events after 7322 // calling DispatchPrecedingPointerEvent(). 7323 7324 MOZ_ASSERT(aSourceEvent.mMessage != eMouseRawUpdate); 7325 MOZ_ASSERT(aSourceEvent.mMessage != eTouchRawUpdate); 7326 7327 // If no window in the browser child has `pointerrawupdate` event listener, 7328 // we should do nothing. 7329 if (auto* const browserChild = BrowserChild::GetFrom(this)) { 7330 if (!browserChild->HasPointerRawUpdateEventListeners()) { 7331 return NS_OK; 7332 } 7333 } 7334 7335 if (const WidgetMouseEvent* const mouseEvent = aSourceEvent.AsMouseEvent()) { 7336 // If `convertToPointer` is `false`, it means that we've already handled the 7337 // event to dispatch a preceding pointer event. Therefore, its preceding 7338 // event should've already been handled. 7339 // If `convertToPointerRawUpdate` is `false`, it means that the event was in 7340 // the queue in BrowserChild and BrowserChild has already dispatched 7341 // `eMouseRawUpdate`. Therefore, we don't need to dispatch it again here. 7342 if (mouseEvent->IsSynthesized() || !mouseEvent->convertToPointer || 7343 !mouseEvent->convertToPointerRawUpdate) { 7344 return NS_OK; 7345 } 7346 WidgetMouseEvent mouseRawUpdateEvent(*mouseEvent); 7347 mouseRawUpdateEvent.mMessage = eMouseRawUpdate; 7348 mouseRawUpdateEvent.mCoalescedWidgetEvents = nullptr; 7349 // PointerEvent.button of `pointerrawupdate` should always be -1 if the 7350 // source event is not eMouseDown nor eMouseUp. PointerEventHandler cannot 7351 // distinguish whether eMouseRawUpdate is caused by eMouseDown/eMouseUp or 7352 // not. Therefore, we need to set the proper value in the latter case here 7353 // (In the former case, the copy constructor did it already). 7354 if (mouseEvent->mMessage != eMouseDown && 7355 mouseEvent->mMessage != eMouseUp) { 7356 mouseRawUpdateEvent.mButton = MouseButton::eNotPressed; 7357 } 7358 nsEventStatus rawUpdateStatus = nsEventStatus_eIgnore; 7359 EventHandler eventHandler(*this); 7360 return eventHandler.HandleEvent(aWeakFrameForPresShell, 7361 &mouseRawUpdateEvent, aDontRetargetEvents, 7362 &rawUpdateStatus); 7363 } 7364 if (const WidgetTouchEvent* const touchEvent = aSourceEvent.AsTouchEvent()) { 7365 WidgetTouchEvent touchRawUpdate(*touchEvent, 7366 WidgetTouchEvent::CloneTouches::No); 7367 touchRawUpdate.mMessage = eTouchRawUpdate; 7368 touchRawUpdate.mTouches.Clear(); 7369 for (const RefPtr<Touch>& touch : touchEvent->mTouches) { 7370 // If `convertToPointer` is `false`, it means that we've already handled 7371 // the event to dispatch a preceding pointer event. Therefore, its 7372 // preceding event should've already been handled. 7373 // If ShouldConvertTouchToPointer() returns `false`, the touch is not an 7374 // active pointer or the touch hasn't been changed from the previous 7375 // state. Therefore, we don't need to dispatch ePointerRawUpdate for the 7376 // touch. 7377 if (!touch->convertToPointerRawUpdate || 7378 !TouchManager::ShouldConvertTouchToPointer(touch, &touchRawUpdate)) { 7379 continue; 7380 } 7381 RefPtr<Touch> newTouch = new Touch(*touch); 7382 newTouch->mMessage = eTouchRawUpdate; 7383 newTouch->mCoalescedWidgetEvents = nullptr; 7384 touchRawUpdate.mTouches.AppendElement(std::move(newTouch)); 7385 } 7386 nsEventStatus rawUpdateStatus = nsEventStatus_eIgnore; 7387 if (touchRawUpdate.mTouches.IsEmpty()) { 7388 return NS_OK; 7389 } 7390 EventHandler eventHandler(*this); 7391 return eventHandler.HandleEvent(aWeakFrameForPresShell, &touchRawUpdate, 7392 aDontRetargetEvents, &rawUpdateStatus); 7393 } 7394 MOZ_ASSERT_UNREACHABLE("Handle the event to dispatch ePointerRawUpdate"); 7395 return NS_OK; 7396 } 7397 7398 bool PresShell::EventHandler::UpdateFocusSequenceNumber( 7399 nsIFrame* aFrameForPresShell, uint64_t aEventFocusSequenceNumber) { 7400 uint64_t focusSequenceNumber; 7401 nsMenuPopupFrame* popup = do_QueryFrame(aFrameForPresShell); 7402 if (popup) { 7403 focusSequenceNumber = popup->GetAPZFocusSequenceNumber(); 7404 } else { 7405 focusSequenceNumber = mPresShell->mAPZFocusSequenceNumber; 7406 } 7407 if (focusSequenceNumber >= aEventFocusSequenceNumber) { 7408 return false; 7409 } 7410 7411 if (popup) { 7412 popup->UpdateAPZFocusSequenceNumber(aEventFocusSequenceNumber); 7413 } else { 7414 mPresShell->mAPZFocusSequenceNumber = aEventFocusSequenceNumber; 7415 } 7416 return true; 7417 } 7418 7419 nsresult PresShell::EventHandler::HandleEvent( 7420 AutoWeakFrame& aWeakFrameForPresShell, WidgetGUIEvent* aGUIEvent, 7421 bool aDontRetargetEvents, nsEventStatus* aEventStatus) { 7422 MOZ_ASSERT(aGUIEvent); 7423 MOZ_DIAGNOSTIC_ASSERT(aGUIEvent->IsTrusted()); 7424 MOZ_ASSERT(aEventStatus); 7425 7426 NS_ASSERTION(aWeakFrameForPresShell.IsAlive(), 7427 "aWeakFrameForPresShell should refer a frame"); 7428 7429 // Update the latest focus sequence number with this new sequence number; 7430 // the next transasction that gets sent to the compositor will carry this over 7431 if (UpdateFocusSequenceNumber(aWeakFrameForPresShell.GetFrame(), 7432 aGUIEvent->mFocusSequenceNumber)) { 7433 if (aWeakFrameForPresShell.IsAlive() && 7434 StaticPrefs::apz_keyboard_focus_optimization()) { 7435 aWeakFrameForPresShell->SchedulePaint(nsIFrame::PAINT_COMPOSITE_ONLY); 7436 } 7437 } 7438 7439 if (mPresShell->IsDestroying() || 7440 (PresShell::sDisableNonTestMouseEvents && 7441 !aGUIEvent->mFlags.mIsSynthesizedForTests && 7442 aGUIEvent->HasMouseEventMessage())) { 7443 return NS_OK; 7444 } 7445 7446 mPresShell->RecordPointerLocation(aGUIEvent); 7447 7448 const bool wasFrameForPresShellNull = !aWeakFrameForPresShell.GetFrame(); 7449 if (MaybeHandleEventWithAccessibleCaret(aWeakFrameForPresShell, aGUIEvent, 7450 aEventStatus)) { 7451 // Handled by AccessibleCaretEventHub. 7452 return NS_OK; 7453 } 7454 7455 if (MaybeDiscardEvent(aGUIEvent)) { 7456 // Cannot handle the event for now. 7457 return NS_OK; 7458 } 7459 7460 if (!aDontRetargetEvents) { 7461 const DebugOnly<bool> wasFrameForPresShellAlive = 7462 aWeakFrameForPresShell.IsAlive(); 7463 // If aGUIEvent should be handled in another PresShell, we should call its 7464 // HandleEvent() and do nothing here. 7465 nsresult rv = NS_OK; 7466 if (MaybeHandleEventWithAnotherPresShell(aWeakFrameForPresShell, aGUIEvent, 7467 aEventStatus, &rv)) { 7468 // Handled by another PresShell or nobody can handle the event. 7469 return rv; 7470 } 7471 // If MaybeHandleEventWithAnotherPresShell() returns false, it shouldn't 7472 // have run any script. So, aWeakFrameForPresShell must be alive. 7473 MOZ_ASSERT_IF(wasFrameForPresShellAlive, aWeakFrameForPresShell.IsAlive()); 7474 } 7475 7476 if (MaybeDiscardOrDelayKeyboardEvent(aGUIEvent)) { 7477 // The event is discarded or put into the delayed event queue. 7478 return NS_OK; 7479 } 7480 7481 if (aGUIEvent->IsUsingCoordinates()) { 7482 return HandleEventUsingCoordinates(aWeakFrameForPresShell, aGUIEvent, 7483 aEventStatus, aDontRetargetEvents); 7484 } 7485 7486 // Activation events need to be dispatched even if no frame was found, since 7487 // we don't want the focus to be out of sync. 7488 if (MOZ_UNLIKELY(wasFrameForPresShellNull)) { 7489 if (!NS_EVENT_NEEDS_FRAME(aGUIEvent)) { 7490 // Push nullptr for both current event target content and frame since 7491 // there is no frame but the event does not require a frame. 7492 AutoCurrentEventInfoSetter eventInfoSetter(*this); 7493 return HandleEventWithCurrentEventInfo(aGUIEvent, aEventStatus, true, 7494 nullptr); 7495 } 7496 7497 if (aGUIEvent->HasKeyEventMessage()) { 7498 // Keypress events in new blank tabs should not be completely thrown away. 7499 // Retarget them -- the parent chrome shell might make use of them. 7500 return RetargetEventToParent(aGUIEvent, aEventStatus); 7501 } 7502 7503 return NS_OK; 7504 } 7505 7506 if (aGUIEvent->IsTargetedAtFocusedContent()) { 7507 return HandleEventAtFocusedContent(aGUIEvent, aEventStatus); 7508 } 7509 7510 return HandleEventWithFrameForPresShell(aWeakFrameForPresShell, aGUIEvent, 7511 aEventStatus); 7512 } 7513 7514 nsresult PresShell::EventHandler::HandleEventUsingCoordinates( 7515 AutoWeakFrame& aWeakFrameForPresShell, WidgetGUIEvent* aGUIEvent, 7516 nsEventStatus* aEventStatus, bool aDontRetargetEvents) { 7517 MOZ_ASSERT(aGUIEvent); 7518 MOZ_ASSERT(aGUIEvent->IsUsingCoordinates()); 7519 MOZ_ASSERT(aEventStatus); 7520 7521 // Flush pending notifications to handle the event with the latest layout. 7522 // But if it causes destroying the frame for mPresShell, stop handling the 7523 // event. (why?) 7524 MaybeFlushPendingNotifications(aGUIEvent); 7525 if (MOZ_UNLIKELY(!aWeakFrameForPresShell.IsAlive())) { 7526 *aEventStatus = nsEventStatus_eIgnore; 7527 return NS_OK; 7528 } 7529 7530 // If we are trying to dispatch an ePointerRawUpdate but it's not allowed in 7531 // the (maybe retargetted) document, we should not flush the capture state 7532 // below. 7533 if (aGUIEvent->mMessage == eMouseRawUpdate || 7534 aGUIEvent->mMessage == eTouchRawUpdate) { 7535 EventTargetDataWithCapture eventTargetData = 7536 EventTargetDataWithCapture::QueryEventTargetUsingCoordinates( 7537 *this, aWeakFrameForPresShell, 7538 EventTargetDataWithCapture::Query::PendingState, aGUIEvent); 7539 if (!PointerEventHandler::NeedToDispatchPointerRawUpdate( 7540 eventTargetData.GetDocument())) { 7541 return NS_OK; 7542 } 7543 // Then, we need to recompute the target with processing the pending pointer 7544 // capture. Note that the result may be differnet since `gotpointercapture` 7545 // event listener does something tricky things. 7546 } 7547 7548 EventTargetDataWithCapture eventTargetData = 7549 EventTargetDataWithCapture::QueryEventTargetUsingCoordinates( 7550 *this, aWeakFrameForPresShell, 7551 EventTargetDataWithCapture::Query::LatestState, aGUIEvent, 7552 aEventStatus); 7553 if (MOZ_UNLIKELY(!eventTargetData.CanHandleEvent())) { 7554 // We cannot handle the event within the PresShell anymore. Let's stop 7555 // handling the event without returning error since it's not illegal 7556 // case. 7557 return NS_OK; 7558 } 7559 if (MOZ_UNLIKELY(!eventTargetData.GetFrame())) { 7560 if (eventTargetData.mPointerCapturingElement && 7561 aWeakFrameForPresShell.IsAlive()) { 7562 return HandleEventWithPointerCapturingContentWithoutItsFrame( 7563 aWeakFrameForPresShell, aGUIEvent, 7564 MOZ_KnownLive(eventTargetData.mPointerCapturingElement), 7565 aEventStatus); 7566 } 7567 return NS_OK; 7568 } 7569 7570 // Suppress mouse event if it's being targeted at an element inside 7571 // a document which needs events suppressed 7572 if (MaybeDiscardOrDelayMouseEvent(eventTargetData.GetFrame(), aGUIEvent)) { 7573 return NS_OK; 7574 } 7575 7576 // Check if we have an active EventStateManager which isn't the 7577 // EventStateManager of the current PresContext. If that is the case, and 7578 // mouse is over some ancestor document, forward event handling to the 7579 // active document. This way content can get mouse events even when mouse 7580 // is over the chrome or outside the window. 7581 if (eventTargetData.MaybeRetargetToActiveDocument(aGUIEvent) && 7582 NS_WARN_IF(!eventTargetData.GetFrame())) { 7583 return NS_OK; 7584 } 7585 7586 // Wheel events only apply to elements. If this is a wheel event, attempt to 7587 // update the event target from the current wheel transaction before we 7588 // compute the element from the target frame. 7589 eventTargetData.UpdateWheelEventTarget(aGUIEvent); 7590 7591 if (!eventTargetData.ComputeElementFromFrame(aGUIEvent)) { 7592 return NS_OK; 7593 } 7594 // Note that even if ComputeElementFromFrame() returns true, 7595 // eventTargetData.mContent can be nullptr here. 7596 7597 // Dispatch a pointer event if Pointer Events is enabled. Note that if 7598 // pointer event listeners change the layout, eventTargetData is 7599 // automatically updated. 7600 if (!DispatchPrecedingPointerEvent( 7601 aWeakFrameForPresShell, aGUIEvent, 7602 MOZ_KnownLive(eventTargetData.mPointerCapturingElement), 7603 aDontRetargetEvents, &eventTargetData, aEventStatus)) { 7604 return NS_OK; 7605 } 7606 7607 // Handle the event in the correct shell. 7608 // We pass the subshell's root frame as the frame to start from. This is 7609 // the only correct alternative; if the event was captured then it 7610 // must have been captured by us or some ancestor shell and we 7611 // now ask the subshell to dispatch it normally. 7612 EventHandler eventHandler(*eventTargetData.mPresShell); 7613 AutoCurrentEventInfoSetter eventInfoSetter(eventHandler, aGUIEvent->mMessage, 7614 eventTargetData); 7615 // eventTargetData is on the stack and is guaranteed to keep its 7616 // mOverrideClickTarget alive, so we can just use MOZ_KnownLive here. 7617 nsresult rv = eventHandler.HandleEventWithCurrentEventInfo( 7618 aGUIEvent, aEventStatus, true, 7619 MOZ_KnownLive(eventTargetData.mOverrideClickTarget)); 7620 if (NS_FAILED(rv) || 7621 MOZ_UNLIKELY(eventTargetData.mPresShell->IsDestroying())) { 7622 return rv; 7623 } 7624 7625 if (aGUIEvent->mMessage == eTouchEnd) { 7626 MaybeSynthesizeCompatMouseEventsForTouchEnd(aGUIEvent->AsTouchEvent(), 7627 aEventStatus); 7628 } 7629 7630 return NS_OK; 7631 } 7632 7633 // static 7634 PresShell::EventHandler::EventTargetDataWithCapture::EventTargetDataWithCapture( 7635 EventHandler& aEventHandler, AutoWeakFrame& aWeakFrameForPresShell, 7636 Query aQueryState, WidgetGUIEvent* aGUIEvent, 7637 nsEventStatus* aEventStatus /* = nullptr*/) 7638 : EventTargetData(aWeakFrameForPresShell.GetFrame()) { 7639 MOZ_ASSERT(aGUIEvent); 7640 MOZ_ASSERT(aGUIEvent->IsUsingCoordinates()); 7641 // EventHandler::GetFrameToHandleNonTouchEvent() may need to flush pending 7642 // notifications of the target child document if eMouseDown or eMouseUp. 7643 // Currently, this class does not support the case with Query::PendingState. 7644 MOZ_ASSERT_IF(aQueryState == Query::PendingState, 7645 aGUIEvent->mMessage != eMouseDown); 7646 MOZ_ASSERT_IF(aQueryState == Query::PendingState, 7647 aGUIEvent->mMessage != eMouseUp); 7648 7649 const bool queryLatestState = aQueryState == Query::LatestState; 7650 7651 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED 7652 nsMutationGuard mutationGuard; 7653 const auto assertMutation = MakeScopeExit([&]() { 7654 if (!queryLatestState) { 7655 MOZ_DIAGNOSTIC_ASSERT(!mutationGuard.Mutated(0)); 7656 } 7657 }); 7658 Maybe<JS::AutoAssertNoGC> assertNoGC; 7659 if (!queryLatestState) { 7660 assertNoGC.emplace(); 7661 } 7662 #endif // #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED 7663 7664 // XXX Retrieving capturing content here. However, some of the following 7665 // methods allow to run script. So, isn't it possible the capturing 7666 // content outdated? 7667 mCapturingContent = EventHandler::GetCapturingContentFor(aGUIEvent); 7668 if (queryLatestState) { 7669 if (GetDocument() && aGUIEvent->mClass == eTouchEventClass) { 7670 PointerLockManager::Unlock("TouchEvent"); 7671 } 7672 // XXX If aGUIEvent is eMouseRawUpdate or eTouchRawUpdate and it's 7673 // dispatched by BrowserChild, i.e., the event won't cause ePointerMove 7674 // immediately after ePointerRawUpdate, should we skip fluhsing pending 7675 // animations here? Doing this could cause different animation result while 7676 // the user moves mouse cursor during a long animation whether there is a 7677 // `pointerrawupdate` event listener or not. 7678 aEventHandler.MaybeFlushThrottledStyles(aWeakFrameForPresShell); 7679 // Previously, MaybeFlushThrottledStyles() recomputed the closest ancestor 7680 // frame for view of mPresShell if it's reframed. Therefore, we should keep 7681 // computing it here. 7682 // FIXME: GetFrame() may be target content's frame if aGUIEvent is a touch 7683 // event. So, we need to use different computation for such cases. 7684 if (MOZ_UNLIKELY(!aWeakFrameForPresShell.IsAlive())) { 7685 Clear(); 7686 MOZ_ASSERT(!CanHandleEvent()); 7687 return; 7688 } 7689 } 7690 7691 AutoWeakFrame weakRootFrameToHandleEvent = 7692 aEventHandler.ComputeRootFrameToHandleEvent( 7693 aWeakFrameForPresShell.GetFrame(), aGUIEvent, mCapturingContent, 7694 &mCapturingContentIgnored, &mCaptureRetargeted); 7695 if (mCapturingContentIgnored) { 7696 mCapturingContent = nullptr; 7697 } 7698 7699 // The order to generate pointer event is 7700 // 1. check pending pointer capture. 7701 // 2. check if there is a capturing content. 7702 // 3. hit test 7703 // 4. dispatch pointer events 7704 // 5. check whether the targets of all Touch instances are in the same 7705 // document and suppress invalid instances. 7706 // 6. dispatch mouse or touch events. 7707 7708 // Try to keep frame for following check, because frame can be damaged 7709 // during MaybeProcessPointerCapture. 7710 if (queryLatestState) { 7711 PointerEventHandler::MaybeProcessPointerCapture(aGUIEvent); 7712 // Prevent application crashes, in case damaged frame. 7713 if (NS_WARN_IF(!weakRootFrameToHandleEvent.IsAlive())) { 7714 Clear(); 7715 MOZ_ASSERT(!CanHandleEvent()); 7716 return; 7717 } 7718 } 7719 7720 // We want to query the pointer capture element which **will** capture the 7721 // following pointer event. If we've already processed the pointer capture 7722 // above, current override element is it. Otherwise, we will process the 7723 // pending pointer capture before dispatching a pointer event. Therefore, 7724 // the pending pointer capture element will be the next override element 7725 // if and only if they are different. (If they are the same element, the 7726 // element will keep capturing the pointer. So, referring to the pending 7727 // element is also fine in the case.) 7728 mPointerCapturingElement = 7729 queryLatestState 7730 ? PointerEventHandler::GetPointerCapturingElement(aGUIEvent) 7731 : PointerEventHandler::GetPendingPointerCapturingElement(aGUIEvent); 7732 7733 if (mPointerCapturingElement) { 7734 weakRootFrameToHandleEvent = mPointerCapturingElement->GetPrimaryFrame(); 7735 if (!weakRootFrameToHandleEvent.IsAlive()) { 7736 // The caller should not keep handling the event with the frame stored by 7737 // the super class. Therefore, we need to clear the frame. 7738 ClearFrameToHandleEvent(); 7739 // Although the pointer capturing element does not have a frame, the event 7740 // should be handled on the element. 7741 MOZ_ASSERT(CanHandleEvent()); 7742 return; 7743 } 7744 } 7745 7746 const WidgetMouseEvent* mouseEvent = aGUIEvent->AsMouseEvent(); 7747 const bool isWindowLevelMouseExit = 7748 (aGUIEvent->mMessage == eMouseExitFromWidget) && 7749 (mouseEvent && 7750 (mouseEvent->mExitFrom.value() == WidgetMouseEvent::ePlatformTopLevel || 7751 mouseEvent->mExitFrom.value() == WidgetMouseEvent::ePuppet)); 7752 7753 // Get the frame at the event point. However, don't do this if we're 7754 // capturing and retargeting the event because the captured frame will 7755 // be used instead below. Also keep using the root frame if we're dealing 7756 // with a window-level mouse exit event since we want to start sending 7757 // mouse out events at the root EventStateManager. 7758 SetFrameAndComputePresShell(weakRootFrameToHandleEvent.GetFrame()); 7759 if (!mCaptureRetargeted && !isWindowLevelMouseExit && 7760 !mPointerCapturingElement) { 7761 if (!aEventHandler.ComputeEventTargetFrameAndPresShellAtEventPoint( 7762 weakRootFrameToHandleEvent, aGUIEvent, this)) { 7763 Clear(); 7764 MOZ_ASSERT(!CanHandleEvent()); 7765 if (aEventStatus) { 7766 *aEventStatus = nsEventStatus_eIgnore; 7767 } 7768 return; 7769 } 7770 } 7771 7772 // if a node is capturing the mouse, check if the event needs to be 7773 // retargeted at the capturing content instead. This will be the case when 7774 // capture retargeting is being used, no frame was found or the frame's 7775 // content is not a descendant of the capturing content. 7776 if (mCapturingContent && !mPointerCapturingElement && 7777 (PresShell::sCapturingContentInfo.mRetargetToElement || 7778 !GetFrameContent() || 7779 !nsContentUtils::ContentIsCrossDocDescendantOf(GetFrameContent(), 7780 mCapturingContent))) { 7781 if (nsIFrame* const capturingFrame = mCapturingContent->GetPrimaryFrame()) { 7782 SetFrameAndComputePresShell(capturingFrame); 7783 } 7784 } 7785 7786 MOZ_ASSERT(CanHandleEvent()); 7787 } 7788 7789 bool PresShell::EventHandler::MaybeFlushPendingNotifications( 7790 WidgetGUIEvent* aGUIEvent) { 7791 MOZ_ASSERT(aGUIEvent); 7792 7793 switch (aGUIEvent->mMessage) { 7794 case eMouseDown: 7795 case eMouseUp: { 7796 RefPtr<nsPresContext> presContext = mPresShell->GetPresContext(); 7797 if (NS_WARN_IF(!presContext)) { 7798 return false; 7799 } 7800 uint64_t framesConstructedCount = presContext->FramesConstructedCount(); 7801 uint64_t framesReflowedCount = presContext->FramesReflowedCount(); 7802 7803 MOZ_KnownLive(mPresShell)->FlushPendingNotifications(FlushType::Layout); 7804 return framesConstructedCount != presContext->FramesConstructedCount() || 7805 framesReflowedCount != presContext->FramesReflowedCount(); 7806 } 7807 default: 7808 return false; 7809 } 7810 } 7811 7812 // The type of coordinates to use for hit-testing input events 7813 // that are relative to the RCD's viewport frame. 7814 // On most platforms, use visual coordinates so that scrollbars 7815 // can be targeted. 7816 // On mobile, use layout coordinates because hit-testing in 7817 // visual coordinates clashes with mobile viewport sizing, where 7818 // the ViewportFrame is sized to the initial containing block 7819 // (ICB) size, which is in layout coordinates. This is fine 7820 // because we don't need to be able to target scrollbars on mobile 7821 // (scrollbar dragging isn't supported). 7822 static ViewportType ViewportTypeForInputEventsRelativeToRoot() { 7823 #ifdef MOZ_WIDGET_ANDROID 7824 return ViewportType::Layout; 7825 #else 7826 return ViewportType::Visual; 7827 #endif 7828 } 7829 7830 nsIFrame* PresShell::EventHandler::GetFrameToHandleNonTouchEvent( 7831 AutoWeakFrame& aWeakRootFrameToHandleEvent, WidgetGUIEvent* aGUIEvent) { 7832 MOZ_ASSERT(aGUIEvent); 7833 MOZ_ASSERT(aGUIEvent->mClass != eTouchEventClass); 7834 7835 if (MOZ_UNLIKELY(!aWeakRootFrameToHandleEvent.IsAlive())) { 7836 return nullptr; 7837 } 7838 7839 ViewportType viewportType = ViewportType::Layout; 7840 if (aWeakRootFrameToHandleEvent->Type() == LayoutFrameType::Viewport) { 7841 nsPresContext* pc = aWeakRootFrameToHandleEvent->PresContext(); 7842 if (pc->IsChrome()) { 7843 viewportType = ViewportType::Visual; 7844 } else if (pc->IsRootContentDocumentCrossProcess()) { 7845 viewportType = ViewportTypeForInputEventsRelativeToRoot(); 7846 } 7847 } 7848 RelativeTo relativeTo{aWeakRootFrameToHandleEvent.GetFrame(), viewportType}; 7849 nsPoint eventPoint = 7850 nsLayoutUtils::GetEventCoordinatesRelativeTo(aGUIEvent, relativeTo); 7851 7852 uint32_t flags = 0; 7853 if (aGUIEvent->IsMouseEventClassOrHasClickRelatedPointerEvent()) { 7854 WidgetMouseEvent* mouseEvent = aGUIEvent->AsMouseEvent(); 7855 if (mouseEvent && mouseEvent->mIgnoreRootScrollFrame) { 7856 flags |= INPUT_IGNORE_ROOT_SCROLL_FRAME; 7857 } 7858 } 7859 7860 nsIFrame* targetFrame = 7861 FindFrameTargetedByInputEvent(aGUIEvent, relativeTo, eventPoint, flags); 7862 if (!targetFrame) { 7863 return aWeakRootFrameToHandleEvent.GetFrame(); 7864 } 7865 7866 if (targetFrame->PresShell() == mPresShell) { 7867 // If found target is in mPresShell, we've already found it in the latest 7868 // layout so that we can use it. 7869 return targetFrame; 7870 } 7871 7872 // If target is in a child document, we've not flushed its layout yet. 7873 PresShell* childPresShell = targetFrame->PresShell(); 7874 EventHandler childEventHandler(*childPresShell); 7875 bool layoutChanged = 7876 childEventHandler.MaybeFlushPendingNotifications(aGUIEvent); 7877 if (!aWeakRootFrameToHandleEvent.IsAlive()) { 7878 // Stop handling the event if the root frame to handle event is destroyed 7879 // by the reflow. (but why?) 7880 return nullptr; 7881 } 7882 if (!layoutChanged) { 7883 // If the layout in the child PresShell hasn't been changed, we don't 7884 // need to recompute the target. 7885 return targetFrame; 7886 } 7887 7888 // Finally, we need to recompute the target with the latest layout. 7889 targetFrame = 7890 FindFrameTargetedByInputEvent(aGUIEvent, relativeTo, eventPoint, flags); 7891 7892 return targetFrame ? targetFrame : aWeakRootFrameToHandleEvent.GetFrame(); 7893 } 7894 7895 bool PresShell::EventHandler::ComputeEventTargetFrameAndPresShellAtEventPoint( 7896 AutoWeakFrame& aWeakRootFrameToHandleEvent, WidgetGUIEvent* aGUIEvent, 7897 EventTargetData* aEventTargetData) { 7898 MOZ_ASSERT(aGUIEvent); 7899 MOZ_ASSERT(aEventTargetData); 7900 7901 if (aGUIEvent->mClass == eTouchEventClass) { 7902 nsIFrame* targetFrameAtTouchEvent = TouchManager::SetupTarget( 7903 aGUIEvent->AsTouchEvent(), aWeakRootFrameToHandleEvent.GetFrame()); 7904 aEventTargetData->SetFrameAndComputePresShell(targetFrameAtTouchEvent); 7905 return true; 7906 } 7907 7908 nsIFrame* targetFrame = 7909 GetFrameToHandleNonTouchEvent(aWeakRootFrameToHandleEvent, aGUIEvent); 7910 aEventTargetData->SetFrameAndComputePresShell(targetFrame); 7911 return !!aEventTargetData->GetFrame(); 7912 } 7913 7914 bool PresShell::EventHandler::DispatchPrecedingPointerEvent( 7915 AutoWeakFrame& aWeakFrameForPresShell, WidgetGUIEvent* aGUIEvent, 7916 Element* aPointerCapturingElement, bool aDontRetargetEvents, 7917 EventTargetData* aEventTargetData, nsEventStatus* aEventStatus) { 7918 MOZ_ASSERT(aGUIEvent); 7919 MOZ_ASSERT(aEventTargetData); 7920 MOZ_ASSERT(aEventStatus); 7921 7922 // Dispatch pointer events from the mouse or touch events. Regarding 7923 // pointer events from mouse, we should dispatch those pointer events to 7924 // the same target as the source mouse events. We pass the frame found 7925 // in hit test to PointerEventHandler and dispatch pointer events to it. 7926 // 7927 // Regarding pointer events from touch, the behavior is different. Touch 7928 // events are dispatched to the same target as the target of touchstart. 7929 // Multiple touch points must be dispatched to the same document. Pointer 7930 // events from touch can be dispatched to different documents. We Pass the 7931 // original frame to PointerEventHandler, reentry PresShell::HandleEvent, 7932 // and do hit test for each point. 7933 auto targetFrameOrError = [&]() -> Result<nsIFrame*, nsresult> { 7934 if (aGUIEvent->mClass == eTouchEventClass) { 7935 // If aWeakFrameForPresShell has already been reframed before this is 7936 // called, we don't need to handle the event. 7937 if (MOZ_UNLIKELY(!aWeakFrameForPresShell.IsAlive())) { 7938 return Err(NS_ERROR_FAILURE); 7939 } 7940 return aWeakFrameForPresShell.GetFrame(); 7941 } 7942 return aEventTargetData->GetFrame(); 7943 }(); 7944 if (MOZ_UNLIKELY(targetFrameOrError.isErr())) { 7945 return false; 7946 } 7947 nsIFrame* targetFrame = targetFrameOrError.unwrap(); 7948 7949 if (aPointerCapturingElement) { 7950 Result<nsIContent*, nsresult> overrideClickTargetOrError = 7951 GetOverrideClickTarget(aGUIEvent, aWeakFrameForPresShell.GetFrame(), 7952 aPointerCapturingElement); 7953 if (MOZ_UNLIKELY(overrideClickTargetOrError.isErr())) { 7954 return false; 7955 } 7956 aEventTargetData->mOverrideClickTarget = 7957 overrideClickTargetOrError.unwrap(); 7958 aEventTargetData->mPresShell = 7959 PresShell::GetShellForEventTarget(nullptr, aPointerCapturingElement); 7960 if (!aEventTargetData->mPresShell) { 7961 // If we can't process event for the capturing content, release 7962 // the capture. 7963 PointerEventHandler::ReleaseIfCaptureByDescendant( 7964 aPointerCapturingElement); 7965 return false; 7966 } 7967 7968 targetFrame = aPointerCapturingElement->GetPrimaryFrame(); 7969 aEventTargetData->SetFrameAndContent(targetFrame, aPointerCapturingElement); 7970 } 7971 7972 AutoWeakFrame weakTargetFrame(targetFrame); 7973 AutoWeakFrame weakFrame(aEventTargetData->GetFrame()); 7974 nsCOMPtr<nsIContent> pointerEventTargetContent( 7975 aEventTargetData->GetContent()); 7976 RefPtr<PresShell> presShell(aEventTargetData->mPresShell); 7977 nsCOMPtr<nsIContent> mouseOrTouchEventTargetContent; 7978 PointerEventHandler::DispatchPointerFromMouseOrTouch( 7979 presShell, aEventTargetData->GetFrame(), pointerEventTargetContent, 7980 aPointerCapturingElement, aGUIEvent, aDontRetargetEvents, aEventStatus, 7981 getter_AddRefs(mouseOrTouchEventTargetContent)); 7982 7983 const bool maybeCallerCanHandleEvent = 7984 aGUIEvent->mMessage != eMouseRawUpdate && 7985 aGUIEvent->mMessage != eTouchRawUpdate; 7986 7987 // If the target frame is alive, the caller should keep handling the event 7988 // unless event target frame is destroyed. 7989 if (weakTargetFrame.IsAlive() && weakFrame.IsAlive()) { 7990 aEventTargetData->UpdateTouchEventTarget(aGUIEvent); 7991 return maybeCallerCanHandleEvent; 7992 } 7993 7994 presShell->FlushPendingNotifications(FlushType::Layout); 7995 if (MOZ_UNLIKELY(mPresShell->IsDestroying())) { 7996 return false; 7997 } 7998 7999 // The spec defines that mouse events must be dispatched to the same target as 8000 // the pointer event. 8001 // The Touch Events spec defines that touch events must be dispatched to the 8002 // same target as touch start and the other browsers dispatch touch events 8003 // even if the touch event target is not connected to the document. 8004 // Retargetting the event is handled by AutoPointerEventTargetUpdater and 8005 // mouseOrTouchEventTargetContent stores the result. 8006 8007 // If the target is no longer participating in its ownerDocument's tree, 8008 // fire the event at the original target's nearest ancestor node. 8009 if (!mouseOrTouchEventTargetContent) { 8010 MOZ_ASSERT(aGUIEvent->IsMouseEventClassOrHasClickRelatedPointerEvent()); 8011 return false; 8012 } 8013 8014 aEventTargetData->SetFrameAndContent( 8015 mouseOrTouchEventTargetContent->GetPrimaryFrame(), 8016 mouseOrTouchEventTargetContent); 8017 aEventTargetData->mPresShell = 8018 mouseOrTouchEventTargetContent->IsInComposedDoc() 8019 ? PresShell::GetShellForEventTarget(aEventTargetData->GetFrame(), 8020 aEventTargetData->GetContent()) 8021 : mouseOrTouchEventTargetContent->OwnerDoc()->GetPresShell(); 8022 8023 // If new target PresShel is not found, we cannot keep handling the event. 8024 if (!aEventTargetData->mPresShell) { 8025 return false; 8026 } 8027 8028 aEventTargetData->UpdateTouchEventTarget(aGUIEvent); 8029 return maybeCallerCanHandleEvent; 8030 } 8031 8032 /** 8033 * Event retargetting may retarget a mouse event and change the reference point. 8034 * If event retargetting changes the reference point of a event that accessible 8035 * caret will not handle, restore the original reference point. 8036 */ 8037 class AutoEventTargetPointResetter { 8038 public: 8039 explicit AutoEventTargetPointResetter(WidgetGUIEvent* aGUIEvent) 8040 : mGUIEvent(aGUIEvent), 8041 mRefPoint(aGUIEvent->mRefPoint), 8042 mHandledByAccessibleCaret(false) {} 8043 8044 void SetHandledByAccessibleCaret() { mHandledByAccessibleCaret = true; } 8045 8046 ~AutoEventTargetPointResetter() { 8047 if (!mHandledByAccessibleCaret) { 8048 mGUIEvent->mRefPoint = mRefPoint; 8049 } 8050 } 8051 8052 private: 8053 WidgetGUIEvent* mGUIEvent; 8054 LayoutDeviceIntPoint mRefPoint; 8055 bool mHandledByAccessibleCaret; 8056 }; 8057 8058 bool PresShell::EventHandler::MaybeHandleEventWithAccessibleCaret( 8059 AutoWeakFrame& aWeakFrameForPresShell, WidgetGUIEvent* aGUIEvent, 8060 nsEventStatus* aEventStatus) { 8061 MOZ_ASSERT(aGUIEvent); 8062 MOZ_ASSERT(aEventStatus); 8063 8064 // Don't dispatch event to AccessibleCaretEventHub when the event status 8065 // is nsEventStatus_eConsumeNoDefault. This might be happened when content 8066 // preventDefault on the pointer events. In such case, we also call 8067 // preventDefault on mouse events to stop default behaviors. 8068 if (*aEventStatus == nsEventStatus_eConsumeNoDefault) { 8069 return false; 8070 } 8071 8072 if (!AccessibleCaretEnabled(GetDocument()->GetDocShell())) { 8073 return false; 8074 } 8075 8076 // AccessibleCaretEventHub handles only mouse, touch, and keyboard events. 8077 if (!aGUIEvent->IsMouseEventClassOrHasClickRelatedPointerEvent() && 8078 aGUIEvent->mClass != eTouchEventClass && 8079 aGUIEvent->mClass != eKeyboardEventClass) { 8080 return false; 8081 } 8082 8083 // AccessibleCaretEventHub::HandleEvent may return nsEventStatus_eIgnore to 8084 // allow the context menu to appear correctly on desktop Firefox (see 8085 // AccessibleCaretEventHub::LongTapState::OnLongTap). When this happens, and 8086 // the event hub at the event location is the same as the hub attached to 8087 // the currently‑focused window, HandleEvent would be invoked a second time. 8088 // Avoid the duplicate call by explicitly checking for this condition. 8089 AccessibleCaretEventHub* alreadyHandledEventHub = nullptr; 8090 8091 AutoEventTargetPointResetter autoEventTargetPointResetter(aGUIEvent); 8092 // First, try the event hub at the event point to handle a long press to 8093 // select a word in an unfocused window. 8094 do { 8095 EventTargetData eventTargetData(nullptr); 8096 if (!ComputeEventTargetFrameAndPresShellAtEventPoint( 8097 aWeakFrameForPresShell, aGUIEvent, &eventTargetData)) { 8098 break; 8099 } 8100 8101 if (!eventTargetData.mPresShell) { 8102 break; 8103 } 8104 8105 RefPtr<AccessibleCaretEventHub> eventHub = 8106 eventTargetData.mPresShell->GetAccessibleCaretEventHub(); 8107 if (!eventHub) { 8108 break; 8109 } 8110 alreadyHandledEventHub = eventHub.get(); 8111 8112 *aEventStatus = eventHub->HandleEvent(aGUIEvent); 8113 if (*aEventStatus != nsEventStatus_eConsumeNoDefault) { 8114 break; 8115 } 8116 8117 // If the event is consumed, cancel APZC panning by setting 8118 // mMultipleActionsPrevented. 8119 aGUIEvent->mFlags.mMultipleActionsPrevented = true; 8120 autoEventTargetPointResetter.SetHandledByAccessibleCaret(); 8121 return true; 8122 } while (false); 8123 8124 // Then, we target the event to the event hub at the focused window. 8125 nsCOMPtr<nsPIDOMWindowOuter> window = GetFocusedDOMWindowInOurWindow(); 8126 if (!window) { 8127 return false; 8128 } 8129 RefPtr<Document> retargetEventDoc = window->GetExtantDoc(); 8130 if (!retargetEventDoc) { 8131 return false; 8132 } 8133 RefPtr<PresShell> presShell = retargetEventDoc->GetPresShell(); 8134 if (!presShell) { 8135 return false; 8136 } 8137 8138 RefPtr<AccessibleCaretEventHub> eventHub = 8139 presShell->GetAccessibleCaretEventHub(); 8140 if (!eventHub || eventHub.get() == alreadyHandledEventHub) { 8141 return false; 8142 } 8143 *aEventStatus = eventHub->HandleEvent(aGUIEvent); 8144 if (*aEventStatus != nsEventStatus_eConsumeNoDefault) { 8145 return false; 8146 } 8147 // If the event is consumed, cancel APZC panning by setting 8148 // mMultipleActionsPrevented. 8149 aGUIEvent->mFlags.mMultipleActionsPrevented = true; 8150 autoEventTargetPointResetter.SetHandledByAccessibleCaret(); 8151 return true; 8152 } 8153 8154 void PresShell::EventHandler::MaybeSynthesizeCompatMouseEventsForTouchEnd( 8155 const WidgetTouchEvent* aTouchEndEvent, 8156 const nsEventStatus* aStatus) const { 8157 MOZ_ASSERT(aTouchEndEvent->mMessage == eTouchEnd); 8158 8159 // If the eTouchEnd event is dispatched via APZ, APZCCallbackHelper dispatches 8160 // a set of mouse events with better handling. Therefore, we don't need to 8161 // handle that here. 8162 if (!aTouchEndEvent->mFlags.mIsSynthesizedForTests || 8163 StaticPrefs::test_events_async_enabled()) { 8164 return; 8165 } 8166 8167 auto cleanUpPointerCapturingElementAtLastPointerUp = MakeScopeExit([]() { 8168 PointerEventHandler::ReleasePointerCapturingElementAtLastPointerUp(); 8169 }); 8170 8171 // If the tap was consumed or 2 or more touches occurred, we don't need the 8172 // compatibility mouse events. 8173 if (*aStatus == nsEventStatus_eConsumeNoDefault || 8174 !TouchManager::IsSingleTapEndToDoDefault(aTouchEndEvent)) { 8175 return; 8176 } 8177 8178 if (NS_WARN_IF(!aTouchEndEvent->mWidget)) { 8179 return; 8180 } 8181 8182 nsCOMPtr<nsIWidget> widget = aTouchEndEvent->mWidget; 8183 8184 // NOTE: I think that we don't need to implement a double click here becase 8185 // WebDriver does not support a way to synthesize a double click and Chrome 8186 // does not fire "dblclick" even if doing `pointerDown().pointerUp()` twice. 8187 // FIXME: Currently we don't support long tap. 8188 RefPtr<PresShell> presShell = mPresShell; 8189 for (const EventMessage message : {eMouseMove, eMouseDown, eMouseUp}) { 8190 if (MOZ_UNLIKELY(presShell->IsDestroying())) { 8191 break; 8192 } 8193 nsIFrame* const frameForPresShell = presShell->GetRootFrame(); 8194 if (!frameForPresShell) { 8195 break; 8196 } 8197 WidgetMouseEvent event(true, message, widget, WidgetMouseEvent::eReal); 8198 event.mFlags.mIsSynthesizedForTests = 8199 aTouchEndEvent->mFlags.mIsSynthesizedForTests; 8200 event.mRefPoint = aTouchEndEvent->mTouches[0]->mRefPoint; 8201 event.mButton = MouseButton::ePrimary; 8202 event.mButtons = message == eMouseDown ? MouseButtonsFlag::ePrimaryFlag 8203 : MouseButtonsFlag::eNoButtons; 8204 event.mInputSource = MouseEvent_Binding::MOZ_SOURCE_TOUCH; 8205 event.mClickCount = message == eMouseMove ? 0 : 1; 8206 event.mModifiers = aTouchEndEvent->mModifiers; 8207 event.pointerId = aTouchEndEvent->mTouches[0]->mIdentifier; 8208 event.convertToPointer = false; 8209 if (TouchManager::IsPrecedingTouchPointerDownConsumedByContent()) { 8210 event.PreventDefault(false); 8211 event.mFlags.mOnlyChromeDispatch = true; 8212 } 8213 nsEventStatus mouseEventStatus = nsEventStatus_eIgnore; 8214 presShell->HandleEvent(frameForPresShell, &event, false, &mouseEventStatus); 8215 } 8216 } 8217 8218 bool PresShell::EventHandler::MaybeDiscardEvent(WidgetGUIEvent* aGUIEvent) { 8219 MOZ_ASSERT(aGUIEvent); 8220 8221 // If it is safe to dispatch events now, don't discard the event. 8222 if (nsContentUtils::IsSafeToRunScript()) { 8223 return false; 8224 } 8225 8226 // If the event does not cause dispatching DOM event (i.e., internal event), 8227 // we can keep handling it even when it's not safe to run script. 8228 if (!aGUIEvent->IsAllowedToDispatchDOMEvent()) { 8229 return false; 8230 } 8231 8232 // If the event is a composition event, we need to let IMEStateManager know 8233 // it's discarded because it needs to listen all composition events to manage 8234 // TextComposition instance. 8235 if (aGUIEvent->mClass == eCompositionEventClass) { 8236 IMEStateManager::OnCompositionEventDiscarded( 8237 aGUIEvent->AsCompositionEvent()); 8238 } 8239 8240 #ifdef DEBUG 8241 if (aGUIEvent->IsIMERelatedEvent()) { 8242 nsPrintfCString warning("%s event is discarded", 8243 ToChar(aGUIEvent->mMessage)); 8244 NS_WARNING(warning.get()); 8245 } 8246 #endif // #ifdef DEBUG 8247 8248 nsContentUtils::WarnScriptWasIgnored(GetDocument()); 8249 return true; 8250 } 8251 8252 // static 8253 nsIContent* PresShell::EventHandler::GetCapturingContentFor( 8254 WidgetGUIEvent* aGUIEvent) { 8255 if (aGUIEvent->mClass != ePointerEventClass && 8256 aGUIEvent->mClass != eWheelEventClass && 8257 !aGUIEvent->HasMouseEventMessage()) { 8258 return nullptr; 8259 } 8260 8261 // PointerEventHandler may synthesize ePointerMove event before releasing the 8262 // mouse capture (it's done by a default handler of eMouseUp) after handling 8263 // ePointerUp. Then, we need to dispatch pointer boundary events for the 8264 // element under the pointer to emulate a pointer move after a pointer 8265 // capture. Therefore, we need to ignore the capturing element if the event 8266 // dispatcher requests it. 8267 if (aGUIEvent->ShouldIgnoreCapturingContent()) { 8268 return nullptr; 8269 } 8270 8271 return PresShell::GetCapturingContent(); 8272 } 8273 8274 bool PresShell::EventHandler::GetRetargetEventDocument( 8275 WidgetGUIEvent* aGUIEvent, Document** aRetargetEventDocument) { 8276 MOZ_ASSERT(aGUIEvent); 8277 MOZ_ASSERT(aRetargetEventDocument); 8278 8279 *aRetargetEventDocument = nullptr; 8280 8281 // key and IME related events should not cross top level window boundary. 8282 // Basically, such input events should be fired only on focused widget. 8283 // However, some IMEs might need to clean up composition after focused 8284 // window is deactivated. And also some tests on MozMill want to test key 8285 // handling on deactivated window because MozMill window can be activated 8286 // during tests. So, there is no merit the events should be redirected to 8287 // active window. So, the events should be handled on the last focused 8288 // content in the last focused DOM window in same top level window. 8289 // Note, if no DOM window has been focused yet, we can discard the events. 8290 if (aGUIEvent->IsTargetedAtFocusedWindow()) { 8291 nsCOMPtr<nsPIDOMWindowOuter> window = GetFocusedDOMWindowInOurWindow(); 8292 // No DOM window in same top level window has not been focused yet, 8293 // discard the events. 8294 if (!window) { 8295 return false; 8296 } 8297 8298 RefPtr<Document> retargetEventDoc = window->GetExtantDoc(); 8299 if (!retargetEventDoc) { 8300 return false; 8301 } 8302 retargetEventDoc.forget(aRetargetEventDocument); 8303 return true; 8304 } 8305 8306 const nsIContent* const capturingContent = 8307 aGUIEvent->ShouldIgnoreCapturingContent() 8308 ? nullptr 8309 : EventHandler::GetCapturingContentFor(aGUIEvent); 8310 if (capturingContent) { 8311 // if the mouse is being captured then retarget the mouse event at the 8312 // document that is being captured. 8313 RefPtr<Document> retargetEventDoc = capturingContent->GetComposedDoc(); 8314 retargetEventDoc.forget(aRetargetEventDocument); 8315 return true; 8316 } 8317 8318 #ifdef ANDROID 8319 if (aGUIEvent->mClass == eTouchEventClass || 8320 aGUIEvent->IsMouseEventClassOrHasClickRelatedPointerEvent() || 8321 aGUIEvent->mClass == eWheelEventClass) { 8322 RefPtr<Document> retargetEventDoc = mPresShell->GetPrimaryContentDocument(); 8323 retargetEventDoc.forget(aRetargetEventDocument); 8324 return true; 8325 } 8326 #endif // #ifdef ANDROID 8327 8328 // When we don't find another document to handle the event, we need to keep 8329 // handling the event by ourselves. 8330 return true; 8331 } 8332 8333 nsIFrame* PresShell::EventHandler::GetFrameForHandlingEventWith( 8334 WidgetGUIEvent* aGUIEvent, Document* aRetargetDocument, 8335 nsIFrame* aFrameForPresShell) { 8336 MOZ_ASSERT(aGUIEvent); 8337 MOZ_ASSERT(aRetargetDocument); 8338 8339 RefPtr<PresShell> retargetPresShell = aRetargetDocument->GetPresShell(); 8340 // Even if the document doesn't have PresShell, i.e., it's invisible, we 8341 // need to dispatch only KeyboardEvent in its nearest visible document 8342 // because key focus shouldn't be caught by invisible document. 8343 if (!retargetPresShell) { 8344 if (!aGUIEvent->HasKeyEventMessage()) { 8345 return nullptr; 8346 } 8347 Document* retargetEventDoc = aRetargetDocument; 8348 while (!retargetPresShell) { 8349 retargetEventDoc = retargetEventDoc->GetInProcessParentDocument(); 8350 if (!retargetEventDoc) { 8351 return nullptr; 8352 } 8353 retargetPresShell = retargetEventDoc->GetPresShell(); 8354 } 8355 } 8356 8357 // If the found PresShell is this instance, caller needs to keep handling 8358 // aGUIEvent by itself. Therefore, return the given frame which was set 8359 // to aFrame of HandleEvent(). 8360 if (retargetPresShell == mPresShell) { 8361 return aFrameForPresShell; 8362 } 8363 8364 // Use root frame of the new PresShell if there is. 8365 nsIFrame* rootFrame = retargetPresShell->GetRootFrame(); 8366 if (rootFrame) { 8367 return rootFrame; 8368 } 8369 8370 // Otherwise, and if aGUIEvent requires content of PresShell, caller should 8371 // stop handling the event. 8372 if (aGUIEvent->mMessage == eQueryTextContent || 8373 aGUIEvent->IsContentCommandEvent()) { 8374 return nullptr; 8375 } 8376 8377 // Otherwise, use nearest ancestor frame which includes the PresShell. 8378 return retargetPresShell->GetRootFrame(); 8379 } 8380 8381 bool PresShell::EventHandler::MaybeHandleEventWithAnotherPresShell( 8382 AutoWeakFrame& aWeakFrameForPresShell, WidgetGUIEvent* aGUIEvent, 8383 nsEventStatus* aEventStatus, nsresult* aRv) { 8384 MOZ_ASSERT(aGUIEvent); 8385 MOZ_ASSERT(aEventStatus); 8386 MOZ_ASSERT(aRv); 8387 8388 *aRv = NS_OK; 8389 8390 RefPtr<Document> retargetEventDoc; 8391 if (!GetRetargetEventDocument(aGUIEvent, getter_AddRefs(retargetEventDoc))) { 8392 // Nobody can handle this event. So, treat as handled by somebody to make 8393 // caller do nothing anymore. 8394 return true; 8395 } 8396 8397 // If there is no proper retarget document, the caller should handle the 8398 // event by itself. 8399 if (!retargetEventDoc) { 8400 return false; 8401 } 8402 8403 nsIFrame* frame = GetFrameForHandlingEventWith( 8404 aGUIEvent, retargetEventDoc, aWeakFrameForPresShell.GetFrame()); 8405 if (!frame) { 8406 // Nobody can handle this event. So, treat as handled by somebody to make 8407 // caller do nothing anymore. 8408 // NOTE: If aWeakFrameForPresShell does not refer to a frame (i.e., it's 8409 // already been reframed) and aGUIEvent needs to be handled in mPresShell, 8410 // we are here because GetFrameForHandlingEventWith() returns 8411 // aWeakFrameForPresShell.GetFrame() as-is. In the case, we don't need to 8412 // handle aGUIEvent, so, it's fine to return true from this method. 8413 return true; 8414 } 8415 8416 // If we reached same frame as set to HandleEvent(), the caller should handle 8417 // the event by itself. 8418 if (frame == aWeakFrameForPresShell.GetFrame()) { 8419 return false; 8420 } 8421 8422 // We need to handle aGUIEvent with another PresShell. 8423 RefPtr<PresShell> presShell = frame->PresContext()->PresShell(); 8424 *aRv = presShell->HandleEvent(frame, aGUIEvent, true, aEventStatus); 8425 return true; 8426 } 8427 8428 bool PresShell::EventHandler::MaybeDiscardOrDelayKeyboardEvent( 8429 WidgetGUIEvent* aGUIEvent) { 8430 MOZ_ASSERT(aGUIEvent); 8431 8432 if (aGUIEvent->mClass != eKeyboardEventClass) { 8433 return false; 8434 } 8435 8436 Document* document = GetDocument(); 8437 if (!document || !document->EventHandlingSuppressed()) { 8438 return false; 8439 } 8440 8441 MOZ_ASSERT_IF(InputTaskManager::CanSuspendInputEvent(), 8442 !InputTaskManager::Get()->IsSuspended()); 8443 8444 if (aGUIEvent->mMessage == eKeyDown) { 8445 mPresShell->mNoDelayedKeyEvents = true; 8446 } else if (!mPresShell->mNoDelayedKeyEvents) { 8447 UniquePtr<DelayedKeyEvent> delayedKeyEvent = 8448 MakeUnique<DelayedKeyEvent>(aGUIEvent->AsKeyboardEvent()); 8449 mPresShell->mDelayedEvents.AppendElement(std::move(delayedKeyEvent)); 8450 } 8451 aGUIEvent->mFlags.mIsSuppressedOrDelayed = true; 8452 return true; 8453 } 8454 8455 bool PresShell::EventHandler::MaybeDiscardOrDelayMouseEvent( 8456 nsIFrame* aFrameToHandleEvent, WidgetGUIEvent* aGUIEvent) { 8457 MOZ_ASSERT(aFrameToHandleEvent); 8458 MOZ_ASSERT(aGUIEvent); 8459 8460 // We must not need to let suspend listeners know ePointerRawUpdate events. 8461 // And also the delayed events will be dispatched via widget. Therefore, 8462 // ePointerRawUpdate event will be dispatched by PresShell::HandleEvent() 8463 // again. 8464 if (aGUIEvent->mMessage == eMouseRawUpdate || 8465 aGUIEvent->mMessage == eTouchRawUpdate || 8466 aGUIEvent->mMessage == ePointerRawUpdate) { 8467 return false; 8468 } 8469 8470 if (!aGUIEvent->IsMouseEventClassOrHasClickRelatedPointerEvent() && 8471 aGUIEvent->mMessage != eTouchStart) { 8472 return false; 8473 } 8474 8475 if (!aFrameToHandleEvent->PresContext() 8476 ->Document() 8477 ->EventHandlingSuppressed()) { 8478 return false; 8479 } 8480 8481 MOZ_ASSERT_IF(InputTaskManager::CanSuspendInputEvent() && 8482 aGUIEvent->mMessage != eMouseMove, 8483 !InputTaskManager::Get()->IsSuspended()); 8484 8485 RefPtr<PresShell> ps = aFrameToHandleEvent->PresShell(); 8486 8487 switch (aGUIEvent->mMessage) { 8488 case eTouchStart: { 8489 // If we receive a single touch start during the suppression, its 8490 // compatibility mouse events should not be fired later because the single 8491 // tap sequence has not been sent to the web app. 8492 const WidgetTouchEvent* const touchEvent = aGUIEvent->AsTouchEvent(); 8493 if (touchEvent->mTouches.Length() == 1) { 8494 ps->mNoDelayedSingleTap = true; 8495 } 8496 // We won't dispatch eTouchStart as a delayed event later so that return 8497 // false. 8498 return false; 8499 } 8500 case eMouseDown: { 8501 // If we receive a click sequence start during the suppression, we should 8502 // not fire `click` event later because its sequence has not been send to 8503 // the web app. Note that if the eMouseDown is caused by a touch, we may 8504 // have already sent the touch sequence to the web app. In such case, 8505 // the eMouseDown is NOT start of the click sequence. 8506 const WidgetMouseEvent* const mouseEvent = aGUIEvent->AsMouseEvent(); 8507 if (ps->mNoDelayedSingleTap || 8508 mouseEvent->mInputSource != MouseEvent_Binding::MOZ_SOURCE_TOUCH) { 8509 ps->mNoDelayedMouseEvents = true; 8510 break; 8511 } 8512 // Otherwise, put the event into the queue. 8513 [[fallthrough]]; 8514 } 8515 case eMouseUp: 8516 case eMouseExitFromWidget: { 8517 if (ps->mNoDelayedMouseEvents) { 8518 break; 8519 } 8520 UniquePtr<DelayedMouseEvent> delayedMouseEvent = 8521 MakeUnique<DelayedMouseEvent>(aGUIEvent->AsMouseEvent()); 8522 ps->mDelayedEvents.AppendElement(std::move(delayedMouseEvent)); 8523 break; 8524 } 8525 case eContextMenu: { 8526 if (ps->mNoDelayedMouseEvents) { 8527 break; 8528 } 8529 // contextmenu is triggered after right mouseup on Windows and right 8530 // mousedown on other platforms. 8531 UniquePtr<DelayedPointerEvent> delayedPointerEvent = 8532 MakeUnique<DelayedPointerEvent>(aGUIEvent->AsPointerEvent()); 8533 ps->mDelayedEvents.AppendElement(std::move(delayedPointerEvent)); 8534 break; 8535 } 8536 default: 8537 break; 8538 } 8539 8540 // If there is a suppressed event listener associated with the document, 8541 // notify it about the suppressed mouse event. This allows devtools 8542 // features to continue receiving mouse events even when the devtools 8543 // debugger has paused execution in a page. 8544 RefPtr<EventListener> suppressedListener = aFrameToHandleEvent->PresContext() 8545 ->Document() 8546 ->GetSuppressedEventListener(); 8547 if (!suppressedListener || 8548 aGUIEvent->AsMouseEvent()->mReason == WidgetMouseEvent::eSynthesized) { 8549 return true; 8550 } 8551 8552 if (auto* target = aFrameToHandleEvent->GetContentForEvent(aGUIEvent)) { 8553 aGUIEvent->mTarget = target; 8554 } 8555 8556 nsCOMPtr<EventTarget> eventTarget = aGUIEvent->mTarget; 8557 RefPtr<Event> event = EventDispatcher::CreateEvent( 8558 eventTarget, aFrameToHandleEvent->PresContext(), aGUIEvent, u""_ns); 8559 8560 suppressedListener->HandleEvent(*event); 8561 return true; 8562 } 8563 8564 void PresShell::EventHandler::MaybeFlushThrottledStyles( 8565 AutoWeakFrame& aWeakFrameForPresShell) { 8566 if (!GetDocument()) { 8567 return; 8568 } 8569 8570 PresShell* rootPresShell = mPresShell->GetRootPresShell(); 8571 if (NS_WARN_IF(!rootPresShell)) { 8572 return; 8573 } 8574 Document* rootDocument = rootPresShell->GetDocument(); 8575 if (NS_WARN_IF(!rootDocument)) { 8576 return; 8577 } 8578 8579 { // scope for scriptBlocker. 8580 nsAutoScriptBlocker scriptBlocker; 8581 FlushThrottledStyles(*rootDocument); 8582 } 8583 8584 if (MOZ_UNLIKELY(!aWeakFrameForPresShell.IsAlive()) && 8585 MOZ_LIKELY(!mPresShell->IsDestroying())) { 8586 // FIXME: aWeakFrameForPresShell may be target content's frame if aGUIEvent 8587 // of the caller is a touch event. So, we need to use different computation 8588 // for such cases. 8589 aWeakFrameForPresShell = mPresShell->GetRootFrame(); 8590 } 8591 } 8592 8593 nsIFrame* PresShell::EventHandler::ComputeRootFrameToHandleEvent( 8594 nsIFrame* aFrameForPresShell, WidgetGUIEvent* aGUIEvent, 8595 nsIContent* aCapturingContent, bool* aIsCapturingContentIgnored, 8596 bool* aIsCaptureRetargeted) { 8597 MOZ_ASSERT(aFrameForPresShell); 8598 MOZ_ASSERT(aGUIEvent); 8599 MOZ_ASSERT(aIsCapturingContentIgnored); 8600 MOZ_ASSERT(aIsCaptureRetargeted); 8601 8602 nsIFrame* rootFrameToHandleEvent = ComputeRootFrameToHandleEventWithPopup( 8603 aFrameForPresShell, aGUIEvent, aCapturingContent, 8604 aIsCapturingContentIgnored); 8605 if (*aIsCapturingContentIgnored) { 8606 // If the capturing content is ignored, we don't need to respect it. 8607 return rootFrameToHandleEvent; 8608 } 8609 8610 if (!aCapturingContent) { 8611 return rootFrameToHandleEvent; 8612 } 8613 8614 // If we have capturing content, let's compute root frame with it again. 8615 return ComputeRootFrameToHandleEventWithCapturingContent( 8616 rootFrameToHandleEvent, aCapturingContent, aIsCapturingContentIgnored, 8617 aIsCaptureRetargeted); 8618 } 8619 8620 nsIFrame* PresShell::EventHandler::ComputeRootFrameToHandleEventWithPopup( 8621 nsIFrame* aRootFrameToHandleEvent, WidgetGUIEvent* aGUIEvent, 8622 nsIContent* aCapturingContent, bool* aIsCapturingContentIgnored) { 8623 MOZ_ASSERT(aRootFrameToHandleEvent); 8624 MOZ_ASSERT(aGUIEvent); 8625 MOZ_ASSERT(aIsCapturingContentIgnored); 8626 8627 *aIsCapturingContentIgnored = false; 8628 8629 nsPresContext* framePresContext = aRootFrameToHandleEvent->PresContext(); 8630 nsPresContext* rootPresContext = framePresContext->GetRootPresContext(); 8631 NS_ASSERTION(rootPresContext == GetPresContext()->GetRootPresContext(), 8632 "How did we end up outside the connected " 8633 "prescontext hierarchy?"); 8634 nsIFrame* popupFrame = nsLayoutUtils::GetPopupFrameForEventCoordinates( 8635 rootPresContext, aGUIEvent); 8636 if (!popupFrame) { 8637 return aRootFrameToHandleEvent; 8638 } 8639 8640 // If a remote browser is currently capturing input break out if we 8641 // detect a chrome generated popup. 8642 // XXXedgar, do we need to check fission OOP iframe? 8643 if (aCapturingContent && 8644 EventStateManager::IsTopLevelRemoteTarget(aCapturingContent)) { 8645 *aIsCapturingContentIgnored = true; 8646 } 8647 8648 // If the popupFrame is an ancestor of the 'frame', the frame should 8649 // handle the event, otherwise, the popup should handle it. 8650 if (nsContentUtils::ContentIsCrossDocDescendantOf( 8651 framePresContext->GetPresShell()->GetDocument(), 8652 popupFrame->GetContent())) { 8653 return aRootFrameToHandleEvent; 8654 } 8655 8656 // If we aren't starting our event dispatch from the root frame of the 8657 // root prescontext, then someone must be capturing the mouse. In that 8658 // case we only want to use the popup list if the capture is 8659 // inside the popup. 8660 if (framePresContext == rootPresContext && 8661 aRootFrameToHandleEvent == FrameConstructor()->GetRootFrame()) { 8662 return popupFrame; 8663 } 8664 8665 if (aCapturingContent && !*aIsCapturingContentIgnored && 8666 aCapturingContent->IsInclusiveDescendantOf(popupFrame->GetContent())) { 8667 return popupFrame; 8668 } 8669 8670 return aRootFrameToHandleEvent; 8671 } 8672 8673 nsIFrame* 8674 PresShell::EventHandler::ComputeRootFrameToHandleEventWithCapturingContent( 8675 nsIFrame* aRootFrameToHandleEvent, nsIContent* aCapturingContent, 8676 bool* aIsCapturingContentIgnored, bool* aIsCaptureRetargeted) { 8677 MOZ_ASSERT(aRootFrameToHandleEvent); 8678 MOZ_ASSERT(aCapturingContent); 8679 MOZ_ASSERT(aIsCapturingContentIgnored); 8680 MOZ_ASSERT(aIsCaptureRetargeted); 8681 8682 *aIsCapturingContentIgnored = false; 8683 *aIsCaptureRetargeted = false; 8684 8685 // If a capture is active, determine if the BrowsingContext is active. If 8686 // not, clear the capture and target the mouse event normally instead. This 8687 // would occur if the mouse button is held down while a tab change occurs. 8688 // If the BrowsingContext is active, look for a scrolling container. 8689 BrowsingContext* bc = GetPresContext()->Document()->GetBrowsingContext(); 8690 if (!bc || !bc->IsActive()) { 8691 ClearMouseCapture(); 8692 *aIsCapturingContentIgnored = true; 8693 return aRootFrameToHandleEvent; 8694 } 8695 8696 if (PresShell::sCapturingContentInfo.mRetargetToElement) { 8697 *aIsCaptureRetargeted = true; 8698 return aRootFrameToHandleEvent; 8699 } 8700 8701 nsIFrame* captureFrame = aCapturingContent->GetPrimaryFrame(); 8702 if (!captureFrame) { 8703 return aRootFrameToHandleEvent; 8704 } 8705 8706 // scrollable frames should use the scrolling container as the root instead 8707 // of the document 8708 ScrollContainerFrame* scrollFrame = do_QueryFrame(captureFrame); 8709 return scrollFrame ? scrollFrame->GetScrolledFrame() 8710 : aRootFrameToHandleEvent; 8711 } 8712 8713 nsresult 8714 PresShell::EventHandler::HandleEventWithPointerCapturingContentWithoutItsFrame( 8715 AutoWeakFrame& aWeakFrameForPresShell, WidgetGUIEvent* aGUIEvent, 8716 Element* aPointerCapturingElement, nsEventStatus* aEventStatus) { 8717 MOZ_ASSERT(aGUIEvent); 8718 MOZ_ASSERT(aPointerCapturingElement); 8719 MOZ_ASSERT(!aPointerCapturingElement->GetPrimaryFrame(), 8720 "Handle the event with frame rather than only with the content"); 8721 MOZ_ASSERT(aEventStatus); 8722 8723 RefPtr<PresShell> presShellForCapturingContent = 8724 PresShell::GetShellForEventTarget(nullptr, aPointerCapturingElement); 8725 if (!presShellForCapturingContent) { 8726 // If we can't process event for the capturing content, release 8727 // the capture. 8728 PointerEventHandler::ReleaseIfCaptureByDescendant(aPointerCapturingElement); 8729 // Since we don't dispatch ePointeUp nor ePointerCancel in this case, 8730 // EventStateManager::PostHandleEvent does not have a chance to dispatch 8731 // ePointerLostCapture event. Therefore, we need to dispatch it here. 8732 PointerEventHandler::MaybeImplicitlyReleasePointerCapture(aGUIEvent); 8733 return NS_OK; 8734 } 8735 8736 Result<nsIContent*, nsresult> overrideClickTargetOrError = 8737 GetOverrideClickTarget(aGUIEvent, aWeakFrameForPresShell.GetFrame(), 8738 aPointerCapturingElement); 8739 if (MOZ_UNLIKELY(overrideClickTargetOrError.isErr())) { 8740 return NS_OK; 8741 } 8742 nsCOMPtr<nsIContent> overrideClickTarget = 8743 overrideClickTargetOrError.unwrap(); 8744 8745 // Dispatch events to the capturing content even it's frame is 8746 // destroyed. 8747 PointerEventHandler::DispatchPointerFromMouseOrTouch( 8748 presShellForCapturingContent, nullptr, aPointerCapturingElement, 8749 aPointerCapturingElement, aGUIEvent, false, aEventStatus, nullptr); 8750 8751 if (presShellForCapturingContent == mPresShell) { 8752 return HandleEventWithTarget(aGUIEvent, nullptr, aPointerCapturingElement, 8753 aEventStatus, true, nullptr, 8754 overrideClickTarget); 8755 } 8756 8757 EventHandler eventHandlerForCapturingContent( 8758 std::move(presShellForCapturingContent)); 8759 return eventHandlerForCapturingContent.HandleEventWithTarget( 8760 aGUIEvent, nullptr, aPointerCapturingElement, aEventStatus, true, nullptr, 8761 overrideClickTarget); 8762 } 8763 8764 nsresult PresShell::EventHandler::HandleEventAtFocusedContent( 8765 WidgetGUIEvent* aGUIEvent, nsEventStatus* aEventStatus) { 8766 MOZ_ASSERT(aGUIEvent); 8767 MOZ_ASSERT(aGUIEvent->IsTargetedAtFocusedContent()); 8768 MOZ_ASSERT(aEventStatus); 8769 8770 AutoCurrentEventInfoSetter eventInfoSetter(*this); 8771 8772 RefPtr<Element> eventTargetElement = 8773 ComputeFocusedEventTargetElement(aGUIEvent); 8774 8775 // mCurrentEventTarget is cleared by eventInfoSetter and 8776 // ComputeFocusedEventTargetElement shouldn't set it again. 8777 MOZ_ASSERT(!mPresShell->mCurrentEventTarget.IsSet()); 8778 8779 if (eventTargetElement) { 8780 nsresult rv = NS_OK; 8781 if (MaybeHandleEventWithAnotherPresShell(eventTargetElement, aGUIEvent, 8782 aEventStatus, &rv)) { 8783 return rv; 8784 } 8785 } 8786 8787 // If we cannot handle the event with mPresShell, let's try to handle it 8788 // with parent PresShell. 8789 // However, we don't want to handle IME related events with parent document 8790 // because it may leak the content of parent document and the IME state was 8791 // set for the empty document. So, dispatching on the parent document may be 8792 // handled by nobody. Additionally, IMEContentObserver may send notifications 8793 // to PuppetWidget in a content process while document which is in the design 8794 // mode but does not have content nodes has focus. At that time, PuppetWidget 8795 // makes ContentCacheInChild collect the latest content data with dispatching 8796 // query content events. Therefore, we want they handle in the empty document 8797 // rather than the parent document. So, we must not retarget in this case 8798 // anyway. 8799 mPresShell->mCurrentEventTarget.SetFrameAndContent( 8800 aGUIEvent->mMessage, nullptr, eventTargetElement); 8801 if (aGUIEvent->mClass != eCompositionEventClass && 8802 aGUIEvent->mClass != eQueryContentEventClass && 8803 aGUIEvent->mClass != eSelectionEventClass && 8804 (!mPresShell->GetCurrentEventContent() || 8805 !mPresShell->GetCurrentEventFrame() || 8806 InZombieDocument(mPresShell->mCurrentEventTarget.mContent))) { 8807 return RetargetEventToParent(aGUIEvent, aEventStatus); 8808 } 8809 8810 nsresult rv = 8811 HandleEventWithCurrentEventInfo(aGUIEvent, aEventStatus, true, nullptr); 8812 return rv; 8813 } 8814 8815 Element* PresShell::EventHandler::ComputeFocusedEventTargetElement( 8816 WidgetGUIEvent* aGUIEvent) { 8817 MOZ_ASSERT(aGUIEvent); 8818 MOZ_ASSERT(aGUIEvent->IsTargetedAtFocusedContent()); 8819 8820 // key and IME related events go to the focused frame in this DOM window. 8821 nsPIDOMWindowOuter* window = GetDocument()->GetWindow(); 8822 nsCOMPtr<nsPIDOMWindowOuter> focusedWindow; 8823 Element* eventTargetElement = nsFocusManager::GetFocusedDescendant( 8824 window, nsFocusManager::eOnlyCurrentWindow, 8825 getter_AddRefs(focusedWindow)); 8826 8827 // otherwise, if there is no focused content or the focused content has 8828 // no frame, just use the root content. This ensures that key events 8829 // still get sent to the window properly if nothing is focused or if a 8830 // frame goes away while it is focused. 8831 if (!eventTargetElement || !eventTargetElement->GetPrimaryFrame()) { 8832 eventTargetElement = GetDocument()->GetUnfocusedKeyEventTarget(); 8833 } 8834 8835 switch (aGUIEvent->mMessage) { 8836 case eKeyDown: 8837 sLastKeyDownEventTargetElement = eventTargetElement; 8838 return eventTargetElement; 8839 case eKeyPress: 8840 case eKeyUp: 8841 if (!sLastKeyDownEventTargetElement) { 8842 return eventTargetElement; 8843 } 8844 // If a different element is now focused for the keypress/keyup event 8845 // than what was focused during the keydown event, check if the new 8846 // focused element is not in a chrome document any more, and if so, 8847 // retarget the event back at the keydown target. This prevents a 8848 // content area from grabbing the focus from chrome in-between key 8849 // events. 8850 if (eventTargetElement) { 8851 bool keyDownIsChrome = nsContentUtils::IsChromeDoc( 8852 sLastKeyDownEventTargetElement->GetComposedDoc()); 8853 if (keyDownIsChrome != nsContentUtils::IsChromeDoc( 8854 eventTargetElement->GetComposedDoc()) || 8855 (keyDownIsChrome && BrowserParent::GetFrom(eventTargetElement))) { 8856 eventTargetElement = sLastKeyDownEventTargetElement; 8857 } 8858 } 8859 8860 if (aGUIEvent->mMessage == eKeyUp) { 8861 sLastKeyDownEventTargetElement = nullptr; 8862 } 8863 [[fallthrough]]; 8864 default: 8865 return eventTargetElement; 8866 } 8867 } 8868 8869 bool PresShell::EventHandler::MaybeHandleEventWithAnotherPresShell( 8870 Element* aEventTargetElement, WidgetGUIEvent* aGUIEvent, 8871 nsEventStatus* aEventStatus, nsresult* aRv) { 8872 MOZ_ASSERT(aEventTargetElement); 8873 MOZ_ASSERT(aGUIEvent); 8874 MOZ_ASSERT(!aGUIEvent->IsUsingCoordinates()); 8875 MOZ_ASSERT(aEventStatus); 8876 MOZ_ASSERT(aRv); 8877 8878 Document* eventTargetDocument = aEventTargetElement->OwnerDoc(); 8879 if (!eventTargetDocument || eventTargetDocument == GetDocument()) { 8880 *aRv = NS_OK; 8881 return false; 8882 } 8883 8884 RefPtr<PresShell> eventTargetPresShell = eventTargetDocument->GetPresShell(); 8885 if (!eventTargetPresShell) { 8886 *aRv = NS_OK; 8887 return true; // No PresShell can handle the event. 8888 } 8889 8890 EventHandler eventHandler(std::move(eventTargetPresShell)); 8891 *aRv = eventHandler.HandleRetargetedEvent(aGUIEvent, aEventStatus, 8892 aEventTargetElement); 8893 return true; 8894 } 8895 8896 nsresult PresShell::EventHandler::HandleEventWithFrameForPresShell( 8897 AutoWeakFrame& aWeakFrameForPresShell, WidgetGUIEvent* aGUIEvent, 8898 nsEventStatus* aEventStatus) { 8899 MOZ_ASSERT(aGUIEvent); 8900 MOZ_ASSERT(!aGUIEvent->IsUsingCoordinates()); 8901 MOZ_ASSERT(!aGUIEvent->IsTargetedAtFocusedContent()); 8902 MOZ_ASSERT(aEventStatus); 8903 8904 AutoCurrentEventInfoSetter eventInfoSetter( 8905 *this, EventTargetInfo(aGUIEvent->mMessage, 8906 aWeakFrameForPresShell.GetFrame(), nullptr)); 8907 8908 nsresult rv = NS_OK; 8909 if (mPresShell->GetCurrentEventFrame()) { 8910 rv = 8911 HandleEventWithCurrentEventInfo(aGUIEvent, aEventStatus, true, nullptr); 8912 } 8913 8914 return rv; 8915 } 8916 8917 Document* PresShell::GetPrimaryContentDocument() { 8918 nsPresContext* context = GetPresContext(); 8919 if (!context || !context->IsRoot()) { 8920 return nullptr; 8921 } 8922 8923 nsCOMPtr<nsIDocShellTreeItem> shellAsTreeItem = context->GetDocShell(); 8924 if (!shellAsTreeItem) { 8925 return nullptr; 8926 } 8927 8928 nsCOMPtr<nsIDocShellTreeOwner> owner; 8929 shellAsTreeItem->GetTreeOwner(getter_AddRefs(owner)); 8930 if (!owner) { 8931 return nullptr; 8932 } 8933 8934 // now get the primary content shell (active tab) 8935 nsCOMPtr<nsIDocShellTreeItem> item; 8936 owner->GetPrimaryContentShell(getter_AddRefs(item)); 8937 nsCOMPtr<nsIDocShell> childDocShell = do_QueryInterface(item); 8938 if (!childDocShell) { 8939 return nullptr; 8940 } 8941 8942 return childDocShell->GetExtantDocument(); 8943 } 8944 8945 nsresult PresShell::EventHandler::HandleEventWithTarget( 8946 WidgetEvent* aEvent, nsIFrame* aNewEventFrame, nsIContent* aNewEventContent, 8947 nsEventStatus* aEventStatus, bool aIsHandlingNativeEvent, 8948 nsIContent** aTargetContent, nsIContent* aOverrideClickTarget) { 8949 MOZ_ASSERT(aEvent); 8950 MOZ_DIAGNOSTIC_ASSERT(aEvent->IsTrusted()); 8951 MOZ_ASSERT(!aNewEventFrame || aNewEventFrame->PresShell() == mPresShell, 8952 "wrong shell"); 8953 // NOTE: We don't require that the document still have a PresShell. 8954 // See bug 1375940. 8955 NS_ASSERTION(!aNewEventContent || aNewEventContent->IsInComposedDoc(), 8956 "event for content that isn't in a document"); 8957 NS_ENSURE_STATE(!aNewEventContent || 8958 aNewEventContent->GetComposedDoc() == GetDocument()); 8959 if (aEvent->mClass == ePointerEventClass || 8960 aEvent->mClass == eDragEventClass) { 8961 mPresShell->RecordPointerLocation(aEvent->AsMouseEvent()); 8962 } 8963 AutoPointerEventTargetUpdater updater(mPresShell, aEvent, aNewEventFrame, 8964 aNewEventContent, aTargetContent); 8965 AutoCurrentEventInfoSetter eventInfoSetter( 8966 *this, 8967 EventTargetInfo(aEvent->mMessage, aNewEventFrame, aNewEventContent)); 8968 nsresult rv = HandleEventWithCurrentEventInfo(aEvent, aEventStatus, false, 8969 aOverrideClickTarget); 8970 return rv; 8971 } 8972 8973 namespace { 8974 8975 class MOZ_RAII AutoEventHandler final { 8976 public: 8977 AutoEventHandler(WidgetEvent* aEvent, Document* aDocument) : mEvent(aEvent) { 8978 MOZ_ASSERT(mEvent); 8979 MOZ_ASSERT(mEvent->IsTrusted()); 8980 8981 if (mEvent->mMessage == eMouseDown) { 8982 PresShell::ReleaseCapturingContent(); 8983 PresShell::AllowMouseCapture(true); 8984 } 8985 if (NeedsToUpdateCurrentMouseBtnState()) { 8986 WidgetMouseEvent* mouseEvent = mEvent->AsMouseEvent(); 8987 if (mouseEvent) { 8988 EventStateManager::sCurrentMouseBtn = mouseEvent->mButton; 8989 } 8990 } 8991 } 8992 8993 ~AutoEventHandler() { 8994 if (mEvent->mMessage == eMouseDown) { 8995 PresShell::AllowMouseCapture(false); 8996 } 8997 if (NeedsToUpdateCurrentMouseBtnState()) { 8998 EventStateManager::sCurrentMouseBtn = MouseButton::eNotPressed; 8999 } 9000 } 9001 9002 protected: 9003 bool NeedsToUpdateCurrentMouseBtnState() const { 9004 return mEvent->mMessage == eMouseDown || mEvent->mMessage == eMouseUp || 9005 mEvent->mMessage == ePointerDown || mEvent->mMessage == ePointerUp; 9006 } 9007 9008 WidgetEvent* mEvent; 9009 }; 9010 9011 } // anonymous namespace 9012 9013 nsresult PresShell::EventHandler::HandleEventWithCurrentEventInfo( 9014 WidgetEvent* aEvent, nsEventStatus* aEventStatus, 9015 bool aIsHandlingNativeEvent, nsIContent* aOverrideClickTarget) { 9016 MOZ_ASSERT(aEvent); 9017 MOZ_ASSERT(aEventStatus); 9018 9019 RefPtr<EventStateManager> manager = GetPresContext()->EventStateManager(); 9020 9021 // If we cannot handle the event with mPresShell because of no target, 9022 // just record the response time. 9023 // XXX Is this intentional? In such case, the score is really good because 9024 // of nothing to do. So, it may make average and median better. 9025 if (NS_EVENT_NEEDS_FRAME(aEvent) && !mPresShell->GetCurrentEventFrame() && 9026 !mPresShell->GetCurrentEventContent()) { 9027 RecordEventHandlingResponsePerformance(aEvent); 9028 return NS_OK; 9029 } 9030 9031 if (mPresShell->mCurrentEventTarget.mContent && 9032 aEvent->IsTargetedAtFocusedWindow() && 9033 aEvent->AllowFlushingPendingNotifications()) { 9034 if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) { 9035 // This may run script now. So, mPresShell might be destroyed after here. 9036 nsCOMPtr<nsIContent> currentEventContent = 9037 mPresShell->mCurrentEventTarget.mContent; 9038 fm->FlushBeforeEventHandlingIfNeeded(currentEventContent); 9039 } 9040 } 9041 9042 bool touchIsNew = false; 9043 if (!PrepareToDispatchEvent(aEvent, aEventStatus, &touchIsNew)) { 9044 return NS_OK; 9045 } 9046 9047 // We finished preparing to dispatch the event. So, let's record the 9048 // performance. 9049 RecordEventPreparationPerformance(aEvent); 9050 9051 AutoHandlingUserInputStatePusher userInpStatePusher( 9052 UserActivation::IsUserInteractionEvent(aEvent), aEvent); 9053 AutoEventHandler eventHandler(aEvent, GetDocument()); 9054 AutoPopupStatePusher popupStatePusher( 9055 PopupBlocker::GetEventPopupControlState(aEvent)); 9056 9057 // FIXME. If the event was reused, we need to clear the old target, 9058 // bug 329430 9059 aEvent->mTarget = nullptr; 9060 9061 nsresult rv = DispatchEvent(manager, aEvent, touchIsNew, aEventStatus, 9062 aOverrideClickTarget); 9063 9064 if (!mPresShell->IsDestroying() && aIsHandlingNativeEvent && 9065 aEvent->mClass != eQueryContentEventClass) { 9066 // Ensure that notifications to IME should be sent before getting next 9067 // native event from the event queue. 9068 // XXX Should we check the event message or event class instead of 9069 // using aIsHandlingNativeEvent? 9070 manager->TryToFlushPendingNotificationsToIME(); 9071 } 9072 9073 FinalizeHandlingEvent(aEvent, aEventStatus); 9074 9075 RecordEventHandlingResponsePerformance(aEvent); 9076 9077 return rv; // Result of DispatchEvent() 9078 } 9079 9080 nsresult PresShell::EventHandler::DispatchEvent( 9081 EventStateManager* aEventStateManager, WidgetEvent* aEvent, 9082 bool aTouchIsNew, nsEventStatus* aEventStatus, 9083 nsIContent* aOverrideClickTarget) { 9084 MOZ_ASSERT(aEventStateManager); 9085 MOZ_ASSERT(aEvent); 9086 MOZ_ASSERT(aEventStatus); 9087 9088 // 1. Give event to event manager for pre event state changes and 9089 // generation of synthetic events. 9090 { // Scope for presContext 9091 RefPtr<nsPresContext> presContext = GetPresContext(); 9092 nsCOMPtr<nsIContent> eventContent = 9093 mPresShell->mCurrentEventTarget.mContent; 9094 nsresult rv = aEventStateManager->PreHandleEvent( 9095 presContext, aEvent, mPresShell->mCurrentEventTarget.mFrame, 9096 eventContent, aEventStatus, aOverrideClickTarget); 9097 if (NS_FAILED(rv)) { 9098 return rv; 9099 } 9100 // Let's retarget eMouseMove target if the preceding mouse boundary events 9101 // caused removing the target from the tree and EventStateManager knows that 9102 // the deepest connected mouseenter target which was an ancestor of the 9103 // removed target. This matches with Chrome Canary with enabling the 9104 // new mouse/pointer boundary event feature. However, they stop dispatching 9105 // "pointermove" in the same case. Therefore, for now, we should do this 9106 // only for eMouseMove. 9107 if (eventContent && aEvent->mMessage == eMouseMove && 9108 (!eventContent->IsInComposedDoc() || 9109 eventContent->OwnerDoc() != mPresShell->GetDocument())) { 9110 const OverOutElementsWrapper* const boundaryEventTargets = 9111 aEventStateManager->GetExtantMouseBoundaryEventTarget(); 9112 const nsIContent* outEventTarget = 9113 boundaryEventTargets ? boundaryEventTargets->GetOutEventTarget() 9114 : nullptr; 9115 nsIContent* const deepestLeaveEventTarget = 9116 boundaryEventTargets 9117 ? boundaryEventTargets->GetDeepestLeaveEventTarget() 9118 : nullptr; 9119 // If the last "over" target (next "out" target) is there, it means that 9120 // it was temporarily removed. In such case, EventStateManager treats 9121 // it as never disconnected. Therefore, we need to do nothing here. 9122 // Additionally, if there is no last deepest "enter" event target, we 9123 // lost the target. Therefore, we should keep the traditional behavior, 9124 // to dispatch it on the Document node. 9125 if (!outEventTarget && deepestLeaveEventTarget) { 9126 nsIFrame* const frame = 9127 deepestLeaveEventTarget->GetPrimaryFrame(FlushType::Layout); 9128 if (MOZ_UNLIKELY(mPresShell->IsDestroying())) { 9129 return NS_OK; 9130 } 9131 if (frame) { 9132 mPresShell->mCurrentEventTarget.mFrame = frame; 9133 mPresShell->mCurrentEventTarget.mContent = deepestLeaveEventTarget; 9134 } 9135 } 9136 } 9137 } 9138 9139 // 2. Give event to the DOM for third party and JS use. 9140 bool wasHandlingKeyBoardEvent = nsContentUtils::IsHandlingKeyBoardEvent(); 9141 if (aEvent->mClass == eKeyboardEventClass) { 9142 nsContentUtils::SetIsHandlingKeyBoardEvent(true); 9143 } 9144 // If EventStateManager or something wants reply from remote process and 9145 // needs to win any other event listeners in chrome, the event is both 9146 // stopped its propagation and marked as "waiting reply from remote 9147 // process". In this case, PresShell shouldn't dispatch the event into 9148 // the DOM tree because they don't have a chance to stop propagation in 9149 // the system event group. On the other hand, if its propagation is not 9150 // stopped, that means that the event may be reserved by chrome. If it's 9151 // reserved by chrome, the event shouldn't be sent to any remote 9152 // processes. In this case, PresShell needs to dispatch the event to 9153 // the DOM tree for checking if it's reserved. 9154 if (aEvent->IsAllowedToDispatchDOMEvent() && 9155 !(aEvent->PropagationStopped() && 9156 aEvent->IsWaitingReplyFromRemoteProcess())) { 9157 MOZ_ASSERT(nsContentUtils::IsSafeToRunScript(), 9158 "Somebody changed aEvent to cause a DOM event!"); 9159 nsPresShellEventCB eventCB(mPresShell); 9160 if (nsIFrame* target = mPresShell->GetCurrentEventFrame()) { 9161 if (target->OnlySystemGroupDispatch(aEvent->mMessage)) { 9162 aEvent->StopPropagation(); 9163 } 9164 } 9165 if (aEvent->mClass == eTouchEventClass) { 9166 DispatchTouchEventToDOM(aEvent, aEventStatus, &eventCB, aTouchIsNew); 9167 } else { 9168 DispatchEventToDOM(aEvent, aEventStatus, &eventCB); 9169 } 9170 } 9171 9172 nsContentUtils::SetIsHandlingKeyBoardEvent(wasHandlingKeyBoardEvent); 9173 9174 if (mPresShell->IsDestroying()) { 9175 return NS_OK; 9176 } 9177 9178 // 3. Give event to event manager for post event state changes and 9179 // generation of synthetic events. 9180 // Refetch the prescontext, in case it changed. 9181 RefPtr<nsPresContext> presContext = GetPresContext(); 9182 return aEventStateManager->PostHandleEvent( 9183 presContext, aEvent, mPresShell->GetCurrentEventFrame(), aEventStatus, 9184 aOverrideClickTarget); 9185 } 9186 9187 bool PresShell::EventHandler::PrepareToDispatchEvent( 9188 WidgetEvent* aEvent, nsEventStatus* aEventStatus, bool* aTouchIsNew) { 9189 MOZ_ASSERT(aEvent->IsTrusted()); 9190 MOZ_ASSERT(aEventStatus); 9191 MOZ_ASSERT(aTouchIsNew); 9192 9193 *aTouchIsNew = false; 9194 if (aEvent->IsUserAction()) { 9195 mPresShell->mHasHandledUserInput = true; 9196 } 9197 9198 switch (aEvent->mMessage) { 9199 case eKeyPress: 9200 case eKeyDown: 9201 case eKeyUp: { 9202 WidgetKeyboardEvent* keyboardEvent = aEvent->AsKeyboardEvent(); 9203 MaybeHandleKeyboardEventBeforeDispatch(keyboardEvent); 9204 return true; 9205 } 9206 case eMouseRawUpdate: 9207 MOZ_ASSERT_UNREACHABLE( 9208 "eMouseRawUpdate shouldn't be handled as a DOM event"); 9209 return false; 9210 9211 case eMouseMove: { 9212 bool allowCapture = EventStateManager::GetActiveEventStateManager() && 9213 GetPresContext() && 9214 GetPresContext()->EventStateManager() == 9215 EventStateManager::GetActiveEventStateManager(); 9216 PresShell::AllowMouseCapture(allowCapture); 9217 return true; 9218 } 9219 case eDrop: { 9220 nsCOMPtr<nsIDragSession> session = 9221 nsContentUtils::GetDragSession(GetPresContext()); 9222 if (session) { 9223 bool onlyChromeDrop = false; 9224 session->GetOnlyChromeDrop(&onlyChromeDrop); 9225 if (onlyChromeDrop) { 9226 aEvent->mFlags.mOnlyChromeDispatch = true; 9227 } 9228 } 9229 return true; 9230 } 9231 case eDragExit: { 9232 if (!StaticPrefs::dom_event_dragexit_enabled()) { 9233 aEvent->mFlags.mOnlyChromeDispatch = true; 9234 } 9235 return true; 9236 } 9237 case eContextMenu: { 9238 // If we cannot open context menu even though eContextMenu is fired, we 9239 // should stop dispatching it into the DOM. 9240 WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent(); 9241 if (mouseEvent->IsContextMenuKeyEvent() && 9242 !AdjustContextMenuKeyEvent(mouseEvent)) { 9243 return false; 9244 } 9245 9246 // If "Shift" state is active, context menu should be forcibly opened even 9247 // if web apps want to prevent it since we respect our users' intention. 9248 // In this case, we don't fire "contextmenu" event on web content because 9249 // of not cancelable. 9250 if (mouseEvent->IsShift() && 9251 StaticPrefs::dom_event_contextmenu_shift_suppresses_event()) { 9252 aEvent->mFlags.mOnlyChromeDispatch = true; 9253 aEvent->mFlags.mRetargetToNonNativeAnonymous = true; 9254 } 9255 return true; 9256 } 9257 case eTouchStart: 9258 case eTouchMove: 9259 case eTouchEnd: 9260 case eTouchCancel: 9261 case eTouchPointerCancel: 9262 return mPresShell->mTouchManager.PreHandleEvent( 9263 aEvent, aEventStatus, *aTouchIsNew, 9264 mPresShell->mCurrentEventTarget.mContent); 9265 case eTouchRawUpdate: 9266 MOZ_ASSERT_UNREACHABLE( 9267 "eTouchRawUpdate shouldn't be handled as a DOM event"); 9268 return false; 9269 default: 9270 return true; 9271 } 9272 } 9273 9274 void PresShell::EventHandler::FinalizeHandlingEvent( 9275 WidgetEvent* aEvent, const nsEventStatus* aStatus) { 9276 switch (aEvent->mMessage) { 9277 case eKeyPress: 9278 case eKeyDown: 9279 case eKeyUp: { 9280 if (aEvent->AsKeyboardEvent()->mKeyCode == NS_VK_ESCAPE) { 9281 if (aEvent->mMessage == eKeyUp) { 9282 // Reset this flag after key up is handled. 9283 mPresShell->mIsLastChromeOnlyEscapeKeyConsumed = false; 9284 } else { 9285 if (aEvent->mFlags.mOnlyChromeDispatch && 9286 aEvent->mFlags.mDefaultPreventedByChrome) { 9287 mPresShell->mIsLastChromeOnlyEscapeKeyConsumed = true; 9288 } 9289 if (aEvent->mMessage == eKeyDown && 9290 !aEvent->mFlags.mDefaultPrevented) { 9291 if (RefPtr<Document> doc = GetDocument()) { 9292 if (StaticPrefs::dom_closewatcher_enabled()) { 9293 doc->ProcessCloseRequest(); 9294 } else { 9295 doc->HandleEscKey(); 9296 } 9297 } 9298 } 9299 } 9300 } 9301 if (aEvent->mMessage == eKeyDown) { 9302 mPresShell->mIsLastKeyDownCanceled = aEvent->mFlags.mDefaultPrevented; 9303 } 9304 break; 9305 } 9306 case eMouseUp: 9307 // reset the capturing content now that the mouse button is up 9308 PresShell::ReleaseCapturingContent(); 9309 break; 9310 case eMouseRawUpdate: 9311 MOZ_ASSERT_UNREACHABLE( 9312 "eMouseRawUpdate shouldn't be handled as a DOM event"); 9313 break; 9314 case eMouseMove: 9315 PresShell::AllowMouseCapture(false); 9316 break; 9317 case eDrag: 9318 case eDragEnd: 9319 case eDragEnter: 9320 case eDragExit: 9321 case eDragLeave: 9322 case eDragOver: 9323 case eDrop: { 9324 // After any drag event other than dragstart (which is handled 9325 // separately, as we need to collect the data first), the DataTransfer 9326 // needs to be made protected, and then disconnected. 9327 DataTransfer* dataTransfer = aEvent->AsDragEvent()->mDataTransfer; 9328 if (dataTransfer) { 9329 dataTransfer->Disconnect(); 9330 } 9331 break; 9332 } 9333 case eTouchStart: 9334 case eTouchMove: 9335 case eTouchEnd: 9336 case eTouchCancel: 9337 case eTouchPointerCancel: 9338 case eMouseLongTap: 9339 case eContextMenu: { 9340 mPresShell->mTouchManager.PostHandleEvent(aEvent, aStatus); 9341 break; 9342 } 9343 case eTouchRawUpdate: 9344 MOZ_ASSERT_UNREACHABLE( 9345 "eTouchRawUpdate shouldn't be handled as a DOM event"); 9346 break; 9347 default: 9348 break; 9349 } 9350 9351 if (WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent()) { 9352 if (mouseEvent->mSynthesizeMoveAfterDispatch) { 9353 PointerEventHandler::SynthesizeMoveToDispatchBoundaryEvents(mouseEvent); 9354 } 9355 } 9356 } 9357 9358 void PresShell::EventHandler::MaybeHandleKeyboardEventBeforeDispatch( 9359 WidgetKeyboardEvent* aKeyboardEvent) { 9360 MOZ_ASSERT(aKeyboardEvent); 9361 9362 if (aKeyboardEvent->mKeyCode != NS_VK_ESCAPE) { 9363 return; 9364 } 9365 9366 // If we're in fullscreen mode, exit from it forcibly when Escape key is 9367 // pressed. 9368 Document* doc = mPresShell->GetCurrentEventContent() 9369 ? mPresShell->mCurrentEventTarget.mContent->OwnerDoc() 9370 : nullptr; 9371 Document* root = nsContentUtils::GetInProcessSubtreeRootDocument(doc); 9372 if (root && root->GetFullscreenElement()) { 9373 // Prevent default action on ESC key press when exiting 9374 // DOM fullscreen mode. This prevents the browser ESC key 9375 // handler from stopping all loads in the document, which 9376 // would cause <video> loads to stop. 9377 // XXX We need to claim the Escape key event which will be 9378 // dispatched only into chrome is already consumed by 9379 // content because we need to prevent its default here 9380 // for some reasons (not sure) but we need to detect 9381 // if a chrome event handler will call PreventDefault() 9382 // again and check it later. 9383 aKeyboardEvent->PreventDefaultBeforeDispatch(CrossProcessForwarding::eStop); 9384 aKeyboardEvent->mFlags.mOnlyChromeDispatch = true; 9385 9386 // The event listeners in chrome can prevent this ESC behavior by 9387 // calling prevent default on the preceding keydown/press events. 9388 if (aKeyboardEvent->mMessage == eKeyUp) { 9389 bool shouldExitFullscreen = 9390 !mPresShell->mIsLastChromeOnlyEscapeKeyConsumed; 9391 if (!shouldExitFullscreen) { 9392 if (mPresShell->mLastConsumedEscapeKeyUpForFullscreen && 9393 (aKeyboardEvent->mTimeStamp - 9394 mPresShell->mLastConsumedEscapeKeyUpForFullscreen) <= 9395 TimeDuration::FromMilliseconds( 9396 StaticPrefs:: 9397 dom_fullscreen_force_exit_on_multiple_escape_interval())) { 9398 shouldExitFullscreen = true; 9399 mPresShell->mLastConsumedEscapeKeyUpForFullscreen = TimeStamp(); 9400 } else { 9401 mPresShell->mLastConsumedEscapeKeyUpForFullscreen = 9402 aKeyboardEvent->mTimeStamp; 9403 } 9404 } 9405 9406 if (shouldExitFullscreen) { 9407 // ESC key released while in DOM fullscreen mode. 9408 // Fully exit fullscreen mode for the browser window and documents that 9409 // received the event. 9410 Document::AsyncExitFullscreen(root); 9411 } 9412 } 9413 } 9414 9415 if (XRE_IsParentProcess() && 9416 !mPresShell->mIsLastChromeOnlyEscapeKeyConsumed) { 9417 if (PointerLockManager::GetLockedRemoteTarget() || 9418 PointerLockManager::IsLocked()) { 9419 // XXX See above comment to understand the reason why this needs 9420 // to claim that the Escape key event is consumed by content 9421 // even though it will be dispatched only into chrome. 9422 aKeyboardEvent->PreventDefaultBeforeDispatch( 9423 CrossProcessForwarding::eStop); 9424 aKeyboardEvent->mFlags.mOnlyChromeDispatch = true; 9425 if (aKeyboardEvent->mMessage == eKeyUp) { 9426 PointerLockManager::Unlock("EscapeKey"); 9427 } 9428 } 9429 } 9430 } 9431 9432 void PresShell::EventHandler::RecordEventPreparationPerformance( 9433 const WidgetEvent* aEvent) { 9434 MOZ_ASSERT(aEvent); 9435 9436 switch (aEvent->mMessage) { 9437 case eKeyPress: 9438 case eKeyDown: 9439 case eKeyUp: 9440 if (aEvent->AsKeyboardEvent()->ShouldInteractionTimeRecorded()) { 9441 GetPresContext()->RecordInteractionTime( 9442 nsPresContext::InteractionType::KeyInteraction, aEvent->mTimeStamp); 9443 } 9444 glean::layout::input_event_queued_keyboard.AccumulateRawDuration( 9445 TimeStamp::Now() - aEvent->mTimeStamp); 9446 return; 9447 9448 case eMouseDown: 9449 case eMouseUp: 9450 glean::layout::input_event_queued_click.AccumulateRawDuration( 9451 TimeStamp::Now() - aEvent->mTimeStamp); 9452 [[fallthrough]]; 9453 case ePointerDown: 9454 case ePointerUp: 9455 GetPresContext()->RecordInteractionTime( 9456 nsPresContext::InteractionType::ClickInteraction, aEvent->mTimeStamp); 9457 return; 9458 9459 case eMouseRawUpdate: 9460 MOZ_ASSERT_UNREACHABLE( 9461 "eMouseRawUpdate shouldn't be handled as a DOM event"); 9462 break; 9463 case eMouseMove: 9464 GetPresContext()->RecordInteractionTime( 9465 nsPresContext::InteractionType::MouseMoveInteraction, 9466 aEvent->mTimeStamp); 9467 return; 9468 9469 default: 9470 return; 9471 } 9472 } 9473 9474 void PresShell::EventHandler::RecordEventHandlingResponsePerformance( 9475 const WidgetEvent* aEvent) { 9476 if (!Telemetry::CanRecordBase() || aEvent->mTimeStamp.IsNull() || 9477 aEvent->mTimeStamp <= mPresShell->mLastOSWake || 9478 !aEvent->AsInputEvent()) { 9479 return; 9480 } 9481 9482 TimeStamp now = TimeStamp::Now(); 9483 TimeDuration duration = now - aEvent->mTimeStamp; 9484 glean::layout::input_event_response.AccumulateRawDuration(duration); 9485 if (GetDocument() && 9486 GetDocument()->GetReadyStateEnum() != Document::READYSTATE_COMPLETE) { 9487 glean::layout::load_input_event_response.AccumulateRawDuration(duration); 9488 } 9489 9490 if (!sLastInputProcessed || sLastInputProcessed < aEvent->mTimeStamp) { 9491 if (sLastInputProcessed) { 9492 // This input event was created after we handled the last one. 9493 // Accumulate the previous events' coalesced duration. 9494 glean::layout::input_event_response_coalesced.AccumulateRawDuration( 9495 sLastInputProcessed - sLastInputCreated); 9496 9497 if (MOZ_UNLIKELY(!PresShell::sProcessInteractable)) { 9498 // For content process, we use the ready state of 9499 // top-level-content-document to know if the process has finished the 9500 // start-up. 9501 // For parent process, see the topic 9502 // 'sessionstore-one-or-no-tab-restored' in PresShell::Observe. 9503 if (XRE_IsContentProcess() && GetDocument() && 9504 GetDocument()->IsTopLevelContentDocument()) { 9505 switch (GetDocument()->GetReadyStateEnum()) { 9506 case Document::READYSTATE_INTERACTIVE: 9507 case Document::READYSTATE_COMPLETE: 9508 PresShell::sProcessInteractable = true; 9509 break; 9510 default: 9511 break; 9512 } 9513 } 9514 } 9515 } 9516 sLastInputCreated = aEvent->mTimeStamp; 9517 } else if (aEvent->mTimeStamp < sLastInputCreated) { 9518 // This event was created before the last input. May be processing out 9519 // of order, so coalesce backwards, too. 9520 sLastInputCreated = aEvent->mTimeStamp; 9521 } 9522 sLastInputProcessed = now; 9523 } 9524 9525 // static 9526 nsIPrincipal* 9527 PresShell::EventHandler::GetDocumentPrincipalToCompareWithBlacklist( 9528 PresShell& aPresShell) { 9529 nsPresContext* presContext = aPresShell.GetPresContext(); 9530 if (NS_WARN_IF(!presContext)) { 9531 return nullptr; 9532 } 9533 return presContext->Document()->GetPrincipalForPrefBasedHacks(); 9534 } 9535 9536 nsresult PresShell::EventHandler::DispatchEventToDOM( 9537 WidgetEvent* aEvent, nsEventStatus* aEventStatus, 9538 nsPresShellEventCB* aEventCB) { 9539 nsresult rv = NS_OK; 9540 nsCOMPtr<nsINode> eventTarget = mPresShell->mCurrentEventTarget.mContent; 9541 nsPresShellEventCB* eventCBPtr = aEventCB; 9542 if (!eventTarget) { 9543 nsCOMPtr<nsIContent> targetContent; 9544 if (mPresShell->mCurrentEventTarget.mFrame) { 9545 targetContent = 9546 mPresShell->mCurrentEventTarget.mFrame->GetContentForEvent(aEvent); 9547 if (targetContent && !targetContent->IsElement() && 9548 IsForbiddenDispatchingToNonElementContent(aEvent->mMessage)) { 9549 targetContent = 9550 targetContent->GetInclusiveFlattenedTreeAncestorElement(); 9551 } 9552 } 9553 if (targetContent) { 9554 eventTarget = targetContent; 9555 } else if (GetDocument()) { 9556 eventTarget = GetDocument(); 9557 // If we don't have any content, the callback wouldn't probably 9558 // do nothing. 9559 eventCBPtr = nullptr; 9560 } 9561 } 9562 if (eventTarget) { 9563 if (eventTarget->OwnerDoc()->ShouldResistFingerprinting( 9564 RFPTarget::WidgetEvents) && 9565 aEvent->IsBlockedForFingerprintingResistance()) { 9566 aEvent->mFlags.mOnlySystemGroupDispatchInContent = true; 9567 } else if (aEvent->mMessage == eKeyPress) { 9568 // If eKeyPress event is marked as not dispatched in the default event 9569 // group in web content, it's caused by non-printable key or key 9570 // combination. In this case, UI Events declares that browsers 9571 // shouldn't dispatch keypress event. However, some web apps may be 9572 // broken with this strict behavior due to historical issue. 9573 // Therefore, we need to keep dispatching keypress event for such keys 9574 // even with breaking the standard. 9575 // Similarly, the other browsers sets non-zero value of keyCode or 9576 // charCode of keypress event to the other. Therefore, we should 9577 // behave so, however, some web apps may be broken. On such web apps, 9578 // we should keep using legacy our behavior. 9579 if (!mPresShell->mInitializedWithKeyPressEventDispatchingBlacklist) { 9580 mPresShell->mInitializedWithKeyPressEventDispatchingBlacklist = true; 9581 nsCOMPtr<nsIPrincipal> principal = 9582 GetDocumentPrincipalToCompareWithBlacklist(*mPresShell); 9583 if (principal) { 9584 mPresShell->mForceDispatchKeyPressEventsForNonPrintableKeys = 9585 principal->IsURIInPrefList( 9586 "dom.keyboardevent.keypress.hack.dispatch_non_printable_" 9587 "keys") || 9588 principal->IsURIInPrefList( 9589 "dom.keyboardevent.keypress.hack." 9590 "dispatch_non_printable_keys.addl"); 9591 9592 mPresShell->mForceUseLegacyKeyCodeAndCharCodeValues |= 9593 principal->IsURIInPrefList( 9594 "dom.keyboardevent.keypress.hack." 9595 "use_legacy_keycode_and_charcode") || 9596 principal->IsURIInPrefList( 9597 "dom.keyboardevent.keypress.hack." 9598 "use_legacy_keycode_and_charcode.addl"); 9599 } 9600 } 9601 if (mPresShell->mForceDispatchKeyPressEventsForNonPrintableKeys) { 9602 aEvent->mFlags.mOnlySystemGroupDispatchInContent = false; 9603 } 9604 if (mPresShell->mForceUseLegacyKeyCodeAndCharCodeValues) { 9605 aEvent->AsKeyboardEvent()->mUseLegacyKeyCodeAndCharCodeValues = true; 9606 } 9607 } 9608 9609 if (aEvent->mClass == eCompositionEventClass) { 9610 RefPtr<nsPresContext> presContext = GetPresContext(); 9611 RefPtr<BrowserParent> browserParent = 9612 IMEStateManager::GetActiveBrowserParent(); 9613 IMEStateManager::DispatchCompositionEvent( 9614 eventTarget, presContext, browserParent, aEvent->AsCompositionEvent(), 9615 aEventStatus, eventCBPtr); 9616 } else { 9617 if (aEvent->IsMouseEventClassOrHasClickRelatedPointerEvent()) { 9618 PointerEventHandler::RecordMouseButtons(*aEvent->AsMouseEvent()); 9619 #ifdef DEBUG 9620 if (eventTarget->IsContent() && !eventTarget->IsElement()) { 9621 NS_WARNING(nsPrintfCString( 9622 "%s (IsReal()=%s) target is not an elemnet content " 9623 "node, %s\n", 9624 ToChar(aEvent->mMessage), 9625 aEvent->AsMouseEvent()->IsReal() ? "true" : "false", 9626 ToString(*eventTarget).c_str()) 9627 .get()); 9628 MOZ_CRASH("MouseEvent target must be an element"); 9629 } 9630 #endif // #ifdef DEBUG 9631 } 9632 RefPtr<nsPresContext> presContext = GetPresContext(); 9633 EventDispatcher::Dispatch(eventTarget, presContext, aEvent, nullptr, 9634 aEventStatus, eventCBPtr); 9635 } 9636 } 9637 return rv; 9638 } 9639 9640 void PresShell::EventHandler::DispatchTouchEventToDOM( 9641 WidgetEvent* aEvent, nsEventStatus* aEventStatus, 9642 nsPresShellEventCB* aEventCB, bool aTouchIsNew) { 9643 MOZ_ASSERT(aEvent->mMessage != eTouchRawUpdate); 9644 // calling preventDefault on touchstart or the first touchmove for a 9645 // point prevents mouse events. calling it on the touchend should 9646 // prevent click dispatching. 9647 bool canPrevent = (aEvent->mMessage == eTouchStart) || 9648 (aEvent->mMessage == eTouchMove && aTouchIsNew) || 9649 (aEvent->mMessage == eTouchEnd); 9650 bool preventDefault = false; 9651 nsEventStatus tmpStatus = nsEventStatus_eIgnore; 9652 WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent(); 9653 9654 // loop over all touches and dispatch events on any that have changed 9655 for (dom::Touch* touch : touchEvent->mTouches) { 9656 // We should remove all suppressed touch instances in 9657 // TouchManager::PreHandleEvent. 9658 MOZ_ASSERT(!touch->mIsTouchEventSuppressed); 9659 9660 if (!touch || !touch->mChanged) { 9661 continue; 9662 } 9663 9664 nsCOMPtr<EventTarget> targetPtr = touch->mTarget; 9665 nsCOMPtr<nsIContent> content = do_QueryInterface(targetPtr); 9666 if (!content) { 9667 continue; 9668 } 9669 9670 Document* doc = content->OwnerDoc(); 9671 nsIContent* capturingContent = PresShell::GetCapturingContent(); 9672 if (capturingContent) { 9673 if (capturingContent->OwnerDoc() != doc) { 9674 // Wrong document, don't dispatch anything. 9675 continue; 9676 } 9677 content = capturingContent; 9678 } 9679 // copy the event 9680 MOZ_ASSERT(touchEvent->IsTrusted()); 9681 WidgetTouchEvent newEvent(true, touchEvent->mMessage, touchEvent->mWidget); 9682 newEvent.AssignTouchEventData(*touchEvent, false); 9683 newEvent.mTarget = targetPtr; 9684 newEvent.mFlags.mHandledByAPZ = touchEvent->mFlags.mHandledByAPZ; 9685 9686 RefPtr<PresShell> contentPresShell; 9687 if (doc == GetDocument()) { 9688 contentPresShell = doc->GetPresShell(); 9689 if (contentPresShell) { 9690 // XXXsmaug huge hack. Pushing possibly capturing content, 9691 // even though event target is something else. 9692 contentPresShell->PushCurrentEventInfo(EventTargetInfo( 9693 newEvent.mMessage, content->GetPrimaryFrame(), content)); 9694 } 9695 } 9696 9697 RefPtr<nsPresContext> presContext = doc->GetPresContext(); 9698 if (!presContext) { 9699 if (contentPresShell) { 9700 contentPresShell->PopCurrentEventInfo(); 9701 } 9702 continue; 9703 } 9704 9705 tmpStatus = nsEventStatus_eIgnore; 9706 EventDispatcher::Dispatch(targetPtr, presContext, &newEvent, nullptr, 9707 &tmpStatus, aEventCB); 9708 if (nsEventStatus_eConsumeNoDefault == tmpStatus || 9709 newEvent.mFlags.mMultipleActionsPrevented) { 9710 preventDefault = true; 9711 } 9712 9713 if (newEvent.mFlags.mMultipleActionsPrevented) { 9714 touchEvent->mFlags.mMultipleActionsPrevented = true; 9715 } 9716 9717 if (contentPresShell) { 9718 contentPresShell->PopCurrentEventInfo(); 9719 } 9720 } 9721 9722 if (preventDefault && canPrevent) { 9723 *aEventStatus = nsEventStatus_eConsumeNoDefault; 9724 } else { 9725 *aEventStatus = nsEventStatus_eIgnore; 9726 } 9727 } 9728 9729 // Dispatch event to content only (NOT full processing) 9730 // See also HandleEventWithTarget which does full event processing. 9731 nsresult PresShell::HandleDOMEventWithTarget(nsIContent* aTargetContent, 9732 WidgetEvent* aEvent, 9733 nsEventStatus* aStatus) { 9734 nsresult rv = NS_OK; 9735 9736 PushCurrentEventInfo( 9737 EventTargetInfo(aEvent->mMessage, nullptr, aTargetContent)); 9738 9739 // Bug 41013: Check if the event should be dispatched to content. 9740 // It's possible that we are in the middle of destroying the window 9741 // and the js context is out of date. This check detects the case 9742 // that caused a crash in bug 41013, but there may be a better way 9743 // to handle this situation! 9744 nsCOMPtr<nsISupports> container = mPresContext->GetContainerWeak(); 9745 if (container) { 9746 // Dispatch event to content 9747 rv = EventDispatcher::Dispatch(aTargetContent, mPresContext, aEvent, 9748 nullptr, aStatus); 9749 } 9750 9751 PopCurrentEventInfo(); 9752 return rv; 9753 } 9754 9755 // See the method above. 9756 nsresult PresShell::HandleDOMEventWithTarget(nsIContent* aTargetContent, 9757 Event* aEvent, 9758 nsEventStatus* aStatus) { 9759 nsresult rv = NS_OK; 9760 9761 PushCurrentEventInfo(EventTargetInfo(aEvent->WidgetEventPtr()->mMessage, 9762 nullptr, aTargetContent)); 9763 nsCOMPtr<nsISupports> container = mPresContext->GetContainerWeak(); 9764 if (container) { 9765 rv = EventDispatcher::DispatchDOMEvent(aTargetContent, nullptr, aEvent, 9766 mPresContext, aStatus); 9767 } 9768 9769 PopCurrentEventInfo(); 9770 return rv; 9771 } 9772 9773 bool PresShell::EventHandler::AdjustContextMenuKeyEvent( 9774 WidgetMouseEvent* aMouseEvent) { 9775 // if a menu is open, open the context menu relative to the active item on the 9776 // menu. 9777 if (nsXULPopupManager* pm = nsXULPopupManager::GetInstance()) { 9778 nsIFrame* popupFrame = pm->GetTopPopup(widget::PopupType::Menu); 9779 if (popupFrame) { 9780 nsIFrame* itemFrame = (static_cast<nsMenuPopupFrame*>(popupFrame)) 9781 ->GetCurrentMenuItemFrame(); 9782 if (!itemFrame) { 9783 itemFrame = popupFrame; 9784 } 9785 9786 nsCOMPtr<nsIWidget> widget = popupFrame->GetNearestWidget(); 9787 aMouseEvent->mWidget = widget; 9788 LayoutDeviceIntPoint widgetPoint = widget->WidgetToScreenOffset(); 9789 aMouseEvent->mRefPoint = 9790 LayoutDeviceIntPoint::FromAppUnitsToNearest( 9791 itemFrame->GetScreenRectInAppUnits().BottomLeft(), 9792 itemFrame->PresContext()->AppUnitsPerDevPixel()) - 9793 widgetPoint; 9794 9795 mPresShell->mCurrentEventTarget.SetFrameAndContent( 9796 aMouseEvent->mMessage, itemFrame, 9797 itemFrame->GetContent() 9798 ? itemFrame->GetContent() 9799 ->GetInclusiveFlattenedTreeAncestorElement() 9800 : nullptr); 9801 9802 return true; 9803 } 9804 } 9805 9806 // If we're here because of the key-equiv for showing context menus, we 9807 // have to twiddle with the NS event to make sure the context menu comes 9808 // up in the upper left of the relevant content area before we create 9809 // the DOM event. Since we never call InitMouseEvent() on the event, 9810 // the client X/Y will be 0,0. We can make use of that if the widget is null. 9811 // Use the root widget since it's most likely to exist, and the coordinates 9812 // returned by GetCurrentItemAndPositionForElement are relative to it. 9813 nsRootPresContext* rootPC = GetPresContext()->GetRootPresContext(); 9814 aMouseEvent->mRefPoint = LayoutDeviceIntPoint(); 9815 if (rootPC) { 9816 aMouseEvent->mWidget = rootPC->PresShell()->GetRootWidget(); 9817 if (aMouseEvent->mWidget) { 9818 // default the refpoint to the topleft of our document 9819 if (nsIFrame* rootFrame = FrameConstructor()->GetRootFrame()) { 9820 auto frameToWidgetOffset = 9821 nsLayoutUtils::FrameToWidgetOffset(rootFrame, aMouseEvent->mWidget); 9822 MOZ_ASSERT(frameToWidgetOffset, "If rootPC has a widget, so should we"); 9823 if (frameToWidgetOffset) { 9824 aMouseEvent->mRefPoint = LayoutDeviceIntPoint::FromAppUnitsToNearest( 9825 *frameToWidgetOffset, GetPresContext()->AppUnitsPerDevPixel()); 9826 } 9827 } 9828 } 9829 } else { 9830 aMouseEvent->mWidget = nullptr; 9831 } 9832 9833 // see if we should use the caret position for the popup 9834 LayoutDeviceIntPoint caretPoint; 9835 // Beware! This may flush notifications via synchronous 9836 // ScrollSelectionIntoView. 9837 if (PrepareToUseCaretPosition(MOZ_KnownLive(aMouseEvent->mWidget), 9838 caretPoint)) { 9839 // caret position is good 9840 int32_t devPixelRatio = GetPresContext()->AppUnitsPerDevPixel(); 9841 caretPoint = LayoutDeviceIntPoint::FromAppUnitsToNearest( 9842 ViewportUtils::LayoutToVisual( 9843 LayoutDeviceIntPoint::ToAppUnits(caretPoint, devPixelRatio), 9844 GetPresContext()->PresShell()), 9845 devPixelRatio); 9846 aMouseEvent->mRefPoint = caretPoint; 9847 return true; 9848 } 9849 9850 // If we're here because of the key-equiv for showing context menus, we 9851 // have to reset the event target to the currently focused element. Get it 9852 // from the focus controller. 9853 RefPtr<Element> currentFocus = nsFocusManager::GetFocusedElementStatic(); 9854 9855 // Reset event coordinates relative to focused frame in view 9856 if (currentFocus) { 9857 nsCOMPtr<nsIContent> currentPointElement; 9858 GetCurrentItemAndPositionForElement( 9859 currentFocus, getter_AddRefs(currentPointElement), 9860 aMouseEvent->mRefPoint, MOZ_KnownLive(aMouseEvent->mWidget)); 9861 if (currentPointElement) { 9862 mPresShell->mCurrentEventTarget.SetFrameAndContent( 9863 aMouseEvent->mMessage, nullptr, currentPointElement); 9864 mPresShell->GetCurrentEventFrame(); 9865 } 9866 } 9867 9868 return true; 9869 } 9870 9871 // PresShell::EventHandler::PrepareToUseCaretPosition 9872 // 9873 // This checks to see if we should use the caret position for popup context 9874 // menus. Returns true if the caret position should be used, and the 9875 // coordinates of that position is returned in aTargetPt. This function 9876 // will also scroll the window as needed to make the caret visible. 9877 // 9878 // The event widget should be the widget that generated the event, and 9879 // whose coordinate system the resulting event's mRefPoint should be 9880 // relative to. The returned point is in device pixels realtive to the 9881 // widget passed in. 9882 bool PresShell::EventHandler::PrepareToUseCaretPosition( 9883 nsIWidget* aEventWidget, LayoutDeviceIntPoint& aTargetPt) { 9884 nsresult rv; 9885 9886 // check caret visibility 9887 RefPtr<nsCaret> caret = mPresShell->GetCaret(); 9888 NS_ENSURE_TRUE(caret, false); 9889 9890 bool caretVisible = caret->IsVisible(); 9891 if (!caretVisible) { 9892 return false; 9893 } 9894 9895 // caret selection, this is a temporary weak reference, so no refcounting is 9896 // needed 9897 Selection* domSelection = caret->GetSelection(); 9898 NS_ENSURE_TRUE(domSelection, false); 9899 9900 // since the match could be an anonymous textnode inside a 9901 // <textarea> or text <input>, we need to get the outer frame 9902 // note: frames are not refcounted 9903 nsIFrame* frame = nullptr; // may be nullptr 9904 nsINode* node = domSelection->GetFocusNode(); 9905 NS_ENSURE_TRUE(node, false); 9906 nsCOMPtr<nsIContent> content = nsIContent::FromNode(node); 9907 if (content) { 9908 nsIContent* nonNative = content->FindFirstNonChromeOnlyAccessContent(); 9909 content = nonNative; 9910 } 9911 9912 if (content) { 9913 // It seems like ScrollSelectionIntoView should be enough, but it's 9914 // not. The problem is that scrolling the selection into view when it is 9915 // below the current viewport will align the top line of the frame exactly 9916 // with the bottom of the window. This is fine, BUT, the popup event causes 9917 // the control to be re-focused which does this exact call to 9918 // ScrollContentIntoView, which has a one-pixel disagreement of whether the 9919 // frame is actually in view. The result is that the frame is aligned with 9920 // the top of the window, but the menu is still at the bottom. 9921 // 9922 // Doing this call first forces the frame to be in view, eliminating the 9923 // problem. The only difference in the result is that if your cursor is in 9924 // an edit box below the current view, you'll get the edit box aligned with 9925 // the top of the window. This is arguably better behavior anyway. 9926 rv = MOZ_KnownLive(mPresShell) 9927 ->ScrollContentIntoView( 9928 content, 9929 ScrollAxis(WhereToScroll::Nearest, WhenToScroll::IfNotVisible), 9930 ScrollAxis(WhereToScroll::Nearest, WhenToScroll::IfNotVisible), 9931 ScrollFlags::ScrollOverflowHidden); 9932 NS_ENSURE_SUCCESS(rv, false); 9933 frame = content->GetPrimaryFrame(); 9934 NS_WARNING_ASSERTION(frame, "No frame for focused content?"); 9935 } 9936 9937 // Actually scroll the selection (ie caret) into view. Note that this must 9938 // be synchronous since we will be checking the caret position on the screen. 9939 // 9940 // Be easy about errors, and just don't scroll in those cases. Better to have 9941 // the correct menu at a weird place than the wrong menu. 9942 // After ScrollSelectionIntoView(), the pending notifications might be 9943 // flushed and PresShell/PresContext/Frames may be dead. See bug 418470. 9944 const nsCOMPtr<nsISelectionController> selCon = 9945 frame ? frame->GetSelectionController() 9946 : static_cast<nsISelectionController*>(mPresShell); 9947 if (selCon) { 9948 rv = selCon->ScrollSelectionIntoView( 9949 SelectionType::eNormal, nsISelectionController::SELECTION_FOCUS_REGION, 9950 SelectionScrollMode::SyncFlush); 9951 NS_ENSURE_SUCCESS(rv, false); 9952 } 9953 9954 nsPresContext* presContext = GetPresContext(); 9955 9956 if (!aEventWidget) { 9957 return false; 9958 } 9959 // get caret position relative to the closest widget 9960 nsRect caretCoords; 9961 nsIFrame* caretFrame = caret->GetGeometry(&caretCoords); 9962 if (!caretFrame) { 9963 return false; 9964 } 9965 9966 if (aEventWidget) { 9967 if (auto offset = 9968 nsLayoutUtils::FrameToWidgetOffset(caretFrame, aEventWidget)) { 9969 caretCoords.MoveBy(*offset); 9970 } 9971 } 9972 9973 // caret coordinates are in app units, convert to pixels 9974 aTargetPt.x = 9975 presContext->AppUnitsToDevPixels(caretCoords.x + caretCoords.width); 9976 aTargetPt.y = 9977 presContext->AppUnitsToDevPixels(caretCoords.y + caretCoords.height); 9978 9979 // make sure rounding doesn't return a pixel which is outside the caret 9980 // (e.g. one line lower) 9981 aTargetPt.y -= 1; 9982 9983 return true; 9984 } 9985 9986 void PresShell::EventHandler::GetCurrentItemAndPositionForElement( 9987 Element* aFocusedElement, nsIContent** aTargetToUse, 9988 LayoutDeviceIntPoint& aTargetPt, nsIWidget* aRootWidget) { 9989 nsCOMPtr<nsIContent> focusedContent = aFocusedElement; 9990 MOZ_KnownLive(mPresShell) 9991 ->ScrollContentIntoView(focusedContent, ScrollAxis(), ScrollAxis(), 9992 ScrollFlags::ScrollOverflowHidden); 9993 9994 nsPresContext* presContext = GetPresContext(); 9995 9996 bool istree = false, checkLineHeight = true; 9997 nscoord extraTreeY = 0; 9998 9999 // Set the position to just underneath the current item for multi-select 10000 // lists or just underneath the selected item for single-select lists. If 10001 // the element is not a list, or there is no selection, leave the position 10002 // as is. 10003 nsCOMPtr<Element> item; 10004 nsCOMPtr<nsIDOMXULMultiSelectControlElement> multiSelect = 10005 aFocusedElement->AsXULMultiSelectControl(); 10006 if (multiSelect) { 10007 checkLineHeight = false; 10008 10009 int32_t currentIndex; 10010 multiSelect->GetCurrentIndex(¤tIndex); 10011 if (currentIndex >= 0) { 10012 RefPtr<XULTreeElement> tree = XULTreeElement::FromNode(focusedContent); 10013 // Tree view special case (tree items have no frames) 10014 // Get the focused row and add its coordinates, which are already in 10015 // pixels 10016 // XXX Boris, should we create a new interface so that this doesn't 10017 // need to know about trees? Something like nsINodelessChildCreator 10018 // which could provide the current focus coordinates? 10019 if (tree) { 10020 tree->EnsureRowIsVisible(currentIndex); 10021 int32_t firstVisibleRow = tree->GetFirstVisibleRow(); 10022 int32_t rowHeight = tree->RowHeight(); 10023 10024 extraTreeY += nsPresContext::CSSPixelsToAppUnits( 10025 (currentIndex - firstVisibleRow + 1) * rowHeight); 10026 istree = true; 10027 10028 RefPtr<nsTreeColumns> cols = tree->GetColumns(); 10029 if (cols) { 10030 nsTreeColumn* col = cols->GetFirstColumn(); 10031 if (col) { 10032 RefPtr<Element> colElement = col->Element(); 10033 nsIFrame* frame = colElement->GetPrimaryFrame(); 10034 if (frame) { 10035 extraTreeY += frame->GetSize().height; 10036 } 10037 } 10038 } 10039 } else { 10040 multiSelect->GetCurrentItem(getter_AddRefs(item)); 10041 } 10042 } 10043 } else { 10044 // don't check menulists as the selected item will be inside a popup. 10045 nsCOMPtr<nsIDOMXULMenuListElement> menulist = 10046 aFocusedElement->AsXULMenuList(); 10047 if (!menulist) { 10048 nsCOMPtr<nsIDOMXULSelectControlElement> select = 10049 aFocusedElement->AsXULSelectControl(); 10050 if (select) { 10051 checkLineHeight = false; 10052 select->GetSelectedItem(getter_AddRefs(item)); 10053 } 10054 } 10055 } 10056 10057 if (item) { 10058 focusedContent = item; 10059 } 10060 10061 if (nsIFrame* frame = focusedContent->GetPrimaryFrame()) { 10062 NS_ASSERTION( 10063 frame->PresContext() == GetPresContext(), 10064 "handling event for focused content that is not in our document?"); 10065 10066 nsPoint widgetOffset; 10067 if (aRootWidget) { 10068 if (auto offset = 10069 nsLayoutUtils::FrameToWidgetOffset(frame, aRootWidget)) { 10070 widgetOffset = *offset; 10071 } 10072 } 10073 10074 // Start context menu down and to the right from top left of frame 10075 // use the lineheight. This is a good distance to move the context 10076 // menu away from the top left corner of the frame. If we always 10077 // used the frame height, the context menu could end up far away, 10078 // for example when we're focused on linked images. 10079 // On the other hand, we want to use the frame height if it's less 10080 // than the current line height, so that the context menu appears 10081 // associated with the correct frame. 10082 nscoord extra = 0; 10083 if (!istree) { 10084 extra = frame->GetSize().height; 10085 if (checkLineHeight) { 10086 ScrollContainerFrame* scrollContainerFrame = 10087 nsLayoutUtils::GetNearestScrollContainerFrame( 10088 frame, nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN | 10089 nsLayoutUtils::SCROLLABLE_FIXEDPOS_FINDS_ROOT); 10090 if (scrollContainerFrame) { 10091 nsSize scrollAmount = scrollContainerFrame->GetLineScrollAmount(); 10092 int32_t APD = presContext->AppUnitsPerDevPixel(); 10093 int32_t scrollAPD = 10094 scrollContainerFrame->PresContext()->AppUnitsPerDevPixel(); 10095 scrollAmount = scrollAmount.ScaleToOtherAppUnits(scrollAPD, APD); 10096 if (extra > scrollAmount.height) { 10097 extra = scrollAmount.height; 10098 } 10099 } 10100 } 10101 } 10102 10103 aTargetPt.x = presContext->AppUnitsToDevPixels(widgetOffset.x); 10104 aTargetPt.y = 10105 presContext->AppUnitsToDevPixels(widgetOffset.y + extra + extraTreeY); 10106 } 10107 10108 NS_IF_ADDREF(*aTargetToUse = focusedContent); 10109 } 10110 10111 bool PresShell::ShouldIgnoreInvalidation() { 10112 return mPaintingSuppressed || !mIsActive || mIsNeverPainting; 10113 } 10114 10115 void PresShell::WillPaint() { 10116 // Check the simplest things first. In particular, it's important to 10117 // check mIsActive before making any of the more expensive calls such 10118 // as GetRootPresContext, for the case of a browser with a large 10119 // number of tabs. 10120 // Don't bother doing anything if we still have painting suppressed or we are 10121 // not active. 10122 if (!mIsActive || mPaintingSuppressed || !IsVisible()) { 10123 return; 10124 } 10125 10126 nsRootPresContext* rootPresContext = mPresContext->GetRootPresContext(); 10127 if (!rootPresContext) { 10128 // In some edge cases, such as when we don't have a root frame yet, 10129 // we can't find the root prescontext. There's nothing to do in that 10130 // case. 10131 return; 10132 } 10133 10134 rootPresContext->FlushWillPaintObservers(); 10135 if (mIsDestroying) { 10136 return; 10137 } 10138 10139 // Process reflows, if we have them, to reduce flicker due to invalidates and 10140 // reflow being interspersed. Note that we _do_ allow this to be 10141 // interruptible; if we can't do all the reflows it's better to flicker a bit 10142 // than to freeze up. 10143 FlushPendingNotifications(ChangesToFlush(FlushType::InterruptibleLayout, 10144 /* aFlushAnimations = */ false, 10145 /* aUpdateRelevancy = */ false)); 10146 if (mIsDestroying) { 10147 return; 10148 } 10149 mDocument->EnumerateSubDocuments( 10150 [](Document& aSubdoc) MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA { 10151 if (RefPtr ps = aSubdoc.GetPresShell()) { 10152 if (!ps->IsUnderHiddenEmbedderElement()) { 10153 ps->WillPaint(); 10154 } 10155 } 10156 return CallState::Continue; 10157 }); 10158 } 10159 10160 void PresShell::DidPaintWindow() { 10161 if (mHasReceivedPaintMessage) { 10162 return; 10163 } 10164 mHasReceivedPaintMessage = true; 10165 nsPIDOMWindowOuter* win = mDocument->GetWindow(); 10166 if (!win || !nsGlobalWindowOuter::Cast(win)->IsChromeWindow()) { 10167 return; 10168 } 10169 if (nsCOMPtr<nsIObserverService> obsvc = services::GetObserverService()) { 10170 obsvc->NotifyObservers(win, "widget-first-paint", nullptr); 10171 } 10172 } 10173 10174 nsSubDocumentFrame* PresShell::GetInProcessEmbedderFrame() const { 10175 nsIFrame* f = mEmbedderFrame.GetFrame(); 10176 MOZ_ASSERT_IF(f, f->IsSubDocumentFrame()); 10177 return static_cast<nsSubDocumentFrame*>(f); 10178 } 10179 10180 bool PresShell::IsVisible() const { 10181 return mIsActive && !IsUnderHiddenEmbedderElement(); 10182 } 10183 10184 void PresShell::SuppressDisplayport(bool aEnabled) { 10185 if (aEnabled) { 10186 mActiveSuppressDisplayport++; 10187 } else if (mActiveSuppressDisplayport > 0) { 10188 bool isSuppressed = IsDisplayportSuppressed(); 10189 mActiveSuppressDisplayport--; 10190 if (isSuppressed && !IsDisplayportSuppressed()) { 10191 // We unsuppressed the displayport, trigger a paint 10192 if (nsIFrame* rootFrame = mFrameConstructor->GetRootFrame()) { 10193 rootFrame->SchedulePaint(); 10194 } 10195 } 10196 } 10197 } 10198 10199 static bool sDisplayPortSuppressionRespected = true; 10200 10201 void PresShell::RespectDisplayportSuppression(bool aEnabled) { 10202 bool isSuppressed = IsDisplayportSuppressed(); 10203 sDisplayPortSuppressionRespected = aEnabled; 10204 if (isSuppressed && !IsDisplayportSuppressed()) { 10205 // We unsuppressed the displayport, trigger a paint 10206 if (nsIFrame* rootFrame = mFrameConstructor->GetRootFrame()) { 10207 rootFrame->SchedulePaint(); 10208 } 10209 } 10210 } 10211 10212 bool PresShell::IsDisplayportSuppressed() { 10213 return sDisplayPortSuppressionRespected && mActiveSuppressDisplayport > 0; 10214 } 10215 10216 static CallState FreezeSubDocument(Document& aDocument) { 10217 if (PresShell* presShell = aDocument.GetPresShell()) { 10218 presShell->Freeze(); 10219 } 10220 return CallState::Continue; 10221 } 10222 10223 void PresShell::Freeze(bool aIncludeSubDocuments) { 10224 mUpdateApproximateFrameVisibilityEvent.Revoke(); 10225 10226 MaybeReleaseCapturingContent(); 10227 10228 if (mCaret) { 10229 SetCaretEnabled(false); 10230 } 10231 10232 mPaintingSuppressed = true; 10233 10234 if (aIncludeSubDocuments && mDocument) { 10235 mDocument->EnumerateSubDocuments(FreezeSubDocument); 10236 } 10237 10238 nsPresContext* presContext = GetPresContext(); 10239 if (presContext) { 10240 presContext->DisableInteractionTimeRecording(); 10241 if (presContext->RefreshDriver()->GetPresContext() == presContext) { 10242 presContext->RefreshDriver()->Freeze(); 10243 } 10244 10245 if (nsPresContext* rootPresContext = presContext->GetRootPresContext()) { 10246 rootPresContext->ResetUserInputEventsAllowed(); 10247 } 10248 } 10249 10250 mFrozen = true; 10251 if (mDocument) { 10252 UpdateImageLockingState(); 10253 } 10254 } 10255 10256 void PresShell::FireOrClearDelayedEvents(bool aFireEvents) { 10257 mNoDelayedMouseEvents = false; 10258 mNoDelayedKeyEvents = false; 10259 mNoDelayedSingleTap = false; 10260 if (!aFireEvents) { 10261 mDelayedEvents.Clear(); 10262 return; 10263 } 10264 10265 if (mDocument) { 10266 RefPtr<Document> doc = mDocument; 10267 while (!mIsDestroying && mDelayedEvents.Length() && 10268 !doc->EventHandlingSuppressed()) { 10269 UniquePtr<DelayedEvent> ev = std::move(mDelayedEvents[0]); 10270 mDelayedEvents.RemoveElementAt(0); 10271 if (ev->IsKeyPressEvent() && mIsLastKeyDownCanceled) { 10272 continue; 10273 } 10274 ev->Dispatch(); 10275 } 10276 if (!doc->EventHandlingSuppressed()) { 10277 mDelayedEvents.Clear(); 10278 } 10279 } 10280 } 10281 10282 void PresShell::Thaw(bool aIncludeSubDocuments) { 10283 nsPresContext* presContext = GetPresContext(); 10284 if (presContext && 10285 presContext->RefreshDriver()->GetPresContext() == presContext) { 10286 presContext->RefreshDriver()->Thaw(); 10287 } 10288 10289 if (aIncludeSubDocuments && mDocument) { 10290 mDocument->EnumerateSubDocuments([](Document& aSubDoc) { 10291 if (PresShell* presShell = aSubDoc.GetPresShell()) { 10292 presShell->Thaw(); 10293 } 10294 return CallState::Continue; 10295 }); 10296 } 10297 10298 // Get the activeness of our presshell, as this might have changed 10299 // while we were in the bfcache 10300 ActivenessMaybeChanged(); 10301 10302 // We're now unfrozen 10303 mFrozen = false; 10304 UpdateImageLockingState(); 10305 10306 UnsuppressPainting(); 10307 10308 // In case the above UnsuppressPainting call didn't start the 10309 // refresh driver, we manually start the refresh driver to 10310 // ensure nsPresContext::MaybeIncreaseMeasuredTicksSinceLoading 10311 // can be called for user input events handling. 10312 if (presContext && presContext->IsRoot()) { 10313 if (!presContext->RefreshDriver()->HasPendingTick()) { 10314 presContext->RefreshDriver()->InitializeTimer(); 10315 } 10316 } 10317 } 10318 10319 //-------------------------------------------------------- 10320 // Start of protected and private methods on the PresShell 10321 //-------------------------------------------------------- 10322 10323 void PresShell::WillCauseReflow() { 10324 nsContentUtils::AddScriptBlocker(); 10325 ++mChangeNestCount; 10326 } 10327 10328 void PresShell::DidCauseReflow() { 10329 NS_ASSERTION(mChangeNestCount != 0, "Unexpected call to DidCauseReflow()"); 10330 --mChangeNestCount; 10331 nsContentUtils::RemoveScriptBlocker(); 10332 } 10333 10334 void PresShell::WillDoReflow() { 10335 mDocument->FlushUserFontSet(); 10336 10337 mPresContext->FlushCounterStyles(); 10338 10339 mPresContext->FlushFontFeatureValues(); 10340 10341 mPresContext->FlushFontPaletteValues(); 10342 10343 mLastReflowStart = GetPerformanceNowUnclamped(); 10344 } 10345 10346 void PresShell::DidDoReflow(bool aInterruptible) { 10347 MOZ_ASSERT(mPendingDidDoReflow); 10348 if (!nsContentUtils::IsSafeToRunScript()) { 10349 // If we're reflowing while script-blocked (e.g. from container query 10350 // updates), defer our reflow callbacks until the end of our next layout 10351 // flush. 10352 SetNeedLayoutFlush(); 10353 return; 10354 } 10355 10356 auto clearPendingDidDoReflow = 10357 MakeScopeExit([&] { mPendingDidDoReflow = false; }); 10358 10359 mHiddenContentInForcedLayout.Clear(); 10360 10361 HandlePostedReflowCallbacks(aInterruptible); 10362 10363 if (mIsDestroying) { 10364 return; 10365 } 10366 10367 { 10368 nsAutoScriptBlocker scriptBlocker; 10369 AutoAssertNoFlush noReentrantFlush(*this); 10370 if (nsCOMPtr<nsIDocShell> docShell = mPresContext->GetDocShell()) { 10371 DOMHighResTimeStamp now = GetPerformanceNowUnclamped(); 10372 docShell->NotifyReflowObservers(aInterruptible, mLastReflowStart, now); 10373 } 10374 10375 SynthesizeMouseMove(false); 10376 10377 mPresContext->NotifyMissingFonts(); 10378 } 10379 10380 if (mIsDestroying) { 10381 return; 10382 } 10383 10384 if (mDirtyRoots.IsEmpty()) { 10385 // We only unsuppress painting if we're out of reflows. It's pointless to 10386 // do so if reflows are still pending, since reflows are just going to 10387 // thrash the frames around some more. By waiting we avoid an overeager 10388 // "jitter" effect. 10389 if (mShouldUnsuppressPainting) { 10390 mShouldUnsuppressPainting = false; 10391 UnsuppressAndInvalidate(); 10392 } 10393 } else { 10394 // If any new reflow commands were enqueued during the reflow (or we didn't 10395 // reflow everything because we were interrupted), schedule another reflow 10396 // event to process them. 10397 // 10398 // Note that we want to do this after DidDoReflow(), since that method can 10399 // change whether there are dirty roots around by flushing, and there's no 10400 // point in posting a reflow event just to have the flush revoke it. 10401 EnsureLayoutFlush(); 10402 } 10403 } 10404 10405 DOMHighResTimeStamp PresShell::GetPerformanceNowUnclamped() { 10406 DOMHighResTimeStamp now = 0; 10407 10408 if (nsPIDOMWindowInner* window = mDocument->GetInnerWindow()) { 10409 Performance* perf = window->GetPerformance(); 10410 10411 if (perf) { 10412 now = perf->NowUnclamped(); 10413 } 10414 } 10415 10416 return now; 10417 } 10418 10419 bool PresShell::DoReflow(nsIFrame* target, bool aInterruptible, 10420 OverflowChangedTracker* aOverflowTracker) { 10421 nsIURI* uri = mDocument->GetDocumentURI(); 10422 AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING_RELEVANT_FOR_JS( 10423 "Reflow", LAYOUT_Reflow, uri ? uri->GetSpecOrDefault() : "N/A"_ns); 10424 10425 PerfStats::AutoMetricRecording<PerfStats::Metric::Reflowing> autoRecording; 10426 10427 gfxTextPerfMetrics* tp = mPresContext->GetTextPerfMetrics(); 10428 TimeStamp timeStart; 10429 if (tp) { 10430 tp->Accumulate(); 10431 tp->reflowCount++; 10432 timeStart = TimeStamp::Now(); 10433 } 10434 10435 // set up a cache that saves all nodes contained in each selection, 10436 // allowing a fast lookup in `nsTextFrame::IsFrameSelected()`. 10437 // This cache only lives throughout this reflow call. 10438 SelectionNodeCache cache(*this); 10439 10440 // Schedule a paint, but don't actually mark this frame as changed for 10441 // retained DL building purposes. If any child frames get moved, then 10442 // they will schedule paint again. We could probaby skip this, and just 10443 // schedule a similar paint when a frame is deleted. 10444 target->SchedulePaint(nsIFrame::PAINT_DEFAULT, false); 10445 10446 Maybe<uint64_t> innerWindowID; 10447 if (auto* window = mDocument->GetInnerWindow()) { 10448 innerWindowID = Some(window->WindowID()); 10449 } 10450 AutoProfilerTracing tracingLayoutFlush( 10451 aInterruptible ? "Reflow (interruptible)" : "Reflow (sync)", 10452 geckoprofiler::category::LAYOUT, std::move(mReflowCause), innerWindowID); 10453 mReflowCause = nullptr; 10454 10455 FlushPendingScrollAnchorSelections(); 10456 10457 const bool isRoot = target == mFrameConstructor->GetRootFrame(); 10458 10459 MOZ_ASSERT(isRoot || aOverflowTracker, 10460 "caller must provide overflow tracker when reflowing " 10461 "non-root frames"); 10462 10463 // CreateReferenceRenderingContext can return nullptr 10464 UniquePtr<gfxContext> rcx(CreateReferenceRenderingContext()); 10465 10466 #ifdef DEBUG 10467 mCurrentReflowRoot = target; 10468 #endif 10469 10470 // If the target frame is the root of the frame hierarchy, then 10471 // use all the available space. If it's simply a `reflow root', 10472 // then use the target frame's size as the available space. 10473 WritingMode wm = target->GetWritingMode(); 10474 LogicalSize size(wm); 10475 if (isRoot) { 10476 size = LogicalSize(wm, mPresContext->GetVisibleArea().Size()); 10477 } else { 10478 size = target->GetLogicalSize(); 10479 } 10480 10481 OverflowAreas oldOverflow; // initialized and used only when !isRoot 10482 if (!isRoot) { 10483 oldOverflow = target->GetOverflowAreas(); 10484 } 10485 10486 NS_ASSERTION(!target->GetNextInFlow() && !target->GetPrevInFlow(), 10487 "reflow roots should never split"); 10488 10489 // Don't pass size directly to the reflow input, since a 10490 // constrained height implies page/column breaking. 10491 LogicalSize reflowSize(wm, size.ISize(wm), NS_UNCONSTRAINEDSIZE); 10492 ReflowInput reflowInput(mPresContext, target, rcx.get(), reflowSize, 10493 ReflowInput::InitFlag::CallerWillInit); 10494 10495 if (isRoot) { 10496 reflowInput.Init(mPresContext); 10497 } else { 10498 // Initialize reflow input with current used border and padding, 10499 // in case this was set specially by the parent frame when the reflow root 10500 // was reflowed by its parent. 10501 reflowInput.Init(mPresContext, Nothing(), 10502 Some(target->GetLogicalUsedBorder(wm)), 10503 Some(target->GetLogicalUsedPadding(wm))); 10504 } 10505 10506 // fix the computed height 10507 NS_ASSERTION(reflowInput.ComputedPhysicalMargin() == nsMargin(0, 0, 0, 0), 10508 "reflow input should not set margin for reflow roots"); 10509 if (size.BSize(wm) != NS_UNCONSTRAINEDSIZE) { 10510 nscoord computedBSize = 10511 size.BSize(wm) - 10512 reflowInput.ComputedLogicalBorderPadding(wm).BStartEnd(wm); 10513 computedBSize = std::max(computedBSize, 0); 10514 reflowInput.SetComputedBSize(computedBSize); 10515 } 10516 NS_ASSERTION( 10517 reflowInput.ComputedISize() == 10518 size.ISize(wm) - 10519 reflowInput.ComputedLogicalBorderPadding(wm).IStartEnd(wm), 10520 "reflow input computed incorrect inline size"); 10521 10522 mPresContext->ReflowStarted(aInterruptible); 10523 mIsReflowing = true; 10524 10525 nsReflowStatus status; 10526 ReflowOutput desiredSize(reflowInput); 10527 target->Reflow(mPresContext, desiredSize, reflowInput, status); 10528 10529 // If an incremental reflow is initiated at a frame other than the 10530 // root frame, then its desired size had better not change! If it's 10531 // initiated at the root, then the size better not change unless its 10532 // height was unconstrained to start with. 10533 nsRect boundsRelativeToTarget = 10534 nsRect(0, 0, desiredSize.Width(), desiredSize.Height()); 10535 const bool isBSizeLimitReflow = 10536 isRoot && size.BSize(wm) == NS_UNCONSTRAINEDSIZE; 10537 NS_ASSERTION(isBSizeLimitReflow || desiredSize.Size(wm) == size, 10538 "non-root frame's desired size changed during an " 10539 "incremental reflow"); 10540 NS_ASSERTION(status.IsEmpty(), "reflow roots should never split"); 10541 10542 target->SetSize(boundsRelativeToTarget.Size()); 10543 target->DidReflow(mPresContext, nullptr); 10544 if (target->IsInScrollAnchorChain()) { 10545 ScrollAnchorContainer* container = ScrollAnchorContainer::FindFor(target); 10546 PostPendingScrollAnchorAdjustment(container); 10547 } 10548 if (MOZ_UNLIKELY(isBSizeLimitReflow)) { 10549 mPresContext->SetVisibleArea(boundsRelativeToTarget); 10550 } 10551 10552 #ifdef DEBUG 10553 mCurrentReflowRoot = nullptr; 10554 #endif 10555 10556 if (!isRoot && oldOverflow != target->GetOverflowAreas()) { 10557 // The overflow area changed. Propagate this change to ancestors. 10558 aOverflowTracker->AddFrame(target->GetParent(), 10559 OverflowChangedTracker::CHILDREN_CHANGED); 10560 } 10561 10562 NS_ASSERTION( 10563 mPresContext->HasPendingInterrupt() || mFramesToDirty.Count() == 0, 10564 "Why do we need to dirty anything if not interrupted?"); 10565 10566 mIsReflowing = false; 10567 bool interrupted = mPresContext->HasPendingInterrupt(); 10568 if (interrupted) { 10569 // Make sure target gets reflowed again. 10570 for (const auto& key : mFramesToDirty) { 10571 // Mark frames dirty until target frame. 10572 for (nsIFrame* f = key; f && !f->IsSubtreeDirty(); f = f->GetParent()) { 10573 f->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN); 10574 if (f->IsFlexItem()) { 10575 nsFlexContainerFrame::MarkCachedFlexMeasurementsDirty(f); 10576 } 10577 10578 if (f == target) { 10579 break; 10580 } 10581 } 10582 } 10583 10584 NS_ASSERTION(target->IsSubtreeDirty(), "Why is the target not dirty?"); 10585 mDirtyRoots.Add(target); 10586 SetNeedLayoutFlush(); 10587 10588 // Clear mFramesToDirty after we've done the target->IsSubtreeDirty() 10589 // assertion so that if it fails it's easier to see what's going on. 10590 #ifdef NOISY_INTERRUPTIBLE_REFLOW 10591 printf("mFramesToDirty.Count() == %u\n", mFramesToDirty.Count()); 10592 #endif /* NOISY_INTERRUPTIBLE_REFLOW */ 10593 mFramesToDirty.Clear(); 10594 10595 // Any FlushPendingNotifications with interruptible reflows 10596 // should be suppressed now. We don't want to do extra reflow work 10597 // before our reflow event happens. 10598 mWasLastReflowInterrupted = true; 10599 EnsureLayoutFlush(); 10600 } 10601 10602 // dump text perf metrics for reflows with significant text processing 10603 if (tp) { 10604 if (tp->current.numChars > 100) { 10605 TimeDuration reflowTime = TimeStamp::Now() - timeStart; 10606 LogTextPerfStats(tp, this, tp->current, reflowTime.ToMilliseconds(), 10607 eLog_reflow, nullptr); 10608 } 10609 tp->Accumulate(); 10610 } 10611 10612 return !interrupted; 10613 } 10614 10615 // used with Telemetry metrics 10616 #define NS_LONG_REFLOW_TIME_MS 5000 10617 10618 bool PresShell::ProcessReflowCommands(bool aInterruptible) { 10619 if (mDirtyRoots.IsEmpty() && !mShouldUnsuppressPainting && 10620 !mPendingDidDoReflow) { 10621 // Nothing to do; bail out 10622 return true; 10623 } 10624 10625 const bool wasProcessingReflowCommands = mProcessingReflowCommands; 10626 auto restoreProcessingReflowCommands = MakeScopeExit( 10627 [&] { mProcessingReflowCommands = wasProcessingReflowCommands; }); 10628 mProcessingReflowCommands = true; 10629 10630 auto timerStart = mozilla::TimeStamp::Now(); 10631 bool interrupted = false; 10632 if (!mDirtyRoots.IsEmpty()) { 10633 // If reflow is interruptible, then make a note of our deadline. 10634 const PRIntervalTime deadline = 10635 aInterruptible 10636 ? PR_IntervalNow() + PR_MicrosecondsToInterval(gMaxRCProcessingTime) 10637 : (PRIntervalTime)0; 10638 10639 // Scope for the reflow entry point 10640 nsAutoScriptBlocker scriptBlocker; 10641 WillDoReflow(); 10642 AUTO_LAYOUT_PHASE_ENTRY_POINT(GetPresContext(), Reflow); 10643 10644 OverflowChangedTracker overflowTracker; 10645 10646 do { 10647 // Send an incremental reflow notification to the target frame. 10648 nsIFrame* target = mDirtyRoots.PopShallowestRoot(); 10649 10650 if (!target->IsSubtreeDirty()) { 10651 // It's not dirty anymore, which probably means the notification 10652 // was posted in the middle of a reflow (perhaps with a reflow 10653 // root in the middle). Don't do anything. 10654 continue; 10655 } 10656 10657 interrupted = !DoReflow(target, aInterruptible, &overflowTracker); 10658 10659 // Keep going until we're out of reflow commands, or we've run 10660 // past our deadline, or we're interrupted. 10661 } while (!interrupted && !mDirtyRoots.IsEmpty() && 10662 (!aInterruptible || PR_IntervalNow() < deadline)); 10663 10664 interrupted = !mDirtyRoots.IsEmpty(); 10665 10666 overflowTracker.Flush(); 10667 10668 if (!interrupted) { 10669 // We didn't get interrupted. Go ahead and perform scroll anchor 10670 // adjustments. 10671 FlushPendingScrollAnchorAdjustments(); 10672 } 10673 mPendingDidDoReflow = true; 10674 } 10675 10676 // Exiting the scriptblocker might have killed us. If we were processing 10677 // scroll commands, let the outermost call deal with it. 10678 if (!mIsDestroying && mPendingDidDoReflow && !wasProcessingReflowCommands) { 10679 DidDoReflow(aInterruptible); 10680 } 10681 10682 { 10683 TimeDuration elapsed = TimeStamp::Now() - timerStart; 10684 int32_t intElapsed = int32_t(elapsed.ToMilliseconds()); 10685 if (intElapsed > NS_LONG_REFLOW_TIME_MS) { 10686 glean::layout::long_reflow_interruptible 10687 .EnumGet(aInterruptible 10688 ? glean::layout::LongReflowInterruptibleLabel::eTrue 10689 : glean::layout::LongReflowInterruptibleLabel::eFalse) 10690 .Add(); 10691 } 10692 } 10693 10694 return !interrupted; 10695 } 10696 10697 bool PresShell::DoFlushLayout(bool aInterruptible) { 10698 mFrameConstructor->RecalcQuotesAndCounters(); 10699 return ProcessReflowCommands(aInterruptible); 10700 } 10701 10702 void PresShell::WindowSizeMoveDone() { 10703 if (mPresContext) { 10704 EventStateManager::ClearGlobalActiveContent(nullptr); 10705 ClearMouseCapture(); 10706 } 10707 } 10708 10709 NS_IMETHODIMP 10710 PresShell::Observe(nsISupports* aSubject, const char* aTopic, 10711 const char16_t* aData) { 10712 if (mIsDestroying) { 10713 NS_WARNING("our observers should have been unregistered by now"); 10714 return NS_OK; 10715 } 10716 10717 if (!nsCRT::strcmp(aTopic, "memory-pressure")) { 10718 if (!AssumeAllFramesVisible() && 10719 mPresContext->IsRootContentDocumentInProcess()) { 10720 DoUpdateApproximateFrameVisibility(/* aRemoveOnly = */ true); 10721 } 10722 return NS_OK; 10723 } 10724 10725 if (!nsCRT::strcmp(aTopic, NS_WIDGET_WAKE_OBSERVER_TOPIC)) { 10726 mLastOSWake = TimeStamp::Now(); 10727 return NS_OK; 10728 } 10729 10730 // For parent process, user may expect the UI is interactable after a 10731 // tab (previously opened page or home page) has restored. 10732 if (!nsCRT::strcmp(aTopic, "sessionstore-one-or-no-tab-restored")) { 10733 MOZ_ASSERT(XRE_IsParentProcess()); 10734 sProcessInteractable = true; 10735 10736 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); 10737 if (os) { 10738 os->RemoveObserver(this, "sessionstore-one-or-no-tab-restored"); 10739 } 10740 return NS_OK; 10741 } 10742 10743 if (!nsCRT::strcmp(aTopic, "font-info-updated")) { 10744 // See how gfxPlatform::ForceGlobalReflow encodes this. 10745 bool needsReframe = aData && !!aData[0]; 10746 mPresContext->ForceReflowForFontInfoUpdate(needsReframe); 10747 return NS_OK; 10748 } 10749 10750 // The "look-and-feel-changed" notification for JS observers will be 10751 // dispatched HandleGlobalThemeChange once LookAndFeel caches are cleared. 10752 if (!nsCRT::strcmp(aTopic, "internal-look-and-feel-changed")) { 10753 // See how LookAndFeel::NotifyChangedAllWindows encodes this. 10754 auto kind = widget::ThemeChangeKind(aData[0]); 10755 mPresContext->ThemeChanged(kind); 10756 return NS_OK; 10757 } 10758 10759 NS_WARNING(nsPrintfCString("unrecognized topic %s", aTopic).get()); 10760 return NS_ERROR_FAILURE; 10761 } 10762 10763 bool PresShell::AddRefreshObserver(nsARefreshObserver* aObserver, 10764 FlushType aFlushType, 10765 const char* aObserverDescription) { 10766 nsPresContext* presContext = GetPresContext(); 10767 if (MOZ_UNLIKELY(!presContext)) { 10768 return false; 10769 } 10770 presContext->RefreshDriver()->AddRefreshObserver(aObserver, aFlushType, 10771 aObserverDescription); 10772 return true; 10773 } 10774 10775 bool PresShell::RemoveRefreshObserver(nsARefreshObserver* aObserver, 10776 FlushType aFlushType) { 10777 nsPresContext* presContext = GetPresContext(); 10778 return presContext && presContext->RefreshDriver()->RemoveRefreshObserver( 10779 aObserver, aFlushType); 10780 } 10781 10782 bool PresShell::AddPostRefreshObserver(nsAPostRefreshObserver* aObserver) { 10783 nsPresContext* presContext = GetPresContext(); 10784 if (!presContext) { 10785 return false; 10786 } 10787 presContext->RefreshDriver()->AddPostRefreshObserver(aObserver); 10788 return true; 10789 } 10790 10791 bool PresShell::RemovePostRefreshObserver(nsAPostRefreshObserver* aObserver) { 10792 nsPresContext* presContext = GetPresContext(); 10793 if (!presContext) { 10794 return false; 10795 } 10796 presContext->RefreshDriver()->RemovePostRefreshObserver(aObserver); 10797 return true; 10798 } 10799 10800 void PresShell::ScheduleFlush() { 10801 if (MOZ_UNLIKELY(IsDestroying()) || 10802 MOZ_UNLIKELY(mDocument->GetBFCacheEntry())) { 10803 return; 10804 } 10805 mPresContext->RefreshDriver()->ScheduleRenderingPhase(RenderingPhase::Layout); 10806 } 10807 10808 //------------------------------------------------------ 10809 // End of protected and private methods on the PresShell 10810 //------------------------------------------------------ 10811 10812 //------------------------------------------------------------------ 10813 //-- Delayed event Classes Impls 10814 //------------------------------------------------------------------ 10815 10816 PresShell::DelayedInputEvent::DelayedInputEvent() 10817 : DelayedEvent(), mEvent(nullptr) {} 10818 10819 PresShell::DelayedInputEvent::~DelayedInputEvent() { delete mEvent; } 10820 10821 void PresShell::DelayedInputEvent::Dispatch() { 10822 if (!mEvent || !mEvent->mWidget) { 10823 return; 10824 } 10825 nsCOMPtr<nsIWidget> widget = mEvent->mWidget; 10826 widget->DispatchEvent(mEvent); 10827 } 10828 10829 PresShell::DelayedMouseEvent::DelayedMouseEvent(WidgetMouseEvent* aEvent) { 10830 MOZ_DIAGNOSTIC_ASSERT(aEvent->IsTrusted()); 10831 WidgetMouseEvent* mouseEvent = 10832 new WidgetMouseEvent(true, aEvent->mMessage, aEvent->mWidget, 10833 aEvent->mReason, aEvent->mContextMenuTrigger); 10834 mouseEvent->AssignMouseEventData(*aEvent, false); 10835 mEvent = mouseEvent; 10836 } 10837 10838 PresShell::DelayedPointerEvent::DelayedPointerEvent( 10839 WidgetPointerEvent* aEvent) { 10840 MOZ_DIAGNOSTIC_ASSERT(aEvent->IsTrusted()); 10841 MOZ_ASSERT(aEvent->mMessage == eContextMenu); 10842 WidgetPointerEvent* pointerEvent = new WidgetPointerEvent( 10843 true, aEvent->mMessage, aEvent->mWidget, aEvent->mContextMenuTrigger); 10844 pointerEvent->AssignPointerEventData(*aEvent, false); 10845 mEvent = pointerEvent; 10846 } 10847 10848 PresShell::DelayedKeyEvent::DelayedKeyEvent(WidgetKeyboardEvent* aEvent) { 10849 MOZ_DIAGNOSTIC_ASSERT(aEvent->IsTrusted()); 10850 WidgetKeyboardEvent* keyEvent = 10851 new WidgetKeyboardEvent(true, aEvent->mMessage, aEvent->mWidget); 10852 keyEvent->AssignKeyEventData(*aEvent, false); 10853 keyEvent->mFlags.mIsSynthesizedForTests = 10854 aEvent->mFlags.mIsSynthesizedForTests; 10855 keyEvent->mFlags.mIsSuppressedOrDelayed = true; 10856 mEvent = keyEvent; 10857 } 10858 10859 bool PresShell::DelayedKeyEvent::IsKeyPressEvent() { 10860 return mEvent->mMessage == eKeyPress; 10861 } 10862 10863 #ifdef DEBUG 10864 // Layout debugging hooks 10865 void PresShell::ListComputedStyles(FILE* out, int32_t aIndent) { 10866 nsIFrame* rootFrame = GetRootFrame(); 10867 if (rootFrame) { 10868 rootFrame->Style()->List(out, aIndent); 10869 } 10870 10871 // The root element's frame's ComputedStyle is the root of a separate tree. 10872 Element* rootElement = mDocument->GetRootElement(); 10873 if (rootElement) { 10874 nsIFrame* rootElementFrame = rootElement->GetPrimaryFrame(); 10875 if (rootElementFrame) { 10876 rootElementFrame->Style()->List(out, aIndent); 10877 } 10878 } 10879 } 10880 #endif 10881 10882 #if defined(DEBUG) || defined(MOZ_LAYOUT_DEBUGGER) 10883 void PresShell::ListStyleSheets(FILE* out, int32_t aIndent) { 10884 auto ListStyleSheetsAtOrigin = [this, out, aIndent](StyleOrigin origin) { 10885 int32_t sheetCount = StyleSet()->SheetCount(origin); 10886 for (int32_t i = 0; i < sheetCount; ++i) { 10887 StyleSet()->SheetAt(origin, i)->List(out, aIndent); 10888 } 10889 }; 10890 10891 ListStyleSheetsAtOrigin(StyleOrigin::UserAgent); 10892 ListStyleSheetsAtOrigin(StyleOrigin::User); 10893 ListStyleSheetsAtOrigin(StyleOrigin::Author); 10894 } 10895 #endif 10896 10897 //============================================================= 10898 //============================================================= 10899 //-- Debug Reflow Counts 10900 //============================================================= 10901 //============================================================= 10902 #ifdef MOZ_REFLOW_PERF 10903 //------------------------------------------------------------- 10904 void PresShell::DumpReflows() { 10905 if (mReflowCountMgr) { 10906 nsAutoCString uriStr; 10907 if (mDocument) { 10908 nsIURI* uri = mDocument->GetDocumentURI(); 10909 if (uri) { 10910 uri->GetPathQueryRef(uriStr); 10911 } 10912 } 10913 mReflowCountMgr->DisplayTotals(uriStr.get()); 10914 mReflowCountMgr->DisplayHTMLTotals(uriStr.get()); 10915 mReflowCountMgr->DisplayDiffsInTotals(); 10916 } 10917 } 10918 10919 //------------------------------------------------------------- 10920 void PresShell::CountReflows(const char* aName, nsIFrame* aFrame) { 10921 if (mReflowCountMgr) { 10922 mReflowCountMgr->Add(aName, aFrame); 10923 } 10924 } 10925 10926 //------------------------------------------------------------- 10927 void PresShell::PaintCount(const char* aName, gfxContext* aRenderingContext, 10928 nsPresContext* aPresContext, nsIFrame* aFrame, 10929 const nsPoint& aOffset, uint32_t aColor) { 10930 if (mReflowCountMgr) { 10931 mReflowCountMgr->PaintCount(aName, aRenderingContext, aPresContext, aFrame, 10932 aOffset, aColor); 10933 } 10934 } 10935 10936 //------------------------------------------------------------- 10937 void PresShell::SetPaintFrameCount(bool aPaintFrameCounts) { 10938 if (mReflowCountMgr) { 10939 mReflowCountMgr->SetPaintFrameCounts(aPaintFrameCounts); 10940 } 10941 } 10942 10943 bool PresShell::IsPaintingFrameCounts() { 10944 if (mReflowCountMgr) return mReflowCountMgr->IsPaintingFrameCounts(); 10945 return false; 10946 } 10947 10948 //------------------------------------------------------------------ 10949 //-- Reflow Counter Classes Impls 10950 //------------------------------------------------------------------ 10951 10952 //------------------------------------------------------------------ 10953 ReflowCounter::ReflowCounter(ReflowCountMgr* aMgr) : mMgr(aMgr) { 10954 ClearTotals(); 10955 SetTotalsCache(); 10956 } 10957 10958 //------------------------------------------------------------------ 10959 ReflowCounter::~ReflowCounter() = default; 10960 10961 //------------------------------------------------------------------ 10962 void ReflowCounter::ClearTotals() { mTotal = 0; } 10963 10964 //------------------------------------------------------------------ 10965 void ReflowCounter::SetTotalsCache() { mCacheTotal = mTotal; } 10966 10967 //------------------------------------------------------------------ 10968 void ReflowCounter::CalcDiffInTotals() { mCacheTotal = mTotal - mCacheTotal; } 10969 10970 //------------------------------------------------------------------ 10971 void ReflowCounter::DisplayTotals(const char* aStr) { 10972 DisplayTotals(mTotal, aStr ? aStr : "Totals"); 10973 } 10974 10975 //------------------------------------------------------------------ 10976 void ReflowCounter::DisplayDiffTotals(const char* aStr) { 10977 DisplayTotals(mCacheTotal, aStr ? aStr : "Diff Totals"); 10978 } 10979 10980 //------------------------------------------------------------------ 10981 void ReflowCounter::DisplayHTMLTotals(const char* aStr) { 10982 DisplayHTMLTotals(mTotal, aStr ? aStr : "Totals"); 10983 } 10984 10985 //------------------------------------------------------------------ 10986 void ReflowCounter::DisplayTotals(uint32_t aTotal, const char* aTitle) { 10987 // figure total 10988 if (aTotal == 0) { 10989 return; 10990 } 10991 ReflowCounter* gTots = (ReflowCounter*)mMgr->LookUp(kGrandTotalsStr); 10992 10993 printf("%25s\t", aTitle); 10994 printf("%d\t", aTotal); 10995 if (gTots != this && aTotal > 0) { 10996 gTots->Add(aTotal); 10997 } 10998 } 10999 11000 //------------------------------------------------------------------ 11001 void ReflowCounter::DisplayHTMLTotals(uint32_t aTotal, const char* aTitle) { 11002 if (aTotal == 0) { 11003 return; 11004 } 11005 11006 ReflowCounter* gTots = (ReflowCounter*)mMgr->LookUp(kGrandTotalsStr); 11007 FILE* fd = mMgr->GetOutFile(); 11008 if (!fd) { 11009 return; 11010 } 11011 11012 fprintf(fd, "<tr><td><center>%s</center></td>", aTitle); 11013 fprintf(fd, "<td><center>%d</center></td></tr>\n", aTotal); 11014 11015 if (gTots != this && aTotal > 0) { 11016 gTots->Add(aTotal); 11017 } 11018 } 11019 11020 //------------------------------------------------------------------ 11021 //-- ReflowCountMgr 11022 //------------------------------------------------------------------ 11023 11024 # define KEY_BUF_SIZE_FOR_PTR \ 11025 24 // adequate char[] buffer to sprintf a pointer 11026 11027 ReflowCountMgr::ReflowCountMgr() : mCounts(10), mIndiFrameCounts(10) { 11028 mCycledOnce = false; 11029 mDumpFrameCounts = false; 11030 mDumpFrameByFrameCounts = false; 11031 mPaintFrameByFrameCounts = false; 11032 } 11033 11034 //------------------------------------------------------------------ 11035 ReflowCountMgr::~ReflowCountMgr() = default; 11036 11037 //------------------------------------------------------------------ 11038 ReflowCounter* ReflowCountMgr::LookUp(const char* aName) { 11039 return mCounts.Get(aName); 11040 } 11041 11042 //------------------------------------------------------------------ 11043 void ReflowCountMgr::Add(const char* aName, nsIFrame* aFrame) { 11044 NS_ASSERTION(aName != nullptr, "Name shouldn't be null!"); 11045 11046 if (mDumpFrameCounts) { 11047 auto* const counter = mCounts.GetOrInsertNew(aName, this); 11048 counter->Add(); 11049 } 11050 11051 if ((mDumpFrameByFrameCounts || mPaintFrameByFrameCounts) && 11052 aFrame != nullptr) { 11053 char key[KEY_BUF_SIZE_FOR_PTR]; 11054 SprintfLiteral(key, "%p", (void*)aFrame); 11055 auto* const counter = 11056 mIndiFrameCounts 11057 .LookupOrInsertWith(key, 11058 [&aName, &aFrame, this]() { 11059 auto counter = 11060 MakeUnique<IndiReflowCounter>(this); 11061 counter->mFrame = aFrame; 11062 counter->mName.AssignASCII(aName); 11063 return counter; 11064 }) 11065 .get(); 11066 // this eliminates extra counts from super classes 11067 if (counter && counter->mName.EqualsASCII(aName)) { 11068 counter->mCount++; 11069 counter->mCounter.Add(1); 11070 } 11071 } 11072 } 11073 11074 //------------------------------------------------------------------ 11075 void ReflowCountMgr::PaintCount(const char* aName, 11076 gfxContext* aRenderingContext, 11077 nsPresContext* aPresContext, nsIFrame* aFrame, 11078 const nsPoint& aOffset, uint32_t aColor) { 11079 if (mPaintFrameByFrameCounts && aFrame != nullptr) { 11080 char key[KEY_BUF_SIZE_FOR_PTR]; 11081 SprintfLiteral(key, "%p", (void*)aFrame); 11082 IndiReflowCounter* counter = mIndiFrameCounts.Get(key); 11083 if (counter != nullptr && counter->mName.EqualsASCII(aName)) { 11084 DrawTarget* drawTarget = aRenderingContext->GetDrawTarget(); 11085 int32_t appUnitsPerDevPixel = aPresContext->AppUnitsPerDevPixel(); 11086 11087 aRenderingContext->Save(); 11088 gfxPoint devPixelOffset = 11089 nsLayoutUtils::PointToGfxPoint(aOffset, appUnitsPerDevPixel); 11090 aRenderingContext->SetMatrixDouble( 11091 aRenderingContext->CurrentMatrixDouble().PreTranslate( 11092 devPixelOffset)); 11093 11094 // We don't care about the document language or user fonts here; 11095 // just get a default Latin font. 11096 nsFont font(StyleGenericFontFamily::Serif, Length::FromPixels(11)); 11097 nsFontMetrics::Params params; 11098 params.language = nsGkAtoms::x_western; 11099 params.textPerf = aPresContext->GetTextPerfMetrics(); 11100 params.featureValueLookup = aPresContext->GetFontFeatureValuesLookup(); 11101 RefPtr<nsFontMetrics> fm = aPresContext->GetMetricsFor(font, params); 11102 11103 char buf[16]; 11104 int len = SprintfLiteral(buf, "%d", counter->mCount); 11105 nscoord x = 0, y = fm->MaxAscent(); 11106 nscoord width, height = fm->MaxHeight(); 11107 fm->SetTextRunRTL(false); 11108 width = fm->GetWidth(buf, len, drawTarget); 11109 11110 sRGBColor color; 11111 sRGBColor color2; 11112 if (aColor != 0) { 11113 color = sRGBColor::FromABGR(aColor); 11114 color2 = sRGBColor(0.f, 0.f, 0.f); 11115 } else { 11116 gfx::Float rc = 0.f, gc = 0.f, bc = 0.f; 11117 if (counter->mCount < 5) { 11118 rc = 1.f; 11119 gc = 1.f; 11120 } else if (counter->mCount < 11) { 11121 gc = 1.f; 11122 } else { 11123 rc = 1.f; 11124 } 11125 color = sRGBColor(rc, gc, bc); 11126 color2 = sRGBColor(rc / 2, gc / 2, bc / 2); 11127 } 11128 11129 nsRect rect(0, 0, width + 15, height + 15); 11130 Rect devPxRect = 11131 NSRectToSnappedRect(rect, appUnitsPerDevPixel, *drawTarget); 11132 ColorPattern black(ToDeviceColor(sRGBColor::OpaqueBlack())); 11133 drawTarget->FillRect(devPxRect, black); 11134 11135 aRenderingContext->SetColor(color2); 11136 fm->DrawString(buf, len, x + 15, y + 15, aRenderingContext); 11137 aRenderingContext->SetColor(color); 11138 fm->DrawString(buf, len, x, y, aRenderingContext); 11139 11140 aRenderingContext->Restore(); 11141 } 11142 } 11143 } 11144 11145 //------------------------------------------------------------------ 11146 void ReflowCountMgr::DoGrandTotals() { 11147 mCounts.WithEntryHandle(kGrandTotalsStr, [this](auto&& entry) { 11148 if (!entry) { 11149 entry.Insert(MakeUnique<ReflowCounter>(this)); 11150 } else { 11151 entry.Data()->ClearTotals(); 11152 } 11153 }); 11154 11155 printf("\t\t\t\tTotal\n"); 11156 for (uint32_t i = 0; i < 78; i++) { 11157 printf("-"); 11158 } 11159 printf("\n"); 11160 for (const auto& entry : mCounts) { 11161 entry.GetData()->DisplayTotals(entry.GetKey()); 11162 } 11163 } 11164 11165 static void RecurseIndiTotals( 11166 nsPresContext* aPresContext, 11167 nsClassHashtable<nsCharPtrHashKey, IndiReflowCounter>& aHT, 11168 nsIFrame* aParentFrame, int32_t aLevel) { 11169 if (aParentFrame == nullptr) { 11170 return; 11171 } 11172 11173 char key[KEY_BUF_SIZE_FOR_PTR]; 11174 SprintfLiteral(key, "%p", (void*)aParentFrame); 11175 IndiReflowCounter* counter = aHT.Get(key); 11176 if (counter) { 11177 counter->mHasBeenOutput = true; 11178 char* name = ToNewCString(counter->mName); 11179 for (int32_t i = 0; i < aLevel; i++) printf(" "); 11180 printf("%s - %p [%d][", name, (void*)aParentFrame, counter->mCount); 11181 printf("%d", counter->mCounter.GetTotal()); 11182 printf("]\n"); 11183 free(name); 11184 } 11185 11186 for (nsIFrame* child : aParentFrame->PrincipalChildList()) { 11187 RecurseIndiTotals(aPresContext, aHT, child, aLevel + 1); 11188 } 11189 } 11190 11191 //------------------------------------------------------------------ 11192 void ReflowCountMgr::DoIndiTotalsTree() { 11193 printf("\n------------------------------------------------\n"); 11194 printf("-- Individual Frame Counts\n"); 11195 printf("------------------------------------------------\n"); 11196 11197 if (mPresShell) { 11198 nsIFrame* rootFrame = mPresShell->GetRootFrame(); 11199 RecurseIndiTotals(mPresContext, mIndiFrameCounts, rootFrame, 0); 11200 printf("------------------------------------------------\n"); 11201 printf("-- Individual Counts of Frames not in Root Tree\n"); 11202 printf("------------------------------------------------\n"); 11203 for (const auto& counter : mIndiFrameCounts.Values()) { 11204 if (!counter->mHasBeenOutput) { 11205 char* name = ToNewCString(counter->mName); 11206 printf("%s - %p [%d][", name, (void*)counter->mFrame, 11207 counter->mCount); 11208 printf("%d", counter->mCounter.GetTotal()); 11209 printf("]\n"); 11210 free(name); 11211 } 11212 } 11213 } 11214 } 11215 11216 //------------------------------------------------------------------ 11217 void ReflowCountMgr::DoGrandHTMLTotals() { 11218 mCounts.WithEntryHandle(kGrandTotalsStr, [this](auto&& entry) { 11219 if (!entry) { 11220 entry.Insert(MakeUnique<ReflowCounter>(this)); 11221 } else { 11222 entry.Data()->ClearTotals(); 11223 } 11224 }); 11225 11226 static const char* title[] = {"Class", "Reflows"}; 11227 fprintf(mFD, "<tr>"); 11228 for (uint32_t i = 0; i < std::size(title); i++) { 11229 fprintf(mFD, "<td><center><b>%s<b></center></td>", title[i]); 11230 } 11231 fprintf(mFD, "</tr>\n"); 11232 11233 for (const auto& entry : mCounts) { 11234 entry.GetData()->DisplayHTMLTotals(entry.GetKey()); 11235 } 11236 } 11237 11238 //------------------------------------ 11239 void ReflowCountMgr::DisplayTotals(const char* aStr) { 11240 if (mDumpFrameCounts) { 11241 DoGrandTotals(); 11242 } 11243 if (mDumpFrameByFrameCounts) { 11244 DoIndiTotalsTree(); 11245 } 11246 } 11247 //------------------------------------ 11248 void ReflowCountMgr::DisplayHTMLTotals(const char* aStr) { 11249 # ifdef WIN32x // XXX NOT XP! 11250 char name[1024]; 11251 11252 char* sptr = strrchr(aStr, '/'); 11253 if (sptr) { 11254 sptr++; 11255 strcpy(name, sptr); 11256 char* eptr = strrchr(name, '.'); 11257 if (eptr) { 11258 *eptr = 0; 11259 } 11260 strcat(name, "_stats.html"); 11261 } 11262 mFD = fopen(name, "w"); 11263 if (mFD) { 11264 fprintf(mFD, "<html><head><title>Reflow Stats</title></head><body>\n"); 11265 const char* title = aStr ? aStr : "No name"; 11266 fprintf(mFD, 11267 "<center><b>%s</b><br><table border=1 " 11268 "style=\"background-color:#e0e0e0\">", 11269 title); 11270 DoGrandHTMLTotals(); 11271 fprintf(mFD, "</center></table>\n"); 11272 fprintf(mFD, "</body></html>\n"); 11273 fclose(mFD); 11274 mFD = nullptr; 11275 } 11276 # endif // not XP! 11277 } 11278 11279 //------------------------------------------------------------------ 11280 void ReflowCountMgr::ClearTotals() { 11281 for (const auto& data : mCounts.Values()) { 11282 data->ClearTotals(); 11283 } 11284 } 11285 11286 //------------------------------------------------------------------ 11287 void ReflowCountMgr::ClearGrandTotals() { 11288 mCounts.WithEntryHandle(kGrandTotalsStr, [&](auto&& entry) { 11289 if (!entry) { 11290 entry.Insert(MakeUnique<ReflowCounter>(this)); 11291 } else { 11292 entry.Data()->ClearTotals(); 11293 entry.Data()->SetTotalsCache(); 11294 } 11295 }); 11296 } 11297 11298 //------------------------------------------------------------------ 11299 void ReflowCountMgr::DisplayDiffsInTotals() { 11300 if (mCycledOnce) { 11301 printf("Differences\n"); 11302 for (int32_t i = 0; i < 78; i++) { 11303 printf("-"); 11304 } 11305 printf("\n"); 11306 ClearGrandTotals(); 11307 } 11308 11309 for (const auto& entry : mCounts) { 11310 if (mCycledOnce) { 11311 entry.GetData()->CalcDiffInTotals(); 11312 entry.GetData()->DisplayDiffTotals(entry.GetKey()); 11313 } 11314 entry.GetData()->SetTotalsCache(); 11315 } 11316 11317 mCycledOnce = true; 11318 } 11319 11320 #endif // MOZ_REFLOW_PERF 11321 11322 nsIFrame* PresShell::GetAbsoluteContainingBlock(nsIFrame* aFrame) { 11323 return FrameConstructor()->GetAbsoluteContainingBlock( 11324 aFrame, nsCSSFrameConstructor::ABS_POS); 11325 } 11326 11327 nsIFrame* PresShell::GetAnchorPosAnchor( 11328 const nsAtom* aName, const nsIFrame* aPositionedFrame) const { 11329 MOZ_ASSERT(aName); 11330 MOZ_ASSERT(!aName->IsEmpty()); 11331 MOZ_ASSERT(mLazyAnchorPosAnchorChanges.IsEmpty()); 11332 if (aName == nsGkAtoms::AnchorPosImplicitAnchor) { 11333 return AnchorPositioningUtils::GetAnchorPosImplicitAnchor(aPositionedFrame); 11334 } 11335 if (const auto& entry = mAnchorPosAnchors.Lookup(aName)) { 11336 return AnchorPositioningUtils::FindFirstAcceptableAnchor( 11337 aName, aPositionedFrame, entry.Data()); 11338 } 11339 return nullptr; 11340 } 11341 11342 void PresShell::AddAnchorPosAnchorImpl(const nsAtom* aName, nsIFrame* aFrame, 11343 bool aForMerge) { 11344 MOZ_ASSERT(aName); 11345 11346 auto& entry = mAnchorPosAnchors.LookupOrInsertWith( 11347 aName, []() { return nsTArray<nsIFrame*>(); }); 11348 11349 if (entry.IsEmpty()) { 11350 entry.AppendElement(aFrame); 11351 return; 11352 } 11353 11354 struct FrameTreeComparator { 11355 nsIFrame* mFrame; 11356 11357 int32_t operator()(nsIFrame* aOther) const { 11358 return nsLayoutUtils::CompareTreePosition(mFrame, aOther, nullptr); 11359 } 11360 }; 11361 11362 FrameTreeComparator cmp{aFrame}; 11363 11364 size_t matchOrInsertionIdx = entry.Length(); 11365 // If the same element is already in the array, 11366 // someone forgot to call RemoveAnchorPosAnchor. 11367 if (BinarySearchIf(entry, 0, entry.Length(), cmp, &matchOrInsertionIdx)) { 11368 if (entry.ElementAt(matchOrInsertionIdx) == aFrame) { 11369 // nsLayoutUtils::CompareTreePosition() returns 0 when the frames are 11370 // in different documents or child lists. This could indicate that 11371 // the tree is being restructured and we can defer anchor insertion 11372 // to a MergeAnchorPosAnchors call after the restructuring is complete. 11373 MOZ_ASSERT_UNREACHABLE("Attempt to insert a frame twice was made"); 11374 return; 11375 } 11376 MOZ_ASSERT(!entry.Contains(aFrame)); 11377 11378 if (!aForMerge) { 11379 // nsLayoutUtils::CompareTreePosition() returns 0 when the frames are 11380 // in different documents or child lists. This could indicate that 11381 // the tree is being restructured and we can defer anchor insertion 11382 // to a MergeAnchorPosAnchors call after the restructuring is complete. 11383 mLazyAnchorPosAnchorChanges.AppendElement( 11384 AnchorPosAnchorChange{RefPtr<const nsAtom>(aName), aFrame}); 11385 return; 11386 } 11387 } 11388 11389 MOZ_ASSERT(!entry.Contains(aFrame)); 11390 entry.InsertElementAt(matchOrInsertionIdx, aFrame); 11391 } 11392 11393 void PresShell::AddAnchorPosAnchor(const nsAtom* aName, nsIFrame* aFrame) { 11394 AddAnchorPosAnchorImpl(aName, aFrame, /* aForMerge = */ false); 11395 } 11396 11397 void PresShell::RemoveAnchorPosAnchor(const nsAtom* aName, nsIFrame* aFrame) { 11398 MOZ_ASSERT(aName); 11399 11400 if (!mLazyAnchorPosAnchorChanges.IsEmpty()) { 11401 mLazyAnchorPosAnchorChanges.RemoveElementsBy( 11402 [&](const AnchorPosAnchorChange& change) { 11403 return change.mFrame == aFrame; 11404 }); 11405 } 11406 11407 auto entry = mAnchorPosAnchors.Lookup(aName); 11408 if (!entry) { 11409 return; // Nothing to remove. 11410 } 11411 11412 #ifdef ACCESSIBILITY 11413 if (nsAccessibilityService* accService = GetAccService()) { 11414 accService->NotifyAnchorRemoved(this, aFrame); 11415 } 11416 #endif 11417 11418 auto& anchorArray = entry.Data(); 11419 11420 // XXX: Once the implementation is more complete, 11421 // we should probably assert here that anchorArray 11422 // is not empty and aFrame is in it. 11423 11424 anchorArray.RemoveElement(aFrame); 11425 if (anchorArray.IsEmpty()) { 11426 entry.Remove(); 11427 } 11428 } 11429 11430 void PresShell::MergeAnchorPosAnchorChanges() { 11431 for (const auto& [name, frame] : mLazyAnchorPosAnchorChanges) { 11432 AddAnchorPosAnchorImpl(name, frame, /* aForMerge = */ true); 11433 } 11434 11435 mLazyAnchorPosAnchorChanges.Clear(); 11436 } 11437 11438 static bool NeedReflowForAnchorPos( 11439 const nsIFrame* aAnchor, const nsIFrame* aPositioned, 11440 const Maybe<AnchorPosResolutionData>& aData) { 11441 const bool validityChanged = (aAnchor && !aData) || (!aAnchor && aData); 11442 if (validityChanged) { 11443 return true; 11444 } 11445 if (!aData) { 11446 // Was invalid, still invalid. No more consideration needed. 11447 return false; 11448 } 11449 // Was valid, still valid Did the referenced value change? 11450 if (!aAnchor) { 11451 MOZ_ASSERT_UNREACHABLE("Anchor is supposed to be valid"); 11452 return false; 11453 } 11454 const auto& anchorReference = aData.ref(); 11455 const auto anchorSize = aAnchor->GetSize(); 11456 if (anchorReference.mSize != anchorSize) { 11457 // Size changed, needs reflow. 11458 return true; 11459 } 11460 if (!anchorReference.mOffsetData) { 11461 // Didn't resolve offsets, no need to reflow based on it. 11462 return false; 11463 } 11464 11465 const auto nearestScrollFrameInfo = 11466 AnchorPositioningUtils::GetNearestScrollFrame(aAnchor); 11467 if (anchorReference.mOffsetData->mDistanceToNearestScrollContainer != 11468 nearestScrollFrameInfo.mDistance) { 11469 // Scroll container relationship changed, need to reflow. 11470 return true; 11471 } 11472 11473 const auto posInfo = AnchorPositioningUtils::GetAnchorPosRect( 11474 aPositioned->GetParent(), aAnchor, true); 11475 MOZ_ASSERT(posInfo, "Can't resolve anchor rect?"); 11476 const auto newOrigin = posInfo.ref().TopLeft(); 11477 const auto& prevOrigin = anchorReference.mOffsetData.ref().mOrigin; 11478 // Did the offset change? 11479 return newOrigin != prevOrigin; 11480 } 11481 11482 struct DefaultAnchorInfo { 11483 const nsAtom* mName; 11484 const nsIFrame* mAnchor; 11485 DistanceToNearestScrollContainer mDistanceToNearestScrollContainer; 11486 }; 11487 11488 PresShell::AnchorPosUpdateResult PresShell::UpdateAnchorPosLayout() { 11489 if (mAnchorPosPositioned.IsEmpty()) { 11490 return AnchorPosUpdateResult::NotApplicable; 11491 } 11492 11493 // Flush the layout, so that positioned frames' anchor references are valid. 11494 DoFlushLayout(/* aInterruptible = */ false); 11495 11496 auto result = AnchorPosUpdateResult::Flushed; 11497 AUTO_PROFILER_MARKER_UNTYPED("UpdateAnchorPosLayout", LAYOUT, {}); 11498 for (auto* positioned : mAnchorPosPositioned) { 11499 MOZ_ASSERT(positioned->IsAbsolutelyPositioned(), 11500 "Anchor positioned frame is not absolutely positioned?"); 11501 const auto* anchorPosReferenceData = 11502 positioned->GetProperty(nsIFrame::AnchorPosReferences()); 11503 // Note that it's possible (Though unlikely) to register as anchor 11504 // positioned but not actually make any anchor resolution - e.g. 11505 // `position-anchor` is set, but no other anchor positioning property is 11506 // used. 11507 if (!anchorPosReferenceData || anchorPosReferenceData->IsEmpty()) { 11508 continue; 11509 } 11510 if (positioned->HasAnyStateBits(NS_FRAME_IS_DIRTY)) { 11511 // Already marked for reflow. 11512 continue; 11513 } 11514 const auto defaultAnchorInfo = [&]() -> Maybe<DefaultAnchorInfo> { 11515 const auto* anchorName = 11516 AnchorPositioningUtils::GetUsedAnchorName(positioned, nullptr); 11517 if (!anchorName) { 11518 return Nothing{}; 11519 } 11520 const auto* anchor = GetAnchorPosAnchor(anchorName, positioned); 11521 if (!anchor) { 11522 return Nothing{}; 11523 } 11524 const auto nearestScrollFrame = 11525 AnchorPositioningUtils::GetNearestScrollFrame(anchor); 11526 return Some( 11527 DefaultAnchorInfo{anchorName, anchor, nearestScrollFrame.mDistance}); 11528 }(); 11529 bool shouldReflow = false; 11530 if (defaultAnchorInfo && 11531 defaultAnchorInfo->mDistanceToNearestScrollContainer != 11532 anchorPosReferenceData->mDistanceToDefaultScrollContainer) { 11533 // Default anchor's nearest scroller changed, reflow. 11534 shouldReflow = true; 11535 } else { 11536 const auto GetAnchor = 11537 [&](const nsAtom* aName, 11538 const nsIFrame* aPositioned) -> const nsIFrame* { 11539 if (!defaultAnchorInfo) { 11540 return GetAnchorPosAnchor(aName, aPositioned); 11541 } 11542 const auto* defaultAnchorName = defaultAnchorInfo->mName; 11543 if (aName != defaultAnchorName) { 11544 return GetAnchorPosAnchor(aName, aPositioned); 11545 } 11546 return defaultAnchorInfo->mAnchor; 11547 }; 11548 for (const auto& kv : *anchorPosReferenceData) { 11549 const auto& data = kv.GetData(); 11550 const auto& anchorName = kv.GetKey(); 11551 const auto* anchor = GetAnchor(anchorName, positioned); 11552 if (NeedReflowForAnchorPos(anchor, positioned, data)) { 11553 shouldReflow = true; 11554 break; 11555 } 11556 } 11557 } 11558 if (shouldReflow) { 11559 result = AnchorPosUpdateResult::NeedReflow; 11560 MarkPositionedFrameForReflow(positioned); 11561 } 11562 } 11563 return result; 11564 } 11565 11566 using AffectedAnchor = AnchorPosDefaultAnchorCache; 11567 struct AffectedAnchorGroup { 11568 const nsAtom* mAnchorName; 11569 nsTArray<AffectedAnchor> mFrames; 11570 }; 11571 11572 static const nsIFrame* NearestScrollContainerOfAffectedAnchor( 11573 const nsIFrame* aAnchor, const ScrollContainerFrame* aScrollContainer) { 11574 const auto* scrollContainer = 11575 AnchorPositioningUtils::GetNearestScrollFrame(aAnchor).mScrollContainer; 11576 if (!scrollContainer) { 11577 // Fixed-pos anchor, likely 11578 return nullptr; 11579 } 11580 // Does this scroll container match a anchor's nearest scroll container, 11581 // or contain it? 11582 if (scrollContainer == aScrollContainer || 11583 nsLayoutUtils::IsProperAncestorFrame(aScrollContainer, scrollContainer)) { 11584 return scrollContainer; 11585 } 11586 return nullptr; 11587 } 11588 11589 static nsTArray<AffectedAnchorGroup> FindAnchorsAffectedByScroll( 11590 const nsTHashMap<RefPtr<const nsAtom>, nsTArray<nsIFrame*>>& aAnchors, 11591 const ScrollContainerFrame* aScrollContainer) { 11592 nsTArray<AffectedAnchorGroup> affectedAnchors; 11593 // We keep only referenced anchors' name in positioned frames to avoid dealing 11594 // with lifetime issues associated with it. Now we need to re-establish that 11595 // association. 11596 for (const auto& kv : aAnchors) { 11597 const auto& anchorFrames = kv.GetData(); 11598 Maybe<nsTArray<AffectedAnchor>> affected; 11599 for (const auto& frame : anchorFrames) { 11600 const auto* scrollContainer = 11601 NearestScrollContainerOfAffectedAnchor(frame, aScrollContainer); 11602 if (!scrollContainer) { 11603 continue; 11604 } 11605 if (affected.isNothing()) { 11606 affected = Some(nsTArray<AffectedAnchor>{anchorFrames.Length()}); 11607 } 11608 affected.ref().AppendElement(AffectedAnchor{frame, scrollContainer}); 11609 } 11610 if (affected.isSome()) { 11611 affectedAnchors.AppendElement( 11612 AffectedAnchorGroup{kv.GetKey(), std::move(*affected)}); 11613 } 11614 } 11615 return affectedAnchors; 11616 } 11617 11618 // Given a list of anchors affected by scrolling, find one that the given 11619 // positioned frame need to compensate scroll for. 11620 static Maybe<AffectedAnchor> FindScrollCompensatedAnchor( 11621 const PresShell* aPresShell, 11622 const ScrollContainerFrame* aScrolledScrollContainer, 11623 const nsTArray<AffectedAnchorGroup>& aAffectedAnchors, 11624 const nsIFrame* aPositioned, const AnchorPosReferenceData& aReferenceData, 11625 const nsIFrame** aResolvedDefaultAnchor) { 11626 MOZ_ASSERT(aPositioned->IsAbsolutelyPositioned(), 11627 "Anchor positioned frame is not absolutely positioned?"); 11628 if (aResolvedDefaultAnchor) { 11629 *aResolvedDefaultAnchor = nullptr; 11630 } 11631 11632 if (aReferenceData.IsEmpty()) { 11633 return Nothing{}; 11634 } 11635 11636 const auto* defaultAnchorName = aReferenceData.mDefaultAnchorName.get(); 11637 if (!defaultAnchorName) { 11638 return Nothing{}; 11639 } 11640 11641 const auto* defaultAnchor = 11642 aPresShell->GetAnchorPosAnchor(defaultAnchorName, aPositioned); 11643 if (!defaultAnchor) { 11644 return Nothing{}; 11645 } 11646 if (aResolvedDefaultAnchor) { 11647 *aResolvedDefaultAnchor = defaultAnchor; 11648 } 11649 11650 const auto compensatingForScroll = aReferenceData.CompensatingForScrollAxes(); 11651 if (compensatingForScroll.isEmpty()) { 11652 return Nothing{}; 11653 } 11654 11655 if (defaultAnchorName == nsGkAtoms::AnchorPosImplicitAnchor) { 11656 // We're not going to find this in `aAffectedAnchors`, which works off of 11657 // `PresShell::mAnchorPosAnchors`, which doesn't store implicit anchors. 11658 const auto* anchor = 11659 AnchorPositioningUtils::GetAnchorPosImplicitAnchor(aPositioned); 11660 if (!anchor) { 11661 return Nothing{}; 11662 } 11663 const auto* scrollContainer = NearestScrollContainerOfAffectedAnchor( 11664 anchor, aScrolledScrollContainer); 11665 if (!scrollContainer) { 11666 return Nothing{}; 11667 } 11668 return Some(AffectedAnchor{anchor, scrollContainer}); 11669 } 11670 11671 struct Comparator { 11672 bool Equals(const AffectedAnchor& aEntry, const nsIFrame* aFrame) const { 11673 return aEntry.mAnchor == aFrame; 11674 } 11675 }; 11676 11677 // Find the relevant default anchor. 11678 for (const auto& group : aAffectedAnchors) { 11679 if (group.mAnchorName != defaultAnchorName) { 11680 // Default anchor has a name, and it's different from this affected 11681 // anchor group. 11682 continue; 11683 } 11684 const auto& anchors = group.mFrames; 11685 // Find the affected anchor that not only matches in name, but in actual 11686 // frame. 11687 const auto idx = anchors.IndexOf(defaultAnchor, 0, Comparator{}); 11688 if (idx == anchors.NoIndex) { 11689 // We found the default anchor, but it wasn't correct. 11690 break; 11691 } 11692 const auto& info = anchors.ElementAt(idx); 11693 return Some(info); 11694 } 11695 11696 return Nothing{}; 11697 } 11698 11699 static bool CheckOverflow(nsIFrame* aPositioned, 11700 const AnchorPosReferenceData& aData) { 11701 const auto* stylePos = aPositioned->StylePosition(); 11702 const auto hasFallbacks = !stylePos->mPositionTryFallbacks._0.IsEmpty(); 11703 if (!hasFallbacks) { 11704 return false; 11705 } 11706 return !AnchorPositioningUtils::FitsInContainingBlock(aPositioned, aData); 11707 } 11708 11709 // HACK(dshin, Bug 1999954): This is a workaround. While we try to lay out 11710 // against the scroll-ignored position of an anchor, sticky and chain anchor 11711 // positioned frames actually end up containing scroll offset in their position. 11712 // Additionally, scroll offset collection does not do any special handling for 11713 // such frames (Which is impossible unless we can cleanly separate the 11714 // scroll-ignored position). 11715 // For now, we detect such frames and just trigger a reflow. 11716 // Bug 2002789 tracks the proper fix. 11717 static bool AnchorIsStickyOrChainedToScrollCompensatedAnchor( 11718 const nsIFrame* aAnchor) { 11719 if (!aAnchor) { 11720 return false; 11721 } 11722 11723 if (aAnchor->IsStickyPositioned()) { 11724 return true; 11725 } 11726 11727 if (aAnchor->StylePosition()->mPositionAnchor.IsNone()) { 11728 // Not anchored, or anchored but not scroll compensated. 11729 return false; 11730 } 11731 11732 const auto* referenceData = 11733 aAnchor->GetProperty(nsIFrame::AnchorPosReferences()); 11734 if (!referenceData) { 11735 return false; 11736 } 11737 11738 // Theoretically, we should look at the entire anchor chain to see if this 11739 // anchor will be affected by scroll compensation. However, that does not seem 11740 // worth it - A long anchor chain seems like an edge case anyway. 11741 return !referenceData->CompensatingForScrollAxes().isEmpty(); 11742 } 11743 11744 // https://drafts.csswg.org/css-anchor-position-1/#default-scroll-shift 11745 void PresShell::UpdateAnchorPosForScroll( 11746 const ScrollContainerFrame* aScrollContainer) { 11747 if (mAnchorPosAnchors.IsEmpty() && mAnchorPosPositioned.IsEmpty()) { 11748 return; 11749 } 11750 11751 AUTO_PROFILER_MARKER_UNTYPED("UpdateAnchorPosForScroll", LAYOUT, {}); 11752 11753 // First, find all anchors under this scroll container. Can look at positioned 11754 // frames' anchor references first, but we want to avoid anchor lookups if we 11755 // can. 11756 nsTArray<AffectedAnchorGroup> affectedAnchors = 11757 FindAnchorsAffectedByScroll(mAnchorPosAnchors, aScrollContainer); 11758 // Affected anchors may be empty, an implicit anchor may have scrolled. 11759 11760 // Now, update all affected positioned elements' scroll offsets. 11761 for (auto* positioned : mAnchorPosPositioned) { 11762 auto* referenceData = 11763 positioned->GetProperty(nsIFrame::AnchorPosReferences()); 11764 if (!referenceData) { 11765 continue; 11766 } 11767 const nsIFrame* defaultAnchor = nullptr; 11768 const auto scrollDependency = 11769 FindScrollCompensatedAnchor(this, aScrollContainer, affectedAnchors, 11770 positioned, *referenceData, &defaultAnchor); 11771 const bool offsetChanged = [&]() { 11772 if (!scrollDependency) { 11773 return false; 11774 } 11775 const auto offset = AnchorPositioningUtils::GetScrollOffsetFor( 11776 referenceData->CompensatingForScrollAxes(), positioned, 11777 *scrollDependency); 11778 if (referenceData->mDefaultScrollShift == offset) { 11779 return false; 11780 } 11781 positioned->SetPosition(positioned->GetNormalPosition() - offset); 11782 // Update positioned frame's overflow, then the absolute containing 11783 // block's. 11784 positioned->UpdateOverflow(); 11785 positioned->GetParent()->UpdateOverflow(); 11786 // APZ-handled scrolling may skip scheduling of paint for the relevant 11787 // scroll container - We need to ensure that we schedule a paint for this 11788 // positioned frame. Could theoretically do this when deciding to skip 11789 // painting in `ScrollContainerFrame::ScrollToImpl`, that'd be conditional 11790 // on finding a dependent anchor anyway, we should be as specific as 11791 // possible as to what gets scheduled to paint. 11792 positioned->SchedulePaint(); 11793 referenceData->mDefaultScrollShift = offset; 11794 return true; 11795 }(); 11796 const bool cbScrolls = 11797 positioned->GetParent() == aScrollContainer->GetScrolledFrame(); 11798 // HACK(dshin): Check if this positioned frame is anchoring to a sticky or 11799 // another anchor positioned frame, even if we may not be scroll 11800 // compensating against it. Such frames, even when in the same scroll 11801 // container, as the positioned element, don't (always) scroll with the 11802 // scroll container. Also see comment for 11803 // `AnchorIsStickyOrChainedToScrollCompensatedAnchor`. 11804 const bool anchorIsStickyOrScrollCompensatedAnchor = 11805 defaultAnchor && 11806 AnchorIsStickyOrChainedToScrollCompensatedAnchor(defaultAnchor); 11807 if (offsetChanged || cbScrolls || anchorIsStickyOrScrollCompensatedAnchor) { 11808 if (CheckOverflow(positioned, *referenceData) || 11809 anchorIsStickyOrScrollCompensatedAnchor) { 11810 #ifdef ACCESSIBILITY 11811 if (nsAccessibilityService* accService = GetAccService()) { 11812 accService->NotifyAnchorPositionedScrollUpdate(this, positioned); 11813 } 11814 #endif 11815 MarkPositionedFrameForReflow(positioned); 11816 } 11817 } 11818 } 11819 } 11820 11821 void PresShell::ActivenessMaybeChanged() { 11822 if (!mDocument) { 11823 return; 11824 } 11825 SetIsActive(ComputeActiveness()); 11826 } 11827 11828 // A PresShell being active means that it is visible (or close to be visible, if 11829 // the front-end is warming it). That means that when it is active we always 11830 // tick its refresh driver at full speed if needed. 11831 // 11832 // Image documents behave specially in the sense that they are always "active" 11833 // and never "in the active tab". However these documents tick manually so 11834 // there's not much to worry about there. 11835 bool PresShell::ComputeActiveness() const { 11836 MOZ_LOG(gLog, LogLevel::Debug, 11837 ("PresShell::ComputeActiveness(%s, %d)\n", 11838 mDocument->GetDocumentURI() 11839 ? mDocument->GetDocumentURI()->GetSpecOrDefault().get() 11840 : "(no uri)", 11841 mIsActive)); 11842 11843 Document* doc = mDocument; 11844 11845 if (doc->IsBeingUsedAsImage()) { 11846 // Documents used as an image can remain active. They do not tick their 11847 // refresh driver if not painted, and they can't run script or such so they 11848 // can't really observe much else. 11849 // 11850 // Image docs can be displayed in multiple docs at the same time so the "in 11851 // active tab" bool doesn't make much sense for them. 11852 return true; 11853 } 11854 11855 if (Document* displayDoc = doc->GetDisplayDocument()) { 11856 // Ok, we're an external resource document -- we need to use our display 11857 // document's docshell to determine "IsActive" status, since we lack 11858 // a browsing context of our own. 11859 MOZ_ASSERT(!doc->GetBrowsingContext(), 11860 "external resource doc shouldn't have its own BC"); 11861 doc = displayDoc; 11862 } 11863 11864 BrowsingContext* bc = doc->GetBrowsingContext(); 11865 const bool inActiveTab = bc && bc->IsActive(); 11866 11867 MOZ_LOG(gLog, LogLevel::Debug, 11868 (" > BrowsingContext %p active: %d", bc, inActiveTab)); 11869 11870 if (StaticPrefs::layout_testing_top_level_always_active() && bc && 11871 bc->IsTop()) { 11872 MOZ_LOG(gLog, LogLevel::Debug, (" > Activeness overridden by pref")); 11873 return true; 11874 } 11875 11876 Document* root = nsContentUtils::GetInProcessSubtreeRootDocument(doc); 11877 if (auto* browserChild = BrowserChild::GetFrom(root->GetDocShell())) { 11878 // We might want to activate a tab even though the browsing-context is not 11879 // active if the BrowserChild is considered visible. This serves two 11880 // purposes: 11881 // 11882 // * For top-level tabs, we use this for tab warming. The browsing-context 11883 // might still be inactive, but we want to activate the pres shell and 11884 // the refresh driver. 11885 // 11886 // * For oop iframes, we do want to throttle them if they're not visible. 11887 // 11888 // TODO(emilio): Consider unifying the in-process vs. fission iframe 11889 // throttling code (in-process throttling for non-visible iframes lives 11890 // right now in Document::ShouldThrottleFrameRequests(), but that only 11891 // throttles rAF). 11892 if (!browserChild->IsVisible()) { 11893 MOZ_LOG(gLog, LogLevel::Debug, 11894 (" > BrowserChild %p is not visible", browserChild)); 11895 return false; 11896 } 11897 11898 // If the browser is visible but just due to be preserving layers 11899 // artificially, we do want to fall back to the browsing context activeness 11900 // instead. Otherwise we do want to be active for the use cases above. 11901 if (!browserChild->IsPreservingLayers()) { 11902 MOZ_LOG(gLog, LogLevel::Debug, 11903 (" > BrowserChild %p is visible and not preserving layers", 11904 browserChild)); 11905 return true; 11906 } 11907 MOZ_LOG( 11908 gLog, LogLevel::Debug, 11909 (" > BrowserChild %p is visible and preserving layers", browserChild)); 11910 } 11911 return inActiveTab; 11912 } 11913 11914 void PresShell::SetIsActive(bool aIsActive) { 11915 MOZ_ASSERT(mDocument, "should only be called with a document"); 11916 11917 const bool activityChanged = mIsActive != aIsActive; 11918 11919 mIsActive = aIsActive; 11920 11921 nsPresContext* presContext = GetPresContext(); 11922 if (presContext && 11923 presContext->RefreshDriver()->GetPresContext() == presContext) { 11924 presContext->RefreshDriver()->SetActivity(aIsActive); 11925 } 11926 11927 if (activityChanged) { 11928 // Propagate state-change to my resource documents' PresShells and other 11929 // subdocuments. 11930 // 11931 // Note that it is fine to not propagate to fission iframes. Those will 11932 // become active / inactive as needed as a result of they getting painted / 11933 // not painted eventually. 11934 auto recurse = [aIsActive](Document& aSubDoc) { 11935 if (PresShell* presShell = aSubDoc.GetPresShell()) { 11936 presShell->SetIsActive(aIsActive); 11937 } 11938 return CallState::Continue; 11939 }; 11940 mDocument->EnumerateExternalResources(recurse); 11941 mDocument->EnumerateSubDocuments(recurse); 11942 } 11943 11944 UpdateImageLockingState(); 11945 11946 if (activityChanged) { 11947 #if defined(MOZ_WIDGET_ANDROID) 11948 if (!aIsActive && presContext && 11949 presContext->IsRootContentDocumentCrossProcess()) { 11950 // Reset the dynamic toolbar offset state. 11951 presContext->UpdateDynamicToolbarOffset(0); 11952 } 11953 #endif 11954 } 11955 11956 if (aIsActive) { 11957 #ifdef ACCESSIBILITY 11958 if (nsAccessibilityService* accService = GetAccService()) { 11959 accService->PresShellActivated(this); 11960 } 11961 #endif 11962 if (nsIFrame* rootFrame = GetRootFrame()) { 11963 rootFrame->SchedulePaint(); 11964 } 11965 } 11966 } 11967 11968 MobileViewportManager* PresShell::GetMobileViewportManager() const { 11969 return mMobileViewportManager; 11970 } 11971 11972 Maybe<MobileViewportManager::ManagerType> UseMobileViewportManager( 11973 PresShell* aPresShell, Document* aDocument) { 11974 // If we're not using APZ, we won't be able to zoom, so there is no 11975 // point in having an MVM. 11976 if (nsIWidget* widget = aPresShell->GetNearestWidget()) { 11977 if (!widget->AsyncPanZoomEnabled()) { 11978 return Nothing(); 11979 } 11980 } 11981 if (nsLayoutUtils::ShouldHandleMetaViewport(aDocument)) { 11982 return Some(MobileViewportManager::ManagerType::VisualAndMetaViewport); 11983 } 11984 if (nsLayoutUtils::AllowZoomingForDocument(aDocument)) { 11985 return Some(MobileViewportManager::ManagerType::VisualViewportOnly); 11986 } 11987 return Nothing(); 11988 } 11989 11990 void PresShell::MaybeRecreateMobileViewportManager(bool aAfterInitialization) { 11991 // Determine if we require a MobileViewportManager, and what kind if so. We 11992 // need one any time we allow resolution zooming for a document, and any time 11993 // we want to obey <meta name="viewport"> tags for it. 11994 Maybe<MobileViewportManager::ManagerType> mvmType = 11995 UseMobileViewportManager(this, mDocument); 11996 11997 if (mvmType.isNothing() && !mMobileViewportManager) { 11998 // We don't need one and don't have it. So we're done. 11999 return; 12000 } 12001 if (mvmType && mMobileViewportManager && 12002 *mvmType == mMobileViewportManager->GetManagerType()) { 12003 // We need one and we have one of the correct type, so we're done. 12004 return; 12005 } 12006 12007 if (!mPresContext->IsRootContentDocumentCrossProcess()) { 12008 MOZ_ASSERT(!mMobileViewportManager, "We never create MVMs for subframes"); 12009 return; 12010 } 12011 12012 if (mMobileViewportManager) { 12013 // We have one, but we need to either destroy it completely to replace it 12014 // with another one of the correct type. So either way, let's destroy the 12015 // one we have. 12016 mMobileViewportManager->Destroy(); 12017 mMobileViewportManager = nullptr; 12018 mMVMContext = nullptr; 12019 12020 ResetVisualViewportSize(); 12021 } 12022 12023 if (mvmType) { 12024 // Let's create the MVM of the type that we need. At this point we shouldn't 12025 // have one. 12026 MOZ_ASSERT(!mMobileViewportManager); 12027 12028 mMVMContext = new GeckoMVMContext(mDocument, this); 12029 mMobileViewportManager = new MobileViewportManager(mMVMContext, *mvmType); 12030 if (MOZ_UNLIKELY( 12031 MOZ_LOG_TEST(MobileViewportManager::gLog, LogLevel::Debug))) { 12032 nsIURI* uri = mDocument->GetDocumentURI(); 12033 MOZ_LOG( 12034 MobileViewportManager::gLog, LogLevel::Debug, 12035 ("Created MVM %p (type %d) for URI %s", mMobileViewportManager.get(), 12036 (int)*mvmType, uri ? uri->GetSpecOrDefault().get() : "(null)")); 12037 } 12038 if (BrowserChild* browserChild = BrowserChild::GetFrom(this)) { 12039 mMobileViewportManager->UpdateKeyboardHeight( 12040 browserChild->GetKeyboardHeight()); 12041 } 12042 } 12043 12044 if (aAfterInitialization) { 12045 // Setting the initial viewport will trigger a reflow. 12046 if (mMobileViewportManager) { 12047 mMobileViewportManager->SetInitialViewport(); 12048 } else { 12049 // Force a reflow to our correct layout viewport size. 12050 ForceResizeReflowWithCurrentDimensions(); 12051 } 12052 // After we clear out the MVM and the MVMContext, also reset the 12053 // resolution to 1. 12054 SetResolutionAndScaleTo(1.0f, ResolutionChangeOrigin::MainThreadRestore); 12055 } 12056 } 12057 12058 bool PresShell::UsesMobileViewportSizing() const { 12059 return mMobileViewportManager && 12060 nsLayoutUtils::ShouldHandleMetaViewport(mDocument); 12061 } 12062 12063 /* 12064 * Determines the current image locking state. Called when one of the 12065 * dependent factors changes. 12066 */ 12067 void PresShell::UpdateImageLockingState() { 12068 // We're locked if we're both thawed and active. 12069 const bool locked = !mFrozen && mIsActive; 12070 if (locked == mDocument->GetLockingImages()) { 12071 return; 12072 } 12073 mDocument->SetLockingImages(locked); 12074 if (locked) { 12075 // Request decodes for visible image frames; we want to start decoding as 12076 // quickly as possible when we get foregrounded to minimize flashing. 12077 for (const auto& key : mApproximatelyVisibleFrames) { 12078 if (nsImageFrame* imageFrame = do_QueryFrame(key)) { 12079 imageFrame->MaybeDecodeForPredictedSize(); 12080 } 12081 } 12082 } 12083 } 12084 12085 nsIWidget* PresShell::GetRootWidget() const { 12086 if (!mPresContext) { 12087 return nullptr; 12088 } 12089 for (nsPresContext* pc = mPresContext; pc; pc = pc->GetParentPresContext()) { 12090 if (auto* widget = pc->PresShell()->GetOwnWidget()) { 12091 return widget; 12092 } 12093 } 12094 return nullptr; 12095 } 12096 12097 PresShell* PresShell::GetRootPresShell() const { 12098 if (mPresContext) { 12099 nsPresContext* rootPresContext = mPresContext->GetRootPresContext(); 12100 if (rootPresContext) { 12101 return rootPresContext->PresShell(); 12102 } 12103 } 12104 return nullptr; 12105 } 12106 12107 void PresShell::AddSizeOfIncludingThis(nsWindowSizes& aSizes) const { 12108 MallocSizeOf mallocSizeOf = aSizes.mState.mMallocSizeOf; 12109 mFrameArena.AddSizeOfExcludingThis(aSizes, Arena::ArenaKind::PresShell); 12110 aSizes.mLayoutPresShellSize += mallocSizeOf(this); 12111 if (mCaret) { 12112 aSizes.mLayoutPresShellSize += mCaret->SizeOfIncludingThis(mallocSizeOf); 12113 } 12114 aSizes.mLayoutPresShellSize += 12115 mApproximatelyVisibleFrames.ShallowSizeOfExcludingThis(mallocSizeOf) + 12116 mFramesToDirty.ShallowSizeOfExcludingThis(mallocSizeOf) + 12117 mPendingScrollAnchorSelection.ShallowSizeOfExcludingThis(mallocSizeOf) + 12118 mPendingScrollAnchorAdjustment.ShallowSizeOfExcludingThis(mallocSizeOf); 12119 12120 aSizes.mLayoutTextRunsSize += SizeOfTextRuns(mallocSizeOf); 12121 12122 aSizes.mLayoutPresContextSize += 12123 mPresContext->SizeOfIncludingThis(mallocSizeOf); 12124 12125 mFrameConstructor->AddSizeOfIncludingThis(aSizes); 12126 } 12127 12128 size_t PresShell::SizeOfTextRuns(MallocSizeOf aMallocSizeOf) const { 12129 nsIFrame* rootFrame = mFrameConstructor->GetRootFrame(); 12130 if (!rootFrame) { 12131 return 0; 12132 } 12133 12134 // clear the TEXT_RUN_MEMORY_ACCOUNTED flags 12135 nsLayoutUtils::SizeOfTextRunsForFrames(rootFrame, nullptr, 12136 /* clear = */ true); 12137 12138 // collect the total memory in use for textruns 12139 return nsLayoutUtils::SizeOfTextRunsForFrames(rootFrame, aMallocSizeOf, 12140 /* clear = */ false); 12141 } 12142 12143 void PresShell::MarkPositionedFrameForReflow(nsIFrame* aFrame) { 12144 // Abspos frames don't affect intrinsic sizes of ancestors. 12145 FrameNeedsReflow(aFrame, IntrinsicDirty::None, NS_FRAME_HAS_DIRTY_CHILDREN, 12146 ReflowRootHandling::PositionOrSizeChange); 12147 } 12148 12149 void PresShell::MarkFixedFramesForReflow() { 12150 nsIFrame* rootFrame = mFrameConstructor->GetRootFrame(); 12151 if (rootFrame) { 12152 const nsFrameList& childList = 12153 rootFrame->GetChildList(FrameChildListID::Fixed); 12154 for (nsIFrame* childFrame : childList) { 12155 MarkPositionedFrameForReflow(childFrame); 12156 } 12157 } 12158 } 12159 12160 void PresShell::MarkStickyFramesForReflow() { 12161 ScrollContainerFrame* sc = GetRootScrollContainerFrame(); 12162 if (!sc) { 12163 return; 12164 } 12165 12166 StickyScrollContainer* ssc = sc->GetStickyContainer(); 12167 if (!ssc) { 12168 return; 12169 } 12170 12171 ssc->MarkFramesForReflow(); 12172 } 12173 12174 static void AppendSubtree(nsIDocShell* aDocShell, 12175 nsTArray<nsCOMPtr<nsIDocumentViewer>>& aArray) { 12176 if (nsCOMPtr<nsIDocumentViewer> viewer = aDocShell->GetDocViewer()) { 12177 aArray.AppendElement(viewer); 12178 } 12179 12180 int32_t n = aDocShell->GetInProcessChildCount(); 12181 for (int32_t i = 0; i < n; i++) { 12182 nsCOMPtr<nsIDocShellTreeItem> childItem; 12183 aDocShell->GetInProcessChildAt(i, getter_AddRefs(childItem)); 12184 if (childItem) { 12185 nsCOMPtr<nsIDocShell> child(do_QueryInterface(childItem)); 12186 AppendSubtree(child, aArray); 12187 } 12188 } 12189 } 12190 12191 void PresShell::MaybeReflowForInflationScreenSizeChange() { 12192 nsPresContext* pc = GetPresContext(); 12193 const bool fontInflationWasEnabled = FontSizeInflationEnabled(); 12194 RecomputeFontSizeInflationEnabled(); 12195 bool changed = false; 12196 if (FontSizeInflationEnabled() && FontSizeInflationMinTwips() != 0) { 12197 pc->ScreenSizeInchesForFontInflation(&changed); 12198 } 12199 12200 changed = changed || fontInflationWasEnabled != FontSizeInflationEnabled(); 12201 if (!changed) { 12202 return; 12203 } 12204 if (nsCOMPtr<nsIDocShell> docShell = pc->GetDocShell()) { 12205 nsTArray<nsCOMPtr<nsIDocumentViewer>> array; 12206 AppendSubtree(docShell, array); 12207 for (uint32_t i = 0, iEnd = array.Length(); i < iEnd; ++i) { 12208 nsCOMPtr<nsIDocumentViewer> viewer = array[i]; 12209 if (RefPtr<PresShell> descendantPresShell = viewer->GetPresShell()) { 12210 nsIFrame* rootFrame = descendantPresShell->GetRootFrame(); 12211 if (rootFrame) { 12212 descendantPresShell->FrameNeedsReflow( 12213 rootFrame, IntrinsicDirty::FrameAncestorsAndDescendants, 12214 NS_FRAME_IS_DIRTY); 12215 } 12216 } 12217 } 12218 } 12219 } 12220 12221 void PresShell::CompleteChangeToVisualViewportSize() { 12222 // This can get called during reflow, if the caller wants to get the latest 12223 // visual viewport size after scrollbars have been added/removed. In such a 12224 // case, we don't need to mark things as dirty because the things that we 12225 // would mark dirty either just got updated (the root scrollframe's 12226 // scrollbars), or will be laid out later during this reflow cycle (fixed-pos 12227 // items). Callers that update the visual viewport during a reflow are 12228 // responsible for maintaining these invariants. 12229 if (!mIsReflowing) { 12230 if (ScrollContainerFrame* sf = GetRootScrollContainerFrame()) { 12231 sf->MarkScrollbarsDirtyForReflow(); 12232 } 12233 MarkFixedFramesForReflow(); 12234 } 12235 12236 MaybeReflowForInflationScreenSizeChange(); 12237 12238 if (auto* window = nsGlobalWindowInner::Cast(mDocument->GetInnerWindow())) { 12239 window->VisualViewport()->PostResizeEvent(); 12240 } 12241 } 12242 12243 void PresShell::SetVisualViewportSize(nscoord aWidth, nscoord aHeight) { 12244 MOZ_ASSERT(aWidth >= 0.0 && aHeight >= 0.0); 12245 12246 if (!mVisualViewportSizeSet || mVisualViewportSize.width != aWidth || 12247 mVisualViewportSize.height != aHeight) { 12248 mVisualViewportSizeSet = true; 12249 mVisualViewportSize.width = aWidth; 12250 mVisualViewportSize.height = aHeight; 12251 12252 CompleteChangeToVisualViewportSize(); 12253 } 12254 } 12255 12256 void PresShell::ResetVisualViewportSize() { 12257 if (mVisualViewportSizeSet) { 12258 mVisualViewportSizeSet = false; 12259 mVisualViewportSize.width = 0; 12260 mVisualViewportSize.height = 0; 12261 12262 CompleteChangeToVisualViewportSize(); 12263 } 12264 } 12265 12266 void PresShell::SetNeedsWindowPropertiesSync() { 12267 if (XRE_IsContentProcess() || !IsRoot()) { 12268 // Window properties are only relevant to top level widgets in the parent 12269 // process 12270 return; 12271 } 12272 mNeedsWindowPropertiesSync = true; 12273 SchedulePaint(); 12274 } 12275 12276 bool PresShell::SetVisualViewportOffset(const nsPoint& aScrollOffset, 12277 const nsPoint& aPrevLayoutScrollPos) { 12278 nsPoint newOffset = aScrollOffset; 12279 ScrollContainerFrame* rootScrollContainerFrame = 12280 GetRootScrollContainerFrame(); 12281 if (rootScrollContainerFrame) { 12282 // See the comment in ScrollContainerFrame::Reflow above the call to 12283 // SetVisualViewportOffset for why we need to do this. 12284 nsRect scrollRange = 12285 rootScrollContainerFrame->GetScrollRangeForUserInputEvents(); 12286 if (!scrollRange.Contains(newOffset)) { 12287 newOffset.x = std::min(newOffset.x, scrollRange.XMost()); 12288 newOffset.x = std::max(newOffset.x, scrollRange.x); 12289 newOffset.y = std::min(newOffset.y, scrollRange.YMost()); 12290 newOffset.y = std::max(newOffset.y, scrollRange.y); 12291 } 12292 } 12293 12294 // Careful here not to call GetVisualViewportOffset to get the previous visual 12295 // viewport offset because if mVisualViewportOffset is nothing then we'll get 12296 // the layout scroll position directly from the scroll frame and it has likely 12297 // already been updated. 12298 nsPoint prevOffset = aPrevLayoutScrollPos; 12299 if (mVisualViewportOffset.isSome()) { 12300 prevOffset = *mVisualViewportOffset; 12301 } 12302 if (prevOffset == newOffset) { 12303 return false; 12304 } 12305 12306 mVisualViewportOffset = Some(newOffset); 12307 12308 if (auto* window = nsGlobalWindowInner::Cast(mDocument->GetInnerWindow())) { 12309 window->VisualViewport()->PostScrollEvent(prevOffset, aPrevLayoutScrollPos); 12310 } 12311 12312 if (IsVisualViewportSizeSet() && rootScrollContainerFrame) { 12313 rootScrollContainerFrame->Anchor()->UserScrolled(); 12314 } 12315 12316 if (gfxPlatform::UseDesktopZoomingScrollbars()) { 12317 if (rootScrollContainerFrame) { 12318 rootScrollContainerFrame->UpdateScrollbarPosition(); 12319 } 12320 } 12321 12322 return true; 12323 } 12324 12325 void PresShell::ResetVisualViewportOffset() { mVisualViewportOffset.reset(); } 12326 12327 void PresShell::RefreshViewportSize() { 12328 if (mMobileViewportManager) { 12329 mMobileViewportManager->RefreshViewportSize(false); 12330 } 12331 } 12332 12333 void PresShell::ScrollToVisual(const nsPoint& aVisualViewportOffset, 12334 FrameMetrics::ScrollOffsetUpdateType aUpdateType, 12335 ScrollMode aMode) { 12336 if (aMode == ScrollMode::Smooth || aMode == ScrollMode::SmoothMsd) { 12337 if (ScrollContainerFrame* sf = GetRootScrollContainerFrame()) { 12338 if (sf->SmoothScrollVisual(aVisualViewportOffset, aUpdateType, aMode)) { 12339 return; 12340 } 12341 } 12342 } 12343 12344 // If the caller asked for instant scroll, or if we failed 12345 // to do a smooth scroll, do an instant scroll. 12346 SetPendingVisualScrollUpdate(aVisualViewportOffset, aUpdateType); 12347 } 12348 12349 void PresShell::SetPendingVisualScrollUpdate( 12350 const nsPoint& aVisualViewportOffset, 12351 FrameMetrics::ScrollOffsetUpdateType aUpdateType) { 12352 mPendingVisualScrollUpdate = 12353 Some(VisualScrollUpdate{aVisualViewportOffset, aUpdateType}); 12354 12355 // The pending update is picked up during the next paint. 12356 // Schedule a paint to make sure one will happen. 12357 if (nsIFrame* rootFrame = GetRootFrame()) { 12358 rootFrame->SchedulePaint(); 12359 } 12360 } 12361 12362 void PresShell::ClearPendingVisualScrollUpdate() { 12363 if (mPendingVisualScrollUpdate && mPendingVisualScrollUpdate->mAcknowledged) { 12364 mPendingVisualScrollUpdate = mozilla::Nothing(); 12365 } 12366 } 12367 12368 void PresShell::AcknowledgePendingVisualScrollUpdate() { 12369 MOZ_ASSERT(mPendingVisualScrollUpdate); 12370 mPendingVisualScrollUpdate->mAcknowledged = true; 12371 } 12372 12373 nsPoint PresShell::GetVisualViewportOffsetRelativeToLayoutViewport() const { 12374 return GetVisualViewportOffset() - GetLayoutViewportOffset(); 12375 } 12376 12377 nsPoint PresShell::GetLayoutViewportOffset() const { 12378 nsPoint result; 12379 if (ScrollContainerFrame* sf = GetRootScrollContainerFrame()) { 12380 result = sf->GetScrollPosition(); 12381 } 12382 return result; 12383 } 12384 12385 nsSize PresShell::GetLayoutViewportSize() const { 12386 nsSize result; 12387 if (ScrollContainerFrame* sf = GetRootScrollContainerFrame()) { 12388 result = sf->GetScrollPortRect().Size(); 12389 } 12390 return result; 12391 } 12392 12393 nsSize PresShell::GetInnerSize() const { 12394 if (ScrollContainerFrame* sf = GetRootScrollContainerFrame()) { 12395 return sf->GetSizeForWindowInnerSize(); 12396 } 12397 return mPresContext->GetVisibleArea().Size(); 12398 } 12399 12400 nsSize PresShell::GetVisualViewportSizeUpdatedByDynamicToolbar() const { 12401 NS_ASSERTION(mVisualViewportSizeSet, 12402 "asking for visual viewport size when its not set?"); 12403 if (!mMobileViewportManager) { 12404 return mVisualViewportSize; 12405 } 12406 12407 MOZ_ASSERT(GetDynamicToolbarState() == DynamicToolbarState::InTransition || 12408 GetDynamicToolbarState() == DynamicToolbarState::Collapsed); 12409 12410 nsSize sizeUpdatedByDynamicToolbar = 12411 mMobileViewportManager->GetVisualViewportSizeUpdatedByDynamicToolbar(); 12412 return sizeUpdatedByDynamicToolbar == nsSize() ? mVisualViewportSize 12413 : sizeUpdatedByDynamicToolbar; 12414 } 12415 12416 void PresShell::RecomputeFontSizeInflationEnabled() { 12417 mFontSizeInflationEnabled = DetermineFontSizeInflationState(); 12418 } 12419 12420 bool PresShell::DetermineFontSizeInflationState() { 12421 MOZ_ASSERT(mPresContext, "our pres context should not be null"); 12422 if (mPresContext->IsChrome()) { 12423 return false; 12424 } 12425 12426 if (FontSizeInflationEmPerLine() == 0 && FontSizeInflationMinTwips() == 0) { 12427 return false; 12428 } 12429 12430 // Force-enabling font inflation always trumps the heuristics here. 12431 if (!FontSizeInflationForceEnabled()) { 12432 if (BrowserChild* tab = BrowserChild::GetFrom(this)) { 12433 // We're in a child process. Cancel inflation if we're not 12434 // async-pan zoomed. 12435 if (!tab->AsyncPanZoomEnabled()) { 12436 return false; 12437 } 12438 } 12439 } 12440 12441 Maybe<LayoutDeviceIntSize> displaySize; 12442 // The MVM already caches the top-level content viewer size and is therefore 12443 // the fastest way of getting that data. 12444 if (mPresContext->IsRootContentDocumentCrossProcess()) { 12445 if (mMobileViewportManager) { 12446 displaySize = Some(mMobileViewportManager->DisplaySize()); 12447 } 12448 } else if (PresShell* rootPresShell = GetRootPresShell()) { 12449 // With any luck, we can get at the root content document without any cross- 12450 // process shenanigans. 12451 if (auto mvm = rootPresShell->GetMobileViewportManager()) { 12452 displaySize = Some(mvm->DisplaySize()); 12453 } 12454 } 12455 12456 if (!displaySize) { 12457 // Unfortunately, it looks like the root content document lives in a 12458 // different process. For consistency's sake it would be best to always use 12459 // the content viewer size of the root content document, but it's not worth 12460 // the effort, because this only makes a difference in the case of pages 12461 // with an explicitly sized viewport (neither "width=device-width" nor a 12462 // completely missing viewport tag) being loaded within a frame, which is 12463 // hopefully a relatively exotic case. 12464 // More to the point, these viewport size and zoom-based calculations don't 12465 // really make sense for frames anyway, so instead of creating a way to 12466 // access the content viewer size of the top level document cross-process, 12467 // we probably rather want frames to simply inherit the font inflation state 12468 // of their top-level parent and should therefore invest any time spent on 12469 // getting things to work cross-process into that (bug 1724311). 12470 12471 // Until we get around to that though, we just use the content viewer size 12472 // of however high we can get within the same process. 12473 12474 // (This also serves as a fallback code path if the MVM isn't available, 12475 // e.g. when debugging in non-e10s mode on Desktop.) 12476 nsPresContext* topContext = 12477 mPresContext->GetInProcessRootContentDocumentPresContext(); 12478 LayoutDeviceIntSize result; 12479 if (!nsLayoutUtils::GetDocumentViewerSize(topContext, result)) { 12480 return false; 12481 } 12482 displaySize = Some(result); 12483 } 12484 12485 ScreenIntSize screenSize = ViewAs<ScreenPixel>( 12486 displaySize.value(), 12487 PixelCastJustification::LayoutDeviceIsScreenForBounds); 12488 nsViewportInfo vInf = GetDocument()->GetViewportInfo(screenSize); 12489 12490 CSSToScreenScale defaultScale = 12491 mPresContext->CSSToDevPixelScale() * LayoutDeviceToScreenScale(1.0); 12492 12493 if (vInf.GetDefaultZoom() >= defaultScale || vInf.IsAutoSizeEnabled()) { 12494 return false; 12495 } 12496 12497 return true; 12498 } 12499 12500 static nsIWidget* GetPresContextContainerWidget(nsPresContext* aPresContext) { 12501 nsCOMPtr<nsISupports> container = aPresContext->Document()->GetContainer(); 12502 nsCOMPtr<nsIBaseWindow> baseWindow = do_QueryInterface(container); 12503 if (!baseWindow) { 12504 return nullptr; 12505 } 12506 12507 nsCOMPtr<nsIWidget> mainWidget; 12508 baseWindow->GetMainWidget(getter_AddRefs(mainWidget)); 12509 return mainWidget; 12510 } 12511 12512 static bool IsTopLevelWidget(nsIWidget* aWidget) { 12513 using WindowType = mozilla::widget::WindowType; 12514 12515 auto windowType = aWidget->GetWindowType(); 12516 return windowType == WindowType::TopLevel || 12517 windowType == WindowType::Dialog || windowType == WindowType::Popup; 12518 } 12519 12520 PresShell::WindowSizeConstraints PresShell::GetWindowSizeConstraints() { 12521 nsSize minSize(0, 0); 12522 nsSize maxSize(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE); 12523 nsIFrame* rootFrame = FrameConstructor()->GetRootElementStyleFrame(); 12524 if (!rootFrame || !mPresContext) { 12525 return {minSize, maxSize}; 12526 } 12527 const auto* pos = rootFrame->StylePosition(); 12528 const auto anchorResolutionParams = 12529 AnchorPosResolutionParams::From(rootFrame); 12530 if (const auto styleMinWidth = pos->GetMinWidth(anchorResolutionParams); 12531 styleMinWidth->ConvertsToLength()) { 12532 minSize.width = styleMinWidth->ToLength(); 12533 } 12534 if (const auto styleMinHeight = pos->GetMinHeight(anchorResolutionParams); 12535 styleMinHeight->ConvertsToLength()) { 12536 minSize.height = styleMinHeight->ToLength(); 12537 } 12538 if (const auto maxWidth = pos->GetMaxWidth(anchorResolutionParams); 12539 maxWidth->ConvertsToLength()) { 12540 maxSize.width = maxWidth->ToLength(); 12541 } 12542 if (const auto maxHeight = pos->GetMaxHeight(anchorResolutionParams); 12543 maxHeight->ConvertsToLength()) { 12544 maxSize.height = maxHeight->ToLength(); 12545 } 12546 return {minSize, maxSize}; 12547 } 12548 12549 void PresShell::PaintSynchronously() { 12550 MOZ_ASSERT(!mIsPainting, "re-entrant paint?"); 12551 if (IsNeverPainting() || IsPaintingSuppressed() || !IsVisible() || 12552 MOZ_UNLIKELY(NS_WARN_IF(mIsPainting))) { 12553 return; 12554 } 12555 RefPtr widget = GetOwnWidget(); 12556 if (!widget) { 12557 // We were asked to paint a non-root pres shell, or an already-detached 12558 // shell. 12559 return; 12560 } 12561 MOZ_ASSERT(widget->IsTopLevelWidget()); 12562 if (!widget->NeedsPaint()) { 12563 return; 12564 } 12565 12566 // FIXME: This might not be needed now except for widget paints 12567 // (WillPaintWindow) and maybe FlushWillPaintObservers. 12568 WillPaint(); 12569 12570 if (MOZ_UNLIKELY(mIsDestroying)) { 12571 return; 12572 } 12573 12574 FlushDelayedResize(); 12575 12576 mIsPainting = true; 12577 auto cleanUpPaintingBit = MakeScopeExit([&] { mIsPainting = false; }); 12578 nsAutoScriptBlocker blocker; 12579 RefPtr<WindowRenderer> renderer = widget->GetWindowRenderer(); 12580 PaintAndRequestComposite(GetRootFrame(), renderer, PaintFlags::None); 12581 } 12582 12583 void PresShell::SyncWindowPropertiesIfNeeded() { 12584 if (!mNeedsWindowPropertiesSync) { 12585 return; 12586 } 12587 12588 mNeedsWindowPropertiesSync = false; 12589 12590 RefPtr pc = mPresContext; 12591 if (!pc) { 12592 return; 12593 } 12594 12595 nsCOMPtr<nsIWidget> windowWidget = GetPresContextContainerWidget(pc); 12596 if (!windowWidget || !IsTopLevelWidget(windowWidget)) { 12597 return; 12598 } 12599 12600 nsIFrame* rootFrame = FrameConstructor()->GetRootElementStyleFrame(); 12601 if (!rootFrame) { 12602 return; 12603 } 12604 12605 // Apply color scheme to the top level window widget. 12606 windowWidget->SetColorScheme( 12607 Some(LookAndFeel::ColorSchemeForFrame(rootFrame))); 12608 12609 AutoWeakFrame weak(rootFrame); 12610 auto* canvas = GetCanvasFrame(); 12611 windowWidget->SetTransparencyMode(nsLayoutUtils::GetFrameTransparency( 12612 canvas ? canvas : rootFrame, rootFrame)); 12613 if (!weak.IsAlive()) { 12614 return; 12615 } 12616 12617 const auto& constraints = GetWindowSizeConstraints(); 12618 nsContainerFrame::SetSizeConstraints(pc, windowWidget, constraints.mMinSize, 12619 constraints.mMaxSize); 12620 } 12621 12622 nsresult PresShell::HasRuleProcessorUsedByMultipleStyleSets(uint32_t aSheetType, 12623 bool* aRetVal) { 12624 *aRetVal = false; 12625 return NS_OK; 12626 } 12627 12628 void PresShell::NotifyStyleSheetServiceSheetAdded(StyleSheet* aSheet, 12629 uint32_t aSheetType) { 12630 switch (aSheetType) { 12631 case nsIStyleSheetService::AGENT_SHEET: 12632 AddAgentSheet(aSheet); 12633 break; 12634 case nsIStyleSheetService::USER_SHEET: 12635 AddUserSheet(aSheet); 12636 break; 12637 case nsIStyleSheetService::AUTHOR_SHEET: 12638 AddAuthorSheet(aSheet); 12639 break; 12640 default: 12641 MOZ_ASSERT_UNREACHABLE("unexpected aSheetType value"); 12642 break; 12643 } 12644 } 12645 12646 void PresShell::NotifyStyleSheetServiceSheetRemoved(StyleSheet* aSheet, 12647 uint32_t aSheetType) { 12648 StyleSet()->RemoveStyleSheet(*aSheet); 12649 mDocument->ApplicableStylesChanged(); 12650 } 12651 12652 Result<nsIContent*, nsresult> PresShell::EventHandler::GetOverrideClickTarget( 12653 WidgetGUIEvent* aGUIEvent, nsIFrame* aFrameForPresShell, 12654 nsIContent* aPointerCapturingContent) { 12655 if (aGUIEvent->mMessage != eMouseUp) { 12656 return nullptr; 12657 } 12658 12659 // If aFrameForPresShell has already been reframed before this is called, 12660 // we cannot keep handling aGUIEvent. 12661 auto overrideClickTargetOrError = [&]() -> Result<nsIContent*, nsresult> { 12662 if (PointerEventHandler::ShouldDispatchClickEventOnCapturingElement() && 12663 aPointerCapturingContent) { 12664 return aGUIEvent->AsMouseEvent()->mInputSource == 12665 MouseEvent_Binding::MOZ_SOURCE_TOUCH 12666 // If the event is a compatibility mouse event of Touch Events, 12667 // `click` event target should be the element capturing the 12668 // touch (Note that eTouchStart caused implicit pointer capture 12669 // by default when the web app does not use the pointer capture 12670 // API). However, if the web app released the pointer capture, 12671 // the target should be the closest common ancestor of 12672 // ePointerDown and ePointerUp. These things will be handled 12673 // by EventStateManager::SetClickCount(). Therefore, we should 12674 // not override the click event target for a single tap here. 12675 ? nullptr 12676 // On the other hand, we want to use the pointer capturing 12677 // element as the target of `click` event caused by other input 12678 // devices. 12679 : aPointerCapturingContent; 12680 } 12681 12682 if (MOZ_UNLIKELY(!aFrameForPresShell)) { 12683 return Err(NS_ERROR_FAILURE); 12684 } 12685 12686 MOZ_ASSERT(aGUIEvent->mClass == eMouseEventClass); 12687 WidgetMouseEvent* mouseEvent = aGUIEvent->AsMouseEvent(); 12688 12689 uint32_t flags = 0; 12690 RelativeTo relativeTo{aFrameForPresShell}; 12691 nsPoint eventPoint = 12692 nsLayoutUtils::GetEventCoordinatesRelativeTo(aGUIEvent, relativeTo); 12693 if (mouseEvent->mIgnoreRootScrollFrame) { 12694 flags |= INPUT_IGNORE_ROOT_SCROLL_FRAME; 12695 } 12696 12697 nsIFrame* target = 12698 FindFrameTargetedByInputEvent(aGUIEvent, relativeTo, eventPoint, flags); 12699 if (!target) { 12700 return nullptr; 12701 } 12702 return target->GetContent(); 12703 }(); 12704 if (MOZ_UNLIKELY(overrideClickTargetOrError.isErr())) { 12705 return overrideClickTargetOrError; 12706 } 12707 return overrideClickTargetOrError.inspect() 12708 ? overrideClickTargetOrError.inspect() 12709 ->GetInclusiveFlattenedTreeAncestorElement() 12710 : nullptr; 12711 } 12712 12713 /****************************************************************************** 12714 * PresShell::EventHandler::EventTargetData 12715 ******************************************************************************/ 12716 12717 void PresShell::EventHandler::EventTargetData::SetFrameAndComputePresShell( 12718 nsIFrame* aFrameToHandleEvent) { 12719 if (aFrameToHandleEvent) { 12720 mFrame = aFrameToHandleEvent; 12721 mPresShell = aFrameToHandleEvent->PresShell(); 12722 } else { 12723 mFrame = nullptr; 12724 mPresShell = nullptr; 12725 } 12726 } 12727 12728 void PresShell::EventHandler::EventTargetData:: 12729 SetFrameAndComputePresShellAndContent(nsIFrame* aFrameToHandleEvent, 12730 WidgetGUIEvent* aGUIEvent) { 12731 MOZ_ASSERT(aFrameToHandleEvent); 12732 MOZ_ASSERT(aGUIEvent); 12733 12734 SetFrameAndComputePresShell(aFrameToHandleEvent); 12735 SetContentForEventFromFrame(aGUIEvent); 12736 } 12737 12738 void PresShell::EventHandler::EventTargetData::SetContentForEventFromFrame( 12739 WidgetGUIEvent* aGUIEvent) { 12740 MOZ_ASSERT(mFrame); 12741 mContent = mFrame->GetContentForEvent(aGUIEvent); 12742 AssertIfEventTargetContentAndFrameContentMismatch(aGUIEvent); 12743 } 12744 12745 nsIContent* PresShell::EventHandler::EventTargetData::GetFrameContent() const { 12746 return mFrame ? mFrame->GetContent() : nullptr; 12747 } 12748 12749 void PresShell::EventHandler::EventTargetData:: 12750 AssertIfEventTargetContentAndFrameContentMismatch( 12751 const WidgetGUIEvent* aGUIEvent) const { 12752 #ifdef DEBUG 12753 if (!mContent || !mFrame || !mFrame->GetContent()) { 12754 return; 12755 } 12756 12757 // If we know the event, we can compute the target correctly. 12758 if (aGUIEvent) { 12759 MOZ_ASSERT(mContent == mFrame->GetContentForEvent(aGUIEvent)); 12760 return; 12761 } 12762 // If clicking an image map, mFrame should be the image frame, but mContent 12763 // should be the area element which handles the event at the position. 12764 if (mContent->IsHTMLElement(nsGkAtoms::area)) { 12765 MOZ_ASSERT(mContent->GetPrimaryFrame() == mFrame); 12766 return; 12767 } 12768 12769 // Otherwise, we can check only whether mContent is an inclusive ancestor 12770 // element or not. 12771 if (!mContent->IsElement()) { 12772 MOZ_ASSERT(mContent == mFrame->GetContent()); 12773 return; 12774 } 12775 const Element* const closestInclusiveAncestorElement = 12776 mFrame->GetContent()->GetInclusiveFlattenedTreeAncestorElement(); 12777 if (closestInclusiveAncestorElement == mContent) { 12778 return; 12779 } 12780 if (closestInclusiveAncestorElement->IsInNativeAnonymousSubtree() && 12781 (mContent == closestInclusiveAncestorElement 12782 ->FindFirstNonChromeOnlyAccessContent())) { 12783 return; 12784 } 12785 NS_WARNING(nsPrintfCString("mContent=%s", ToString(*mContent).c_str()).get()); 12786 NS_WARNING(nsPrintfCString("mFrame->GetContent()=%s", 12787 ToString(*mFrame->GetContent()).c_str()) 12788 .get()); 12789 MOZ_ASSERT(mContent == mFrame->GetContent()); 12790 #endif // #ifdef DEBUG 12791 } 12792 12793 bool PresShell::EventHandler::EventTargetData::MaybeRetargetToActiveDocument( 12794 WidgetGUIEvent* aGUIEvent) { 12795 MOZ_ASSERT(aGUIEvent); 12796 MOZ_ASSERT(mFrame); 12797 MOZ_ASSERT(mPresShell); 12798 MOZ_ASSERT(!mContent, "Doesn't support to retarget the content"); 12799 12800 EventStateManager* activeESM = 12801 EventStateManager::GetActiveEventStateManager(); 12802 if (!activeESM) { 12803 return false; 12804 } 12805 12806 if (aGUIEvent->mClass != ePointerEventClass && 12807 !aGUIEvent->HasMouseEventMessage()) { 12808 return false; 12809 } 12810 12811 if (activeESM == GetEventStateManager()) { 12812 return false; 12813 } 12814 12815 if (aGUIEvent->ShouldIgnoreCapturingContent()) { 12816 return false; 12817 } 12818 12819 nsPresContext* activePresContext = activeESM->GetPresContext(); 12820 if (!activePresContext) { 12821 return false; 12822 } 12823 12824 PresShell* activePresShell = activePresContext->GetPresShell(); 12825 if (!activePresShell) { 12826 return false; 12827 } 12828 12829 // Note, currently for backwards compatibility we don't forward mouse events 12830 // to the active document when mouse is over some subdocument. 12831 if (!nsContentUtils::ContentIsCrossDocDescendantOf( 12832 activePresShell->GetDocument(), GetDocument())) { 12833 return false; 12834 } 12835 12836 SetFrameAndComputePresShell(activePresShell->GetRootFrame()); 12837 return true; 12838 } 12839 12840 bool PresShell::EventHandler::EventTargetData::ComputeElementFromFrame( 12841 WidgetGUIEvent* aGUIEvent) { 12842 MOZ_ASSERT(aGUIEvent); 12843 MOZ_ASSERT(aGUIEvent->IsUsingCoordinates()); 12844 MOZ_ASSERT(mPresShell); 12845 MOZ_ASSERT(mFrame); 12846 12847 SetContentForEventFromFrame(aGUIEvent); 12848 12849 // If there is no content for this frame, target it anyway. Some frames can 12850 // be targeted but do not have content, particularly windows with scrolling 12851 // off. 12852 if (!mContent) { 12853 return true; 12854 } 12855 12856 // Bug 103055, bug 185889: mouse events apply to *elements*, not all nodes. 12857 // Thus we get the nearest element parent here. 12858 // XXX we leave the frame the same even if we find an element parent, so that 12859 // the text frame will receive the event (selection and friends are the ones 12860 // who care about that anyway) 12861 // 12862 // We use weak pointers because during this tight loop, the node 12863 // will *not* go away. And this happens on every mousemove. 12864 mContent = mContent->GetInclusiveFlattenedTreeAncestorElement(); 12865 12866 // If we found an element, target it. Otherwise, target *nothing*. 12867 return !!mContent; 12868 } 12869 12870 void PresShell::EventHandler::EventTargetData::UpdateWheelEventTarget( 12871 WidgetGUIEvent* aGUIEvent) { 12872 MOZ_ASSERT(aGUIEvent); 12873 12874 if (aGUIEvent->mMessage != eWheel) { 12875 return; 12876 } 12877 12878 // If dom.event.wheel-event-groups.enabled is not set or the stored 12879 // event target is removed, we will not get a event target frame from the 12880 // wheel transaction here. 12881 nsIFrame* groupFrame = WheelTransaction::GetEventTargetFrame(); 12882 if (!groupFrame) { 12883 return; 12884 } 12885 12886 // If dom.event.wheel-event-groups.enabled is set and whe have a stored 12887 // event target from the wheel transaction, override the event target. 12888 SetFrameAndComputePresShellAndContent(groupFrame, aGUIEvent); 12889 } 12890 12891 void PresShell::EventHandler::EventTargetData::UpdateTouchEventTarget( 12892 WidgetGUIEvent* aGUIEvent) { 12893 MOZ_ASSERT(aGUIEvent); 12894 12895 if (aGUIEvent->mClass != eTouchEventClass) { 12896 return; 12897 } 12898 12899 if (aGUIEvent->mMessage == eTouchStart) { 12900 WidgetTouchEvent* touchEvent = aGUIEvent->AsTouchEvent(); 12901 nsIFrame* newFrame = 12902 TouchManager::SuppressInvalidPointsAndGetTargetedFrame(touchEvent); 12903 if (!newFrame) { 12904 return; 12905 } 12906 SetFrameAndComputePresShellAndContent(newFrame, aGUIEvent); 12907 return; 12908 } 12909 12910 PresShell* newPresShell = PresShell::GetShellForTouchEvent(aGUIEvent); 12911 if (!newPresShell) { 12912 return; // XXX Why don't we stop handling the event in this case? 12913 } 12914 12915 // Touch events (except touchstart) are dispatching to the captured 12916 // element. Get correct shell from it. 12917 mPresShell = newPresShell; 12918 } 12919 12920 void PresShell::EndPaint() { 12921 ClearPendingVisualScrollUpdate(); 12922 12923 if (mDocument) { 12924 mDocument->EnumerateSubDocuments([](Document& aSubDoc) { 12925 if (PresShell* presShell = aSubDoc.GetPresShell()) { 12926 presShell->EndPaint(); 12927 } 12928 return CallState::Continue; 12929 }); 12930 12931 if (nsPresContext* presContext = GetPresContext()) { 12932 if (PerformanceMainThread* perf = 12933 presContext->GetPerformanceMainThread()) { 12934 perf->FinalizeLCPEntriesForText(); 12935 } 12936 } 12937 } 12938 } 12939 12940 bool PresShell::GetZoomableByAPZ() const { 12941 return mZoomConstraintsClient && mZoomConstraintsClient->GetAllowZoom(); 12942 } 12943 12944 bool PresShell::ReflowForHiddenContentIfNeeded() { 12945 if (mHiddenContentInForcedLayout.IsEmpty()) { 12946 return false; 12947 } 12948 mDocument->FlushPendingNotifications(FlushType::Layout); 12949 mHiddenContentInForcedLayout.Clear(); 12950 return true; 12951 } 12952 12953 void PresShell::UpdateHiddenContentInForcedLayout(nsIFrame* aFrame) { 12954 if (!aFrame || !aFrame->IsSubtreeDirty()) { 12955 return; 12956 } 12957 12958 nsIFrame* topmostFrameWithContentHidden = nullptr; 12959 for (nsIFrame* cur = aFrame->GetInFlowParent(); cur; 12960 cur = cur->GetInFlowParent()) { 12961 if (cur->HidesContent()) { 12962 topmostFrameWithContentHidden = cur; 12963 mHiddenContentInForcedLayout.Insert(cur->GetContent()); 12964 } 12965 } 12966 12967 if (mHiddenContentInForcedLayout.IsEmpty()) { 12968 return; 12969 } 12970 12971 // Queue and immediately flush a reflow for this node. 12972 MOZ_ASSERT(topmostFrameWithContentHidden); 12973 FrameNeedsReflow(topmostFrameWithContentHidden, IntrinsicDirty::None, 12974 NS_FRAME_IS_DIRTY); 12975 } 12976 12977 void PresShell::EnsureReflowIfFrameHasHiddenContent(nsIFrame* aFrame) { 12978 MOZ_ASSERT(mHiddenContentInForcedLayout.IsEmpty()); 12979 12980 UpdateHiddenContentInForcedLayout(aFrame); 12981 ReflowForHiddenContentIfNeeded(); 12982 } 12983 12984 bool PresShell::IsForcingLayoutForHiddenContent(const nsIFrame* aFrame) const { 12985 return mHiddenContentInForcedLayout.Contains(aFrame->GetContent()); 12986 } 12987 12988 void PresShell::UpdateRelevancyOfContentVisibilityAutoFrames() { 12989 if (mContentVisibilityRelevancyToUpdate.isEmpty()) { 12990 return; 12991 } 12992 12993 for (nsIFrame* frame : mContentVisibilityAutoFrames) { 12994 frame->UpdateIsRelevantContent(mContentVisibilityRelevancyToUpdate); 12995 } 12996 12997 if (nsPresContext* presContext = GetPresContext()) { 12998 presContext->UpdateHiddenByContentVisibilityForAnimationsIfNeeded(); 12999 } 13000 13001 mContentVisibilityRelevancyToUpdate.clear(); 13002 } 13003 13004 void PresShell::ScheduleContentRelevancyUpdate(ContentRelevancyReason aReason) { 13005 if (MOZ_UNLIKELY(mIsDestroying)) { 13006 return; 13007 } 13008 mContentVisibilityRelevancyToUpdate += aReason; 13009 EnsureLayoutFlush(); 13010 } 13011 13012 PresShell::ProximityToViewportResult PresShell::DetermineProximityToViewport() { 13013 ProximityToViewportResult result; 13014 if (mContentVisibilityAutoFrames.IsEmpty()) { 13015 return result; 13016 } 13017 13018 auto margin = LengthPercentage::FromPercentage( 13019 StaticPrefs::layout_css_content_visibility_relevant_content_margin() / 13020 100.0f); 13021 13022 auto rootMargin = StyleRect<LengthPercentage>::WithAllSides(margin); 13023 13024 auto input = DOMIntersectionObserver::ComputeInput( 13025 *mDocument, /* aRoot = */ nullptr, &rootMargin, nullptr); 13026 13027 for (nsIFrame* frame : mContentVisibilityAutoFrames) { 13028 auto* element = frame->GetContent()->AsElement(); 13029 result.mAnyScrollIntoViewFlag |= 13030 element->TemporarilyVisibleForScrolledIntoViewDescendant(); 13031 13032 // 14.2.3.1 13033 Maybe<bool> oldVisibility = element->GetVisibleForContentVisibility(); 13034 bool checkForInitialDetermination = 13035 oldVisibility.isNothing() && 13036 (element->GetContentRelevancy().isNothing() || 13037 element->GetContentRelevancy()->isEmpty()); 13038 13039 // 14.2.3.2 13040 bool intersects = 13041 DOMIntersectionObserver::Intersect( 13042 input, *element, DOMIntersectionObserver::BoxToUse::OverflowClip, 13043 DOMIntersectionObserver::IsForProximityToViewport::Yes) 13044 .Intersects(); 13045 element->SetVisibleForContentVisibility(intersects); 13046 13047 // 14.2.3.3 13048 if (checkForInitialDetermination && intersects) { 13049 // Initial determination happens sync, otherwise on the next rendering 13050 // opportunity. 13051 frame->UpdateIsRelevantContent(ContentRelevancyReason::Visible); 13052 result.mHadInitialDetermination = true; 13053 } else if (oldVisibility.isNothing() || *oldVisibility != intersects) { 13054 ScheduleContentRelevancyUpdate(ContentRelevancyReason::Visible); 13055 } 13056 } 13057 if (nsPresContext* presContext = GetPresContext()) { 13058 presContext->UpdateHiddenByContentVisibilityForAnimationsIfNeeded(); 13059 } 13060 13061 return result; 13062 } 13063 13064 void PresShell::ClearTemporarilyVisibleForScrolledIntoViewDescendantFlags() 13065 const { 13066 for (nsIFrame* frame : mContentVisibilityAutoFrames) { 13067 frame->GetContent() 13068 ->AsElement() 13069 ->SetTemporarilyVisibleForScrolledIntoViewDescendant(false); 13070 } 13071 } 13072 13073 void PresShell::UpdateContentRelevancyImmediately( 13074 ContentRelevancyReason aReason) { 13075 if (MOZ_UNLIKELY(mIsDestroying)) { 13076 return; 13077 } 13078 13079 mContentVisibilityRelevancyToUpdate += aReason; 13080 13081 EnsureLayoutFlush(); 13082 UpdateRelevancyOfContentVisibilityAutoFrames(); 13083 }