ScrollContainerFrame.cpp (314421B)
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 /* rendering object to wrap rendering objects that should be scrollable */ 8 9 #include "mozilla/ScrollContainerFrame.h" 10 11 #include <stdint.h> 12 13 #include <algorithm> 14 #include <cmath> // for std::abs(float/double) 15 #include <cstdlib> // for std::abs(int/long) 16 #include <tuple> // for std::tie 17 18 #include "DisplayItemClip.h" 19 #include "MobileViewportManager.h" 20 #include "ScrollAnimationBezierPhysics.h" 21 #include "ScrollAnimationMSDPhysics.h" 22 #include "ScrollAnimationPhysics.h" 23 #include "ScrollPositionUpdate.h" 24 #include "ScrollSnap.h" 25 #include "ScrollbarActivity.h" 26 #include "StickyScrollContainer.h" 27 #include "TextOverflow.h" 28 #include "UnitTransforms.h" 29 #include "ViewportFrame.h" 30 #include "VisualViewport.h" 31 #include "WindowRenderer.h" 32 #include "gfxPlatform.h" 33 #include "mozilla/Attributes.h" 34 #include "mozilla/ContentEvents.h" 35 #include "mozilla/DisplayPortUtils.h" 36 #include "mozilla/EventDispatcher.h" 37 #include "mozilla/LookAndFeel.h" 38 #include "mozilla/MathAlgorithms.h" 39 #include "mozilla/Preferences.h" 40 #include "mozilla/PresShell.h" 41 #include "mozilla/PresState.h" 42 #include "mozilla/SVGOuterSVGFrame.h" 43 #include "mozilla/ScopeExit.h" 44 #include "mozilla/ScrollbarPreferences.h" 45 #include "mozilla/ScrollingMetrics.h" 46 #include "mozilla/StaticPrefs_apz.h" 47 #include "mozilla/StaticPrefs_bidi.h" 48 #include "mozilla/StaticPrefs_browser.h" 49 #include "mozilla/StaticPrefs_general.h" 50 #include "mozilla/StaticPrefs_layers.h" 51 #include "mozilla/StaticPrefs_layout.h" 52 #include "mozilla/StaticPrefs_mousewheel.h" 53 #include "mozilla/StaticPrefs_toolkit.h" 54 #include "mozilla/StaticPtr.h" 55 #include "mozilla/ToString.h" 56 #include "mozilla/ViewportUtils.h" 57 #include "mozilla/dom/BrowserChild.h" 58 #include "mozilla/dom/DocumentInlines.h" 59 #include "mozilla/dom/Element.h" 60 #include "mozilla/dom/Event.h" 61 #include "mozilla/dom/HTMLMarqueeElement.h" 62 #include "mozilla/dom/HTMLOptionElement.h" 63 #include "mozilla/dom/NodeInfo.h" 64 #include "mozilla/dom/ScrollTimeline.h" 65 #include "mozilla/gfx/gfxVars.h" 66 #include "mozilla/intl/BidiEmbeddingLevel.h" 67 #include "mozilla/layers/APZCCallbackHelper.h" 68 #include "mozilla/layers/APZPublicUtils.h" 69 #include "mozilla/layers/AxisPhysicsMSDModel.h" 70 #include "mozilla/layers/AxisPhysicsModel.h" 71 #include "mozilla/layers/LayersTypes.h" 72 #include "mozilla/layers/ScrollLinkedEffectDetector.h" 73 #include "mozilla/layers/ScrollingInteractionContext.h" 74 #include "nsBidiPresUtils.h" 75 #include "nsBidiUtils.h" 76 #include "nsBlockFrame.h" 77 #include "nsCOMPtr.h" 78 #include "nsCSSRendering.h" 79 #include "nsContainerFrame.h" 80 #include "nsContentCreatorFunctions.h" 81 #include "nsContentUtils.h" 82 #include "nsDisplayList.h" 83 #include "nsDocShell.h" 84 #include "nsFlexContainerFrame.h" 85 #include "nsFontMetrics.h" 86 #include "nsGkAtoms.h" 87 #include "nsHTMLDocument.h" 88 #include "nsIDocumentViewer.h" 89 #include "nsIFrameInlines.h" 90 #include "nsILayoutHistoryState.h" 91 #include "nsINode.h" 92 #include "nsIScrollbarMediator.h" 93 #include "nsIXULRuntime.h" 94 #include "nsLayoutUtils.h" 95 #include "nsListControlFrame.h" 96 #include "nsNameSpaceManager.h" 97 #include "nsNodeInfoManager.h" 98 #include "nsPresContext.h" 99 #include "nsPresContextInlines.h" 100 #include "nsRefreshDriver.h" 101 #include "nsScrollbarFrame.h" 102 #include "nsSliderFrame.h" 103 #include "nsStyleConsts.h" 104 #include "nsStyleTransformMatrix.h" 105 #include "nsSubDocumentFrame.h" 106 #include "nsViewportInfo.h" 107 108 static mozilla::LazyLogModule sApzPaintSkipLog("apz.paintskip"); 109 #define PAINT_SKIP_LOG(...) \ 110 MOZ_LOG(sApzPaintSkipLog, LogLevel::Debug, (__VA_ARGS__)) 111 static mozilla::LazyLogModule sScrollRestoreLog("scrollrestore"); 112 #define SCROLLRESTORE_LOG(...) \ 113 MOZ_LOG(sScrollRestoreLog, LogLevel::Debug, (__VA_ARGS__)) 114 static mozilla::LazyLogModule sRootScrollbarsLog("rootscrollbars"); 115 #define ROOT_SCROLLBAR_LOG(...) \ 116 if (mIsRoot) { \ 117 MOZ_LOG(sRootScrollbarsLog, LogLevel::Debug, (__VA_ARGS__)); \ 118 } 119 static mozilla::LazyLogModule sDisplayportLog("apz.displayport"); 120 121 using namespace mozilla; 122 using namespace mozilla::dom; 123 using namespace mozilla::gfx; 124 using namespace mozilla::layers; 125 using namespace mozilla::layout; 126 using nsStyleTransformMatrix::TransformReferenceBox; 127 128 static ScrollDirections GetOverflowChange(const nsRect& aCurScrolledRect, 129 const nsRect& aPrevScrolledRect) { 130 ScrollDirections result; 131 if (aPrevScrolledRect.x != aCurScrolledRect.x || 132 aPrevScrolledRect.width != aCurScrolledRect.width) { 133 result += ScrollDirection::eHorizontal; 134 } 135 if (aPrevScrolledRect.y != aCurScrolledRect.y || 136 aPrevScrolledRect.height != aCurScrolledRect.height) { 137 result += ScrollDirection::eVertical; 138 } 139 return result; 140 } 141 142 /** 143 * This class handles the dispatching of scroll events to content. 144 * 145 * Scroll events are posted to the refresh driver via 146 * nsRefreshDriver::PostScrollEvent(), and they are fired during a refresh 147 * driver tick, after running requestAnimationFrame callbacks but before 148 * the style flush. This allows rAF callbacks to perform scrolling and have 149 * that scrolling be reflected on the same refresh driver tick, while at 150 * the same time allowing scroll event listeners to make style changes and 151 * have those style changes be reflected on the same refresh driver tick. 152 * 153 * ScrollEvents cannot be refresh observers, because none of the existing 154 * categories of refresh observers (FlushType::Style, FlushType::Layout, 155 * and FlushType::Display) are run at the desired time in a refresh driver 156 * tick. They behave similarly to refresh observers in that their presence 157 * causes the refresh driver to tick. 158 * 159 * ScrollEvents are one-shot runnables; the refresh driver drops them after 160 * running them. 161 */ 162 class ScrollContainerFrame::ScrollEvent : public Runnable { 163 public: 164 NS_DECL_NSIRUNNABLE 165 explicit ScrollEvent(ScrollContainerFrame* aHelper); 166 void Revoke() { mHelper = nullptr; } 167 168 private: 169 ScrollContainerFrame* mHelper; 170 }; 171 172 class ScrollContainerFrame::ScrollEndEvent : public Runnable { 173 public: 174 NS_DECL_NSIRUNNABLE 175 explicit ScrollEndEvent(ScrollContainerFrame* aHelper); 176 void Revoke() { mHelper = nullptr; } 177 178 private: 179 ScrollContainerFrame* mHelper; 180 }; 181 182 class ScrollContainerFrame::AsyncScrollPortEvent : public Runnable { 183 public: 184 NS_DECL_NSIRUNNABLE 185 explicit AsyncScrollPortEvent(ScrollContainerFrame* helper) 186 : Runnable("ScrollContainerFrame::AsyncScrollPortEvent"), 187 mHelper(helper) {} 188 void Revoke() { mHelper = nullptr; } 189 190 private: 191 ScrollContainerFrame* mHelper; 192 }; 193 194 class ScrollContainerFrame::ScrolledAreaEvent : public Runnable { 195 public: 196 NS_DECL_NSIRUNNABLE 197 explicit ScrolledAreaEvent(ScrollContainerFrame* helper) 198 : Runnable("ScrollContainerFrame::ScrolledAreaEvent"), mHelper(helper) {} 199 void Revoke() { mHelper = nullptr; } 200 201 private: 202 ScrollContainerFrame* mHelper; 203 }; 204 205 class ScrollFrameActivityTracker final 206 : public nsExpirationTracker<ScrollContainerFrame, 4> { 207 public: 208 // Wait for 3-4s between scrolls before we remove our layers. 209 // That's 4 generations of 1s each. 210 enum { TIMEOUT_MS = 1000 }; 211 explicit ScrollFrameActivityTracker(nsIEventTarget* aEventTarget) 212 : nsExpirationTracker<ScrollContainerFrame, 4>( 213 TIMEOUT_MS, "ScrollFrameActivityTracker"_ns, aEventTarget) {} 214 ~ScrollFrameActivityTracker() { AgeAllGenerations(); } 215 216 virtual void NotifyExpired(ScrollContainerFrame* aObject) override { 217 RemoveObject(aObject); 218 aObject->MarkNotRecentlyScrolled(); 219 } 220 }; 221 static StaticAutoPtr<ScrollFrameActivityTracker> gScrollFrameActivityTracker; 222 223 ScrollContainerFrame* NS_NewScrollContainerFrame(mozilla::PresShell* aPresShell, 224 ComputedStyle* aStyle, 225 bool aIsRoot) { 226 return new (aPresShell) 227 ScrollContainerFrame(aStyle, aPresShell->GetPresContext(), aIsRoot); 228 } 229 230 NS_IMPL_FRAMEARENA_HELPERS(ScrollContainerFrame) 231 232 ScrollContainerFrame::ScrollContainerFrame(ComputedStyle* aStyle, 233 nsPresContext* aPresContext, 234 nsIFrame::ClassID aID, bool aIsRoot) 235 : nsContainerFrame(aStyle, aPresContext, aID), 236 mHScrollbarBox(nullptr), 237 mVScrollbarBox(nullptr), 238 mScrolledFrame(nullptr), 239 mScrollCornerBox(nullptr), 240 mResizerBox(nullptr), 241 mReferenceFrameDuringPainting(nullptr), 242 mAsyncScroll(nullptr), 243 mAsyncSmoothMSDScroll(nullptr), 244 mLastScrollOrigin(ScrollOrigin::None), 245 mDestination(0, 0), 246 mRestorePos(-1, -1), 247 mLastPos(-1, -1), 248 mApzScrollPos(0, 0), 249 mLastUpdateFramesPos(-1, -1), 250 mScrollParentID(mozilla::layers::ScrollableLayerGuid::NULL_SCROLL_ID), 251 mAnchor(this), 252 mCurrentAPZScrollAnimationType(APZScrollAnimationType::No), 253 mIsFirstScrollableFrameSequenceNumber(Nothing()), 254 mInScrollingGesture(InScrollingGesture::No), 255 mAllowScrollOriginDowngrade(false), 256 mHadDisplayPortAtLastFrameUpdate(false), 257 mHasVerticalScrollbar(false), 258 mHasHorizontalScrollbar(false), 259 mOnlyNeedVScrollbarToScrollVVInsideLV(false), 260 mOnlyNeedHScrollbarToScrollVVInsideLV(false), 261 mFrameIsUpdatingScrollbar(false), 262 mDidHistoryRestore(false), 263 mIsRoot(aIsRoot), 264 mSkippedScrollbarLayout(false), 265 mHadNonInitialReflow(false), 266 mFirstReflow(true), 267 mHorizontalOverflow(false), 268 mVerticalOverflow(false), 269 mPostedReflowCallback(false), 270 mMayHaveDirtyFixedChildren(false), 271 mUpdateScrollbarAttributes(false), 272 mHasBeenScrolledRecently(false), 273 mWillBuildScrollableLayer(false), 274 mIsParentToActiveScrollFrames(false), 275 mHasBeenScrolled(false), 276 mIgnoreMomentumScroll(false), 277 mTransformingByAPZ(false), 278 mScrollableByAPZ(false), 279 mZoomableByAPZ(false), 280 mHasOutOfFlowContentInsideFilter(false), 281 mSuppressScrollbarRepaints(false), 282 mIsUsingMinimumScaleSize(false), 283 mMinimumScaleSizeChanged(false), 284 mProcessingScrollEvent(false), 285 mApzAnimationRequested(false), 286 mApzAnimationTriggeredByScriptRequested(false), 287 mReclampVVOffsetInReflowFinished(false), 288 mMayScheduleScrollAnimations(false), 289 #ifdef MOZ_WIDGET_ANDROID 290 mHasVerticalOverflowForDynamicToolbar(false), 291 #endif 292 mVelocityQueue(PresContext()) { 293 AppendScrollUpdate(ScrollPositionUpdate::NewScrollframe(nsPoint())); 294 295 if (UsesOverlayScrollbars()) { 296 mScrollbarActivity = new ScrollbarActivity(this); 297 } 298 299 if (mIsRoot) { 300 mZoomableByAPZ = PresShell()->GetZoomableByAPZ(); 301 } 302 } 303 304 ScrollContainerFrame::~ScrollContainerFrame() = default; 305 306 void ScrollContainerFrame::ScrollbarActivityStarted() const { 307 if (mScrollbarActivity) { 308 mScrollbarActivity->ActivityStarted(); 309 } 310 } 311 312 void ScrollContainerFrame::ScrollbarActivityStopped() const { 313 if (mScrollbarActivity) { 314 mScrollbarActivity->ActivityStopped(); 315 } 316 } 317 318 void ScrollContainerFrame::Destroy(DestroyContext& aContext) { 319 DestroyAbsoluteFrames(aContext); 320 if (mIsRoot) { 321 PresShell()->ResetVisualViewportOffset(); 322 } 323 324 mAnchor.Destroy(); 325 326 if (mScrollbarActivity) { 327 mScrollbarActivity->Destroy(); 328 mScrollbarActivity = nullptr; 329 } 330 331 // Unbind the content created in CreateAnonymousContent later... 332 aContext.AddAnonymousContent(mHScrollbarContent.forget()); 333 aContext.AddAnonymousContent(mVScrollbarContent.forget()); 334 aContext.AddAnonymousContent(mScrollCornerContent.forget()); 335 aContext.AddAnonymousContent(mResizerContent.forget()); 336 337 if (mPostedReflowCallback) { 338 PresShell()->CancelReflowCallback(this); 339 mPostedReflowCallback = false; 340 } 341 342 if (mDisplayPortExpiryTimer) { 343 mDisplayPortExpiryTimer->Cancel(); 344 mDisplayPortExpiryTimer = nullptr; 345 } 346 if (mActivityExpirationState.IsTracked()) { 347 gScrollFrameActivityTracker->RemoveObject(this); 348 } 349 if (gScrollFrameActivityTracker && gScrollFrameActivityTracker->IsEmpty()) { 350 gScrollFrameActivityTracker = nullptr; 351 } 352 353 if (mScrollActivityTimer) { 354 mScrollActivityTimer->Cancel(); 355 mScrollActivityTimer = nullptr; 356 } 357 RemoveObservers(); 358 if (mScrollEvent) { 359 mScrollEvent->Revoke(); 360 } 361 if (mScrollEndEvent) { 362 mScrollEndEvent->Revoke(); 363 } 364 nsContainerFrame::Destroy(aContext); 365 } 366 367 void ScrollContainerFrame::SetInitialChildList(ChildListID aListID, 368 nsFrameList&& aChildList) { 369 nsContainerFrame::SetInitialChildList(aListID, std::move(aChildList)); 370 ReloadChildFrames(); 371 } 372 373 void ScrollContainerFrame::AppendFrames(ChildListID aListID, 374 nsFrameList&& aFrameList) { 375 NS_ASSERTION(aListID == FrameChildListID::Principal, 376 "Only main list supported"); 377 mFrames.AppendFrames(nullptr, std::move(aFrameList)); 378 ReloadChildFrames(); 379 } 380 381 void ScrollContainerFrame::InsertFrames( 382 ChildListID aListID, nsIFrame* aPrevFrame, 383 const nsLineList::iterator* aPrevFrameLine, nsFrameList&& aFrameList) { 384 NS_ASSERTION(aListID == FrameChildListID::Principal, 385 "Only main list supported"); 386 NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this, 387 "inserting after sibling frame with different parent"); 388 mFrames.InsertFrames(nullptr, aPrevFrame, std::move(aFrameList)); 389 ReloadChildFrames(); 390 } 391 392 void ScrollContainerFrame::RemoveFrame(DestroyContext& aContext, 393 ChildListID aListID, 394 nsIFrame* aOldFrame) { 395 NS_ASSERTION(aListID == FrameChildListID::Principal, 396 "Only main list supported"); 397 mFrames.DestroyFrame(aContext, aOldFrame); 398 ReloadChildFrames(); 399 } 400 401 /** 402 HTML scrolling implementation 403 404 All other things being equal, we prefer layouts with fewer scrollbars showing. 405 */ 406 407 namespace mozilla { 408 409 enum class ShowScrollbar : uint8_t { 410 Auto, 411 Always, 412 // Never is a misnomer. We can still get a scrollbar if we need to scroll the 413 // visual viewport inside the layout viewport. Thus this enum is best thought 414 // of as value used by layout, which does not know about the visual viewport. 415 // The visual viewport does not affect any layout sizes, so this is sound. 416 Never, 417 }; 418 419 static ShowScrollbar ShouldShowScrollbar(StyleOverflow aOverflow) { 420 switch (aOverflow) { 421 case StyleOverflow::Scroll: 422 return ShowScrollbar::Always; 423 case StyleOverflow::Hidden: 424 return ShowScrollbar::Never; 425 default: 426 case StyleOverflow::Auto: 427 return ShowScrollbar::Auto; 428 } 429 } 430 431 struct MOZ_STACK_CLASS ScrollReflowInput { 432 // === Filled in by the constructor. Members in this section shouldn't change 433 // their values after the constructor. === 434 const ReflowInput& mReflowInput; 435 ShowScrollbar mHScrollbar; 436 // If the horizontal scrollbar is allowed (even if mHScrollbar == 437 // ShowScrollbar::Never) provided that it is for scrolling the visual viewport 438 // inside the layout viewport only. 439 bool mHScrollbarAllowedForScrollingVVInsideLV = true; 440 ShowScrollbar mVScrollbar; 441 // If the vertical scrollbar is allowed (even if mVScrollbar == 442 // ShowScrollbar::Never) provided that it is for scrolling the visual viewport 443 // inside the layout viewport only. 444 bool mVScrollbarAllowedForScrollingVVInsideLV = true; 445 nsMargin mComputedBorder; 446 447 // === Filled in by ReflowScrolledFrame === 448 OverflowAreas mContentsOverflowAreas; 449 // The scrollbar gutter sizes used in the most recent reflow of 450 // mScrolledFrame. The writing-mode is the same as the scroll 451 // container. 452 LogicalMargin mScrollbarGutterFromLastReflow; 453 // True if the most recent reflow of mScrolledFrame is with the 454 // horizontal scrollbar. 455 bool mReflowedContentsWithHScrollbar = false; 456 // True if the most recent reflow of mScrolledFrame is with the 457 // vertical scrollbar. 458 bool mReflowedContentsWithVScrollbar = false; 459 460 // === Filled in when TryLayout succeeds === 461 // The size of the inside-border area 462 nsSize mInsideBorderSize; 463 // Whether we decided to show the horizontal scrollbar in the most recent 464 // TryLayout. 465 bool mShowHScrollbar = false; 466 // Whether we decided to show the vertical scrollbar in the most recent 467 // TryLayout. 468 bool mShowVScrollbar = false; 469 // If mShow(H|V)Scrollbar is true then 470 // mOnlyNeed(V|H)ScrollbarToScrollVVInsideLV indicates if the only reason we 471 // need that scrollbar is to scroll the visual viewport inside the layout 472 // viewport. These scrollbars are special in that even if they are layout 473 // scrollbars they do not take up any layout space. 474 bool mOnlyNeedHScrollbarToScrollVVInsideLV = false; 475 bool mOnlyNeedVScrollbarToScrollVVInsideLV = false; 476 477 ScrollReflowInput(ScrollContainerFrame* aFrame, 478 const ReflowInput& aReflowInput); 479 480 nscoord VScrollbarMinHeight() const { return mVScrollbarPrefSize.height; } 481 nscoord VScrollbarPrefWidth() const { return mVScrollbarPrefSize.width; } 482 nscoord HScrollbarMinWidth() const { return mHScrollbarPrefSize.width; } 483 nscoord HScrollbarPrefHeight() const { return mHScrollbarPrefSize.height; } 484 485 // Returns the sizes occupied by the scrollbar gutters. If aShowVScroll or 486 // aShowHScroll is true, the sizes occupied by the scrollbars are also 487 // included. 488 nsMargin ScrollbarGutter(bool aShowVScrollbar, bool aShowHScrollbar, 489 bool aScrollbarOnRight) const { 490 if (mOverlayScrollbars) { 491 return mScrollbarGutter; 492 } 493 nsMargin gutter = mScrollbarGutter; 494 if (aShowVScrollbar && gutter.right == 0 && gutter.left == 0) { 495 const nscoord w = VScrollbarPrefWidth(); 496 if (aScrollbarOnRight) { 497 gutter.right = w; 498 } else { 499 gutter.left = w; 500 } 501 } 502 if (aShowHScrollbar && gutter.bottom == 0) { 503 // The horizontal scrollbar is always at the bottom side. 504 gutter.bottom = HScrollbarPrefHeight(); 505 } 506 return gutter; 507 } 508 509 bool OverlayScrollbars() const { return mOverlayScrollbars; } 510 511 private: 512 // Filled in by the constructor. Put variables here to keep them unchanged 513 // after initializing them in the constructor. 514 nsSize mVScrollbarPrefSize; 515 nsSize mHScrollbarPrefSize; 516 bool mOverlayScrollbars = false; 517 // The scrollbar gutter sizes resolved from the scrollbar-gutter and 518 // scrollbar-width property. 519 nsMargin mScrollbarGutter; 520 }; 521 522 ScrollReflowInput::ScrollReflowInput(ScrollContainerFrame* aFrame, 523 const ReflowInput& aReflowInput) 524 : mReflowInput(aReflowInput), 525 mComputedBorder(aReflowInput.ComputedPhysicalBorderPadding() - 526 aReflowInput.ComputedPhysicalPadding()), 527 mScrollbarGutterFromLastReflow(aFrame->GetWritingMode()) { 528 ScrollStyles styles = aFrame->GetScrollStyles(); 529 mHScrollbar = ShouldShowScrollbar(styles.mHorizontal); 530 mVScrollbar = ShouldShowScrollbar(styles.mVertical); 531 mOverlayScrollbars = aFrame->UsesOverlayScrollbars(); 532 533 if (nsScrollbarFrame* scrollbar = aFrame->GetScrollbarBox(false)) { 534 mHScrollbarPrefSize = scrollbar->ScrollbarMinSize(); 535 // A zero minimum size is a bug with non-overlay scrollbars. That means 536 // we'll always try to place the scrollbar, even if it will ultimately not 537 // fit, see bug 1809630. XUL collapsing is the exception because the 538 // front-end uses it. 539 MOZ_ASSERT(mHScrollbarPrefSize.width && mHScrollbarPrefSize.height, 540 "Shouldn't have a zero horizontal scrollbar-size"); 541 } else { 542 mHScrollbar = ShowScrollbar::Never; 543 mHScrollbarAllowedForScrollingVVInsideLV = false; 544 } 545 if (nsScrollbarFrame* scrollbar = aFrame->GetScrollbarBox(true)) { 546 mVScrollbarPrefSize = scrollbar->ScrollbarMinSize(); 547 // See above. 548 MOZ_ASSERT(mVScrollbarPrefSize.width && mVScrollbarPrefSize.height, 549 "Shouldn't have a zero vertical scrollbar-size"); 550 } else { 551 mVScrollbar = ShowScrollbar::Never; 552 mVScrollbarAllowedForScrollingVVInsideLV = false; 553 } 554 555 const auto* scrollbarStyle = 556 nsLayoutUtils::StyleForScrollbar(mReflowInput.mFrame); 557 // Hide the scrollbar when the scrollbar-width is set to none. 558 // 559 // Note: In some cases this is unnecessary, because scrollbar-width:none 560 // makes us suppress scrollbars in CreateAnonymousContent. But if this frame 561 // initially had a non-'none' scrollbar-width and dynamically changed to 562 // 'none', then we'll need to handle it here. 563 const auto scrollbarWidth = scrollbarStyle->StyleUIReset()->ScrollbarWidth(); 564 if (scrollbarWidth == StyleScrollbarWidth::None) { 565 mHScrollbar = ShowScrollbar::Never; 566 mHScrollbarAllowedForScrollingVVInsideLV = false; 567 mVScrollbar = ShowScrollbar::Never; 568 mVScrollbarAllowedForScrollingVVInsideLV = false; 569 } 570 571 mScrollbarGutter = aFrame->ComputeStableScrollbarGutter( 572 scrollbarWidth, scrollbarStyle->StyleDisplay()->mScrollbarGutter); 573 } 574 575 } // namespace mozilla 576 577 static nsSize ComputeInsideBorderSize(const ScrollReflowInput& aState, 578 const nsSize& aDesiredInsideBorderSize) { 579 // aDesiredInsideBorderSize is the frame size; i.e., it includes 580 // borders and padding (but the scrolled child doesn't have 581 // borders). The scrolled child has the same padding as us. 582 const WritingMode wm = aState.mReflowInput.GetWritingMode(); 583 const LogicalSize desiredInsideBorderSize(wm, aDesiredInsideBorderSize); 584 LogicalSize contentSize = aState.mReflowInput.ComputedSize(); 585 const LogicalMargin padding = aState.mReflowInput.ComputedLogicalPadding(wm); 586 587 if (contentSize.ISize(wm) == NS_UNCONSTRAINEDSIZE) { 588 contentSize.ISize(wm) = 589 desiredInsideBorderSize.ISize(wm) - padding.IStartEnd(wm); 590 } 591 if (contentSize.BSize(wm) == NS_UNCONSTRAINEDSIZE) { 592 contentSize.BSize(wm) = 593 desiredInsideBorderSize.BSize(wm) - padding.BStartEnd(wm); 594 } 595 596 contentSize.ISize(wm) = 597 aState.mReflowInput.ApplyMinMaxISize(contentSize.ISize(wm)); 598 contentSize.BSize(wm) = 599 aState.mReflowInput.ApplyMinMaxBSize(contentSize.BSize(wm)); 600 601 return (contentSize + padding.Size(wm)).GetPhysicalSize(wm); 602 } 603 604 /** 605 * Assuming that we know the metrics for our wrapped frame and 606 * whether the horizontal and/or vertical scrollbars are present, 607 * compute the resulting layout and return true if the layout is 608 * consistent. If the layout is consistent then we fill in the 609 * computed fields of the ScrollReflowInput. 610 * 611 * The layout is consistent when both scrollbars are showing if and only 612 * if they should be showing. A horizontal scrollbar should be showing if all 613 * following conditions are met: 614 * 1) the style is not HIDDEN 615 * 2) our inside-border height is at least the scrollbar height (i.e., the 616 * scrollbar fits vertically) 617 * 3) the style is SCROLL, or the kid's overflow-area XMost is 618 * greater than the scrollport width 619 * 620 * @param aForce if true, then we just assume the layout is consistent. 621 */ 622 bool ScrollContainerFrame::TryLayout(ScrollReflowInput& aState, 623 ReflowOutput* aKidMetrics, 624 bool aAssumeHScroll, bool aAssumeVScroll, 625 bool aForce) { 626 if ((aState.mVScrollbar == ShowScrollbar::Never && aAssumeVScroll) || 627 (aState.mHScrollbar == ShowScrollbar::Never && aAssumeHScroll)) { 628 NS_ASSERTION(!aForce, "Shouldn't be forcing a hidden scrollbar to show!"); 629 return false; 630 } 631 632 const auto wm = GetWritingMode(); 633 const nsMargin scrollbarGutter = aState.ScrollbarGutter( 634 aAssumeVScroll, aAssumeHScroll, IsScrollbarOnRight()); 635 const LogicalMargin logicalScrollbarGutter(wm, scrollbarGutter); 636 637 const bool inlineEndsGutterChanged = 638 aState.mScrollbarGutterFromLastReflow.IStartEnd(wm) != 639 logicalScrollbarGutter.IStartEnd(wm); 640 const bool blockEndsGutterChanged = 641 aState.mScrollbarGutterFromLastReflow.BStartEnd(wm) != 642 logicalScrollbarGutter.BStartEnd(wm); 643 const bool shouldReflowScrolledFrame = 644 inlineEndsGutterChanged || 645 (blockEndsGutterChanged && ScrolledContentDependsOnBSize(aState)); 646 647 if (shouldReflowScrolledFrame) { 648 if (blockEndsGutterChanged) { 649 nsLayoutUtils::MarkIntrinsicISizesDirtyIfDependentOnBSize(mScrolledFrame); 650 } 651 aKidMetrics->mOverflowAreas.Clear(); 652 ROOT_SCROLLBAR_LOG( 653 "TryLayout reflowing scrolled frame with scrollbars h=%d, v=%d\n", 654 aAssumeHScroll, aAssumeVScroll); 655 ReflowScrolledFrame(aState, aAssumeHScroll, aAssumeVScroll, aKidMetrics); 656 } 657 658 const nsSize scrollbarGutterSize(scrollbarGutter.LeftRight(), 659 scrollbarGutter.TopBottom()); 660 661 // First, compute our inside-border size and scrollport size 662 nsSize kidSize = GetContainSizeAxes().ContainSize( 663 aKidMetrics->PhysicalSize(), *aState.mReflowInput.mFrame); 664 const nsSize desiredInsideBorderSize = kidSize + scrollbarGutterSize; 665 aState.mInsideBorderSize = 666 ComputeInsideBorderSize(aState, desiredInsideBorderSize); 667 668 nsSize layoutSize = 669 mIsUsingMinimumScaleSize ? mMinimumScaleSize : aState.mInsideBorderSize; 670 671 const nsSize scrollPortSize = 672 Max(nsSize(0, 0), layoutSize - scrollbarGutterSize); 673 if (mIsUsingMinimumScaleSize) { 674 mICBSize = 675 Max(nsSize(0, 0), aState.mInsideBorderSize - scrollbarGutterSize); 676 } 677 678 nsSize visualViewportSize = scrollPortSize; 679 ROOT_SCROLLBAR_LOG("TryLayout with VV %s\n", 680 ToString(visualViewportSize).c_str()); 681 mozilla::PresShell* presShell = PresShell(); 682 // Note: we check for a non-null MobileViepwortManager here, but ideally we 683 // should be able to drop that clause as well. It's just that in some cases 684 // with extension popups the composition size comes back as stale, because 685 // the content viewer is only resized after the popup contents are reflowed. 686 // That case also happens to have no APZ and no MVM, so we use that as a 687 // way to detect the scenario. Bug 1648669 tracks removing this clause. 688 if (mIsRoot && presShell->GetMobileViewportManager()) { 689 visualViewportSize = nsLayoutUtils::CalculateCompositionSizeForFrame( 690 this, false, &layoutSize); 691 visualViewportSize = 692 Max(nsSize(0, 0), visualViewportSize - scrollbarGutterSize); 693 694 float resolution = presShell->GetResolution(); 695 visualViewportSize.width /= resolution; 696 visualViewportSize.height /= resolution; 697 ROOT_SCROLLBAR_LOG("TryLayout now with VV %s\n", 698 ToString(visualViewportSize).c_str()); 699 } 700 701 nsRect overflowRect = aState.mContentsOverflowAreas.ScrollableOverflow(); 702 // If the content height expanded by the minimum-scale will be taller than 703 // the scrollable overflow area, we need to expand the area here to tell 704 // properly whether we need to render the overlay vertical scrollbar. 705 // NOTE: This expanded size should NOT be used for non-overley scrollbars 706 // cases since putting the vertical non-overlay scrollbar will make the 707 // content width narrow a little bit, which in turn the minimum scale value 708 // becomes a bit bigger than before, then the vertical scrollbar is no longer 709 // needed, which means the content width becomes the original width, then the 710 // minimum-scale is changed to the original one, and so forth. 711 if (UsesOverlayScrollbars() && mIsUsingMinimumScaleSize && 712 mMinimumScaleSize.height > overflowRect.YMost()) { 713 overflowRect.height += mMinimumScaleSize.height - overflowRect.YMost(); 714 } 715 nsRect scrolledRect = 716 GetUnsnappedScrolledRectInternal(overflowRect, scrollPortSize); 717 ROOT_SCROLLBAR_LOG( 718 "TryLayout scrolledRect:%s overflowRect:%s scrollportSize:%s\n", 719 ToString(scrolledRect).c_str(), ToString(overflowRect).c_str(), 720 ToString(scrollPortSize).c_str()); 721 nscoord oneDevPixel = PresContext()->DevPixelsToAppUnits(1); 722 723 bool showHScrollbar = aAssumeHScroll; 724 bool showVScrollbar = aAssumeVScroll; 725 if (!aForce) { 726 nsSize sizeToCompare = visualViewportSize; 727 if (gfxPlatform::UseDesktopZoomingScrollbars()) { 728 sizeToCompare = scrollPortSize; 729 } 730 731 // No need to compute showHScrollbar if we got ShowScrollbar::Never. 732 if (aState.mHScrollbar != ShowScrollbar::Never) { 733 showHScrollbar = 734 aState.mHScrollbar == ShowScrollbar::Always || 735 scrolledRect.XMost() >= sizeToCompare.width + oneDevPixel || 736 scrolledRect.x <= -oneDevPixel; 737 // TODO(emilio): This should probably check this scrollbar's minimum size 738 // in both axes, for consistency? 739 if (aState.mHScrollbar == ShowScrollbar::Auto && 740 scrollPortSize.width < aState.HScrollbarMinWidth()) { 741 showHScrollbar = false; 742 } 743 ROOT_SCROLLBAR_LOG("TryLayout wants H Scrollbar: %d =? %d\n", 744 showHScrollbar, aAssumeHScroll); 745 } 746 747 // No need to compute showVScrollbar if we got ShowScrollbar::Never. 748 if (aState.mVScrollbar != ShowScrollbar::Never) { 749 showVScrollbar = 750 aState.mVScrollbar == ShowScrollbar::Always || 751 scrolledRect.YMost() >= sizeToCompare.height + oneDevPixel || 752 scrolledRect.y <= -oneDevPixel; 753 // TODO(emilio): This should probably check this scrollbar's minimum size 754 // in both axes, for consistency? 755 if (aState.mVScrollbar == ShowScrollbar::Auto && 756 scrollPortSize.height < aState.VScrollbarMinHeight()) { 757 showVScrollbar = false; 758 } 759 ROOT_SCROLLBAR_LOG("TryLayout wants V Scrollbar: %d =? %d\n", 760 showVScrollbar, aAssumeVScroll); 761 } 762 763 if (showHScrollbar != aAssumeHScroll || showVScrollbar != aAssumeVScroll) { 764 const nsMargin wantedScrollbarGutter = aState.ScrollbarGutter( 765 showVScrollbar, showHScrollbar, IsScrollbarOnRight()); 766 // We report an inconsistent layout only when the desired visibility of 767 // the scrollbars can change the size of the scrollbar gutters. 768 if (scrollbarGutter != wantedScrollbarGutter) { 769 return false; 770 } 771 } 772 } 773 774 // If we reach here, the layout is consistent. Record the desired visibility 775 // of the scrollbars. 776 aState.mShowHScrollbar = showHScrollbar; 777 aState.mShowVScrollbar = showVScrollbar; 778 const nsPoint scrollPortOrigin( 779 aState.mComputedBorder.left + scrollbarGutter.left, 780 aState.mComputedBorder.top + scrollbarGutter.top); 781 SetScrollPort(nsRect(scrollPortOrigin, scrollPortSize)); 782 783 if (mIsRoot && gfxPlatform::UseDesktopZoomingScrollbars()) { 784 bool vvChanged = true; 785 const bool overlay = aState.OverlayScrollbars(); 786 // This loop can run at most twice since we can only add a scrollbar once. 787 // At this point we've already decided that this layout is consistent so we 788 // will return true. Scrollbars added here never take up layout space even 789 // if they are layout scrollbars so any changes made here will not make us 790 // return false. 791 while (vvChanged) { 792 vvChanged = false; 793 if (!aState.mShowHScrollbar && 794 aState.mHScrollbarAllowedForScrollingVVInsideLV) { 795 if (ScrollPort().width >= visualViewportSize.width + oneDevPixel && 796 (overlay || 797 visualViewportSize.width >= aState.HScrollbarMinWidth())) { 798 vvChanged = true; 799 if (!overlay) { 800 visualViewportSize.height -= aState.HScrollbarPrefHeight(); 801 } 802 aState.mShowHScrollbar = true; 803 aState.mOnlyNeedHScrollbarToScrollVVInsideLV = true; 804 ROOT_SCROLLBAR_LOG("TryLayout added H scrollbar for VV, VV now %s\n", 805 ToString(visualViewportSize).c_str()); 806 } 807 } 808 809 if (!aState.mShowVScrollbar && 810 aState.mVScrollbarAllowedForScrollingVVInsideLV) { 811 if (ScrollPort().height >= visualViewportSize.height + oneDevPixel && 812 (overlay || 813 visualViewportSize.height >= aState.VScrollbarMinHeight())) { 814 vvChanged = true; 815 if (!overlay) { 816 visualViewportSize.width -= aState.VScrollbarPrefWidth(); 817 } 818 aState.mShowVScrollbar = true; 819 aState.mOnlyNeedVScrollbarToScrollVVInsideLV = true; 820 ROOT_SCROLLBAR_LOG("TryLayout added V scrollbar for VV, VV now %s\n", 821 ToString(visualViewportSize).c_str()); 822 } 823 } 824 } 825 } 826 827 return true; 828 } 829 830 bool ScrollContainerFrame::ScrolledContentDependsOnBSize( 831 const ScrollReflowInput& aState) const { 832 return mScrolledFrame->HasAnyStateBits( 833 NS_FRAME_CONTAINS_RELATIVE_BSIZE | 834 NS_FRAME_DESCENDANT_INTRINSIC_ISIZE_DEPENDS_ON_BSIZE) || 835 aState.mReflowInput.ComputedBSize() != NS_UNCONSTRAINEDSIZE || 836 aState.mReflowInput.ComputedMinBSize() > 0 || 837 aState.mReflowInput.ComputedMaxBSize() != NS_UNCONSTRAINEDSIZE; 838 } 839 840 void ScrollContainerFrame::ReflowScrolledFrame(ScrollReflowInput& aState, 841 bool aAssumeHScroll, 842 bool aAssumeVScroll, 843 ReflowOutput* aMetrics) { 844 const WritingMode wm = GetWritingMode(); 845 846 // these could be NS_UNCONSTRAINEDSIZE ... std::min arithmetic should 847 // be OK 848 LogicalMargin padding = aState.mReflowInput.ComputedLogicalPadding(wm); 849 nscoord availISize = 850 aState.mReflowInput.ComputedISize() + padding.IStartEnd(wm); 851 852 nscoord computedBSize = aState.mReflowInput.ComputedBSize(); 853 nscoord computedMinBSize = aState.mReflowInput.ComputedMinBSize(); 854 nscoord computedMaxBSize = aState.mReflowInput.ComputedMaxBSize(); 855 if (!ShouldPropagateComputedBSizeToScrolledContent()) { 856 computedBSize = NS_UNCONSTRAINEDSIZE; 857 computedMinBSize = 0; 858 computedMaxBSize = NS_UNCONSTRAINEDSIZE; 859 } 860 861 const LogicalMargin scrollbarGutter( 862 wm, aState.ScrollbarGutter(aAssumeVScroll, aAssumeHScroll, 863 IsScrollbarOnRight())); 864 if (const nscoord inlineEndsGutter = scrollbarGutter.IStartEnd(wm); 865 inlineEndsGutter > 0) { 866 availISize = std::max(0, availISize - inlineEndsGutter); 867 } 868 if (const nscoord blockEndsGutter = scrollbarGutter.BStartEnd(wm); 869 blockEndsGutter > 0) { 870 if (computedBSize != NS_UNCONSTRAINEDSIZE) { 871 computedBSize = std::max(0, computedBSize - blockEndsGutter); 872 } 873 computedMinBSize = std::max(0, computedMinBSize - blockEndsGutter); 874 if (computedMaxBSize != NS_UNCONSTRAINEDSIZE) { 875 computedMaxBSize = std::max(0, computedMaxBSize - blockEndsGutter); 876 } 877 } 878 879 nsPresContext* presContext = PresContext(); 880 881 // Pass InitFlags::CallerWillInit so we can pass in the correct padding. 882 ReflowInput kidReflowInput(presContext, aState.mReflowInput, mScrolledFrame, 883 LogicalSize(wm, availISize, NS_UNCONSTRAINEDSIZE), 884 Nothing(), ReflowInput::InitFlag::CallerWillInit); 885 const WritingMode kidWM = kidReflowInput.GetWritingMode(); 886 kidReflowInput.Init(presContext, Nothing(), Nothing(), 887 Some(padding.ConvertTo(kidWM, wm))); 888 kidReflowInput.mFlags.mAssumingHScrollbar = aAssumeHScroll; 889 kidReflowInput.mFlags.mAssumingVScrollbar = aAssumeVScroll; 890 kidReflowInput.mFlags.mTreatBSizeAsIndefinite = 891 aState.mReflowInput.mFlags.mTreatBSizeAsIndefinite; 892 kidReflowInput.SetComputedBSize(computedBSize); 893 kidReflowInput.SetComputedMinBSize(computedMinBSize); 894 kidReflowInput.SetComputedMaxBSize(computedMaxBSize); 895 if (aState.mReflowInput.IsBResizeForWM(kidWM)) { 896 kidReflowInput.SetBResize(true); 897 } 898 if (aState.mReflowInput.IsBResizeForPercentagesForWM(kidWM)) { 899 kidReflowInput.SetBResizeForPercentages(true); 900 } 901 902 // Temporarily set mHasHorizontalScrollbar/mHasVerticalScrollbar to 903 // reflect our assumptions while we reflow the child. 904 bool didHaveHorizontalScrollbar = mHasHorizontalScrollbar; 905 bool didHaveVerticalScrollbar = mHasVerticalScrollbar; 906 mHasHorizontalScrollbar = aAssumeHScroll; 907 mHasVerticalScrollbar = aAssumeVScroll; 908 909 nsReflowStatus status; 910 // No need to pass a true container-size to ReflowChild or 911 // FinishReflowChild, because it's only used there when positioning 912 // the frame (i.e. if ReflowChildFlags::NoMoveFrame isn't set) 913 const nsSize dummyContainerSize; 914 ReflowChild(mScrolledFrame, presContext, *aMetrics, kidReflowInput, wm, 915 LogicalPoint(wm), dummyContainerSize, 916 ReflowChildFlags::NoMoveFrame, status); 917 918 mHasHorizontalScrollbar = didHaveHorizontalScrollbar; 919 mHasVerticalScrollbar = didHaveVerticalScrollbar; 920 921 FinishReflowChild(mScrolledFrame, presContext, *aMetrics, &kidReflowInput, wm, 922 LogicalPoint(wm), dummyContainerSize, 923 ReflowChildFlags::NoMoveFrame); 924 925 if (mScrolledFrame->HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE)) { 926 // Propagate NS_FRAME_CONTAINS_RELATIVE_BSIZE from our inner scrolled frame 927 // to ourselves so that our containing block is aware of it. 928 // 929 // Note: If the scrolled frame has any child whose block-size depends on the 930 // containing block's block-size, the NS_FRAME_CONTAINS_RELATIVE_BSIZE bit 931 // is set on the scrolled frame when initializing the child's ReflowInput in 932 // ReflowInput::InitResizeFlags(). Therefore, we propagate the bit here 933 // after we reflowed the scrolled frame. 934 AddStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE); 935 } 936 937 // XXX Some frames (e.g. nsFrameFrame, nsTextFrame) don't 938 // bother setting their mOverflowArea. This is wrong because every frame 939 // should always set mOverflowArea. In fact nsFrameFrame doesn't 940 // support the 'outline' property because of this. Rather than fix the 941 // world right now, just fix up the overflow area if necessary. Note that we 942 // don't check HasOverflowRect() because it could be set even though the 943 // overflow area doesn't include the frame bounds. 944 aMetrics->UnionOverflowAreasWithDesiredBounds(); 945 946 aState.mContentsOverflowAreas = aMetrics->mOverflowAreas; 947 aState.mScrollbarGutterFromLastReflow = scrollbarGutter; 948 aState.mReflowedContentsWithHScrollbar = aAssumeHScroll; 949 aState.mReflowedContentsWithVScrollbar = aAssumeVScroll; 950 } 951 952 bool ScrollContainerFrame::GuessHScrollbarNeeded( 953 const ScrollReflowInput& aState) { 954 if (aState.mHScrollbar != ShowScrollbar::Auto) { 955 // no guessing required 956 return aState.mHScrollbar == ShowScrollbar::Always; 957 } 958 // We only care about scrollbars that might take up space when trying to guess 959 // if we need a scrollbar, so we ignore scrollbars only created to scroll the 960 // visual viewport inside the layout viewport because they take up no layout 961 // space. 962 return mHasHorizontalScrollbar && !mOnlyNeedHScrollbarToScrollVVInsideLV; 963 } 964 965 bool ScrollContainerFrame::GuessVScrollbarNeeded( 966 const ScrollReflowInput& aState) { 967 if (aState.mVScrollbar != ShowScrollbar::Auto) { 968 // no guessing required 969 return aState.mVScrollbar == ShowScrollbar::Always; 970 } 971 972 // If we've had at least one non-initial reflow, then just assume 973 // the state of the vertical scrollbar will be what we determined 974 // last time. 975 if (mHadNonInitialReflow) { 976 // We only care about scrollbars that might take up space when trying to 977 // guess if we need a scrollbar, so we ignore scrollbars only created to 978 // scroll the visual viewport inside the layout viewport because they take 979 // up no layout space. 980 return mHasVerticalScrollbar && !mOnlyNeedVScrollbarToScrollVVInsideLV; 981 } 982 983 // If this is the initial reflow, guess false because usually 984 // we have very little content by then. 985 if (InInitialReflow()) { 986 return false; 987 } 988 989 if (mIsRoot) { 990 nsIFrame* f = mScrolledFrame->PrincipalChildList().FirstChild(); 991 if (f && f->IsSVGOuterSVGFrame() && 992 static_cast<SVGOuterSVGFrame*>(f)->VerticalScrollbarNotNeeded()) { 993 // Common SVG case - avoid a bad guess. 994 return false; 995 } 996 // Assume that there will be a scrollbar; it seems to me 997 // that 'most pages' do have a scrollbar, and anyway, it's cheaper 998 // to do an extra reflow for the pages that *don't* need a 999 // scrollbar (because on average they will have less content). 1000 return true; 1001 } 1002 1003 // For non-viewports, just guess that we don't need a scrollbar. 1004 // XXX I wonder if statistically this is the right idea; I'm 1005 // basically guessing that there are a lot of overflow:auto DIVs 1006 // that get their intrinsic size and don't overflow 1007 return false; 1008 } 1009 1010 bool ScrollContainerFrame::InInitialReflow() const { 1011 // We're in an initial reflow if NS_FRAME_FIRST_REFLOW is set, unless we're a 1012 // root scrollframe. In that case we want to skip this clause altogether. 1013 // The guess here is that there are lots of overflow:auto divs out there that 1014 // end up auto-sizing so they don't overflow, and that the root basically 1015 // always needs a scrollbar if it did last time we loaded this page (good 1016 // assumption, because our initial reflow is no longer synchronous). 1017 return !mIsRoot && HasAnyStateBits(NS_FRAME_FIRST_REFLOW); 1018 } 1019 1020 void ScrollContainerFrame::ReflowContents(ScrollReflowInput& aState, 1021 const ReflowOutput& aDesiredSize) { 1022 const WritingMode desiredWm = aDesiredSize.GetWritingMode(); 1023 ReflowOutput kidDesiredSize(desiredWm); 1024 ReflowScrolledFrame(aState, GuessHScrollbarNeeded(aState), 1025 GuessVScrollbarNeeded(aState), &kidDesiredSize); 1026 1027 // There's an important special case ... if the child appears to fit 1028 // in the inside-border rect (but overflows the scrollport), we 1029 // should try laying it out without a vertical scrollbar. It will 1030 // usually fit because making the available-width wider will not 1031 // normally make the child taller. (The only situation I can think 1032 // of is when you have a line containing %-width inline replaced 1033 // elements whose percentages sum to more than 100%, so increasing 1034 // the available width makes the line break where it was fitting 1035 // before.) If we don't treat this case specially, then we will 1036 // decide that showing scrollbars is OK because the content 1037 // overflows when we're showing scrollbars and we won't try to 1038 // remove the vertical scrollbar. 1039 1040 // Detecting when we enter this special case is important for when 1041 // people design layouts that exactly fit the container "most of the 1042 // time". 1043 1044 // XXX Is this check really sufficient to catch all the incremental cases 1045 // where the ideal case doesn't have a scrollbar? 1046 if ((aState.mReflowedContentsWithHScrollbar || 1047 aState.mReflowedContentsWithVScrollbar) && 1048 aState.mVScrollbar != ShowScrollbar::Always && 1049 aState.mHScrollbar != ShowScrollbar::Always) { 1050 nsSize kidSize = GetContainSizeAxes().ContainSize( 1051 kidDesiredSize.PhysicalSize(), *aState.mReflowInput.mFrame); 1052 nsSize insideBorderSize = ComputeInsideBorderSize(aState, kidSize); 1053 nsRect scrolledRect = GetUnsnappedScrolledRectInternal( 1054 kidDesiredSize.ScrollableOverflow(), insideBorderSize); 1055 if (nsRect(nsPoint(0, 0), insideBorderSize).Contains(scrolledRect)) { 1056 // Let's pretend we had no scrollbars coming in here 1057 kidDesiredSize.mOverflowAreas.Clear(); 1058 ReflowScrolledFrame(aState, false, false, &kidDesiredSize); 1059 } 1060 } 1061 1062 if (IsRootScrollFrameOfDocument()) { 1063 UpdateMinimumScaleSize(aState.mContentsOverflowAreas.ScrollableOverflow(), 1064 kidDesiredSize.PhysicalSize()); 1065 } 1066 1067 // Try vertical scrollbar settings that leave the vertical scrollbar 1068 // unchanged. Do this first because changing the vertical scrollbar setting is 1069 // expensive, forcing a reflow always. 1070 1071 // Try leaving the horizontal scrollbar unchanged first. This will be more 1072 // efficient. 1073 ROOT_SCROLLBAR_LOG("Trying layout1 with %d, %d\n", 1074 aState.mReflowedContentsWithHScrollbar, 1075 aState.mReflowedContentsWithVScrollbar); 1076 if (TryLayout(aState, &kidDesiredSize, aState.mReflowedContentsWithHScrollbar, 1077 aState.mReflowedContentsWithVScrollbar, false)) { 1078 return; 1079 } 1080 ROOT_SCROLLBAR_LOG("Trying layout2 with %d, %d\n", 1081 !aState.mReflowedContentsWithHScrollbar, 1082 aState.mReflowedContentsWithVScrollbar); 1083 if (TryLayout(aState, &kidDesiredSize, 1084 !aState.mReflowedContentsWithHScrollbar, 1085 aState.mReflowedContentsWithVScrollbar, false)) { 1086 return; 1087 } 1088 1089 // OK, now try toggling the vertical scrollbar. The performance advantage 1090 // of trying the status-quo horizontal scrollbar state 1091 // does not exist here (we'll have to reflow due to the vertical scrollbar 1092 // change), so always try no horizontal scrollbar first. 1093 bool newVScrollbarState = !aState.mReflowedContentsWithVScrollbar; 1094 ROOT_SCROLLBAR_LOG("Trying layout3 with %d, %d\n", false, newVScrollbarState); 1095 if (TryLayout(aState, &kidDesiredSize, false, newVScrollbarState, false)) { 1096 return; 1097 } 1098 ROOT_SCROLLBAR_LOG("Trying layout4 with %d, %d\n", true, newVScrollbarState); 1099 if (TryLayout(aState, &kidDesiredSize, true, newVScrollbarState, false)) { 1100 return; 1101 } 1102 1103 // OK, we're out of ideas. Try again enabling whatever scrollbars we can 1104 // enable and force the layout to stick even if it's inconsistent. 1105 // This just happens sometimes. 1106 ROOT_SCROLLBAR_LOG("Giving up, adding both scrollbars...\n"); 1107 TryLayout(aState, &kidDesiredSize, aState.mHScrollbar != ShowScrollbar::Never, 1108 aState.mVScrollbar != ShowScrollbar::Never, true); 1109 } 1110 1111 void ScrollContainerFrame::PlaceScrollArea(ScrollReflowInput& aState, 1112 const nsPoint& aScrollPosition) { 1113 // Set the x,y of the scrolled frame to the correct value 1114 mScrolledFrame->SetPosition(ScrollPort().TopLeft() - aScrollPosition); 1115 1116 // Recompute our scrollable overflow, taking perspective children into 1117 // account. Note that this only recomputes the overflow areas stored on the 1118 // helper (which are used to compute scrollable length and scrollbar thumb 1119 // sizes) but not the overflow areas stored on the frame. This seems to work 1120 // for now, but it's possible that we may need to update both in the future. 1121 AdjustForPerspective(aState.mContentsOverflowAreas.ScrollableOverflow()); 1122 1123 // Preserve the width or height of empty rects 1124 const nsSize portSize = ScrollPort().Size(); 1125 nsRect scrolledRect = GetUnsnappedScrolledRectInternal( 1126 aState.mContentsOverflowAreas.ScrollableOverflow(), portSize); 1127 nsRect scrolledArea = 1128 scrolledRect.UnionEdges(nsRect(nsPoint(0, 0), portSize)); 1129 1130 // Store the new overflow area. Note that this changes where an outline 1131 // of the scrolled frame would be painted, but scrolled frames can't have 1132 // outlines (the outline would go on this scrollframe instead). 1133 // Using FinishAndStoreOverflow is needed so the overflow rect gets set 1134 // correctly. It also messes with the overflow rect in the 'clip' case, but 1135 // scrolled frames can't have 'overflow' either. 1136 OverflowAreas overflow(scrolledArea, scrolledArea); 1137 mScrolledFrame->FinishAndStoreOverflow(overflow, mScrolledFrame->GetSize()); 1138 } 1139 1140 nscoord ScrollContainerFrame::IntrinsicScrollbarGutterSizeAtInlineEdges() 1141 const { 1142 const auto wm = GetWritingMode(); 1143 const LogicalMargin gutter(wm, IntrinsicScrollbarGutterSize()); 1144 return gutter.IStartEnd(wm); 1145 } 1146 1147 nsMargin ScrollContainerFrame::IntrinsicScrollbarGutterSize() const { 1148 if (PresContext()->UseOverlayScrollbars()) { 1149 // Overlay scrollbars do not consume space per spec. 1150 return {}; 1151 } 1152 1153 const auto* styleForScrollbar = nsLayoutUtils::StyleForScrollbar(this); 1154 const auto& styleScrollbarWidth = 1155 styleForScrollbar->StyleUIReset()->ScrollbarWidth(); 1156 if (styleScrollbarWidth == StyleScrollbarWidth::None) { 1157 // Scrollbar shouldn't appear at all with "scrollbar-width: none". 1158 return {}; 1159 } 1160 1161 const auto& styleScrollbarGutter = 1162 styleForScrollbar->StyleDisplay()->mScrollbarGutter; 1163 nsMargin gutter = 1164 ComputeStableScrollbarGutter(styleScrollbarWidth, styleScrollbarGutter); 1165 if (gutter.LeftRight() == 0 || gutter.TopBottom() == 0) { 1166 // If there is no stable scrollbar-gutter at vertical or horizontal 1167 // dimension, check if a scrollbar is always shown at that dimension. 1168 ScrollStyles scrollStyles = GetScrollStyles(); 1169 const nscoord scrollbarSize = 1170 GetNonOverlayScrollbarSize(PresContext(), styleScrollbarWidth); 1171 if (gutter.LeftRight() == 0 && 1172 scrollStyles.mVertical == StyleOverflow::Scroll) { 1173 (IsScrollbarOnRight() ? gutter.right : gutter.left) = scrollbarSize; 1174 } 1175 if (gutter.TopBottom() == 0 && 1176 scrollStyles.mHorizontal == StyleOverflow::Scroll) { 1177 // The horizontal scrollbar is always at the bottom side. 1178 gutter.bottom = scrollbarSize; 1179 } 1180 } 1181 return gutter; 1182 } 1183 1184 nsMargin ScrollContainerFrame::ComputeStableScrollbarGutter( 1185 const StyleScrollbarWidth& aStyleScrollbarWidth, 1186 const StyleScrollbarGutter& aStyleScrollbarGutter) const { 1187 if (PresContext()->UseOverlayScrollbars()) { 1188 // Overlay scrollbars do not consume space per spec. 1189 return {}; 1190 } 1191 1192 if (aStyleScrollbarWidth == StyleScrollbarWidth::None) { 1193 // Scrollbar shouldn't appear at all with "scrollbar-width: none". 1194 return {}; 1195 } 1196 1197 if (aStyleScrollbarGutter == StyleScrollbarGutter::AUTO) { 1198 // Scrollbars create space depending on the 'overflow' property and whether 1199 // the content overflows. Callers need to check this scenario if they want 1200 // to consider the space created by the actual scrollbars. 1201 return {}; 1202 } 1203 1204 const bool bothEdges = 1205 bool(aStyleScrollbarGutter & StyleScrollbarGutter::BOTH_EDGES); 1206 const bool isVerticalWM = GetWritingMode().IsVertical(); 1207 const nscoord scrollbarSize = 1208 GetNonOverlayScrollbarSize(PresContext(), aStyleScrollbarWidth); 1209 1210 nsMargin scrollbarGutter; 1211 if (bothEdges) { 1212 if (isVerticalWM) { 1213 scrollbarGutter.top = scrollbarGutter.bottom = scrollbarSize; 1214 } else { 1215 scrollbarGutter.left = scrollbarGutter.right = scrollbarSize; 1216 } 1217 } else { 1218 MOZ_ASSERT(bool(aStyleScrollbarGutter & StyleScrollbarGutter::STABLE), 1219 "scrollbar-gutter value should be 'stable'!"); 1220 if (isVerticalWM) { 1221 // The horizontal scrollbar-gutter is always at the bottom side. 1222 scrollbarGutter.bottom = scrollbarSize; 1223 } else if (IsScrollbarOnRight()) { 1224 scrollbarGutter.right = scrollbarSize; 1225 } else { 1226 scrollbarGutter.left = scrollbarSize; 1227 } 1228 } 1229 return scrollbarGutter; 1230 } 1231 1232 // Legacy, this sucks! 1233 static bool IsMarqueeScrollbox(const nsIFrame& aScrollFrame) { 1234 return HTMLMarqueeElement::FromNodeOrNull(aScrollFrame.GetContent()); 1235 } 1236 1237 nscoord ScrollContainerFrame::IntrinsicISize(const IntrinsicSizeInput& aInput, 1238 IntrinsicISizeType aType) { 1239 nscoord result = [&] { 1240 if (const Maybe<nscoord> containISize = ContainIntrinsicISize()) { 1241 return *containISize; 1242 } 1243 if (aType == IntrinsicISizeType::MinISize && 1244 MOZ_UNLIKELY(IsMarqueeScrollbox(*this))) { 1245 return 0; 1246 } 1247 return mScrolledFrame->IntrinsicISize(aInput, aType); 1248 }(); 1249 1250 return NSCoordSaturatingAdd(result, 1251 IntrinsicScrollbarGutterSizeAtInlineEdges()); 1252 } 1253 1254 // When we have perspective set on the outer scroll frame, and transformed 1255 // children (possibly with preserve-3d) then the effective transform on the 1256 // child depends on the offset to the scroll frame, which changes as we scroll. 1257 // This perspective transform can cause the element to move relative to the 1258 // scrolled inner frame, which would cause the scrollable length changes during 1259 // scrolling if we didn't account for it. Since we don't want scrollHeight/Width 1260 // and the size of scrollbar thumbs to change during scrolling, we compute the 1261 // scrollable overflow by determining the scroll position at which the child 1262 // becomes completely visible within the scrollport rather than using the union 1263 // of the overflow areas at their current position. 1264 static void GetScrollableOverflowForPerspective( 1265 nsIFrame* aScrolledFrame, nsIFrame* aCurrentFrame, 1266 const nsRect& aScrollPort, nsPoint aOffset, 1267 nsRect& aScrolledFrameOverflowArea) { 1268 // Iterate over all children except pop-ups. 1269 for (const auto& [list, listID] : aCurrentFrame->ChildLists()) { 1270 for (nsIFrame* child : list) { 1271 nsPoint offset = aOffset; 1272 1273 // When we reach a direct child of the scroll, then we record the offset 1274 // to convert from that frame's coordinate into the scroll frame's 1275 // coordinates. Preserve-3d descendant frames use the same offset as their 1276 // ancestors, since TransformRect already converts us into the coordinate 1277 // space of the preserve-3d root. 1278 if (aScrolledFrame == aCurrentFrame) { 1279 offset = child->GetPosition(); 1280 } 1281 1282 if (child->Extend3DContext()) { 1283 // If we're a preserve-3d frame, then recurse and include our 1284 // descendants since overflow of preserve-3d frames is only included 1285 // in the post-transform overflow area of the preserve-3d root frame. 1286 GetScrollableOverflowForPerspective(aScrolledFrame, child, aScrollPort, 1287 offset, aScrolledFrameOverflowArea); 1288 } 1289 1290 // If we're transformed, then we want to consider the possibility that 1291 // this frame might move relative to the scrolled frame when scrolling. 1292 // For preserve-3d, leaf frames have correct overflow rects relative to 1293 // themselves. preserve-3d 'nodes' (intermediate frames and the root) have 1294 // only their untransformed children included in their overflow relative 1295 // to self, which is what we want to include here. 1296 if (child->IsTransformed()) { 1297 // Compute the overflow rect for this leaf transform frame in the 1298 // coordinate space of the scrolled frame. 1299 nsPoint scrollPos = aScrolledFrame->GetPosition(); 1300 nsRect preScroll, postScroll; 1301 { 1302 // TODO: Can we reuse the reference box? 1303 TransformReferenceBox refBox(child); 1304 preScroll = nsDisplayTransform::TransformRect( 1305 child->ScrollableOverflowRectRelativeToSelf(), child, refBox); 1306 } 1307 1308 // Temporarily override the scroll position of the scrolled frame by 1309 // 10 CSS pixels, and then recompute what the overflow rect would be. 1310 // This scroll position may not be valid, but that shouldn't matter 1311 // for our calculations. 1312 { 1313 aScrolledFrame->SetPosition(scrollPos + nsPoint(600, 600)); 1314 TransformReferenceBox refBox(child); 1315 postScroll = nsDisplayTransform::TransformRect( 1316 child->ScrollableOverflowRectRelativeToSelf(), child, refBox); 1317 aScrolledFrame->SetPosition(scrollPos); 1318 } 1319 1320 // Compute how many app units the overflow rects moves by when we adjust 1321 // the scroll position by 1 app unit. 1322 double rightDelta = 1323 (postScroll.XMost() - preScroll.XMost() + 600.0) / 600.0; 1324 double bottomDelta = 1325 (postScroll.YMost() - preScroll.YMost() + 600.0) / 600.0; 1326 1327 // We can't ever have negative scrolling. 1328 NS_ASSERTION(rightDelta > 0.0f && bottomDelta > 0.0f, 1329 "Scrolling can't be reversed!"); 1330 1331 // Move preScroll into the coordinate space of the scrollport. 1332 preScroll += offset + scrollPos; 1333 1334 // For each of the four edges of preScroll, figure out how far they 1335 // extend beyond the scrollport. Ignore negative values since that means 1336 // that side is already scrolled in to view and we don't need to add 1337 // overflow to account for it. 1338 nsMargin overhang(std::max(0, aScrollPort.Y() - preScroll.Y()), 1339 std::max(0, preScroll.XMost() - aScrollPort.XMost()), 1340 std::max(0, preScroll.YMost() - aScrollPort.YMost()), 1341 std::max(0, aScrollPort.X() - preScroll.X())); 1342 1343 // Scale according to rightDelta/bottomDelta to adjust for the different 1344 // scroll rates. 1345 overhang.top = NSCoordSaturatingMultiply( 1346 overhang.top, static_cast<float>(1 / bottomDelta)); 1347 overhang.right = NSCoordSaturatingMultiply( 1348 overhang.right, static_cast<float>(1 / rightDelta)); 1349 overhang.bottom = NSCoordSaturatingMultiply( 1350 overhang.bottom, static_cast<float>(1 / bottomDelta)); 1351 overhang.left = NSCoordSaturatingMultiply( 1352 overhang.left, static_cast<float>(1 / rightDelta)); 1353 1354 // Take the minimum overflow rect that would allow the current scroll 1355 // position, using the size of the scroll port and offset by the 1356 // inverse of the scroll position. 1357 nsRect overflow = aScrollPort - scrollPos; 1358 1359 // Expand it by our margins to get an overflow rect that would allow all 1360 // edges of our transformed content to be scrolled into view. 1361 overflow.Inflate(overhang); 1362 1363 // Merge it with the combined overflow 1364 aScrolledFrameOverflowArea.UnionRect(aScrolledFrameOverflowArea, 1365 overflow); 1366 } else if (aCurrentFrame == aScrolledFrame) { 1367 aScrolledFrameOverflowArea.UnionRect( 1368 aScrolledFrameOverflowArea, 1369 child->ScrollableOverflowRectRelativeToParent()); 1370 } 1371 } 1372 } 1373 } 1374 1375 BaselineSharingGroup ScrollContainerFrame::GetDefaultBaselineSharingGroup() 1376 const { 1377 return mScrolledFrame->GetDefaultBaselineSharingGroup(); 1378 } 1379 1380 nscoord ScrollContainerFrame::SynthesizeFallbackBaseline( 1381 mozilla::WritingMode aWM, BaselineSharingGroup aBaselineGroup) const { 1382 // Margin-end even for central baselines. 1383 if (aWM.IsLineInverted()) { 1384 return -GetLogicalUsedMargin(aWM).BStart(aWM); 1385 } 1386 return aBaselineGroup == BaselineSharingGroup::First 1387 ? BSize(aWM) + GetLogicalUsedMargin(aWM).BEnd(aWM) 1388 : -GetLogicalUsedMargin(aWM).BEnd(aWM); 1389 } 1390 1391 Maybe<nscoord> ScrollContainerFrame::GetNaturalBaselineBOffset( 1392 WritingMode aWM, BaselineSharingGroup aBaselineGroup, 1393 BaselineExportContext aExportContext) const { 1394 // Block containers (except buttons) that are scrollable always have a last 1395 // baseline that are synthesized from block-end margin edge. 1396 // Note(dshin): This behaviour is really only relevant to `inline-block` 1397 // alignment context. In the context of table/flex/grid alignment, first/last 1398 // baselines are calculated through `GetFirstLineBaseline`, which does 1399 // calculations of its own. 1400 // https://drafts.csswg.org/css-align/#baseline-export 1401 if (aExportContext == BaselineExportContext::LineLayout && 1402 aBaselineGroup == BaselineSharingGroup::Last) { 1403 if (nsBlockFrame* bf = do_QueryFrame(mScrolledFrame); 1404 bf && !bf->IsButtonLike()) { 1405 return Some(SynthesizeFallbackBaseline(aWM, aBaselineGroup)); 1406 } 1407 } 1408 1409 if (StyleDisplay()->IsContainLayout()) { 1410 return Nothing{}; 1411 } 1412 1413 // OK, here's where we defer to our scrolled frame. 1414 return mScrolledFrame 1415 ->GetNaturalBaselineBOffset(aWM, aBaselineGroup, aExportContext) 1416 .map([this, aWM](nscoord aBaseline) { 1417 // We have to add our border BStart thickness to whatever it returns, to 1418 // produce an offset in our frame-rect's coordinate system. (We don't 1419 // have to add padding, because the scrolled frame handles our padding.) 1420 LogicalMargin border = GetLogicalUsedBorder(aWM); 1421 const auto bSize = GetLogicalSize(aWM).BSize(aWM); 1422 // Clamp the baseline to the border rect. See bug 1791069. 1423 return CSSMinMax(border.BStart(aWM) + aBaseline, 0, bSize); 1424 }); 1425 } 1426 1427 void ScrollContainerFrame::AdjustForPerspective(nsRect& aScrollableOverflow) { 1428 // If we have perspective that is being applied to our children, then 1429 // the effective transform on the child depends on the relative position 1430 // of the child to us and changes during scrolling. 1431 if (!ChildrenHavePerspective()) { 1432 return; 1433 } 1434 aScrollableOverflow.SetEmpty(); 1435 GetScrollableOverflowForPerspective(mScrolledFrame, mScrolledFrame, 1436 ScrollPort(), nsPoint(), 1437 aScrollableOverflow); 1438 } 1439 1440 void ScrollContainerFrame::Reflow(nsPresContext* aPresContext, 1441 ReflowOutput& aDesiredSize, 1442 const ReflowInput& aReflowInput, 1443 nsReflowStatus& aStatus) { 1444 MarkInReflow(); 1445 DO_GLOBAL_REFLOW_COUNT("ScrollContainerFrame"); 1446 MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); 1447 1448 HandleScrollbarStyleSwitching(); 1449 1450 ScrollReflowInput state(this, aReflowInput); 1451 1452 //------------ Handle Incremental Reflow ----------------- 1453 bool reflowHScrollbar = true; 1454 bool reflowVScrollbar = true; 1455 bool reflowScrollCorner = true; 1456 if (!aReflowInput.ShouldReflowAllKids()) { 1457 auto NeedsReflow = [](const nsIFrame* aFrame) { 1458 return aFrame && aFrame->IsSubtreeDirty(); 1459 }; 1460 1461 reflowHScrollbar = NeedsReflow(mHScrollbarBox); 1462 reflowVScrollbar = NeedsReflow(mVScrollbarBox); 1463 reflowScrollCorner = 1464 NeedsReflow(mScrollCornerBox) || NeedsReflow(mResizerBox); 1465 } 1466 1467 if (mIsRoot) { 1468 reflowScrollCorner = false; 1469 } 1470 1471 const nsRect oldScrollPort = ScrollPort(); 1472 nsRect oldScrolledAreaBounds = 1473 mScrolledFrame->ScrollableOverflowRectRelativeToParent(); 1474 nsPoint oldScrollPosition = GetScrollPosition(); 1475 1476 ReflowContents(state, aDesiredSize); 1477 1478 nsSize layoutSize = 1479 mIsUsingMinimumScaleSize ? mMinimumScaleSize : state.mInsideBorderSize; 1480 aDesiredSize.Width() = layoutSize.width + state.mComputedBorder.LeftRight(); 1481 aDesiredSize.Height() = layoutSize.height + state.mComputedBorder.TopBottom(); 1482 1483 // Set the size of the frame now since computing the perspective-correct 1484 // overflow (within PlaceScrollArea) can rely on it. 1485 SetSize(aDesiredSize.GetWritingMode(), 1486 aDesiredSize.Size(aDesiredSize.GetWritingMode())); 1487 1488 // Restore the old scroll position, for now, even if that's not valid anymore 1489 // because we changed size. We'll fix it up in a post-reflow callback, because 1490 // our current size may only be temporary (e.g. we're compute XUL desired 1491 // sizes). 1492 PlaceScrollArea(state, oldScrollPosition); 1493 if (!mPostedReflowCallback) { 1494 // Make sure we'll try scrolling to restored position 1495 PresShell()->PostReflowCallback(this); 1496 mPostedReflowCallback = true; 1497 } 1498 1499 bool didOnlyHScrollbar = mOnlyNeedHScrollbarToScrollVVInsideLV; 1500 bool didOnlyVScrollbar = mOnlyNeedVScrollbarToScrollVVInsideLV; 1501 mOnlyNeedHScrollbarToScrollVVInsideLV = 1502 state.mOnlyNeedHScrollbarToScrollVVInsideLV; 1503 mOnlyNeedVScrollbarToScrollVVInsideLV = 1504 state.mOnlyNeedVScrollbarToScrollVVInsideLV; 1505 1506 bool didHaveHScrollbar = mHasHorizontalScrollbar; 1507 bool didHaveVScrollbar = mHasVerticalScrollbar; 1508 mHasHorizontalScrollbar = state.mShowHScrollbar; 1509 mHasVerticalScrollbar = state.mShowVScrollbar; 1510 const nsRect& newScrollPort = ScrollPort(); 1511 nsRect newScrolledAreaBounds = 1512 mScrolledFrame->ScrollableOverflowRectRelativeToParent(); 1513 if (mSkippedScrollbarLayout || reflowHScrollbar || reflowVScrollbar || 1514 reflowScrollCorner || HasAnyStateBits(NS_FRAME_IS_DIRTY) || 1515 didHaveHScrollbar != state.mShowHScrollbar || 1516 didHaveVScrollbar != state.mShowVScrollbar || 1517 didOnlyHScrollbar != mOnlyNeedHScrollbarToScrollVVInsideLV || 1518 didOnlyVScrollbar != mOnlyNeedVScrollbarToScrollVVInsideLV || 1519 !oldScrollPort.IsEqualEdges(newScrollPort) || 1520 !oldScrolledAreaBounds.IsEqualEdges(newScrolledAreaBounds)) { 1521 mSkippedScrollbarLayout = false; 1522 ScrollContainerFrame::SetScrollbarVisibility(mHScrollbarBox, 1523 state.mShowHScrollbar); 1524 ScrollContainerFrame::SetScrollbarVisibility(mVScrollbarBox, 1525 state.mShowVScrollbar); 1526 // place and reflow scrollbars 1527 const nsRect insideBorderArea( 1528 nsPoint(state.mComputedBorder.left, state.mComputedBorder.top), 1529 layoutSize); 1530 LayoutScrollbars(state, insideBorderArea, oldScrollPort); 1531 } 1532 if (mIsRoot) { 1533 if (RefPtr<MobileViewportManager> manager = 1534 PresShell()->GetMobileViewportManager()) { 1535 // Note that this runs during layout, and when we get here the root 1536 // scrollframe has already been laid out. It may have added or removed 1537 // scrollbars as a result of that layout, so we need to ensure the 1538 // visual viewport is updated to account for that before we read the 1539 // visual viewport size. 1540 manager->UpdateVisualViewportSizeForPotentialScrollbarChange(); 1541 } else if (oldScrollPort.Size() != newScrollPort.Size()) { 1542 // We want to make sure to send a visual viewport resize event if the 1543 // scrollport changed sizes for root scroll frames. The 1544 // MobileViewportManager will do that, but if we don't have one (ie we 1545 // aren't a root content document for example) we have to send one 1546 // ourselves. 1547 if (auto* window = nsGlobalWindowInner::Cast( 1548 aPresContext->Document()->GetInnerWindow())) { 1549 window->VisualViewport()->PostResizeEvent(); 1550 } 1551 } 1552 } 1553 1554 // Note that we need to do this after the 1555 // UpdateVisualViewportSizeForPotentialScrollbarChange call above because that 1556 // is what updates the visual viewport size and we need it to be up to date. 1557 if (mIsRoot && !state.OverlayScrollbars() && 1558 (didHaveHScrollbar != state.mShowHScrollbar || 1559 didHaveVScrollbar != state.mShowVScrollbar || 1560 didOnlyHScrollbar != mOnlyNeedHScrollbarToScrollVVInsideLV || 1561 didOnlyVScrollbar != mOnlyNeedVScrollbarToScrollVVInsideLV) && 1562 PresShell()->IsVisualViewportOffsetSet()) { 1563 // Removing layout/classic scrollbars can make a previously valid vvoffset 1564 // invalid. For example, if we are zoomed in on an overflow hidden document 1565 // and then zoom back out, when apz reaches the initial resolution (ie 1.0) 1566 // it won't know that we can remove the scrollbars, so the vvoffset can 1567 // validly be upto the width/height of the scrollbars. After we reflow and 1568 // remove the scrollbars the only valid vvoffset is (0,0). We could wait 1569 // until we send the new frame metrics to apz and then have it reply with 1570 // the new corrected vvoffset but having an inconsistent vvoffset causes 1571 // problems so trigger the vvoffset to be re-set and re-clamped in 1572 // ReflowFinished. 1573 mReclampVVOffsetInReflowFinished = true; 1574 } 1575 1576 aDesiredSize.SetOverflowAreasToDesiredBounds(); 1577 1578 UpdateSticky(); 1579 FinishReflowWithAbsoluteFrames(aPresContext, aDesiredSize, aReflowInput, 1580 aStatus); 1581 1582 if (!InInitialReflow() && !mHadNonInitialReflow) { 1583 mHadNonInitialReflow = true; 1584 } 1585 1586 if (mIsRoot && !oldScrolledAreaBounds.IsEqualEdges(newScrolledAreaBounds)) { 1587 PostScrolledAreaEvent(); 1588 } 1589 1590 UpdatePrevScrolledRect(); 1591 1592 aStatus.Reset(); // This type of frame can't be split. 1593 PostOverflowEvent(); 1594 } 1595 1596 void ScrollContainerFrame::DidReflow(nsPresContext* aPresContext, 1597 const ReflowInput* aReflowInput) { 1598 nsContainerFrame::DidReflow(aPresContext, aReflowInput); 1599 if (NeedsResnap()) { 1600 PostPendingResnap(); 1601 } else { 1602 PresShell()->PostPendingScrollAnchorAdjustment(Anchor()); 1603 } 1604 } 1605 1606 //////////////////////////////////////////////////////////////////////////////// 1607 1608 #ifdef DEBUG_FRAME_DUMP 1609 nsresult ScrollContainerFrame::GetFrameName(nsAString& aResult) const { 1610 return MakeFrameName(u"ScrollContainer"_ns, aResult); 1611 } 1612 #endif 1613 1614 #ifdef ACCESSIBILITY 1615 a11y::AccType ScrollContainerFrame::AccessibleType() { 1616 if (IsTableCaption()) { 1617 return GetRect().IsEmpty() ? a11y::eNoType : a11y::eHTMLCaptionType; 1618 } 1619 1620 // Create an accessible regardless of focusable state because the state can be 1621 // changed during frame life cycle without any notifications to accessibility. 1622 if (mContent->IsRootOfNativeAnonymousSubtree() || 1623 GetScrollStyles().IsHiddenInBothDirections()) { 1624 return a11y::eNoType; 1625 } 1626 1627 return a11y::eHyperTextType; 1628 } 1629 #endif 1630 1631 NS_QUERYFRAME_HEAD(ScrollContainerFrame) 1632 NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator) 1633 NS_QUERYFRAME_ENTRY(nsIStatefulFrame) 1634 NS_QUERYFRAME_ENTRY(nsIScrollbarMediator) 1635 NS_QUERYFRAME_ENTRY(ScrollContainerFrame) 1636 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame) 1637 1638 nsMargin ScrollContainerFrame::GetDesiredScrollbarSizes() const { 1639 nsPresContext* pc = PresContext(); 1640 if (pc->UseOverlayScrollbars()) { 1641 return {}; 1642 } 1643 1644 const auto& style = *nsLayoutUtils::StyleForScrollbar(this); 1645 const auto scrollbarWidth = style.StyleUIReset()->ScrollbarWidth(); 1646 if (scrollbarWidth == StyleScrollbarWidth::None) { 1647 return {}; 1648 } 1649 1650 ScrollStyles styles = GetScrollStyles(); 1651 nsMargin result(0, 0, 0, 0); 1652 1653 auto size = GetNonOverlayScrollbarSize(pc, scrollbarWidth); 1654 if (styles.mVertical != StyleOverflow::Hidden) { 1655 if (IsScrollbarOnRight()) { 1656 result.right = size; 1657 } else { 1658 result.left = size; 1659 } 1660 } 1661 1662 if (styles.mHorizontal != StyleOverflow::Hidden) { 1663 // We don't currently support any scripts that would require a scrollbar 1664 // at the top. (Are there any?) 1665 result.bottom = size; 1666 } 1667 1668 return result; 1669 } 1670 1671 nscoord ScrollContainerFrame::GetNonOverlayScrollbarSize( 1672 const nsPresContext* aPc, StyleScrollbarWidth aScrollbarWidth) { 1673 const auto size = aPc->Theme()->GetScrollbarSize(aPc, aScrollbarWidth, 1674 nsITheme::Overlay::No); 1675 return aPc->DevPixelsToAppUnits(size); 1676 } 1677 1678 void ScrollContainerFrame::HandleScrollbarStyleSwitching() { 1679 // Check if we switched between scrollbar styles. 1680 if (mScrollbarActivity && !UsesOverlayScrollbars()) { 1681 mScrollbarActivity->Destroy(); 1682 mScrollbarActivity = nullptr; 1683 } else if (!mScrollbarActivity && UsesOverlayScrollbars()) { 1684 mScrollbarActivity = new ScrollbarActivity(this); 1685 } 1686 } 1687 1688 void ScrollContainerFrame::SetScrollableByAPZ(bool aScrollable) { 1689 mScrollableByAPZ = aScrollable; 1690 } 1691 1692 void ScrollContainerFrame::SetZoomableByAPZ(bool aZoomable) { 1693 if (!nsLayoutUtils::UsesAsyncScrolling(this)) { 1694 // If APZ is disabled on this window, then we're never actually going to 1695 // do any zooming. So we don't need to do any of the setup for it. Note 1696 // that this function gets called from ZoomConstraintsClient even if APZ 1697 // is disabled to indicate the zoomability of content. 1698 aZoomable = false; 1699 } 1700 if (mZoomableByAPZ != aZoomable) { 1701 // We might be changing the result of DecideScrollableLayer() so schedule a 1702 // paint to make sure we pick up the result of that change. 1703 mZoomableByAPZ = aZoomable; 1704 SchedulePaint(); 1705 } 1706 } 1707 1708 void ScrollContainerFrame::SetHasOutOfFlowContentInsideFilter() { 1709 mHasOutOfFlowContentInsideFilter = true; 1710 } 1711 1712 bool ScrollContainerFrame::WantAsyncScroll() const { 1713 ScrollStyles styles = GetScrollStyles(); 1714 1715 // First, as an optimization because getting the scrollrange is 1716 // relatively slow, check overflow hidden and not a zoomed scroll frame. 1717 if (styles.mHorizontal == StyleOverflow::Hidden && 1718 styles.mVertical == StyleOverflow::Hidden) { 1719 if (!mIsRoot || GetVisualViewportSize() == mScrollPort.Size()) { 1720 return false; 1721 } 1722 } 1723 1724 nscoord oneDevPixel = 1725 GetScrolledFrame()->PresContext()->AppUnitsPerDevPixel(); 1726 nsRect scrollRange = GetLayoutScrollRange(); 1727 1728 bool isVScrollable = scrollRange.height >= oneDevPixel && 1729 styles.mVertical != StyleOverflow::Hidden; 1730 bool isHScrollable = scrollRange.width >= oneDevPixel && 1731 styles.mHorizontal != StyleOverflow::Hidden; 1732 1733 if (isHScrollable || isVScrollable) { 1734 return true; 1735 } 1736 1737 // If the page has a visual viewport size that's different from 1738 // the layout viewport size at the current zoom level, we need to be 1739 // able to scroll the visual viewport inside the layout viewport 1740 // even if the page is not zoomable. 1741 return mIsRoot && GetVisualViewportSize() != mScrollPort.Size() && 1742 !GetVisualScrollRange().IsEqualInterior(scrollRange); 1743 } 1744 1745 static nsRect GetOnePixelRangeAroundPoint(const nsPoint& aPoint, 1746 bool aIsHorizontal) { 1747 nsRect allowedRange(aPoint, nsSize()); 1748 nscoord halfPixel = nsPresContext::CSSPixelsToAppUnits(0.5f); 1749 if (aIsHorizontal) { 1750 allowedRange.x = aPoint.x - halfPixel; 1751 allowedRange.width = halfPixel * 2 - 1; 1752 } else { 1753 allowedRange.y = aPoint.y - halfPixel; 1754 allowedRange.height = halfPixel * 2 - 1; 1755 } 1756 return allowedRange; 1757 } 1758 1759 void ScrollContainerFrame::ScrollByPage(nsScrollbarFrame* aScrollbar, 1760 int32_t aDirection, 1761 ScrollSnapFlags aSnapFlags) { 1762 ScrollByUnit(aScrollbar, ScrollMode::Smooth, aDirection, ScrollUnit::PAGES, 1763 aSnapFlags); 1764 } 1765 1766 void ScrollContainerFrame::ScrollByWhole(nsScrollbarFrame* aScrollbar, 1767 int32_t aDirection, 1768 ScrollSnapFlags aSnapFlags) { 1769 ScrollByUnit(aScrollbar, ScrollMode::Instant, aDirection, ScrollUnit::WHOLE, 1770 aSnapFlags); 1771 } 1772 1773 void ScrollContainerFrame::ScrollByLine(nsScrollbarFrame* aScrollbar, 1774 int32_t aDirection, 1775 ScrollSnapFlags aSnapFlags) { 1776 bool isHorizontal = aScrollbar->IsHorizontal(); 1777 nsIntPoint delta; 1778 if (isHorizontal) { 1779 const double kScrollMultiplier = 1780 StaticPrefs::toolkit_scrollbox_horizontalScrollDistance(); 1781 delta.x = static_cast<int32_t>(aDirection * kScrollMultiplier); 1782 if (GetLineScrollAmount().width * delta.x > GetPageScrollAmount().width) { 1783 // The scroll frame is so small that the delta would be more 1784 // than an entire page. Scroll by one page instead to maintain 1785 // context. 1786 ScrollByPage(aScrollbar, aDirection); 1787 return; 1788 } 1789 } else { 1790 const double kScrollMultiplier = 1791 StaticPrefs::toolkit_scrollbox_verticalScrollDistance(); 1792 delta.y = static_cast<int32_t>(aDirection * kScrollMultiplier); 1793 if (GetLineScrollAmount().height * delta.y > GetPageScrollAmount().height) { 1794 // The scroll frame is so small that the delta would be more 1795 // than an entire page. Scroll by one page instead to maintain 1796 // context. 1797 ScrollByPage(aScrollbar, aDirection); 1798 return; 1799 } 1800 } 1801 1802 nsIntPoint overflow; 1803 ScrollBy(delta, ScrollUnit::LINES, ScrollMode::Smooth, &overflow, 1804 ScrollOrigin::Other, NOT_MOMENTUM, aSnapFlags); 1805 } 1806 1807 void ScrollContainerFrame::RepeatButtonScroll(nsScrollbarFrame* aScrollbar) { 1808 aScrollbar->MoveToNewPosition(); 1809 } 1810 1811 void ScrollContainerFrame::ThumbMoved(nsScrollbarFrame* aScrollbar, 1812 nscoord aOldPos, nscoord aNewPos) { 1813 MOZ_ASSERT(aScrollbar != nullptr); 1814 bool isHorizontal = aScrollbar->IsHorizontal(); 1815 nsPoint current = GetScrollPosition(); 1816 nsPoint dest = current; 1817 if (isHorizontal) { 1818 dest.x = IsPhysicalLTR() ? aNewPos : aNewPos - GetLayoutScrollRange().width; 1819 } else { 1820 dest.y = aNewPos; 1821 } 1822 nsRect allowedRange = GetOnePixelRangeAroundPoint(dest, isHorizontal); 1823 1824 // Don't try to scroll if we're already at an acceptable place. 1825 // Don't call Contains here since Contains returns false when the point is 1826 // on the bottom or right edge of the rectangle. 1827 if (allowedRange.ClampPoint(current) == current) { 1828 return; 1829 } 1830 1831 ScrollToWithOrigin( 1832 dest, &allowedRange, 1833 ScrollOperationParams{ScrollMode::Instant, ScrollOrigin::Other}); 1834 } 1835 1836 void ScrollContainerFrame::ScrollbarReleased(nsScrollbarFrame* aScrollbar) { 1837 // Scrollbar scrolling does not result in fling gestures, clear any 1838 // accumulated velocity 1839 mVelocityQueue.Reset(); 1840 1841 // Perform scroll snapping, if needed. Scrollbar movement uses the same 1842 // smooth scrolling animation as keyboard scrolling. 1843 ScrollSnap(mDestination, ScrollMode::Smooth); 1844 } 1845 1846 void ScrollContainerFrame::ScrollByUnit(nsScrollbarFrame* aScrollbar, 1847 ScrollMode aMode, int32_t aDirection, 1848 ScrollUnit aUnit, 1849 ScrollSnapFlags aSnapFlags) { 1850 MOZ_ASSERT(aScrollbar != nullptr); 1851 bool isHorizontal = aScrollbar->IsHorizontal(); 1852 nsIntPoint delta; 1853 if (isHorizontal) { 1854 delta.x = aDirection; 1855 } else { 1856 delta.y = aDirection; 1857 } 1858 nsIntPoint overflow; 1859 ScrollBy(delta, aUnit, aMode, &overflow, ScrollOrigin::Other, NOT_MOMENTUM, 1860 aSnapFlags); 1861 } 1862 1863 //-------------------- Helper ---------------------- 1864 1865 // AsyncSmoothMSDScroll has ref counting. 1866 class ScrollContainerFrame::AsyncSmoothMSDScroll final 1867 : public nsARefreshObserver { 1868 public: 1869 AsyncSmoothMSDScroll(const nsPoint& aInitialPosition, 1870 const nsPoint& aInitialDestination, 1871 const nsSize& aInitialVelocity, const nsRect& aRange, 1872 const mozilla::TimeStamp& aStartTime, 1873 nsPresContext* aPresContext, 1874 UniquePtr<ScrollSnapTargetIds> aSnapTargetIds, 1875 ScrollTriggeredByScript aTriggeredByScript) 1876 : mXAxisModel(aInitialPosition.x, aInitialDestination.x, 1877 aInitialVelocity.width, 1878 StaticPrefs::layout_css_scroll_snap_spring_constant(), 1879 StaticPrefs::layout_css_scroll_snap_damping_ratio()), 1880 mYAxisModel(aInitialPosition.y, aInitialDestination.y, 1881 aInitialVelocity.height, 1882 StaticPrefs::layout_css_scroll_snap_spring_constant(), 1883 StaticPrefs::layout_css_scroll_snap_damping_ratio()), 1884 mRange(aRange), 1885 mStartPosition(aInitialPosition), 1886 mLastRefreshTime(aStartTime), 1887 mCallee(nullptr), 1888 mOneDevicePixelInAppUnits(aPresContext->DevPixelsToAppUnits(1)), 1889 mSnapTargetIds(std::move(aSnapTargetIds)), 1890 mTriggeredByScript(aTriggeredByScript) {} 1891 1892 NS_INLINE_DECL_REFCOUNTING(AsyncSmoothMSDScroll, override) 1893 1894 nsSize GetVelocity() { 1895 // In nscoords per second 1896 return nsSize(mXAxisModel.GetVelocity(), mYAxisModel.GetVelocity()); 1897 } 1898 1899 nsPoint GetPosition() { 1900 // In nscoords 1901 return nsPoint(NSToCoordRound(mXAxisModel.GetPosition()), 1902 NSToCoordRound(mYAxisModel.GetPosition())); 1903 } 1904 1905 void SetDestination(const nsPoint& aDestination, 1906 UniquePtr<ScrollSnapTargetIds> aSnapTargetIds, 1907 ScrollTriggeredByScript aTriggeredByScript) { 1908 mXAxisModel.SetDestination(static_cast<int32_t>(aDestination.x)); 1909 mYAxisModel.SetDestination(static_cast<int32_t>(aDestination.y)); 1910 mSnapTargetIds = std::move(aSnapTargetIds); 1911 mTriggeredByScript = aTriggeredByScript; 1912 } 1913 1914 void SetRange(const nsRect& aRange) { mRange = aRange; } 1915 1916 nsRect GetRange() { return mRange; } 1917 1918 nsPoint GetStartPosition() { return mStartPosition; } 1919 1920 void Simulate(const TimeDuration& aDeltaTime) { 1921 mXAxisModel.Simulate(aDeltaTime); 1922 mYAxisModel.Simulate(aDeltaTime); 1923 1924 nsPoint desired = GetPosition(); 1925 nsPoint clamped = mRange.ClampPoint(desired); 1926 if (desired.x != clamped.x) { 1927 // The scroll has hit the "wall" at the left or right edge of the allowed 1928 // scroll range. 1929 // Absorb the impact to avoid bounceback effect. 1930 mXAxisModel.SetVelocity(0.0); 1931 mXAxisModel.SetPosition(clamped.x); 1932 } 1933 1934 if (desired.y != clamped.y) { 1935 // The scroll has hit the "wall" at the left or right edge of the allowed 1936 // scroll range. 1937 // Absorb the impact to avoid bounceback effect. 1938 mYAxisModel.SetVelocity(0.0); 1939 mYAxisModel.SetPosition(clamped.y); 1940 } 1941 } 1942 1943 bool IsFinished() { 1944 return mXAxisModel.IsFinished(mOneDevicePixelInAppUnits) && 1945 mYAxisModel.IsFinished(mOneDevicePixelInAppUnits); 1946 } 1947 1948 virtual void WillRefresh(mozilla::TimeStamp aTime) override { 1949 mozilla::TimeDuration deltaTime = aTime - mLastRefreshTime; 1950 mLastRefreshTime = aTime; 1951 1952 // The callback may release "this". 1953 // We don't access members after returning, so no need for KungFuDeathGrip. 1954 ScrollContainerFrame::AsyncSmoothMSDScrollCallback(mCallee, deltaTime); 1955 } 1956 1957 /* 1958 * Set a refresh observer for smooth scroll iterations (and start observing). 1959 * Should be used at most once during the lifetime of this object. 1960 */ 1961 void SetRefreshObserver(ScrollContainerFrame* aCallee) { 1962 MOZ_ASSERT(aCallee, 1963 "AsyncSmoothMSDScroll::SetRefreshObserver needs " 1964 "a non-null aCallee in order to get a refresh driver"); 1965 MOZ_RELEASE_ASSERT(!mCallee, 1966 "AsyncSmoothMSDScroll::SetRefreshObserver " 1967 "shouldn't be called if we're already registered with " 1968 "a refresh driver, via a preexisting mCallee"); 1969 1970 RefreshDriver(aCallee)->AddRefreshObserver(this, FlushType::Style, 1971 "Smooth scroll (MSD) animation"); 1972 mCallee = aCallee; 1973 } 1974 1975 /** 1976 * The mCallee holds a strong ref to us since the refresh driver doesn't. 1977 * Our dtor and mCallee's Destroy() method both call RemoveObserver() - 1978 * whichever comes first removes us from the refresh driver. 1979 */ 1980 void RemoveObserver() { 1981 if (mCallee) { 1982 RefreshDriver(mCallee)->RemoveRefreshObserver(this, FlushType::Style); 1983 mCallee = nullptr; 1984 } 1985 } 1986 1987 UniquePtr<ScrollSnapTargetIds> TakeSnapTargetIds() { 1988 return std::move(mSnapTargetIds); 1989 } 1990 1991 bool WasTriggeredByScript() const { 1992 return mTriggeredByScript == ScrollTriggeredByScript::Yes; 1993 } 1994 1995 private: 1996 // Private destructor, to discourage deletion outside of Release(): 1997 ~AsyncSmoothMSDScroll() { RemoveObserver(); } 1998 1999 nsRefreshDriver* RefreshDriver(ScrollContainerFrame* aCallee) { 2000 return aCallee->PresContext()->RefreshDriver(); 2001 } 2002 2003 mozilla::layers::AxisPhysicsMSDModel mXAxisModel, mYAxisModel; 2004 nsRect mRange; 2005 nsPoint mStartPosition; 2006 mozilla::TimeStamp mLastRefreshTime; 2007 ScrollContainerFrame* mCallee; 2008 nscoord mOneDevicePixelInAppUnits; 2009 UniquePtr<ScrollSnapTargetIds> mSnapTargetIds; 2010 ScrollTriggeredByScript mTriggeredByScript; 2011 }; 2012 2013 // AsyncScroll has ref counting. 2014 class ScrollContainerFrame::AsyncScroll final : public nsARefreshObserver { 2015 public: 2016 typedef mozilla::TimeStamp TimeStamp; 2017 typedef mozilla::TimeDuration TimeDuration; 2018 2019 explicit AsyncScroll(ScrollTriggeredByScript aTriggeredByScript) 2020 : mOrigin(ScrollOrigin::NotSpecified), 2021 mCallee(nullptr), 2022 mTriggeredByScript(aTriggeredByScript) {} 2023 2024 private: 2025 // Private destructor, to discourage deletion outside of Release(): 2026 ~AsyncScroll() { RemoveObserver(); } 2027 2028 public: 2029 void InitSmoothScroll(TimeStamp aTime, nsPoint aInitialPosition, 2030 nsPoint aDestination, ScrollOrigin aOrigin, 2031 const nsRect& aRange, const nsSize& aCurrentVelocity, 2032 UniquePtr<ScrollSnapTargetIds> aSnapTargetIds); 2033 void Init(nsPoint aInitialPosition, const nsRect& aRange, 2034 UniquePtr<ScrollSnapTargetIds> aSnapTargetIds) { 2035 mAnimationPhysics = nullptr; 2036 mRange = aRange; 2037 mStartPosition = aInitialPosition; 2038 mSnapTargetIds = std::move(aSnapTargetIds); 2039 } 2040 2041 bool IsSmoothScroll() { return mAnimationPhysics != nullptr; } 2042 2043 bool IsFinished(const TimeStamp& aTime) const { 2044 MOZ_RELEASE_ASSERT(mAnimationPhysics); 2045 return mAnimationPhysics->IsFinished(aTime); 2046 } 2047 2048 nsPoint PositionAt(const TimeStamp& aTime) const { 2049 MOZ_RELEASE_ASSERT(mAnimationPhysics); 2050 return mAnimationPhysics->PositionAt(aTime); 2051 } 2052 2053 nsSize VelocityAt(const TimeStamp& aTime) const { 2054 MOZ_RELEASE_ASSERT(mAnimationPhysics); 2055 return mAnimationPhysics->VelocityAt(aTime); 2056 } 2057 2058 nsPoint GetStartPosition() const { return mStartPosition; } 2059 2060 // Most recent scroll origin. 2061 ScrollOrigin mOrigin; 2062 2063 // Allowed destination positions around mDestination 2064 nsRect mRange; 2065 2066 // Initial position where the async scroll was triggered. 2067 nsPoint mStartPosition; 2068 2069 private: 2070 void InitPreferences(TimeStamp aTime, nsAtom* aOrigin); 2071 2072 UniquePtr<ScrollAnimationPhysics> mAnimationPhysics; 2073 2074 // The next section is observer/callback management 2075 // Bodies of WillRefresh and RefreshDriver contain ScrollContainerFrame 2076 // specific code. 2077 public: 2078 NS_INLINE_DECL_REFCOUNTING(AsyncScroll, override) 2079 2080 /* 2081 * Set a refresh observer for smooth scroll iterations (and start observing). 2082 * Should be used at most once during the lifetime of this object. 2083 */ 2084 void SetRefreshObserver(ScrollContainerFrame* aCallee) { 2085 MOZ_ASSERT(aCallee, 2086 "AsyncScroll::SetRefreshObserver needs " 2087 "a non-null aCallee in order to get a refresh driver"); 2088 MOZ_RELEASE_ASSERT(!mCallee, 2089 "AsyncScroll::SetRefreshObserver " 2090 "shouldn't be called if we're already registered with " 2091 "a refresh driver, via a preexisting mCallee"); 2092 2093 RefreshDriver(aCallee)->AddRefreshObserver(this, FlushType::Style, 2094 "Smooth scroll animation"); 2095 mCallee = aCallee; 2096 auto* presShell = mCallee->PresShell(); 2097 MOZ_ASSERT(presShell); 2098 presShell->SuppressDisplayport(true); 2099 } 2100 2101 virtual void WillRefresh(mozilla::TimeStamp aTime) override { 2102 // The callback may release "this". 2103 // We don't access members after returning, so no need for KungFuDeathGrip. 2104 ScrollContainerFrame::AsyncScrollCallback(mCallee, aTime); 2105 } 2106 2107 /** 2108 * The mCallee holds a strong ref to us since the refresh driver doesn't. 2109 * Our dtor and mCallee's Destroy() method both call RemoveObserver() - 2110 * whichever comes first removes us from the refresh driver. 2111 */ 2112 void RemoveObserver() { 2113 if (mCallee) { 2114 RefreshDriver(mCallee)->RemoveRefreshObserver(this, FlushType::Style); 2115 auto* presShell = mCallee->PresShell(); 2116 MOZ_ASSERT(presShell); 2117 presShell->SuppressDisplayport(false); 2118 mCallee = nullptr; 2119 } 2120 } 2121 2122 UniquePtr<ScrollSnapTargetIds> TakeSnapTargetIds() { 2123 return std::move(mSnapTargetIds); 2124 } 2125 2126 bool WasTriggeredByScript() const { 2127 return mTriggeredByScript == ScrollTriggeredByScript::Yes; 2128 } 2129 2130 private: 2131 ScrollContainerFrame* mCallee; 2132 UniquePtr<ScrollSnapTargetIds> mSnapTargetIds; 2133 ScrollTriggeredByScript mTriggeredByScript; 2134 2135 nsRefreshDriver* RefreshDriver(ScrollContainerFrame* aCallee) { 2136 return aCallee->PresContext()->RefreshDriver(); 2137 } 2138 }; 2139 2140 void ScrollContainerFrame::AsyncScroll::InitSmoothScroll( 2141 TimeStamp aTime, nsPoint aInitialPosition, nsPoint aDestination, 2142 ScrollOrigin aOrigin, const nsRect& aRange, const nsSize& aCurrentVelocity, 2143 UniquePtr<ScrollSnapTargetIds> aSnapTargetIds) { 2144 switch (aOrigin) { 2145 case ScrollOrigin::NotSpecified: 2146 case ScrollOrigin::Restore: 2147 case ScrollOrigin::Relative: 2148 // We don't have special prefs for "restore", just treat it as "other". 2149 // "restore" scrolls are (for now) always instant anyway so unless 2150 // something changes we should never have aOrigin == 2151 // ScrollOrigin::Restore here. 2152 aOrigin = ScrollOrigin::Other; 2153 break; 2154 case ScrollOrigin::Apz: 2155 // Likewise we should never get APZ-triggered scrolls here, and if that 2156 // changes something is likely broken somewhere. 2157 MOZ_ASSERT(false); 2158 break; 2159 default: 2160 break; 2161 }; 2162 2163 // Read preferences only on first iteration or for a different event origin. 2164 if (!mAnimationPhysics || aOrigin != mOrigin) { 2165 mOrigin = aOrigin; 2166 if (StaticPrefs::general_smoothScroll_msdPhysics_enabled()) { 2167 mAnimationPhysics = MakeUnique<ScrollAnimationMSDPhysics>( 2168 apz::ScrollAnimationKind::Smooth, aInitialPosition, 2169 /*smallestVisibleIncrement=*/1.0); 2170 } else { 2171 ScrollAnimationBezierPhysicsSettings settings = 2172 layers::apz::ComputeBezierAnimationSettingsForOrigin(mOrigin); 2173 mAnimationPhysics = 2174 MakeUnique<ScrollAnimationBezierPhysics>(aInitialPosition, settings); 2175 } 2176 } 2177 2178 mStartPosition = aInitialPosition; 2179 mRange = aRange; 2180 2181 mAnimationPhysics->Update(aTime, aDestination, aCurrentVelocity); 2182 mSnapTargetIds = std::move(aSnapTargetIds); 2183 } 2184 2185 /* 2186 * Callback function from AsyncSmoothMSDScroll, used in 2187 * ScrollContainerFrame::ScrollTo 2188 */ 2189 void ScrollContainerFrame::AsyncSmoothMSDScrollCallback( 2190 ScrollContainerFrame* aInstance, mozilla::TimeDuration aDeltaTime) { 2191 NS_ASSERTION(aInstance != nullptr, "aInstance must not be null"); 2192 NS_ASSERTION(aInstance->mAsyncSmoothMSDScroll, 2193 "Did not expect AsyncSmoothMSDScrollCallback without an active " 2194 "MSD scroll."); 2195 2196 nsRect range = aInstance->mAsyncSmoothMSDScroll->GetRange(); 2197 aInstance->mAsyncSmoothMSDScroll->Simulate(aDeltaTime); 2198 2199 if (!aInstance->mAsyncSmoothMSDScroll->IsFinished()) { 2200 nsPoint destination = aInstance->mAsyncSmoothMSDScroll->GetPosition(); 2201 // Allow this scroll operation to land on any pixel boundary within the 2202 // allowed scroll range for this frame. 2203 // If the MSD is under-dampened or the destination is changed rapidly, 2204 // it is expected (and desired) that the scrolling may overshoot. 2205 nsRect intermediateRange = nsRect(destination, nsSize()).UnionEdges(range); 2206 aInstance->ScrollToImpl(destination, intermediateRange); 2207 // 'aInstance' might be destroyed here 2208 return; 2209 } 2210 2211 aInstance->CompleteAsyncScroll( 2212 aInstance->mAsyncSmoothMSDScroll->GetStartPosition(), range, 2213 aInstance->mAsyncSmoothMSDScroll->TakeSnapTargetIds()); 2214 } 2215 2216 /* 2217 * Callback function from AsyncScroll, used in ScrollContainerFrame::ScrollTo 2218 */ 2219 void ScrollContainerFrame::AsyncScrollCallback(ScrollContainerFrame* aInstance, 2220 mozilla::TimeStamp aTime) { 2221 MOZ_ASSERT(aInstance != nullptr, "aInstance must not be null"); 2222 MOZ_ASSERT( 2223 aInstance->mAsyncScroll, 2224 "Did not expect AsyncScrollCallback without an active async scroll."); 2225 2226 if (!aInstance || !aInstance->mAsyncScroll) { 2227 return; // XXX wallpaper bug 1107353 for now. 2228 } 2229 2230 nsRect range = aInstance->mAsyncScroll->mRange; 2231 if (aInstance->mAsyncScroll->IsSmoothScroll()) { 2232 if (!aInstance->mAsyncScroll->IsFinished(aTime)) { 2233 nsPoint destination = aInstance->mAsyncScroll->PositionAt(aTime); 2234 // Allow this scroll operation to land on any pixel boundary between the 2235 // current position and the final allowed range. (We don't want 2236 // intermediate steps to be more constrained than the final step!) 2237 nsRect intermediateRange = 2238 nsRect(aInstance->GetScrollPosition(), nsSize()).UnionEdges(range); 2239 aInstance->ScrollToImpl(destination, intermediateRange); 2240 // 'aInstance' might be destroyed here 2241 return; 2242 } 2243 } 2244 2245 aInstance->CompleteAsyncScroll(aInstance->mAsyncScroll->GetStartPosition(), 2246 range, 2247 aInstance->mAsyncScroll->TakeSnapTargetIds()); 2248 } 2249 2250 void ScrollContainerFrame::SetTransformingByAPZ(bool aTransforming) { 2251 if (mTransformingByAPZ == aTransforming) { 2252 return; 2253 } 2254 mTransformingByAPZ = aTransforming; 2255 if (aTransforming) { 2256 ScrollbarActivityStarted(); 2257 } else { 2258 ScrollbarActivityStopped(); 2259 PostScrollEndEvent(); 2260 } 2261 if (!css::TextOverflow::HasClippedTextOverflow(this) || 2262 css::TextOverflow::HasBlockEllipsis(mScrolledFrame)) { 2263 // If the block has some overflow marker stuff we should kick off a paint 2264 // because we have special behaviour for it when APZ scrolling is active. 2265 SchedulePaint(); 2266 } 2267 } 2268 2269 void ScrollContainerFrame::CompleteAsyncScroll( 2270 const nsPoint& aStartPosition, const nsRect& aRange, 2271 UniquePtr<ScrollSnapTargetIds> aSnapTargetIds, ScrollOrigin aOrigin) { 2272 SetLastSnapTargetIds(std::move(aSnapTargetIds)); 2273 2274 bool scrollPositionChanged = mDestination != aStartPosition; 2275 bool isNotHandledByApz = 2276 nsLayoutUtils::CanScrollOriginClobberApz(aOrigin) || 2277 ScrollAnimationState().contains(AnimationState::MainThread); 2278 2279 // Apply desired destination range since this is the last step of scrolling. 2280 RemoveObservers(); 2281 AutoWeakFrame weakFrame(this); 2282 ScrollToImpl(mDestination, aRange, aOrigin); 2283 if (!weakFrame.IsAlive()) { 2284 return; 2285 } 2286 // We are done scrolling, set our destination to wherever we actually ended 2287 // up scrolling to. 2288 mDestination = GetScrollPosition(); 2289 // Post a `scrollend` event for scrolling not handled by APZ, including: 2290 // 2291 // - programmatic instant scrolls 2292 // - the end of a smooth scroll animation running on the main thread 2293 // 2294 // For scrolling handled by APZ, the `scrollend` event is posted in 2295 // SetTransformingByAPZ() when the APZC is transitioning from a transforming 2296 // to a non-transforming state (e.g. a transition from PANNING to NOTHING). 2297 // The scrollend event should not be fired for a scroll that does not 2298 // result in a scroll position change. 2299 if (isNotHandledByApz && scrollPositionChanged) { 2300 PostScrollEndEvent(); 2301 } 2302 } 2303 2304 bool ScrollContainerFrame::HasBgAttachmentLocal() const { 2305 const nsStyleBackground* bg = StyleBackground(); 2306 return bg->HasLocalBackground(); 2307 } 2308 2309 void ScrollContainerFrame::ScrollToInternal( 2310 nsPoint aScrollPosition, ScrollMode aMode, ScrollOrigin aOrigin, 2311 const nsRect* aRange, ScrollSnapFlags aSnapFlags, 2312 ScrollTriggeredByScript aTriggeredByScript) { 2313 if (aOrigin == ScrollOrigin::NotSpecified) { 2314 aOrigin = ScrollOrigin::Other; 2315 } 2316 ScrollToWithOrigin( 2317 aScrollPosition, aRange, 2318 ScrollOperationParams{aMode, aOrigin, aSnapFlags, aTriggeredByScript}); 2319 } 2320 2321 void ScrollContainerFrame::ScrollToCSSPixels(const CSSPoint& aScrollPosition, 2322 ScrollMode aMode) { 2323 CSSPoint currentCSSPixels = GetScrollPositionCSSPixels(); 2324 // Transmogrify this scroll to a relative one if there's any on-going 2325 // animation in APZ triggered by __user__. 2326 // Bug 1740164: We will apply it for cases there's no animation in APZ. 2327 2328 auto scrollAnimationState = ScrollAnimationState(); 2329 bool isScrollAnimating = 2330 scrollAnimationState.contains(AnimationState::MainThread) || 2331 scrollAnimationState.contains(AnimationState::APZPending) || 2332 scrollAnimationState.contains(AnimationState::APZRequested); 2333 if (mCurrentAPZScrollAnimationType == 2334 APZScrollAnimationType::TriggeredByUserInput && 2335 !isScrollAnimating) { 2336 CSSPoint delta = aScrollPosition - currentCSSPixels; 2337 // This transmogrification need to be an intended end position scroll 2338 // operation. 2339 ScrollByCSSPixelsInternal(delta, aMode, 2340 ScrollSnapFlags::IntendedEndPosition); 2341 return; 2342 } 2343 2344 nscoord halfPixel = nsPresContext::CSSPixelsToAppUnits(0.5f); 2345 nsPoint pt = CSSPoint::ToAppUnits(aScrollPosition); 2346 nsRect range(pt.x - halfPixel, pt.y - halfPixel, 2 * halfPixel - 1, 2347 2 * halfPixel - 1); 2348 // XXX I don't think the following blocks are needed anymore, now that 2349 // ScrollToImpl simply tries to scroll an integer number of layer 2350 // pixels from the current position 2351 nsPoint current = GetScrollPosition(); 2352 if (currentCSSPixels.x == aScrollPosition.x) { 2353 pt.x = current.x; 2354 range.x = pt.x; 2355 range.width = 0; 2356 } 2357 if (currentCSSPixels.y == aScrollPosition.y) { 2358 pt.y = current.y; 2359 range.y = pt.y; 2360 range.height = 0; 2361 } 2362 ScrollToWithOrigin( 2363 pt, &range, 2364 ScrollOperationParams{ 2365 aMode, ScrollOrigin::Other, 2366 // This ScrollToCSSPixels is used for Element.scrollTo, 2367 // Element.scrollTop, Element.scrollLeft and for Window.scrollTo. 2368 ScrollSnapFlags::IntendedEndPosition, ScrollTriggeredByScript::Yes}); 2369 // 'this' might be destroyed here 2370 } 2371 2372 void ScrollContainerFrame::ScrollToCSSPixelsForApz( 2373 const CSSPoint& aScrollPosition, ScrollSnapTargetIds&& aLastSnapTargetIds) { 2374 nsPoint pt = CSSPoint::ToAppUnits(aScrollPosition); 2375 nscoord halfRange = nsPresContext::CSSPixelsToAppUnits(1000); 2376 nsRect range(pt.x - halfRange, pt.y - halfRange, 2 * halfRange - 1, 2377 2 * halfRange - 1); 2378 ScrollToWithOrigin( 2379 pt, &range, 2380 ScrollOperationParams{ScrollMode::Instant, ScrollOrigin::Apz, 2381 std::move(aLastSnapTargetIds)}); 2382 // 'this' might be destroyed here 2383 } 2384 2385 CSSIntPoint ScrollContainerFrame::GetRoundedScrollPositionCSSPixels() { 2386 return CSSIntPoint::FromAppUnitsRounded(GetScrollPosition()); 2387 } 2388 2389 /* 2390 * this method wraps calls to ScrollToImpl(), either in one shot or 2391 * incrementally, based on the setting of the smoothness scroll pref 2392 */ 2393 void ScrollContainerFrame::ScrollToWithOrigin(nsPoint aScrollPosition, 2394 const nsRect* aRange, 2395 ScrollOperationParams&& aParams) { 2396 // None is never a valid scroll origin to be passed in. 2397 MOZ_ASSERT(aParams.mOrigin != ScrollOrigin::None); 2398 2399 if (aParams.mOrigin != ScrollOrigin::Restore) { 2400 // If we're doing a non-restore scroll, we don't want to later 2401 // override it by restoring our saved scroll position. 2402 SCROLLRESTORE_LOG("%p: Clearing mRestorePos (cur=%s, dst=%s)\n", this, 2403 ToString(GetScrollPosition()).c_str(), 2404 ToString(aScrollPosition).c_str()); 2405 mRestorePos.x = mRestorePos.y = -1; 2406 } 2407 2408 // Stop suppressing displayport while the page is still loading. 2409 if (MOZ_UNLIKELY(PresShell()->IsDocumentLoading())) { 2410 PresShell()->SuppressDisplayport(false); 2411 } 2412 2413 Maybe<SnapDestination> snapDestination; 2414 if (!aParams.IsScrollSnapDisabled()) { 2415 snapDestination = GetSnapPointForDestination(ScrollUnit::DEVICE_PIXELS, 2416 aParams.mSnapFlags, 2417 mDestination, aScrollPosition); 2418 if (snapDestination) { 2419 aScrollPosition = snapDestination->mPosition; 2420 } 2421 } 2422 2423 nsRect scrollRange = GetLayoutScrollRange(); 2424 mDestination = scrollRange.ClampPoint(aScrollPosition); 2425 if (mDestination != aScrollPosition && 2426 aParams.mOrigin == ScrollOrigin::Restore && 2427 GetPageLoadingState() != LoadingState::Loading) { 2428 // If we're doing a restore but the scroll position is clamped, promote 2429 // the origin from one that APZ can clobber to one that it can't clobber. 2430 aParams.mOrigin = ScrollOrigin::Other; 2431 } 2432 2433 nsRect range = aRange && snapDestination.isNothing() 2434 ? *aRange 2435 : nsRect(aScrollPosition, nsSize(0, 0)); 2436 2437 UniquePtr<ScrollSnapTargetIds> snapTargetIds; 2438 if (snapDestination) { 2439 snapTargetIds = 2440 MakeUnique<ScrollSnapTargetIds>(std::move(snapDestination->mTargetIds)); 2441 } else { 2442 snapTargetIds = 2443 MakeUnique<ScrollSnapTargetIds>(std::move(aParams.mTargetIds)); 2444 } 2445 if (aParams.IsInstant()) { 2446 // Asynchronous scrolling is not allowed, so we'll kill any existing 2447 // async-scrolling process and do an instant scroll. 2448 CompleteAsyncScroll(GetScrollPosition(), range, std::move(snapTargetIds), 2449 aParams.mOrigin); 2450 mApzSmoothScrollDestination = Nothing(); 2451 return; 2452 } 2453 2454 if (!aParams.IsSmoothMsd()) { 2455 // If we get a non-smooth-scroll, reset the cached APZ scroll destination, 2456 // so that we know to process the next smooth-scroll destined for APZ. 2457 mApzSmoothScrollDestination = Nothing(); 2458 } 2459 2460 nsPresContext* presContext = PresContext(); 2461 TimeStamp now = 2462 presContext->RefreshDriver()->IsTestControllingRefreshesEnabled() 2463 ? presContext->RefreshDriver()->MostRecentRefresh() 2464 : TimeStamp::Now(); 2465 2466 nsSize currentVelocity(0, 0); 2467 2468 const bool canHandoffToApz = 2469 nsLayoutUtils::AsyncPanZoomEnabled(this) && WantAsyncScroll() && 2470 CanApzScrollInTheseDirections( 2471 DirectionsInDelta(mDestination - GetScrollPosition())); 2472 2473 if (aParams.IsSmoothMsd()) { 2474 mIgnoreMomentumScroll = true; 2475 if (!mAsyncSmoothMSDScroll) { 2476 nsPoint sv = mVelocityQueue.GetVelocity(); 2477 currentVelocity.width = sv.x; 2478 currentVelocity.height = sv.y; 2479 if (mAsyncScroll) { 2480 if (mAsyncScroll->IsSmoothScroll()) { 2481 currentVelocity = mAsyncScroll->VelocityAt(now); 2482 } 2483 mAsyncScroll = nullptr; 2484 } 2485 2486 if (canHandoffToApz) { 2487 ApzSmoothScrollTo(mDestination, ScrollMode::SmoothMsd, aParams.mOrigin, 2488 aParams.mTriggeredByScript, std::move(snapTargetIds), 2489 ViewportType::Layout); 2490 return; 2491 } 2492 2493 mAsyncSmoothMSDScroll = new AsyncSmoothMSDScroll( 2494 GetScrollPosition(), mDestination, currentVelocity, 2495 GetLayoutScrollRange(), now, presContext, std::move(snapTargetIds), 2496 aParams.mTriggeredByScript); 2497 2498 mAsyncSmoothMSDScroll->SetRefreshObserver(this); 2499 } else { 2500 // A previous smooth MSD scroll is still in progress, so we just need to 2501 // update its range and destination. 2502 mAsyncSmoothMSDScroll->SetRange(GetLayoutScrollRange()); 2503 mAsyncSmoothMSDScroll->SetDestination( 2504 mDestination, std::move(snapTargetIds), aParams.mTriggeredByScript); 2505 } 2506 2507 return; 2508 } 2509 2510 if (mAsyncSmoothMSDScroll) { 2511 currentVelocity = mAsyncSmoothMSDScroll->GetVelocity(); 2512 mAsyncSmoothMSDScroll = nullptr; 2513 } 2514 2515 const bool isSmoothScroll = 2516 aParams.IsSmooth() && nsLayoutUtils::IsSmoothScrollingEnabled(); 2517 if (!mAsyncScroll) { 2518 if (isSmoothScroll && canHandoffToApz) { 2519 ApzSmoothScrollTo(mDestination, ScrollMode::Smooth, aParams.mOrigin, 2520 aParams.mTriggeredByScript, std::move(snapTargetIds), 2521 ViewportType::Layout); 2522 return; 2523 } 2524 2525 mAsyncScroll = new AsyncScroll(aParams.mTriggeredByScript); 2526 mAsyncScroll->SetRefreshObserver(this); 2527 } 2528 2529 if (isSmoothScroll) { 2530 mAsyncScroll->InitSmoothScroll(now, GetScrollPosition(), mDestination, 2531 aParams.mOrigin, range, currentVelocity, 2532 std::move(snapTargetIds)); 2533 } else { 2534 mAsyncScroll->Init(GetScrollPosition(), range, std::move(snapTargetIds)); 2535 } 2536 } 2537 2538 void ScrollContainerFrame::MarkScrollbarsDirtyForReflow() const { 2539 auto* presShell = PresShell(); 2540 if (mVScrollbarBox) { 2541 presShell->FrameNeedsReflow(mVScrollbarBox, 2542 IntrinsicDirty::FrameAncestorsAndDescendants, 2543 NS_FRAME_IS_DIRTY); 2544 } 2545 if (mHScrollbarBox) { 2546 presShell->FrameNeedsReflow(mHScrollbarBox, 2547 IntrinsicDirty::FrameAncestorsAndDescendants, 2548 NS_FRAME_IS_DIRTY); 2549 } 2550 } 2551 2552 void ScrollContainerFrame::InvalidateScrollbars() const { 2553 if (mHScrollbarBox) { 2554 mHScrollbarBox->InvalidateFrameSubtree(); 2555 } 2556 if (mVScrollbarBox) { 2557 mVScrollbarBox->InvalidateFrameSubtree(); 2558 } 2559 } 2560 2561 bool ScrollContainerFrame::IsAlwaysActive() const { 2562 if (nsDisplayItem::ForceActiveLayers()) { 2563 return true; 2564 } 2565 2566 // Unless this is the root scrollframe for a non-chrome document 2567 // which is the direct child of a chrome document, we default to not 2568 // being "active". 2569 if (!(mIsRoot && PresContext()->IsRootContentDocumentCrossProcess())) { 2570 return false; 2571 } 2572 2573 // If we have scrolled before, then we should stay active. 2574 if (mHasBeenScrolled) { 2575 return true; 2576 } 2577 2578 // If we're overflow:hidden, then start as inactive until 2579 // we get scrolled manually. 2580 ScrollStyles styles = GetScrollStyles(); 2581 return (styles.mHorizontal != StyleOverflow::Hidden && 2582 styles.mVertical != StyleOverflow::Hidden); 2583 } 2584 2585 void ScrollContainerFrame::RemoveDisplayPortCallback(nsITimer* aTimer, 2586 void* aClosure) { 2587 ScrollContainerFrame* sf = static_cast<ScrollContainerFrame*>(aClosure); 2588 2589 // This function only ever gets called from the expiry timer, so it must 2590 // be non-null here. Set it to null here so that we don't keep resetting 2591 // it unnecessarily in MarkRecentlyScrolled(). 2592 MOZ_ASSERT(sf->mDisplayPortExpiryTimer); 2593 sf->mDisplayPortExpiryTimer = nullptr; 2594 2595 if (!sf->AllowDisplayPortExpiration() || sf->mIsParentToActiveScrollFrames) { 2596 // If this is a scroll parent for some other scrollable frame, don't 2597 // expire the displayport because it would break scroll handoff. Once the 2598 // descendant scrollframes have their displayports expired, they will 2599 // trigger the displayport expiration on this scrollframe as well, and 2600 // mIsParentToActiveScrollFrames will presumably be false when that kicks 2601 // in. 2602 return; 2603 } 2604 2605 // Remove the displayport from this scrollframe if it's been a while 2606 // since it's scrolled, except if it needs to be always active. Note that 2607 // there is one scrollframe that doesn't fall under this general rule, and 2608 // that is the one that nsLayoutUtils::MaybeCreateDisplayPort decides to put 2609 // a displayport on (i.e. the first scrollframe that WantAsyncScroll()s). 2610 // If that scrollframe is this one, we remove the displayport anyway, and 2611 // as part of the next paint MaybeCreateDisplayPort will put another 2612 // displayport back on it. Although the displayport will "flicker" off and 2613 // back on, the layer itself should never disappear, because this all 2614 // happens between actual painting. If the displayport is reset to a 2615 // different position that's ok; this scrollframe hasn't been scrolled 2616 // recently and so the reset should be correct. 2617 2618 nsIContent* content = sf->GetContent(); 2619 2620 if (ScrollContainerFrame::ShouldActivateAllScrollFrames(nullptr, sf)) { 2621 // If we are activating all scroll frames then we only want to remove the 2622 // regular display port and downgrade to a minimal display port. 2623 MOZ_ASSERT(!content->GetProperty(nsGkAtoms::MinimalDisplayPort)); 2624 content->SetProperty(nsGkAtoms::MinimalDisplayPort, 2625 reinterpret_cast<void*>(true)); 2626 } else { 2627 content->RemoveProperty(nsGkAtoms::MinimalDisplayPort); 2628 DisplayPortUtils::RemoveDisplayPort(content); 2629 // Be conservative and unflag this this scrollframe as being scrollable by 2630 // APZ. If it is still scrollable this will get flipped back soon enough. 2631 sf->mScrollableByAPZ = false; 2632 } 2633 2634 DisplayPortUtils::ExpireDisplayPortOnAsyncScrollableAncestor(sf); 2635 sf->SchedulePaint(); 2636 } 2637 2638 void ScrollContainerFrame::MarkEverScrolled() { 2639 // Mark this frame as having been scrolled. If this is the root 2640 // scroll frame of a content document, then IsAlwaysActive() 2641 // will return true from now on and MarkNotRecentlyScrolled() won't 2642 // have any effect. 2643 mHasBeenScrolled = true; 2644 2645 // PresContext should update the last scroll generation, for 2646 // NavigateEvents to determine if the root scroller has moved. 2647 if (mIsRoot) { 2648 PresContext()->UpdateLastScrollGeneration(); 2649 } 2650 } 2651 2652 void ScrollContainerFrame::MarkNotRecentlyScrolled() { 2653 if (!mHasBeenScrolledRecently) { 2654 return; 2655 } 2656 2657 mHasBeenScrolledRecently = false; 2658 SchedulePaint(); 2659 } 2660 2661 void ScrollContainerFrame::MarkRecentlyScrolled() { 2662 mHasBeenScrolledRecently = true; 2663 if (IsAlwaysActive()) { 2664 return; 2665 } 2666 2667 if (mActivityExpirationState.IsTracked()) { 2668 gScrollFrameActivityTracker->MarkUsed(this); 2669 } else { 2670 if (!gScrollFrameActivityTracker) { 2671 gScrollFrameActivityTracker = 2672 new ScrollFrameActivityTracker(GetMainThreadSerialEventTarget()); 2673 } 2674 gScrollFrameActivityTracker->AddObject(this); 2675 } 2676 2677 // If we just scrolled and there's a displayport expiry timer in place, 2678 // reset the timer. 2679 ResetDisplayPortExpiryTimer(); 2680 } 2681 2682 void ScrollContainerFrame::ResetDisplayPortExpiryTimer() { 2683 if (mDisplayPortExpiryTimer) { 2684 mDisplayPortExpiryTimer->InitWithNamedFuncCallback( 2685 RemoveDisplayPortCallback, this, 2686 StaticPrefs::apz_displayport_expiry_ms(), nsITimer::TYPE_ONE_SHOT, 2687 "ScrollContainerFrame::ResetDisplayPortExpiryTimer"_ns); 2688 } 2689 } 2690 2691 bool ScrollContainerFrame::AllowDisplayPortExpiration() { 2692 if (IsAlwaysActive()) { 2693 return false; 2694 } 2695 2696 if (mIsRoot && PresContext()->IsRoot()) { 2697 return false; 2698 } 2699 2700 // If this was the first scrollable frame found, this displayport should 2701 // not expire. 2702 if (IsFirstScrollableFrameSequenceNumber().isSome()) { 2703 return false; 2704 } 2705 2706 if (ShouldActivateAllScrollFrames(nullptr, this) && 2707 GetContent()->GetProperty(nsGkAtoms::MinimalDisplayPort)) { 2708 return false; 2709 } 2710 return true; 2711 } 2712 2713 void ScrollContainerFrame::TriggerDisplayPortExpiration() { 2714 if (!AllowDisplayPortExpiration()) { 2715 return; 2716 } 2717 2718 if (!StaticPrefs::apz_displayport_expiry_ms()) { 2719 // a zero time disables the expiry 2720 return; 2721 } 2722 2723 if (!mDisplayPortExpiryTimer) { 2724 mDisplayPortExpiryTimer = NS_NewTimer(); 2725 } 2726 ResetDisplayPortExpiryTimer(); 2727 } 2728 2729 void ScrollContainerFrame::ScrollVisual() { 2730 MarkEverScrolled(); 2731 // We need to call this after fixing up the view positions 2732 // to be consistent with the frame hierarchy. 2733 MarkRecentlyScrolled(); 2734 } 2735 2736 /** 2737 * Clamp desired scroll position aDesired and range [aDestLower, aDestUpper] 2738 * to [aBoundLower, aBoundUpper] and then select the appunit value from among 2739 * aBoundLower, aBoundUpper and those such that (aDesired - aCurrent) * 2740 * aRes/aAppUnitsPerPixel is an integer (or as close as we can get 2741 * modulo rounding to appunits) that is in [aDestLower, aDestUpper] and 2742 * closest to aDesired. If no such value exists, return the nearest in 2743 * [aDestLower, aDestUpper]. 2744 */ 2745 static nscoord ClampAndAlignWithPixels(nscoord aDesired, nscoord aBoundLower, 2746 nscoord aBoundUpper, nscoord aDestLower, 2747 nscoord aDestUpper, 2748 nscoord aAppUnitsPerPixel, double aRes, 2749 nscoord aCurrent) { 2750 // Intersect scroll range with allowed range, by clamping the ends 2751 // of aRange to be within bounds 2752 nscoord destLower = std::clamp(aDestLower, aBoundLower, aBoundUpper); 2753 nscoord destUpper = std::clamp(aDestUpper, aBoundLower, aBoundUpper); 2754 2755 nscoord desired = std::clamp(aDesired, destLower, destUpper); 2756 if (StaticPrefs::layout_disable_pixel_alignment()) { 2757 return desired; 2758 } 2759 2760 double currentLayerVal = (aRes * aCurrent) / aAppUnitsPerPixel; 2761 double desiredLayerVal = (aRes * desired) / aAppUnitsPerPixel; 2762 double delta = desiredLayerVal - currentLayerVal; 2763 double nearestLayerVal = NS_round(delta) + currentLayerVal; 2764 2765 // Convert back from PaintedLayer space to appunits relative to the top-left 2766 // of the scrolled frame. 2767 nscoord aligned = 2768 aRes == 0.0 2769 ? 0.0 2770 : NSToCoordRoundWithClamp(nearestLayerVal * aAppUnitsPerPixel / aRes); 2771 2772 // Use a bound if it is within the allowed range and closer to desired than 2773 // the nearest pixel-aligned value. 2774 if (aBoundUpper == destUpper && 2775 static_cast<decltype(Abs(desired))>(aBoundUpper - desired) < 2776 Abs(desired - aligned)) { 2777 return aBoundUpper; 2778 } 2779 2780 if (aBoundLower == destLower && 2781 static_cast<decltype(Abs(desired))>(desired - aBoundLower) < 2782 Abs(aligned - desired)) { 2783 return aBoundLower; 2784 } 2785 2786 // Accept the nearest pixel-aligned value if it is within the allowed range. 2787 if (aligned >= destLower && aligned <= destUpper) { 2788 return aligned; 2789 } 2790 2791 // Check if opposite pixel boundary fits into allowed range. 2792 double oppositeLayerVal = 2793 nearestLayerVal + ((nearestLayerVal < desiredLayerVal) ? 1.0 : -1.0); 2794 nscoord opposite = aRes == 0.0 2795 ? 0.0 2796 : NSToCoordRoundWithClamp(oppositeLayerVal * 2797 aAppUnitsPerPixel / aRes); 2798 if (opposite >= destLower && opposite <= destUpper) { 2799 return opposite; 2800 } 2801 2802 // No alignment available. 2803 return desired; 2804 } 2805 2806 /** 2807 * Clamp desired scroll position aPt to aBounds and then snap 2808 * it to the same layer pixel edges as aCurrent, keeping it within aRange 2809 * during snapping. aCurrent is the current scroll position. 2810 */ 2811 static nsPoint ClampAndAlignWithLayerPixels(const nsPoint& aPt, 2812 const nsRect& aBounds, 2813 const nsRect& aRange, 2814 const nsPoint& aCurrent, 2815 nscoord aAppUnitsPerPixel, 2816 const MatrixScales& aScale) { 2817 return nsPoint( 2818 ClampAndAlignWithPixels(aPt.x, aBounds.x, aBounds.XMost(), aRange.x, 2819 aRange.XMost(), aAppUnitsPerPixel, aScale.xScale, 2820 aCurrent.x), 2821 ClampAndAlignWithPixels(aPt.y, aBounds.y, aBounds.YMost(), aRange.y, 2822 aRange.YMost(), aAppUnitsPerPixel, aScale.yScale, 2823 aCurrent.y)); 2824 } 2825 2826 /* static */ 2827 void ScrollContainerFrame::ScrollActivityCallback(nsITimer* aTimer, 2828 void* anInstance) { 2829 auto* self = static_cast<ScrollContainerFrame*>(anInstance); 2830 2831 // Fire the synth mouse move. 2832 self->mScrollActivityTimer->Cancel(); 2833 self->mScrollActivityTimer = nullptr; 2834 self->PresShell()->SynthesizeMouseMove(true); 2835 } 2836 2837 void ScrollContainerFrame::ScheduleSyntheticMouseMove() { 2838 if (!mScrollActivityTimer) { 2839 mScrollActivityTimer = NS_NewTimer(GetMainThreadSerialEventTarget()); 2840 if (!mScrollActivityTimer) { 2841 return; 2842 } 2843 } 2844 2845 mScrollActivityTimer->InitWithNamedFuncCallback( 2846 ScrollActivityCallback, this, 100, nsITimer::TYPE_ONE_SHOT, 2847 "ScrollContainerFrame::ScheduleSyntheticMouseMove"_ns); 2848 } 2849 2850 void ScrollContainerFrame::NotifyApproximateFrameVisibilityUpdate( 2851 bool aIgnoreDisplayPort) { 2852 mLastUpdateFramesPos = GetScrollPosition(); 2853 if (aIgnoreDisplayPort) { 2854 mHadDisplayPortAtLastFrameUpdate = false; 2855 mDisplayPortAtLastFrameUpdate = nsRect(); 2856 } else { 2857 mHadDisplayPortAtLastFrameUpdate = DisplayPortUtils::GetDisplayPort( 2858 GetContent(), &mDisplayPortAtLastFrameUpdate); 2859 } 2860 } 2861 2862 bool ScrollContainerFrame::GetDisplayPortAtLastApproximateFrameVisibilityUpdate( 2863 nsRect* aDisplayPort) { 2864 if (mHadDisplayPortAtLastFrameUpdate) { 2865 *aDisplayPort = mDisplayPortAtLastFrameUpdate; 2866 } 2867 return mHadDisplayPortAtLastFrameUpdate; 2868 } 2869 2870 /* aIncludeCSSTransform controls if we include CSS transforms that are in this 2871 * process (the BrowserChild EffectsInfo mTransformToAncestorScale will include 2872 * CSS transforms in ancestor processes in all cases). */ 2873 MatrixScales GetPaintedLayerScaleForFrame(nsIFrame* aFrame, 2874 bool aIncludeCSSTransform) { 2875 MOZ_ASSERT(aFrame, "need a frame"); 2876 2877 nsPresContext* presCtx = aFrame->PresContext()->GetRootPresContext(); 2878 2879 if (!presCtx) { 2880 presCtx = aFrame->PresContext(); 2881 MOZ_ASSERT(presCtx); 2882 } 2883 2884 ParentLayerToScreenScale2D transformToAncestorScale; 2885 if (aIncludeCSSTransform) { 2886 transformToAncestorScale = 2887 nsLayoutUtils::GetTransformToAncestorScaleCrossProcessForFrameMetrics( 2888 aFrame); 2889 } else { 2890 if (BrowserChild* browserChild = 2891 BrowserChild::GetFrom(aFrame->PresShell())) { 2892 transformToAncestorScale = 2893 browserChild->GetEffectsInfo().mTransformToAncestorScale; 2894 } 2895 } 2896 transformToAncestorScale = 2897 ParentLayerToParentLayerScale( 2898 presCtx->PresShell()->GetCumulativeResolution()) * 2899 transformToAncestorScale; 2900 2901 return transformToAncestorScale.ToUnknownScale(); 2902 } 2903 2904 void ScrollContainerFrame::ScrollToImpl( 2905 nsPoint aPt, const nsRect& aRange, ScrollOrigin aOrigin, 2906 ScrollTriggeredByScript aTriggeredByScript) { 2907 // None is never a valid scroll origin to be passed in. 2908 MOZ_ASSERT(aOrigin != ScrollOrigin::None); 2909 2910 // Figure out the effective origin for this scroll request. 2911 if (aOrigin == ScrollOrigin::NotSpecified) { 2912 // If no origin was specified, we still want to set it to something that's 2913 // non-unknown, so that we can use eUnknown to distinguish if the frame was 2914 // scrolled at all. Default it to some generic placeholder. 2915 aOrigin = ScrollOrigin::Other; 2916 } 2917 2918 // If this scroll is |relative|, but we've already had a user scroll that 2919 // was not relative, promote this origin to |other|. This ensures that we 2920 // may only transmit a relative update to APZ if all scrolls since the last 2921 // transaction or repaint request have been relative. 2922 if (aOrigin == ScrollOrigin::Relative && 2923 (mLastScrollOrigin != ScrollOrigin::None && 2924 mLastScrollOrigin != ScrollOrigin::NotSpecified && 2925 mLastScrollOrigin != ScrollOrigin::Relative && 2926 mLastScrollOrigin != ScrollOrigin::Apz)) { 2927 aOrigin = ScrollOrigin::Other; 2928 } 2929 2930 // If the origin is a downgrade, and downgrades are allowed, process the 2931 // downgrade even if we're going to early-exit because we're already at 2932 // the correct scroll position. This ensures that if there wasn't a main- 2933 // thread scroll update pending before a frame reconstruction (as indicated 2934 // by mAllowScrollOriginDowngrade=true), then after the frame reconstruction 2935 // the origin is downgraded to "restore" even if the layout scroll offset to 2936 // be restored is (0,0) (which will take the early-exit below). This is 2937 // important so that restoration of a *visual* scroll offset (which might be 2938 // to something other than (0,0)) isn't clobbered. 2939 bool isScrollOriginDowngrade = 2940 nsLayoutUtils::CanScrollOriginClobberApz(mLastScrollOrigin) && 2941 !nsLayoutUtils::CanScrollOriginClobberApz(aOrigin); 2942 bool allowScrollOriginChange = 2943 mAllowScrollOriginDowngrade && isScrollOriginDowngrade; 2944 2945 if (allowScrollOriginChange) { 2946 mLastScrollOrigin = aOrigin; 2947 mAllowScrollOriginDowngrade = false; 2948 } 2949 2950 nsPresContext* presContext = PresContext(); 2951 nscoord appUnitsPerDevPixel = presContext->AppUnitsPerDevPixel(); 2952 // 'scale' is our estimate of the scale factor that will be applied 2953 // when rendering the scrolled content to its own PaintedLayer. 2954 MatrixScales scale = GetPaintedLayerScaleForFrame( 2955 mScrolledFrame, /* aIncludeCSSTransform = */ true); 2956 nsPoint curPos = GetScrollPosition(); 2957 2958 // Try to align aPt with curPos so they have an integer number of layer 2959 // pixels between them. This gives us the best chance of scrolling without 2960 // having to invalidate due to changes in subpixel rendering. 2961 // Note that when we actually draw into a PaintedLayer, the coordinates 2962 // that get mapped onto the layer buffer pixels are from the display list, 2963 // which are relative to the display root frame's top-left increasing down, 2964 // whereas here our coordinates are scroll positions which increase upward 2965 // and are relative to the scrollport top-left. This difference doesn't 2966 // actually matter since all we are about is that there be an integer number 2967 // of layer pixels between pt and curPos. 2968 nsPoint pt = ClampAndAlignWithLayerPixels(aPt, GetLayoutScrollRange(), aRange, 2969 curPos, appUnitsPerDevPixel, scale); 2970 if (pt == curPos) { 2971 // Even if we are bailing out due to no-op main-thread scroll position 2972 // change, we might need to cancel an APZ smooth scroll that we already 2973 // kicked off. It might be reasonable to eventually remove the 2974 // mApzSmoothScrollDestination clause from this if statement, as that 2975 // may simplify this a bit and should be fine from the APZ side. 2976 if (mApzSmoothScrollDestination && aOrigin != ScrollOrigin::Clamp) { 2977 if (aOrigin == ScrollOrigin::Relative) { 2978 AppendScrollUpdate( 2979 ScrollPositionUpdate::NewRelativeScroll(mApzScrollPos, pt)); 2980 mApzScrollPos = pt; 2981 } else if (aOrigin != ScrollOrigin::Apz) { 2982 ScrollOrigin origin = 2983 (mAllowScrollOriginDowngrade || !isScrollOriginDowngrade) 2984 ? aOrigin 2985 : mLastScrollOrigin; 2986 AppendScrollUpdate(ScrollPositionUpdate::NewScroll(origin, pt)); 2987 } 2988 } 2989 return; 2990 } 2991 2992 // If we are scrolling the RCD-RSF, and a visual scroll update is pending, 2993 // cancel it; otherwise, it will clobber this scroll. 2994 if (IsRootScrollFrameOfDocument() && 2995 presContext->IsRootContentDocumentCrossProcess()) { 2996 auto* ps = presContext->GetPresShell(); 2997 if (const auto& visualScrollUpdate = ps->GetPendingVisualScrollUpdate()) { 2998 if (visualScrollUpdate->mVisualScrollOffset != aPt) { 2999 // Only clobber if the scroll was originated by the main thread. 3000 // Respect the priority of origins (an "eRestore" layout scroll should 3001 // not clobber an "eMainThread" visual scroll.) 3002 bool shouldClobber = 3003 aOrigin == ScrollOrigin::Other || 3004 (aOrigin == ScrollOrigin::Restore && 3005 visualScrollUpdate->mUpdateType == FrameMetrics::eRestore); 3006 if (shouldClobber) { 3007 ps->AcknowledgePendingVisualScrollUpdate(); 3008 ps->ClearPendingVisualScrollUpdate(); 3009 } 3010 } 3011 } 3012 } 3013 3014 bool needFrameVisibilityUpdate = mLastUpdateFramesPos == nsPoint(-1, -1); 3015 3016 nsPoint dist(std::abs(pt.x - mLastUpdateFramesPos.x), 3017 std::abs(pt.y - mLastUpdateFramesPos.y)); 3018 nsSize visualViewportSize = GetVisualViewportSize(); 3019 nscoord horzAllowance = std::max( 3020 visualViewportSize.width / 3021 std::max( 3022 StaticPrefs:: 3023 layout_framevisibility_amountscrollbeforeupdatehorizontal(), 3024 1), 3025 AppUnitsPerCSSPixel()); 3026 nscoord vertAllowance = std::max( 3027 visualViewportSize.height / 3028 std::max( 3029 StaticPrefs:: 3030 layout_framevisibility_amountscrollbeforeupdatevertical(), 3031 1), 3032 AppUnitsPerCSSPixel()); 3033 if (dist.x >= horzAllowance || dist.y >= vertAllowance) { 3034 needFrameVisibilityUpdate = true; 3035 } 3036 3037 nsRect oldDisplayPort; 3038 nsIContent* content = GetContent(); 3039 DisplayPortUtils::GetDisplayPort(content, &oldDisplayPort); 3040 oldDisplayPort.MoveBy(-mScrolledFrame->GetPosition()); 3041 3042 // Update frame position for scrolling 3043 mScrolledFrame->SetPosition(mScrollPort.TopLeft() - pt); 3044 3045 // If |mLastScrollOrigin| is already set to something that can clobber APZ's 3046 // scroll offset, then we don't want to change it to something that can't. 3047 // If we allowed this, then we could end up in a state where APZ ignores 3048 // legitimate scroll offset updates because the origin has been masked by 3049 // a later change within the same refresh driver tick. 3050 allowScrollOriginChange = 3051 (mAllowScrollOriginDowngrade || !isScrollOriginDowngrade); 3052 3053 if (allowScrollOriginChange) { 3054 mLastScrollOrigin = aOrigin; 3055 mAllowScrollOriginDowngrade = false; 3056 } 3057 3058 if (aOrigin == ScrollOrigin::Relative) { 3059 MOZ_ASSERT(!isScrollOriginDowngrade); 3060 MOZ_ASSERT(mLastScrollOrigin == ScrollOrigin::Relative); 3061 AppendScrollUpdate( 3062 ScrollPositionUpdate::NewRelativeScroll(mApzScrollPos, pt)); 3063 mApzScrollPos = pt; 3064 } else if (aOrigin != ScrollOrigin::Apz) { 3065 AppendScrollUpdate(ScrollPositionUpdate::NewScroll(mLastScrollOrigin, pt)); 3066 } 3067 3068 if (mLastScrollOrigin == ScrollOrigin::Apz) { 3069 mApzScrollPos = GetScrollPosition(); 3070 } 3071 3072 ScrollVisual(); 3073 mAnchor.UserScrolled(); 3074 3075 // Only report user-triggered scrolling interactions 3076 bool jsOnStack = nsContentUtils::GetCurrentJSContext() != nullptr; 3077 bool scrollingToAnchor = ScrollingInteractionContext::IsScrollingToAnchor(); 3078 if (!jsOnStack && !scrollingToAnchor) { 3079 nsPoint distanceScrolled(std::abs(pt.x - curPos.x), 3080 std::abs(pt.y - curPos.y)); 3081 ScrollingMetrics::OnScrollingInteraction( 3082 CSSPoint::FromAppUnits(distanceScrolled).Length()); 3083 } 3084 3085 bool schedulePaint = true; 3086 if (nsLayoutUtils::AsyncPanZoomEnabled(this) && 3087 !nsLayoutUtils::ShouldDisableApzForElement(content) && 3088 !content->GetProperty(nsGkAtoms::MinimalDisplayPort) && 3089 StaticPrefs::apz_paint_skipping_enabled()) { 3090 // If APZ is enabled with paint-skipping, there are certain conditions in 3091 // which we can skip paints: 3092 // 1) If APZ triggered this scroll, and the tile-aligned displayport is 3093 // unchanged. 3094 // 2) If non-APZ triggered this scroll, but we can handle it by just asking 3095 // APZ to update the scroll position. Again we make this conditional on 3096 // the tile-aligned displayport being unchanged. 3097 // We do the displayport check first since it's common to all scenarios, 3098 // and then if the displayport is unchanged, we check if APZ triggered, 3099 // or can handle, this scroll. If so, we set schedulePaint to false and 3100 // skip the paint. 3101 // Because of bug 1264297, we also don't do paint-skipping for elements with 3102 // perspective, because the displayport may not have captured everything 3103 // that needs to be painted. So even if the final tile-aligned displayport 3104 // is the same, we force a repaint for these elements. Bug 1254260 tracks 3105 // fixing this properly. 3106 nsRect displayPort; 3107 bool usingDisplayPort = 3108 DisplayPortUtils::GetDisplayPort(content, &displayPort); 3109 displayPort.MoveBy(-mScrolledFrame->GetPosition()); 3110 3111 PAINT_SKIP_LOG( 3112 "New scrollpos %s usingDP %d dpEqual %d scrollableByApz " 3113 "%d perspective %d bglocal %d filter %d\n", 3114 ToString(CSSPoint::FromAppUnits(GetScrollPosition())).c_str(), 3115 usingDisplayPort, displayPort.IsEqualEdges(oldDisplayPort), 3116 mScrollableByAPZ, HasPerspective(), HasBgAttachmentLocal(), 3117 mHasOutOfFlowContentInsideFilter); 3118 if (usingDisplayPort && displayPort.IsEqualEdges(oldDisplayPort) && 3119 !HasPerspective() && !HasBgAttachmentLocal() && 3120 !mHasOutOfFlowContentInsideFilter) { 3121 bool haveScrollLinkedEffects = 3122 content->GetComposedDoc()->HasScrollLinkedEffect(); 3123 bool apzDisabled = haveScrollLinkedEffects && 3124 StaticPrefs::apz_disable_for_scroll_linked_effects(); 3125 if (!apzDisabled) { 3126 if (LastScrollOrigin() == ScrollOrigin::Apz) { 3127 schedulePaint = false; 3128 PAINT_SKIP_LOG("Skipping due to APZ scroll\n"); 3129 } else if (mScrollableByAPZ) { 3130 nsIWidget* widget = GetNearestWidget(); 3131 WindowRenderer* renderer = 3132 widget ? widget->GetWindowRenderer() : nullptr; 3133 if (renderer) { 3134 mozilla::layers::ScrollableLayerGuid::ViewID id; 3135 bool success = nsLayoutUtils::FindIDFor(content, &id); 3136 MOZ_ASSERT(success); // we have a displayport, we better have an ID 3137 3138 // Schedule an empty transaction to carry over the scroll offset 3139 // update, instead of a full transaction. This empty transaction 3140 // might still get squashed into a full transaction if something 3141 // happens to trigger one. 3142 MOZ_ASSERT(!mScrollUpdates.IsEmpty()); 3143 success = renderer->AddPendingScrollUpdateForNextTransaction( 3144 id, mScrollUpdates.LastElement()); 3145 if (success) { 3146 schedulePaint = false; 3147 SchedulePaint(nsIFrame::PAINT_COMPOSITE_ONLY); 3148 PAINT_SKIP_LOG( 3149 "Skipping due to APZ-forwarded main-thread scroll\n"); 3150 } else { 3151 PAINT_SKIP_LOG( 3152 "Failed to set pending scroll update on layer manager\n"); 3153 } 3154 } 3155 } 3156 } 3157 } 3158 } 3159 3160 // If the new scroll offset is going to clobber APZ's scroll offset, for 3161 // the RCD-RSF this will have the effect of updating the visual viewport 3162 // offset in a way that keeps the relative offset between the layout and 3163 // visual viewports constant. This will cause APZ to send us a new visual 3164 // viewport offset, but instead of waiting for that, just set the value 3165 // we expect APZ will set ourselves, to minimize the chances of 3166 // inconsistencies from querying a stale value. 3167 if (mIsRoot && nsLayoutUtils::CanScrollOriginClobberApz(aOrigin)) { 3168 AutoWeakFrame weakFrame(this); 3169 AutoScrollbarRepaintSuppression repaintSuppression(this, weakFrame, 3170 !schedulePaint); 3171 3172 nsPoint visualViewportOffset = curPos; 3173 if (presContext->PresShell()->IsVisualViewportOffsetSet()) { 3174 visualViewportOffset = 3175 presContext->PresShell()->GetVisualViewportOffset(); 3176 } 3177 nsPoint relativeOffset = visualViewportOffset - curPos; 3178 3179 presContext->PresShell()->SetVisualViewportOffset(pt + relativeOffset, 3180 curPos); 3181 if (!weakFrame.IsAlive()) { 3182 return; 3183 } 3184 } 3185 3186 if (schedulePaint) { 3187 SchedulePaint(); 3188 3189 if (needFrameVisibilityUpdate) { 3190 presContext->PresShell()->ScheduleApproximateFrameVisibilityUpdateNow(); 3191 } 3192 } 3193 3194 if (ChildrenHavePerspective()) { 3195 // The overflow areas of descendants may depend on the scroll position, 3196 // so ensure they get updated. 3197 3198 // First we recompute the overflow areas of the transformed children 3199 // that use the perspective. FinishAndStoreOverflow only calls this 3200 // if the size changes, so we need to do it manually. 3201 RecomputePerspectiveChildrenOverflow(this); 3202 3203 // Update the overflow for the scrolled frame to take any changes from the 3204 // children into account. 3205 mScrolledFrame->UpdateOverflow(); 3206 3207 // Update the overflow for the outer so that we recompute scrollbars. 3208 UpdateOverflow(); 3209 } 3210 3211 ScheduleSyntheticMouseMove(); 3212 3213 nsAutoScriptBlocker scriptBlocker; 3214 PresShell::AutoAssertNoFlush noFlush(*PresShell()); 3215 3216 { // scope the AutoScrollbarRepaintSuppression 3217 AutoWeakFrame weakFrame(this); 3218 AutoScrollbarRepaintSuppression repaintSuppression(this, weakFrame, 3219 !schedulePaint); 3220 UpdateScrollbarPosition(); 3221 if (!weakFrame.IsAlive()) { 3222 return; 3223 } 3224 } 3225 PresShell()->UpdateAnchorPosForScroll(this); 3226 3227 presContext->RecordInteractionTime( 3228 nsPresContext::InteractionType::ScrollInteraction, TimeStamp::Now()); 3229 3230 PostScrollEvent(); 3231 // If this is a viewport scroll, this could affect the relative offset 3232 // between layout and visual viewport, so we might have to fire a visual 3233 // viewport scroll event as well. 3234 if (mIsRoot) { 3235 if (auto* window = nsGlobalWindowInner::Cast( 3236 PresContext()->Document()->GetInnerWindow())) { 3237 window->VisualViewport()->PostScrollEvent( 3238 presContext->PresShell()->GetVisualViewportOffset(), curPos); 3239 } 3240 } 3241 3242 // Schedule the scroll-timelines linked to its scrollable frame. 3243 // if `pt == curPos`, we early return, so the position must be changed at 3244 // this moment. Therefore, we can schedule scroll animations directly. 3245 ScheduleScrollAnimations(); 3246 3247 if (mStickyContainer) { 3248 mStickyContainer->UpdatePositions(pt, /* aSubtreeRoot = */ nullptr); 3249 } 3250 3251 if (nsCOMPtr<nsIDocShell> docShell = presContext->GetDocShell()) { 3252 docShell->NotifyScrollObservers(); 3253 } 3254 } 3255 3256 // Finds the max z-index of the items in aList that meet the following 3257 // conditions 3258 // 1) have z-index auto or z-index >= 0, and 3259 // 2) aFrame is a proper ancestor of the item's frame. 3260 // Returns Nothing() if there is no such item. 3261 static Maybe<int32_t> MaxZIndexInListOfItemsContainedInFrame( 3262 nsDisplayList* aList, nsIFrame* aFrame) { 3263 Maybe<int32_t> maxZIndex = Nothing(); 3264 for (nsDisplayItem* item : *aList) { 3265 int32_t zIndex = item->ZIndex(); 3266 if (zIndex < 0 || 3267 !nsLayoutUtils::IsProperAncestorFrame(aFrame, item->Frame())) { 3268 continue; 3269 } 3270 if (!maxZIndex) { 3271 maxZIndex = Some(zIndex); 3272 } else { 3273 maxZIndex = Some(std::max(maxZIndex.value(), zIndex)); 3274 } 3275 } 3276 return maxZIndex; 3277 } 3278 3279 template <class T> 3280 static void AppendInternalItemToTop(const nsDisplayListSet& aLists, T* aItem, 3281 const Maybe<int32_t>& aZIndex) { 3282 if (aZIndex) { 3283 aItem->SetOverrideZIndex(aZIndex.value()); 3284 aLists.PositionedDescendants()->AppendToTop(aItem); 3285 } else { 3286 aLists.Content()->AppendToTop(aItem); 3287 } 3288 } 3289 3290 static const uint32_t APPEND_OWN_LAYER = 0x1; 3291 static const uint32_t APPEND_POSITIONED = 0x2; 3292 static const uint32_t APPEND_SCROLLBAR_CONTAINER = 0x4; 3293 static const uint32_t APPEND_OVERLAY = 0x8; 3294 static const uint32_t APPEND_TOP = 0x10; 3295 3296 static void AppendToTop(nsDisplayListBuilder* aBuilder, 3297 const nsDisplayListSet& aLists, nsDisplayList* aSource, 3298 nsIFrame* aSourceFrame, nsIFrame* aScrollFrame, 3299 uint32_t aFlags) { 3300 if (aSource->IsEmpty()) { 3301 return; 3302 } 3303 3304 nsDisplayWrapList* newItem; 3305 const ActiveScrolledRoot* asr = aBuilder->CurrentActiveScrolledRoot(); 3306 if (aFlags & APPEND_OWN_LAYER) { 3307 ScrollbarData scrollbarData; 3308 if (aFlags & APPEND_SCROLLBAR_CONTAINER) { 3309 scrollbarData = ScrollbarData::CreateForScrollbarContainer( 3310 aBuilder->GetCurrentScrollbarDirection(), 3311 aBuilder->GetCurrentScrollbarTarget()); 3312 // Direction should be set 3313 MOZ_ASSERT(scrollbarData.mDirection.isSome()); 3314 } 3315 3316 newItem = MakeDisplayItemWithIndex<nsDisplayOwnLayer>( 3317 aBuilder, aSourceFrame, 3318 /* aIndex = */ nsDisplayOwnLayer::OwnLayerForScrollbar, aSource, asr, 3319 nsDisplayItem::ContainerASRType::Constant, nsDisplayOwnLayerFlags::None, 3320 scrollbarData, true, false); 3321 } else { 3322 // Build the wrap list with an index of 1, since the scrollbar frame itself 3323 // might have already built an nsDisplayWrapList. 3324 newItem = MakeDisplayItemWithIndex<nsDisplayWrapper>(aBuilder, aSourceFrame, 3325 1, aSource, false); 3326 } 3327 if (!newItem) { 3328 return; 3329 } 3330 3331 if (aFlags & APPEND_POSITIONED) { 3332 // We want overlay scrollbars to always be on top of the scrolled content, 3333 // but we don't want them to unnecessarily cover overlapping elements from 3334 // outside our scroll frame. 3335 Maybe<int32_t> zIndex = Nothing(); 3336 if (aFlags & APPEND_TOP) { 3337 zIndex = Some(INT32_MAX); 3338 } else if (aFlags & APPEND_OVERLAY) { 3339 zIndex = MaxZIndexInListOfItemsContainedInFrame( 3340 aLists.PositionedDescendants(), aScrollFrame); 3341 } else if (aSourceFrame->StylePosition()->mZIndex.IsInteger()) { 3342 zIndex = Some(aSourceFrame->StylePosition()->mZIndex.integer._0); 3343 } 3344 AppendInternalItemToTop(aLists, newItem, zIndex); 3345 } else { 3346 aLists.BorderBackground()->AppendToTop(newItem); 3347 } 3348 } 3349 3350 struct HoveredStateComparator { 3351 static bool Hovered(const nsIFrame* aFrame) { 3352 return aFrame->GetContent()->IsElement() && 3353 aFrame->GetContent()->AsElement()->State().HasState( 3354 ElementState::HOVER); 3355 } 3356 3357 bool Equals(nsIFrame* A, nsIFrame* B) const { 3358 return Hovered(A) == Hovered(B); 3359 } 3360 3361 bool LessThan(nsIFrame* A, nsIFrame* B) const { 3362 return !Hovered(A) && Hovered(B); 3363 } 3364 }; 3365 3366 void ScrollContainerFrame::AppendScrollPartsTo(nsDisplayListBuilder* aBuilder, 3367 const nsDisplayListSet& aLists, 3368 bool aCreateLayer, 3369 bool aPositioned) { 3370 MOZ_ASSERT(!HidesContent()); 3371 const bool overlayScrollbars = UsesOverlayScrollbars(); 3372 3373 AutoTArray<nsIFrame*, 3> scrollParts; 3374 for (nsIFrame* kid : PrincipalChildList()) { 3375 if (kid == mScrolledFrame || 3376 (overlayScrollbars || kid->IsAbsPosContainingBlock()) != aPositioned) { 3377 continue; 3378 } 3379 3380 scrollParts.AppendElement(kid); 3381 } 3382 if (scrollParts.IsEmpty()) { 3383 return; 3384 } 3385 3386 // We can't check will-change budget during display list building phase. 3387 // This means that we will build scroll bar layers for out of budget 3388 // will-change: scroll position. 3389 const mozilla::layers::ScrollableLayerGuid::ViewID scrollTargetId = 3390 aBuilder->BuildCompositorHitTestInfo() && IsScrollingActive() 3391 ? nsLayoutUtils::FindOrCreateIDFor(mScrolledFrame->GetContent()) 3392 : mozilla::layers::ScrollableLayerGuid::NULL_SCROLL_ID; 3393 3394 scrollParts.Sort(HoveredStateComparator()); 3395 3396 DisplayListClipState::AutoSaveRestore clipState(aBuilder); 3397 // Don't let scrollparts extent outside our frame's border-box, if these are 3398 // viewport scrollbars. They would create layerization problems. This wouldn't 3399 // normally be an issue but themes can add overflow areas to scrollbar parts. 3400 if (mIsRoot) { 3401 nsRect scrollPartsClip(aBuilder->ToReferenceFrame(this), 3402 TrueOuterSize(aBuilder)); 3403 clipState.ClipContentDescendants(scrollPartsClip); 3404 } 3405 3406 for (uint32_t i = 0; i < scrollParts.Length(); ++i) { 3407 MOZ_ASSERT(scrollParts[i]); 3408 Maybe<ScrollDirection> scrollDirection; 3409 uint32_t appendToTopFlags = 0; 3410 if (scrollParts[i] == mVScrollbarBox) { 3411 scrollDirection.emplace(ScrollDirection::eVertical); 3412 appendToTopFlags |= APPEND_SCROLLBAR_CONTAINER; 3413 } 3414 if (scrollParts[i] == mHScrollbarBox) { 3415 MOZ_ASSERT(!scrollDirection.isSome()); 3416 scrollDirection.emplace(ScrollDirection::eHorizontal); 3417 appendToTopFlags |= APPEND_SCROLLBAR_CONTAINER; 3418 } 3419 3420 // The display port doesn't necessarily include the scrollbars, so just 3421 // include all of the scrollbars if we are in a RCD-RSF. We only do 3422 // this for the root scrollframe of the root content document, which is 3423 // zoomable, and where the scrollbar sizes are bounded by the widget. 3424 const nsRect visible = 3425 mIsRoot && PresContext()->IsRootContentDocumentCrossProcess() 3426 ? scrollParts[i]->InkOverflowRectRelativeToParent() 3427 : aBuilder->GetVisibleRect(); 3428 if (visible.IsEmpty()) { 3429 continue; 3430 } 3431 const nsRect dirty = 3432 mIsRoot && PresContext()->IsRootContentDocumentCrossProcess() 3433 ? scrollParts[i]->InkOverflowRectRelativeToParent() 3434 : aBuilder->GetDirtyRect(); 3435 3436 // Always create layers for overlay scrollbars so that we don't create a 3437 // giant layer covering the whole scrollport if both scrollbars are visible. 3438 const bool isOverlayScrollbar = 3439 scrollDirection.isSome() && overlayScrollbars; 3440 const bool createLayer = 3441 aCreateLayer || isOverlayScrollbar || 3442 StaticPrefs::layout_scrollbars_always_layerize_track(); 3443 3444 nsDisplayListCollection partList(aBuilder); 3445 { 3446 nsDisplayListBuilder::AutoBuildingDisplayList buildingForChild( 3447 aBuilder, this, visible, dirty); 3448 3449 nsDisplayListBuilder::AutoCurrentScrollbarInfoSetter infoSetter( 3450 aBuilder, scrollTargetId, scrollDirection, createLayer); 3451 BuildDisplayListForChild( 3452 aBuilder, scrollParts[i], partList, 3453 nsIFrame::DisplayChildFlag::ForceStackingContext); 3454 } 3455 3456 // DisplayChildFlag::ForceStackingContext put everything into 3457 // partList.PositionedDescendants(). 3458 if (partList.PositionedDescendants()->IsEmpty()) { 3459 continue; 3460 } 3461 3462 if (createLayer) { 3463 appendToTopFlags |= APPEND_OWN_LAYER; 3464 } 3465 if (aPositioned) { 3466 appendToTopFlags |= APPEND_POSITIONED; 3467 } 3468 3469 if (isOverlayScrollbar || scrollParts[i] == mResizerBox) { 3470 if (isOverlayScrollbar && mIsRoot) { 3471 appendToTopFlags |= APPEND_TOP; 3472 } else { 3473 appendToTopFlags |= APPEND_OVERLAY; 3474 aBuilder->SetDisablePartialUpdates(true); 3475 } 3476 } 3477 3478 { 3479 nsDisplayListBuilder::AutoBuildingDisplayList buildingForChild( 3480 aBuilder, scrollParts[i], visible + GetOffsetTo(scrollParts[i]), 3481 dirty + GetOffsetTo(scrollParts[i])); 3482 if (scrollParts[i]->IsTransformed()) { 3483 nsPoint toOuterReferenceFrame; 3484 const nsIFrame* outerReferenceFrame = aBuilder->FindReferenceFrameFor( 3485 scrollParts[i]->GetParent(), &toOuterReferenceFrame); 3486 toOuterReferenceFrame += scrollParts[i]->GetPosition(); 3487 3488 buildingForChild.SetReferenceFrameAndCurrentOffset( 3489 outerReferenceFrame, toOuterReferenceFrame); 3490 } 3491 nsDisplayListBuilder::AutoCurrentScrollbarInfoSetter infoSetter( 3492 aBuilder, scrollTargetId, scrollDirection, createLayer); 3493 3494 ::AppendToTop(aBuilder, aLists, partList.PositionedDescendants(), 3495 scrollParts[i], this, appendToTopFlags); 3496 } 3497 } 3498 } 3499 3500 nsRect ScrollContainerFrame::ExpandRectToNearlyVisible( 3501 const nsRect& aRect) const { 3502 // We don't want to expand a rect in a direction that we can't scroll, so we 3503 // check the scroll range. 3504 nsRect scrollRange = GetLayoutScrollRange(); 3505 nsPoint scrollPos = GetScrollPosition(); 3506 nsMargin expand(0, 0, 0, 0); 3507 3508 nscoord vertShift = 3509 StaticPrefs::layout_framevisibility_numscrollportheights() * aRect.height; 3510 if (scrollRange.y < scrollPos.y) { 3511 expand.top = vertShift; 3512 } 3513 if (scrollPos.y < scrollRange.YMost()) { 3514 expand.bottom = vertShift; 3515 } 3516 3517 nscoord horzShift = 3518 StaticPrefs::layout_framevisibility_numscrollportwidths() * aRect.width; 3519 if (scrollRange.x < scrollPos.x) { 3520 expand.left = horzShift; 3521 } 3522 if (scrollPos.x < scrollRange.XMost()) { 3523 expand.right = horzShift; 3524 } 3525 3526 nsRect rect = aRect; 3527 rect.Inflate(expand); 3528 return rect; 3529 } 3530 3531 // This is similar to a "save-restore" RAII class for 3532 // DisplayListBuilder::ContainsBlendMode(), with a slight enhancement. 3533 // If this class is put on the stack and then unwound, the DL builder's 3534 // ContainsBlendMode flag will be effectively the same as if this class wasn't 3535 // put on the stack. However, if the CaptureContainsBlendMode method is called, 3536 // there will be a difference - the blend mode in the descendant display lists 3537 // will be "captured" and extracted. 3538 // The main goal here is to allow conditionally capturing the flag that 3539 // indicates whether or not a blend mode was encountered in the descendant part 3540 // of the display list. 3541 class MOZ_RAII AutoContainsBlendModeCapturer { 3542 nsDisplayListBuilder& mBuilder; 3543 bool mSavedContainsBlendMode; 3544 3545 public: 3546 explicit AutoContainsBlendModeCapturer(nsDisplayListBuilder& aBuilder) 3547 : mBuilder(aBuilder), 3548 mSavedContainsBlendMode(aBuilder.ContainsBlendMode()) { 3549 mBuilder.ClearStackingContextBits( 3550 StackingContextBits::ContainsMixBlendMode); 3551 } 3552 3553 bool CaptureContainsBlendMode() { 3554 // "Capture" the flag by extracting and clearing the ContainsBlendMode flag 3555 // on the builder. 3556 const bool capturedBlendMode = mBuilder.ContainsBlendMode(); 3557 mBuilder.ClearStackingContextBits( 3558 StackingContextBits::ContainsMixBlendMode); 3559 return capturedBlendMode; 3560 } 3561 3562 ~AutoContainsBlendModeCapturer() { 3563 // If CaptureContainsBlendMode() was called, the descendant blend mode was 3564 // "captured" and so uncapturedContainsBlendMode will be false. If 3565 // CaptureContainsBlendMode() wasn't called, then no capture occurred, and 3566 // uncapturedContainsBlendMode may be true if there was a descendant blend 3567 // mode. In that case, we set the flag on the DL builder so that we restore 3568 // state to what it would have been without this RAII class on the stack. 3569 bool uncapturedContainsBlendMode = mBuilder.ContainsBlendMode(); 3570 if (mSavedContainsBlendMode || uncapturedContainsBlendMode) { 3571 mBuilder.SetStackingContextBits( 3572 StackingContextBits::ContainsMixBlendMode); 3573 } else { 3574 mBuilder.ClearStackingContextBits( 3575 StackingContextBits::ContainsMixBlendMode); 3576 } 3577 } 3578 }; 3579 3580 void ScrollContainerFrame::MaybeCreateTopLayerAndWrapRootItems( 3581 nsDisplayListBuilder* aBuilder, nsDisplayListCollection& aSet, 3582 bool aCreateAsyncZoom, bool aCapturedByViewTransition, 3583 AutoContainsBlendModeCapturer* aAsyncZoomBlendCapture, 3584 const nsRect& aAsyncZoomClipRect, const nsRectCornerRadii* aRadii) { 3585 if (!mIsRoot) { 3586 return; 3587 } 3588 nsIFrame* rootStyleFrame = GetFrameForStyle(); 3589 3590 nsDisplayList rootResultList(aBuilder); 3591 bool serializedList = false; 3592 auto SerializeList = [&] { 3593 if (!serializedList) { 3594 serializedList = true; 3595 aSet.SerializeWithCorrectZOrder(&rootResultList, GetContent()); 3596 } 3597 }; 3598 3599 // Create any required items for the 'top layer' and check if they'll be 3600 // opaque over the entire area of the viewport. If they are, then we can 3601 // skip building display items for the rest of the page. 3602 ViewportFrame* viewportParent = do_QueryFrame(GetParent()); 3603 { 3604 nsDisplayListBuilder::AutoEnterViewTransitionCapture 3605 inViewTransitionCaptureSetter(aBuilder, aCapturedByViewTransition); 3606 nsDisplayListBuilder::AutoCurrentActiveScrolledRootSetter asrSetter( 3607 aBuilder); 3608 DisplayListClipState::AutoSaveRestore clipState(aBuilder); 3609 if (aBuilder->IsInViewTransitionCapture()) { 3610 asrSetter.SetCurrentActiveScrolledRoot(nullptr); 3611 clipState.Clear(); 3612 } 3613 if (viewportParent) { 3614 bool topLayerIsOpaque = false; 3615 if (nsDisplayWrapList* topLayerWrapList = 3616 viewportParent->BuildDisplayListForContentTopLayer( 3617 aBuilder, &topLayerIsOpaque)) { 3618 // If the top layer content is opaque, and we're the root content 3619 // document in the process, we can drop the display items behind it. We 3620 // only support doing this for the root content document in the process, 3621 // since the top layer content might have fixed position items that have 3622 // a scrolltarget referencing the APZ data for the document. APZ builds 3623 // this data implicitly for the root content document in the process, 3624 // but subdocuments etc need their display items to generate it, so we 3625 // can't cull those. 3626 if (topLayerIsOpaque && !serializedList && 3627 PresContext()->IsRootContentDocumentInProcess()) { 3628 aSet.DeleteAll(aBuilder); 3629 } 3630 if (serializedList) { 3631 rootResultList.AppendToTop(topLayerWrapList); 3632 } else { 3633 aSet.PositionedDescendants()->AppendToTop(topLayerWrapList); 3634 } 3635 } 3636 } 3637 3638 if (aCapturedByViewTransition) { 3639 SerializeList(); 3640 rootResultList.AppendNewToTop<nsDisplayViewTransitionCapture>( 3641 aBuilder, this, &rootResultList, nullptr, /* aIsRoot = */ true); 3642 } 3643 } 3644 3645 if (rootStyleFrame) { 3646 bool usingBackdropFilter = 3647 rootStyleFrame->StyleEffects()->HasBackdropFilters() && 3648 rootStyleFrame->IsVisibleForPainting(); 3649 3650 if (rootStyleFrame->StyleEffects()->HasFilters() && 3651 !aBuilder->IsForGenerateGlyphMask()) { 3652 SerializeList(); 3653 rootResultList.AppendNewToTop<nsDisplayFilters>( 3654 aBuilder, this, &rootResultList, rootStyleFrame, usingBackdropFilter); 3655 } 3656 3657 if (usingBackdropFilter) { 3658 SerializeList(); 3659 nsRect backdropRect = 3660 GetRectRelativeToSelf() + aBuilder->ToReferenceFrame(this); 3661 rootResultList.AppendNewToTop<nsDisplayBackdropFilters>( 3662 aBuilder, this, &rootResultList, backdropRect, rootStyleFrame); 3663 } 3664 } 3665 3666 if (viewportParent) { 3667 if (nsDisplayWrapList* topLayerWrapList = 3668 viewportParent->BuildDisplayListForViewTransitionsAndNACTopLayer( 3669 aBuilder)) { 3670 if (serializedList) { 3671 rootResultList.AppendToTop(topLayerWrapList); 3672 } else { 3673 aSet.PositionedDescendants()->AppendToTop(topLayerWrapList); 3674 } 3675 } 3676 } 3677 3678 if (aCreateAsyncZoom) { 3679 MOZ_ASSERT(mIsRoot); 3680 3681 // Wrap all our scrolled contents in an nsDisplayAsyncZoom. This will be 3682 // the layer that gets scaled for APZ zooming. It does not have the 3683 // scrolled ASR, but it does have the composition bounds clip applied to 3684 // it. The children have the layout viewport clip applied to them (above). 3685 // Effectively we are double clipping to the viewport, at potentially 3686 // different async scales. 3687 SerializeList(); 3688 3689 if (aAsyncZoomBlendCapture->CaptureContainsBlendMode()) { 3690 // The async zoom contents contain a mix-blend mode, so let's wrap all 3691 // those contents into a blend container, and then wrap the blend 3692 // container in the async zoom container. Otherwise the blend container 3693 // ends up outside the zoom container which results in blend failure for 3694 // WebRender. 3695 nsDisplayItem* blendContainer = 3696 nsDisplayBlendContainer::CreateForMixBlendMode( 3697 aBuilder, this, &rootResultList, 3698 aBuilder->CurrentActiveScrolledRoot(), 3699 nsDisplayItem::ContainerASRType::Constant); 3700 rootResultList.AppendToTop(blendContainer); 3701 3702 // Blend containers can be created or omitted during partial updates 3703 // depending on the dirty rect. So we basically can't do partial updates 3704 // if there's a blend container involved. There is equivalent code to this 3705 // in the BuildDisplayListForStackingContext function as well, with a more 3706 // detailed comment explaining things better. 3707 if (aBuilder->IsRetainingDisplayList()) { 3708 if (aBuilder->IsPartialUpdate()) { 3709 aBuilder->SetPartialBuildFailed(true); 3710 } else { 3711 aBuilder->SetDisablePartialUpdates(true); 3712 } 3713 } 3714 } 3715 3716 mozilla::layers::FrameMetrics::ViewID viewID = 3717 nsLayoutUtils::FindOrCreateIDFor(mScrolledFrame->GetContent()); 3718 3719 DisplayListClipState::AutoSaveRestore clipState(aBuilder); 3720 clipState.ClipContentDescendants(aAsyncZoomClipRect, aRadii); 3721 3722 rootResultList.AppendNewToTop<nsDisplayAsyncZoom>( 3723 aBuilder, this, &rootResultList, aBuilder->CurrentActiveScrolledRoot(), 3724 nsDisplayItem::ContainerASRType::Constant, viewID); 3725 } 3726 3727 if (serializedList) { 3728 aSet.Content()->AppendToTop(&rootResultList); 3729 } 3730 } 3731 3732 class nsDisplayListFocus final : public nsPaintedDisplayItem { 3733 public: 3734 nsDisplayListFocus(nsDisplayListBuilder* aBuilder, nsListControlFrame* aFrame) 3735 : nsPaintedDisplayItem(aBuilder, aFrame) { 3736 MOZ_COUNT_CTOR(nsDisplayListFocus); 3737 } 3738 3739 Maybe<nsCSSBorderRenderer> Renderer(DrawTarget* aDt) const { 3740 auto* listFrame = static_cast<nsListControlFrame*>(Frame()); 3741 auto* option = listFrame->GetCurrentOption(); 3742 if (!option) { 3743 return {}; 3744 } 3745 nsIFrame* frame = option->GetPrimaryFrame(); 3746 if (!frame) { 3747 return {}; 3748 } 3749 nscolor color = LookAndFeel::Color( 3750 option->Selected() ? LookAndFeel::ColorID::Selecteditemtext 3751 : LookAndFeel::ColorID::Selecteditem, 3752 frame); 3753 auto rect = frame->GetRectRelativeToSelf() + frame->GetOffsetTo(listFrame) + 3754 ToReferenceFrame(); 3755 return Some( 3756 nsCSSRendering::GetBorderRendererForFocus(listFrame, aDt, rect, color)); 3757 } 3758 3759 MOZ_COUNTED_DTOR_FINAL(nsDisplayListFocus) 3760 3761 void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override { 3762 if (auto br = Renderer(aCtx->GetDrawTarget())) { 3763 br->DrawBorders(); 3764 } 3765 } 3766 bool CreateWebRenderCommands( 3767 wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, 3768 const StackingContextHelper& aSc, 3769 layers::RenderRootStateManager* aManager, 3770 nsDisplayListBuilder* aDisplayListBuilder) override { 3771 if (auto br = Renderer(nullptr)) { 3772 br->CreateWebRenderCommands(this, aBuilder, aResources, aSc); 3773 } 3774 return true; 3775 } 3776 NS_DISPLAY_DECL_NAME("ListFocus", TYPE_LIST_FOCUS) 3777 }; 3778 3779 void ScrollContainerFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, 3780 const nsDisplayListSet& aLists) { 3781 SetAndNullOnExit<const nsIFrame> tmpBuilder( 3782 mReferenceFrameDuringPainting, aBuilder->GetCurrentReferenceFrame()); 3783 if (aBuilder->IsForFrameVisibility()) { 3784 NotifyApproximateFrameVisibilityUpdate(false); 3785 } 3786 3787 DisplayBorderBackgroundOutline(aBuilder, aLists); 3788 if (HidesContent()) { 3789 return; 3790 } 3791 3792 const bool isRootContent = 3793 mIsRoot && PresContext()->IsRootContentDocumentCrossProcess(); 3794 3795 const bool capturedByViewTransition = [&] { 3796 if (!mIsRoot) { 3797 return false; 3798 } 3799 auto* styleFrame = GetFrameForStyle(); 3800 return styleFrame && 3801 styleFrame->HasAnyStateBits(NS_FRAME_CAPTURED_IN_VIEW_TRANSITION); 3802 }(); 3803 3804 // Expand the scroll port to the size including the area covered by dynamic 3805 // toolbar in the case where the dynamic toolbar is being used since 3806 // position:fixed elements attached to this root scroller might be taller 3807 // than its scroll port (e.g 100vh). Even if the dynamic toolbar covers the 3808 // taller area, it doesn't mean the area is clipped by the toolbar because 3809 // the dynamic toolbar is laid out outside of our topmost window and it 3810 // transitions without changing our topmost window size. 3811 const nsRect effectiveScrollPort = 3812 GetScrollPortRectAccountingForMaxDynamicToolbar(); 3813 3814 // It's safe to get this value before the DecideScrollableLayer call below 3815 // because that call cannot create a displayport for root scroll frames, 3816 // and hence it cannot create an ignore scroll frame. 3817 const bool ignoringThisScrollFrame = aBuilder->GetIgnoreScrollFrame() == this; 3818 3819 // Overflow clipping can never clip frames outside our subtree, so there 3820 // is no need to worry about whether we are a moving frame that might clip 3821 // non-moving frames. 3822 // Not all our descendants will be clipped by overflow clipping, but all 3823 // the ones that aren't clipped will be out of flow frames that have already 3824 // had dirty rects saved for them by their parent frames calling 3825 // MarkOutOfFlowChildrenForDisplayList, so it's safe to restrict our 3826 // dirty rect here. 3827 nsRect visibleRect = aBuilder->GetVisibleRect(); 3828 nsRect dirtyRect = aBuilder->GetDirtyRect(); 3829 if (!ignoringThisScrollFrame) { 3830 visibleRect = visibleRect.Intersect(effectiveScrollPort); 3831 dirtyRect = dirtyRect.Intersect(effectiveScrollPort); 3832 } 3833 3834 bool dirtyRectHasBeenOverriden = false; 3835 (void)DecideScrollableLayer(aBuilder, &visibleRect, &dirtyRect, 3836 /* aSetBase = */ !mIsRoot, 3837 &dirtyRectHasBeenOverriden); 3838 3839 if (aBuilder->IsForFrameVisibility()) { 3840 // We expand the dirty rect to catch frames just outside of the scroll port. 3841 // We use the dirty rect instead of the whole scroll port to prevent 3842 // too much expansion in the presence of very large (bigger than the 3843 // viewport) scroll ports. 3844 dirtyRect = ExpandRectToNearlyVisible(dirtyRect); 3845 visibleRect = dirtyRect; 3846 } 3847 3848 // We put non-overlay scrollbars in their own layers when this is the root 3849 // scroll frame and we are a toplevel content document. In this situation, 3850 // the scrollbar(s) would normally be assigned their own layer anyway, since 3851 // they're not scrolled with the rest of the document. But when both 3852 // scrollbars are visible, the layer's visible rectangle would be the size 3853 // of the viewport, so most layer implementations would create a layer buffer 3854 // that's much larger than necessary. Creating independent layers for each 3855 // scrollbar works around the problem. 3856 const bool createLayersForScrollbars = isRootContent; 3857 3858 nsDisplayListCollection set(aBuilder); 3859 3860 if (ignoringThisScrollFrame) { 3861 // If we are a root scroll frame that has a display port we want to add 3862 // scrollbars, they will be children of the scrollable layer, but they get 3863 // adjusted by the APZC automatically. 3864 bool addScrollBars = 3865 mIsRoot && mWillBuildScrollableLayer && aBuilder->IsPaintingToWindow(); 3866 3867 if (addScrollBars) { 3868 // Add classic scrollbars. 3869 AppendScrollPartsTo(aBuilder, set, createLayersForScrollbars, false); 3870 } 3871 3872 { 3873 nsDisplayListBuilder::AutoBuildingDisplayList building( 3874 aBuilder, this, visibleRect, dirtyRect); 3875 3876 // Don't clip the scrolled child, and don't paint scrollbars/scrollcorner. 3877 // The scrolled frame shouldn't have its own background/border, so we 3878 // can just pass aLists directly. 3879 BuildDisplayListForChild(aBuilder, mScrolledFrame, set); 3880 } 3881 3882 MaybeCreateTopLayerAndWrapRootItems(aBuilder, set, 3883 /* aCreateAsyncZoom = */ false, 3884 /* aCapturedByViewTransition = */ false, 3885 nullptr, nsRect(), nullptr); 3886 3887 if (addScrollBars) { 3888 // Add overlay scrollbars. 3889 AppendScrollPartsTo(aBuilder, set, createLayersForScrollbars, true); 3890 } 3891 3892 set.MoveTo(aLists); 3893 return; 3894 } 3895 3896 // Whether we might want to build a scrollable layer for this scroll frame 3897 // at some point in the future. This controls whether we add the information 3898 // to the layer tree (a scroll info layer if necessary, and add the right 3899 // area to the dispatch to content layer event regions) necessary to activate 3900 // a scroll frame so it creates a scrollable layer. 3901 const bool couldBuildLayer = [&] { 3902 if (!aBuilder->IsPaintingToWindow()) { 3903 return false; 3904 } 3905 if (mWillBuildScrollableLayer) { 3906 return true; 3907 } 3908 return nsLayoutUtils::AsyncPanZoomEnabled(this) && WantAsyncScroll(); 3909 }(); 3910 3911 // Now display the scrollbars and scrollcorner. These parts are drawn 3912 // in the border-background layer, on top of our own background and 3913 // borders and underneath borders and backgrounds of later elements 3914 // in the tree. 3915 // Note that this does not apply for overlay scrollbars; those are drawn 3916 // in the positioned-elements layer on top of everything else by the call 3917 // to AppendScrollPartsTo(..., true) further down. 3918 AppendScrollPartsTo(aBuilder, aLists, createLayersForScrollbars, false); 3919 3920 const nsStyleDisplay* disp = StyleDisplay(); 3921 if (aBuilder->IsForPainting() && 3922 disp->mWillChange.bits & StyleWillChangeBits::SCROLL) { 3923 aBuilder->AddToWillChangeBudget(this, GetVisualViewportSize()); 3924 } 3925 3926 mScrollParentID = aBuilder->GetCurrentScrollParentId(); 3927 3928 AutoContainsBlendModeCapturer blendCapture(*aBuilder); 3929 3930 const bool willBuildAsyncZoomContainer = 3931 mWillBuildScrollableLayer && aBuilder->ShouldBuildAsyncZoomContainer() && 3932 isRootContent; 3933 3934 nsRect scrollPortClip = 3935 effectiveScrollPort + aBuilder->ToReferenceFrame(this); 3936 nsRect clipRect = scrollPortClip; 3937 // Our override of GetBorderRadii ensures we never have a radius at 3938 // the corners where we have a scrollbar. 3939 nsRectCornerRadii radii; 3940 const bool haveRadii = GetPaddingBoxBorderRadii(radii); 3941 if (mIsRoot) { 3942 clipRect.SizeTo(nsLayoutUtils::CalculateCompositionSizeForFrame( 3943 this, true /* aSubtractScrollbars */, 3944 nullptr /* aOverrideScrollPortSize */, 3945 // With the dynamic toolbar, this CalculateCompositionSizeForFrame call 3946 // basically expands the region being covered up by the dynamic toolbar, 3947 // but if the root scroll container is not scrollable, e.g. the root 3948 // element has `overflow: hidden` or `position: fixed`, the function 3949 // doesn't expand the region since expanding the region in such cases 3950 // will prevent the content from restoring zooming to 1.0 zoom level 3951 // such as bug 1652190. That said, this `clipRect` which will be used 3952 // for the async zoom container needs to be expanded because zoomed-in 3953 // contents can be scrollable __visually__ so that the region under the 3954 // dynamic toolbar area will be revealed. 3955 nsLayoutUtils::IncludeDynamicToolbar::Force)); 3956 3957 // The composition size is essentially in visual coordinates. 3958 // If we are hit-testing in layout coordinates, transform the clip rect 3959 // to layout coordinates to match. 3960 if (aBuilder->IsRelativeToLayoutViewport() && isRootContent) { 3961 clipRect = ViewportUtils::VisualToLayout(clipRect, PresShell()); 3962 } 3963 } 3964 3965 { 3966 DisplayListClipState::AutoSaveRestore clipState(aBuilder); 3967 3968 // If we're building an async zoom container, clip the contents inside 3969 // to the layout viewport (scrollPortClip). The composition bounds clip 3970 // (clipRect) will be applied to the zoom container itself in 3971 // MaybeCreateTopLayerAndWrapRootItems. 3972 nsRect clipRectForContents = 3973 willBuildAsyncZoomContainer ? scrollPortClip : clipRect; 3974 if (mIsRoot) { 3975 clipState.ClipContentDescendants(clipRectForContents, 3976 haveRadii ? &radii : nullptr); 3977 } else { 3978 clipState.ClipContainingBlockDescendants(clipRectForContents, 3979 haveRadii ? &radii : nullptr); 3980 } 3981 3982 nsDisplayListBuilder::AutoCurrentActiveScrolledRootSetter asrSetter( 3983 aBuilder); 3984 3985 if (aBuilder->IsInViewTransitionCapture() || capturedByViewTransition) { 3986 asrSetter.SetCurrentActiveScrolledRoot(nullptr); 3987 } else { 3988 if (mWillBuildScrollableLayer && aBuilder->IsPaintingToWindow()) { 3989 // If this scroll frame has a first scrollable frame sequence number, 3990 // ensure that it matches the current paint sequence number. If it does 3991 // not, reset it so that we can expire the displayport. The stored 3992 // sequence number will not match that of the current paint if the dom 3993 // was mutated in some way that alters the order of scroll frames. 3994 if (IsFirstScrollableFrameSequenceNumber().isSome() && 3995 *IsFirstScrollableFrameSequenceNumber() != 3996 nsDisplayListBuilder::GetPaintSequenceNumber()) { 3997 SetIsFirstScrollableFrameSequenceNumber(Nothing()); 3998 } 3999 asrSetter.EnterScrollFrame(this); 4000 } 4001 if (couldBuildLayer && mScrolledFrame->GetContent()) { 4002 asrSetter.SetCurrentScrollParentId( 4003 nsLayoutUtils::FindOrCreateIDFor(mScrolledFrame->GetContent())); 4004 } 4005 } 4006 4007 if (mWillBuildScrollableLayer && aBuilder->BuildCompositorHitTestInfo()) { 4008 // Create a hit test info item for the scrolled content that's not 4009 // clipped to the displayport. This ensures that within the bounds 4010 // of the scroll frame, the scrolled content is always hit, even 4011 // if we are checkerboarding. 4012 CompositorHitTestInfo info = 4013 mScrolledFrame->GetCompositorHitTestInfoWithoutPointerEvents( 4014 aBuilder); 4015 4016 if (mScrolledFrame->Style()->PointerEvents() != 4017 StylePointerEvents::None && 4018 info != CompositorHitTestInvisibleToHit) { 4019 auto* hitInfo = 4020 MakeDisplayItemWithIndex<nsDisplayCompositorHitTestInfo>( 4021 aBuilder, mScrolledFrame, 1); 4022 if (hitInfo) { 4023 aBuilder->SetInheritedCompositorHitTestInfo(info); 4024 set.BorderBackground()->AppendToTop(hitInfo); 4025 } 4026 } 4027 } 4028 4029 { 4030 // Clip our contents to the unsnapped scrolled rect. This makes sure 4031 // that we don't have display items over the subpixel seam at the edge 4032 // of the scrolled area. 4033 DisplayListClipState::AutoSaveRestore scrolledRectClipState(aBuilder); 4034 nsRect scrolledRectClip = 4035 GetUnsnappedScrolledRectInternal( 4036 mScrolledFrame->ScrollableOverflowRect(), mScrollPort.Size()) + 4037 mScrolledFrame->GetPosition(); 4038 bool clippedToDisplayPort = false; 4039 if (mWillBuildScrollableLayer && aBuilder->IsPaintingToWindow()) { 4040 // Clip the contents to the display port. 4041 // The dirty rect already acts kind of like a clip, in that 4042 // FrameLayerBuilder intersects item bounds and opaque regions with 4043 // it, but it doesn't have the consistent snapping behavior of a 4044 // true clip. 4045 // For a case where this makes a difference, imagine the following 4046 // scenario: The display port has an edge that falls on a fractional 4047 // layer pixel, and there's an opaque display item that covers the 4048 // whole display port up until that fractional edge, and there is a 4049 // transparent display item that overlaps the edge. We want to prevent 4050 // this transparent item from enlarging the scrolled layer's visible 4051 // region beyond its opaque region. The dirty rect doesn't do that - 4052 // it gets rounded out, whereas a true clip gets rounded to nearest 4053 // pixels. 4054 // If there is no display port, we don't need this because the clip 4055 // from the scroll port is still applied. 4056 scrolledRectClip = scrolledRectClip.Intersect(visibleRect); 4057 clippedToDisplayPort = scrolledRectClip.IsEqualEdges(visibleRect); 4058 } 4059 if (clippedToDisplayPort) { 4060 scrolledRectClipState.ClipToDisplayPort( 4061 scrolledRectClip + aBuilder->ToReferenceFrame(this)); 4062 } else { 4063 // We have to do this after the ClipContainingBlockDescendants call 4064 // above, otherwise that call will clobber the flag set by this call 4065 // to SetClippedToDisplayPort. 4066 scrolledRectClipState.ClipContainingBlockDescendants( 4067 scrolledRectClip + aBuilder->ToReferenceFrame(this)); 4068 } 4069 4070 nsRect visibleRectForChildren = visibleRect; 4071 nsRect dirtyRectForChildren = dirtyRect; 4072 4073 // If we are entering the RCD-RSF, we are crossing the async zoom 4074 // container boundary, and need to convert from visual to layout 4075 // coordinates. 4076 if (willBuildAsyncZoomContainer && aBuilder->IsForEventDelivery()) { 4077 MOZ_ASSERT(ViewportUtils::IsZoomedContentRoot(mScrolledFrame)); 4078 visibleRectForChildren = 4079 ViewportUtils::VisualToLayout(visibleRectForChildren, PresShell()); 4080 dirtyRectForChildren = 4081 ViewportUtils::VisualToLayout(dirtyRectForChildren, PresShell()); 4082 } 4083 4084 nsDisplayListBuilder::AutoBuildingDisplayList building( 4085 aBuilder, this, visibleRectForChildren, dirtyRectForChildren); 4086 nsDisplayListBuilder::AutoEnterViewTransitionCapture 4087 inViewTransitionCaptureSetter(aBuilder, capturedByViewTransition); 4088 if (capturedByViewTransition) { 4089 scrolledRectClipState.Clear(); 4090 } 4091 4092 BuildDisplayListForChild(aBuilder, mScrolledFrame, set); 4093 4094 if (nsListControlFrame* lc = do_QueryFrame(this); lc && lc->IsFocused()) { 4095 set.Outlines()->AppendNewToTop<nsDisplayListFocus>(aBuilder, lc); 4096 } 4097 4098 if (dirtyRectHasBeenOverriden && 4099 StaticPrefs::layout_display_list_show_rebuild_area()) { 4100 nsDisplaySolidColor* color = MakeDisplayItem<nsDisplaySolidColor>( 4101 aBuilder, this, 4102 dirtyRect + aBuilder->GetCurrentFrameOffsetToReferenceFrame(), 4103 NS_RGBA(0, 0, 255, 64), false); 4104 if (color) { 4105 color->SetOverrideZIndex(INT32_MAX); 4106 set.PositionedDescendants()->AppendToTop(color); 4107 } 4108 } 4109 } 4110 4111 if (aBuilder->IsPaintingToWindow()) { 4112 mIsParentToActiveScrollFrames = 4113 ShouldActivateAllScrollFrames(aBuilder, this) 4114 ? asrSetter.GetContainsNonMinimalDisplayPort() 4115 : asrSetter.ShouldForceLayerForScrollParent(); 4116 } 4117 4118 if (asrSetter.ShouldForceLayerForScrollParent()) { 4119 // Note that forcing layerization of scroll parents follows the scroll 4120 // handoff chain which is subject to the out-of-flow-frames caveat noted 4121 // above (where the asrSetter variable is created). 4122 MOZ_ASSERT(couldBuildLayer && mScrolledFrame->GetContent() && 4123 aBuilder->IsPaintingToWindow()); 4124 if (!mWillBuildScrollableLayer) { 4125 // Set a displayport so next paint we don't have to force layerization 4126 // after the fact. It's ok to pass DoNotRepaint here, since we've 4127 // already painted the change and we're just optimizing it to be 4128 // detected earlier. We also won't confuse RetainedDisplayLists 4129 // with the silent change, since we explicitly request partial updates 4130 // to be disabled on the next paint. 4131 DisplayPortUtils::SetDisplayPortMargins( 4132 GetContent(), PresShell(), DisplayPortMargins::Empty(GetContent()), 4133 DisplayPortUtils::ClearMinimalDisplayPortProperty::Yes, 0, 4134 DisplayPortUtils::RepaintMode::DoNotRepaint); 4135 // Call DecideScrollableLayer to recompute mWillBuildScrollableLayer 4136 // and recompute the current animated geometry root if needed. It's 4137 // too late to change the dirty rect so pass a copy. 4138 nsRect copyOfDirtyRect = dirtyRect; 4139 nsRect copyOfVisibleRect = visibleRect; 4140 (void)DecideScrollableLayer(aBuilder, ©OfVisibleRect, 4141 ©OfDirtyRect, 4142 /* aSetBase = */ false, nullptr); 4143 if (mWillBuildScrollableLayer) { 4144 #ifndef MOZ_WIDGET_ANDROID 4145 gfxCriticalNoteOnce << "inserted scroll frame"; 4146 #endif 4147 MOZ_ASSERT(!ShouldActivateAllScrollFrames(aBuilder, this)); 4148 asrSetter.InsertScrollFrame(this); 4149 aBuilder->SetDisablePartialUpdates(true); 4150 } 4151 } 4152 } 4153 } 4154 4155 if (mWillBuildScrollableLayer && aBuilder->IsPaintingToWindow()) { 4156 aBuilder->ForceLayerForScrollParent(); 4157 } 4158 4159 MaybeCreateTopLayerAndWrapRootItems( 4160 aBuilder, set, willBuildAsyncZoomContainer, capturedByViewTransition, 4161 &blendCapture, clipRect, haveRadii ? &radii : nullptr); 4162 4163 // We want to call SetContainsNonMinimalDisplayPort if 4164 // mWillBuildScrollableLayer is true for any reason other than having a 4165 // minimal display port. 4166 if (mWillBuildScrollableLayer && aBuilder->IsPaintingToWindow()) { 4167 // Since mWillBuildScrollableLayer = HasDisplayPort || mZoomableByAPZ we can 4168 // simplify this check to avoid getting the display port again. 4169 if (mZoomableByAPZ || 4170 !GetContent()->GetProperty(nsGkAtoms::MinimalDisplayPort)) { 4171 MOZ_ASSERT(DisplayPortUtils::HasNonMinimalDisplayPort(GetContent()) || 4172 mZoomableByAPZ); 4173 aBuilder->SetContainsNonMinimalDisplayPort(); 4174 } 4175 } 4176 4177 if (couldBuildLayer & StyleVisibility()->IsVisible()) { 4178 CompositorHitTestInfo info(CompositorHitTestFlags::eVisibleToHitTest, 4179 CompositorHitTestFlags::eInactiveScrollframe); 4180 // If the scroll frame has non-default overscroll-behavior, instruct 4181 // APZ to require a target confirmation before processing events that 4182 // hit this scroll frame (that is, to drop the events if a 4183 // confirmation does not arrive within the timeout period). Otherwise, 4184 // APZ's fallback behaviour of scrolling the enclosing scroll frame 4185 // would violate the specified overscroll-behavior. 4186 auto overscroll = GetOverscrollBehaviorInfo(); 4187 if (overscroll.mBehaviorX != OverscrollBehavior::Auto || 4188 overscroll.mBehaviorY != OverscrollBehavior::Auto) { 4189 info += CompositorHitTestFlags::eRequiresTargetConfirmation; 4190 } 4191 4192 nsRect area = effectiveScrollPort + aBuilder->ToReferenceFrame(this); 4193 4194 // Make sure that APZ will dispatch events back to content so we can 4195 // create a displayport for this frame. We'll add the item later on. 4196 if (!mWillBuildScrollableLayer && aBuilder->BuildCompositorHitTestInfo()) { 4197 // Make sure the z-index of the inactive item is at least zero. 4198 // Otherwise, it will end up behind non-positioned items in the scrolled 4199 // content. 4200 int32_t zIndex = MaxZIndexInListOfItemsContainedInFrame( 4201 set.PositionedDescendants(), this) 4202 .valueOr(0); 4203 if (aBuilder->IsPartialUpdate()) { 4204 for (nsDisplayItem* item : mScrolledFrame->DisplayItems()) { 4205 if (item->GetType() == 4206 DisplayItemType::TYPE_COMPOSITOR_HITTEST_INFO) { 4207 auto* hitTestItem = 4208 static_cast<nsDisplayCompositorHitTestInfo*>(item); 4209 if (hitTestItem->GetHitTestInfo().Info().contains( 4210 CompositorHitTestFlags::eInactiveScrollframe)) { 4211 zIndex = std::max(zIndex, hitTestItem->ZIndex()); 4212 item->SetCantBeReused(); 4213 } 4214 } 4215 } 4216 } 4217 nsDisplayCompositorHitTestInfo* hitInfo = 4218 MakeDisplayItemWithIndex<nsDisplayCompositorHitTestInfo>( 4219 aBuilder, mScrolledFrame, 1, area, info); 4220 if (hitInfo) { 4221 AppendInternalItemToTop(set, hitInfo, Some(zIndex)); 4222 aBuilder->SetInheritedCompositorHitTestInfo(info); 4223 } 4224 } 4225 4226 if (aBuilder->ShouldBuildScrollInfoItemsForHoisting()) { 4227 aBuilder->AppendNewScrollInfoItemForHoisting( 4228 MakeDisplayItem<nsDisplayScrollInfoLayer>(aBuilder, mScrolledFrame, 4229 this, info, area)); 4230 } 4231 } 4232 4233 // Now display overlay scrollbars and the resizer, if we have one. 4234 AppendScrollPartsTo(aBuilder, set, createLayersForScrollbars, true); 4235 4236 set.MoveTo(aLists); 4237 } 4238 4239 nsRect ScrollContainerFrame::RestrictToRootDisplayPort( 4240 const nsRect& aDisplayportBase) { 4241 // This function clips aDisplayportBase so that it is no larger than the 4242 // root frame's displayport (or the root composition bounds, if we can't 4243 // obtain the root frame's displayport). This is useful for ensuring that 4244 // the displayport of a tall scrollframe doesn't gobble up all the memory. 4245 4246 nsPresContext* pc = PresContext(); 4247 const nsPresContext* rootPresContext = 4248 pc->GetInProcessRootContentDocumentPresContext(); 4249 if (!rootPresContext) { 4250 rootPresContext = pc->GetRootPresContext(); 4251 } 4252 if (!rootPresContext) { 4253 return aDisplayportBase; 4254 } 4255 const mozilla::PresShell* const rootPresShell = rootPresContext->PresShell(); 4256 nsIFrame* displayRootFrame = nsLayoutUtils::GetDisplayRootFrame(this); 4257 nsIFrame* rootFrame = displayRootFrame->IsMenuPopupFrame() 4258 ? displayRootFrame 4259 : rootPresShell->GetRootScrollContainerFrame(); 4260 if (!rootFrame) { 4261 rootFrame = rootPresShell->GetRootFrame(); 4262 } 4263 if (!rootFrame) { 4264 return aDisplayportBase; 4265 } 4266 4267 // Make sure we aren't trying to restrict to our own displayport, which is a 4268 // circular dependency. 4269 MOZ_ASSERT(!mIsRoot || rootPresContext != pc); 4270 4271 nsRect rootDisplayPort; 4272 bool hasDisplayPort = 4273 rootFrame->GetContent() && DisplayPortUtils::GetDisplayPort( 4274 rootFrame->GetContent(), &rootDisplayPort); 4275 if (hasDisplayPort) { 4276 // The display port of the root frame already factors in it's callback 4277 // transform, so subtract it out here, the GetCumulativeApzCallbackTransform 4278 // call below will add it back. 4279 MOZ_LOG(sDisplayportLog, LogLevel::Verbose, 4280 ("RestrictToRootDisplayPort: Existing root displayport is %s\n", 4281 ToString(rootDisplayPort).c_str())); 4282 if (nsIContent* content = rootFrame->GetContent()) { 4283 if (void* property = 4284 content->GetProperty(nsGkAtoms::apzCallbackTransform)) { 4285 rootDisplayPort -= 4286 CSSPoint::ToAppUnits(*static_cast<CSSPoint*>(property)); 4287 } 4288 } 4289 } else { 4290 // If we don't have a display port on the root frame let's fall back to 4291 // the root composition bounds instead. 4292 nsRect rootCompBounds = 4293 nsRect(nsPoint(0, 0), 4294 nsLayoutUtils::CalculateCompositionSizeForFrame(rootFrame)); 4295 4296 // If rootFrame is the RCD-RSF then 4297 // CalculateCompositionSizeForFrame did not take the document's 4298 // resolution into account, so we must. 4299 if (rootPresContext->IsRootContentDocumentCrossProcess() && 4300 rootFrame == rootPresShell->GetRootScrollContainerFrame()) { 4301 MOZ_LOG( 4302 sDisplayportLog, LogLevel::Verbose, 4303 ("RestrictToRootDisplayPort: Removing resolution %f from root " 4304 "composition bounds %s\n", 4305 rootPresShell->GetResolution(), ToString(rootCompBounds).c_str())); 4306 rootCompBounds = 4307 rootCompBounds.RemoveResolution(rootPresShell->GetResolution()); 4308 } 4309 4310 rootDisplayPort = rootCompBounds; 4311 } 4312 MOZ_LOG(sDisplayportLog, LogLevel::Verbose, 4313 ("RestrictToRootDisplayPort: Intermediate root displayport %s\n", 4314 ToString(rootDisplayPort).c_str())); 4315 4316 // We want to convert the root display port from the 4317 // coordinate space of |rootFrame| to the coordinate space of 4318 // |this|. We do that with the TransformRect call below. 4319 // However, since we care about the root display port 4320 // relative to what the user is actually seeing, we also need to 4321 // incorporate the APZ callback transforms into this. Most of the 4322 // time those transforms are negligible, but in some cases (e.g. 4323 // when a zoom is applied on an overflow:hidden document) it is 4324 // not (see bug 1280013). 4325 // XXX: Eventually we may want to create a modified version of 4326 // TransformRect that includes the APZ callback transforms 4327 // directly. 4328 nsLayoutUtils::TransformRect(rootFrame, this, rootDisplayPort); 4329 MOZ_LOG(sDisplayportLog, LogLevel::Verbose, 4330 ("RestrictToRootDisplayPort: Transformed root displayport %s\n", 4331 ToString(rootDisplayPort).c_str())); 4332 rootDisplayPort += CSSPoint::ToAppUnits( 4333 nsLayoutUtils::GetCumulativeApzCallbackTransform(this)); 4334 MOZ_LOG(sDisplayportLog, LogLevel::Verbose, 4335 ("RestrictToRootDisplayPort: Final root displayport %s\n", 4336 ToString(rootDisplayPort).c_str())); 4337 4338 // We want to limit aDisplayportBase to be no larger than 4339 // rootDisplayPort on either axis, but we don't want to just 4340 // blindly intersect the two, because rootDisplayPort might be 4341 // offset from where aDisplayportBase is (see bug 1327095 comment 4342 // 8). Instead, we translate rootDisplayPort so as to maximize the 4343 // overlap with aDisplayportBase, and *then* do the intersection. 4344 if (rootDisplayPort.x > aDisplayportBase.x && 4345 rootDisplayPort.XMost() > aDisplayportBase.XMost()) { 4346 // rootDisplayPort is at a greater x-position for both left and 4347 // right, so translate it such that the XMost() values are the 4348 // same. This will line up the right edge of the two rects, and 4349 // might mean that rootDisplayPort.x is smaller than 4350 // aDisplayportBase.x. We can avoid that by taking the min of the 4351 // x delta and XMost() delta, but it doesn't really matter 4352 // because the intersection between the two rects below will end 4353 // up the same. 4354 rootDisplayPort.x -= (rootDisplayPort.XMost() - aDisplayportBase.XMost()); 4355 } else if (rootDisplayPort.x < aDisplayportBase.x && 4356 rootDisplayPort.XMost() < aDisplayportBase.XMost()) { 4357 // Analaogous code for when the rootDisplayPort is at a smaller 4358 // x-position. 4359 rootDisplayPort.x = aDisplayportBase.x; 4360 } 4361 // Do the same for y-axis 4362 if (rootDisplayPort.y > aDisplayportBase.y && 4363 rootDisplayPort.YMost() > aDisplayportBase.YMost()) { 4364 rootDisplayPort.y -= (rootDisplayPort.YMost() - aDisplayportBase.YMost()); 4365 } else if (rootDisplayPort.y < aDisplayportBase.y && 4366 rootDisplayPort.YMost() < aDisplayportBase.YMost()) { 4367 rootDisplayPort.y = aDisplayportBase.y; 4368 } 4369 MOZ_LOG( 4370 sDisplayportLog, LogLevel::Verbose, 4371 ("RestrictToRootDisplayPort: Root displayport translated to %s to " 4372 "better enclose %s\n", 4373 ToString(rootDisplayPort).c_str(), ToString(aDisplayportBase).c_str())); 4374 4375 // Now we can do the intersection 4376 return aDisplayportBase.Intersect(rootDisplayPort); 4377 } 4378 4379 /* static */ bool ScrollContainerFrame::ShouldActivateAllScrollFrames( 4380 nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) { 4381 if (aBuilder) { 4382 return aBuilder->ShouldActivateAllScrollFrames(); 4383 } 4384 MOZ_ASSERT(aFrame); 4385 if (StaticPrefs::apz_wr_activate_all_scroll_frames()) { 4386 return true; 4387 } 4388 if (StaticPrefs::apz_wr_activate_all_scroll_frames_when_fission() && 4389 FissionAutostart()) { 4390 return true; 4391 } 4392 return StaticPrefs::apz_async_scroll_css_anchor_pos_AtStartup() && 4393 aFrame->PresShell()->GetRootPresShell()->HasSeenAnchorPos(); 4394 } 4395 4396 bool ScrollContainerFrame::DecideScrollableLayerEnsureDisplayport( 4397 nsDisplayListBuilder* aBuilder) { 4398 MOZ_ASSERT(ShouldActivateAllScrollFrames(aBuilder, this)); 4399 nsIContent* content = GetContent(); 4400 bool hasDisplayPort = DisplayPortUtils::HasDisplayPort(content); 4401 4402 // Note this intentionally differs from DecideScrollableLayer below by not 4403 // checking ShouldActivateAllScrollFrames. 4404 if (!hasDisplayPort && aBuilder->IsPaintingToWindow() && 4405 nsLayoutUtils::AsyncPanZoomEnabled(this) && WantAsyncScroll()) { 4406 DisplayPortUtils::SetMinimalDisplayPortDuringPainting(content, PresShell()); 4407 hasDisplayPort = true; 4408 } 4409 4410 mWillBuildScrollableLayer = hasDisplayPort || mZoomableByAPZ; 4411 return mWillBuildScrollableLayer; 4412 } 4413 4414 bool ScrollContainerFrame::DecideScrollableLayer( 4415 nsDisplayListBuilder* aBuilder, nsRect* aVisibleRect, nsRect* aDirtyRect, 4416 bool aSetBase, bool* aDirtyRectHasBeenOverriden) { 4417 if (aBuilder->IsInViewTransitionCapture()) { 4418 // If we're in a view transition, don't activate the scrollframe. We don't 4419 // create APZ data for those subtrees anyways and they can't scroll. 4420 mWillBuildScrollableLayer = false; 4421 return false; 4422 } 4423 4424 nsIContent* content = GetContent(); 4425 bool hasDisplayPort = DisplayPortUtils::HasDisplayPort(content); 4426 // For hit testing purposes with fission we want to create a 4427 // minimal display port for every scroll frame that could be active. (We only 4428 // do this when aSetBase is true because we only want to do this the first 4429 // time this function is called for the same scroll frame.) 4430 if (aSetBase && !hasDisplayPort && aBuilder->IsPaintingToWindow() && 4431 ShouldActivateAllScrollFrames(aBuilder, this) && 4432 nsLayoutUtils::AsyncPanZoomEnabled(this) && WantAsyncScroll()) { 4433 DisplayPortUtils::SetMinimalDisplayPortDuringPainting(content, PresShell()); 4434 hasDisplayPort = true; 4435 } 4436 4437 if (aBuilder->IsPaintingToWindow()) { 4438 if (aSetBase) { 4439 nsRect displayportBase = *aVisibleRect; 4440 nsPresContext* pc = PresContext(); 4441 4442 bool isChromeRootDoc = 4443 !pc->Document()->IsContentDocument() && !pc->GetParentPresContext(); 4444 4445 if (mIsRoot && 4446 (pc->IsRootContentDocumentCrossProcess() || isChromeRootDoc)) { 4447 displayportBase = 4448 nsRect(nsPoint(0, 0), 4449 nsLayoutUtils::CalculateCompositionSizeForFrame(this)); 4450 } else { 4451 // Make the displayport base equal to the visible rect restricted to 4452 // the scrollport and the root composition bounds, relative to the 4453 // scrollport. 4454 displayportBase = aVisibleRect->Intersect(mScrollPort); 4455 4456 mozilla::layers::ScrollableLayerGuid::ViewID viewID = 4457 mozilla::layers::ScrollableLayerGuid::NULL_SCROLL_ID; 4458 if (MOZ_LOG_TEST(sDisplayportLog, LogLevel::Verbose)) { 4459 nsLayoutUtils::FindIDFor(GetContent(), &viewID); 4460 MOZ_LOG( 4461 sDisplayportLog, LogLevel::Verbose, 4462 ("Scroll id %" PRIu64 " has visible rect %s, scroll port %s\n", 4463 viewID, ToString(*aVisibleRect).c_str(), 4464 ToString(mScrollPort).c_str())); 4465 } 4466 4467 // Only restrict to the root displayport bounds if necessary, 4468 // as the required coordinate transformation is expensive. 4469 // And don't call RestrictToRootDisplayPort if we would be trying to 4470 // restrict to our own display port, which doesn't make sense (ie if we 4471 // are a root scroll frame in a process root prescontext). 4472 if (hasDisplayPort && (!mIsRoot || pc->GetParentPresContext()) && 4473 !DisplayPortUtils::WillUseEmptyDisplayPortMargins(content)) { 4474 displayportBase = RestrictToRootDisplayPort(displayportBase); 4475 MOZ_LOG(sDisplayportLog, LogLevel::Verbose, 4476 ("Scroll id %" PRIu64 " has restricted base %s\n", viewID, 4477 ToString(displayportBase).c_str())); 4478 } 4479 displayportBase -= mScrollPort.TopLeft(); 4480 } 4481 4482 DisplayPortUtils::SetDisplayPortBase(GetContent(), displayportBase); 4483 } 4484 4485 // If we don't have aSetBase == true then should have already 4486 // been called with aSetBase == true which should have set a 4487 // displayport base. 4488 MOZ_ASSERT(content->GetProperty(nsGkAtoms::DisplayPortBase)); 4489 nsRect displayPort; 4490 hasDisplayPort = DisplayPortUtils::GetDisplayPort( 4491 content, &displayPort, 4492 DisplayPortOptions().With(DisplayportRelativeTo::ScrollFrame)); 4493 4494 auto OverrideDirtyRect = [&](const nsRect& aRect) { 4495 *aDirtyRect = aRect; 4496 if (aDirtyRectHasBeenOverriden) { 4497 *aDirtyRectHasBeenOverriden = true; 4498 } 4499 }; 4500 4501 if (hasDisplayPort) { 4502 // Override the dirty rectangle if the displayport has been set. 4503 *aVisibleRect = displayPort; 4504 if (aBuilder->IsReusingStackingContextItems() || 4505 !aBuilder->IsPartialUpdate() || aBuilder->InInvalidSubtree() || 4506 IsFrameModified()) { 4507 OverrideDirtyRect(displayPort); 4508 } else if (HasOverrideDirtyRegion()) { 4509 nsRect* rect = GetProperty( 4510 nsDisplayListBuilder::DisplayListBuildingDisplayPortRect()); 4511 if (rect) { 4512 OverrideDirtyRect(*rect); 4513 } 4514 } 4515 } else if (mIsRoot) { 4516 // The displayPort getter takes care of adjusting for resolution. So if 4517 // we have resolution but no displayPort then we need to adjust for 4518 // resolution here. 4519 auto* presShell = PresShell(); 4520 *aVisibleRect = 4521 aVisibleRect->RemoveResolution(presShell->GetResolution()); 4522 *aDirtyRect = aDirtyRect->RemoveResolution(presShell->GetResolution()); 4523 } 4524 } 4525 4526 // Since making new layers is expensive, only create a scrollable layer 4527 // for some scroll frames. 4528 // When a displayport is being used, force building of a layer so that 4529 // the compositor can find the scrollable layer for async scrolling. 4530 // If the element is marked 'scrollgrab', also force building of a layer 4531 // so that APZ can implement scroll grabbing. 4532 mWillBuildScrollableLayer = hasDisplayPort || mZoomableByAPZ; 4533 return mWillBuildScrollableLayer; 4534 } 4535 4536 void ScrollContainerFrame::NotifyApzTransaction() { 4537 mAllowScrollOriginDowngrade = true; 4538 mApzScrollPos = GetScrollPosition(); 4539 mApzAnimationRequested = IsLastScrollUpdateAnimating(); 4540 mApzAnimationTriggeredByScriptRequested = 4541 IsLastScrollUpdateTriggeredByScriptAnimating(); 4542 mScrollUpdates.Clear(); 4543 if (mIsRoot) { 4544 PresShell()->SetResolutionUpdated(false); 4545 } 4546 } 4547 4548 Maybe<ScrollMetadata> ScrollContainerFrame::ComputeScrollMetadata( 4549 WebRenderLayerManager* aLayerManager, const nsIFrame* aItemFrame, 4550 const nsPoint& aOffsetToReferenceFrame) const { 4551 if (!mWillBuildScrollableLayer) { 4552 return Nothing(); 4553 } 4554 4555 bool isRootContent = 4556 mIsRoot && PresContext()->IsRootContentDocumentCrossProcess(); 4557 4558 MOZ_ASSERT(mScrolledFrame->GetContent()); 4559 4560 return Some(nsLayoutUtils::ComputeScrollMetadata( 4561 mScrolledFrame, this, GetContent(), aItemFrame, aOffsetToReferenceFrame, 4562 aLayerManager, mScrollParentID, mScrollPort.Size(), isRootContent)); 4563 } 4564 4565 bool ScrollContainerFrame::IsRectNearlyVisible(const nsRect& aRect) const { 4566 // Use the right rect depending on if a display port is set. 4567 nsRect displayPort; 4568 bool usingDisplayport = DisplayPortUtils::GetDisplayPort( 4569 GetContent(), &displayPort, 4570 DisplayPortOptions().With(DisplayportRelativeTo::ScrollFrame)); 4571 4572 if (mIsRoot && !usingDisplayport && 4573 PresContext()->IsRootContentDocumentInProcess() && 4574 !PresContext()->IsRootContentDocumentCrossProcess()) { 4575 // In the case of the root scroller of OOP iframes, there are cases where 4576 // any display port value isn't set, e.g. the iframe element is out of view 4577 // in the parent document. In such cases we'd consider the iframe is not 4578 // visible. 4579 return false; 4580 } 4581 4582 return aRect.Intersects( 4583 ExpandRectToNearlyVisible(usingDisplayport ? displayPort : mScrollPort)); 4584 } 4585 4586 OverscrollBehaviorInfo ScrollContainerFrame::GetOverscrollBehaviorInfo() const { 4587 nsIFrame* frame = GetFrameForStyle(); 4588 if (!frame) { 4589 return {}; 4590 } 4591 4592 auto& disp = *frame->StyleDisplay(); 4593 return OverscrollBehaviorInfo::FromStyleConstants(disp.mOverscrollBehaviorX, 4594 disp.mOverscrollBehaviorY); 4595 } 4596 4597 ScrollStyles ScrollContainerFrame::GetScrollStyles() const { 4598 nsPresContext* presContext = PresContext(); 4599 if (!presContext->IsDynamic() && 4600 !(mIsRoot && presContext->HasPaginatedScrolling())) { 4601 return ScrollStyles(StyleOverflow::Hidden, StyleOverflow::Hidden); 4602 } 4603 4604 if (!mIsRoot) { 4605 return ScrollStyles(*StyleDisplay(), 4606 ScrollStyles::MapOverflowToValidScrollStyle); 4607 } 4608 4609 ScrollStyles result = presContext->GetViewportScrollStylesOverride(); 4610 if (nsDocShell* ds = presContext->GetDocShell()) { 4611 switch (ds->ScrollbarPreference()) { 4612 case ScrollbarPreference::Auto: 4613 break; 4614 case ScrollbarPreference::Never: 4615 result.mHorizontal = result.mVertical = StyleOverflow::Hidden; 4616 break; 4617 } 4618 } 4619 return result; 4620 } 4621 4622 nsRect ScrollContainerFrame::GetLayoutScrollRange() const { 4623 return GetScrollRange(mScrollPort.width, mScrollPort.height); 4624 } 4625 4626 nsRect ScrollContainerFrame::GetScrollRange(nscoord aWidth, 4627 nscoord aHeight) const { 4628 nsRect range = GetScrolledRect(); 4629 range.width = std::max(range.width - aWidth, 0); 4630 range.height = std::max(range.height - aHeight, 0); 4631 return range; 4632 } 4633 4634 nsRect ScrollContainerFrame::GetVisualScrollRange() const { 4635 nsSize visualViewportSize = GetVisualViewportSize(); 4636 return GetScrollRange(visualViewportSize.width, visualViewportSize.height); 4637 } 4638 4639 nsSize ScrollContainerFrame::GetVisualViewportSize() const { 4640 auto* presShell = PresShell(); 4641 if (mIsRoot && presShell->IsVisualViewportSizeSet()) { 4642 return presShell->GetVisualViewportSize(); 4643 } 4644 return mScrollPort.Size(); 4645 } 4646 4647 nsPoint ScrollContainerFrame::GetVisualViewportOffset() const { 4648 if (mIsRoot) { 4649 auto* presShell = PresShell(); 4650 if (auto pendingUpdate = presShell->GetPendingVisualScrollUpdate()) { 4651 // The pending visual scroll update on the PresShell contains a raw, 4652 // unclamped offset (basically, whatever was passed to ScrollToVisual()). 4653 // It will be clamped on the APZ side, but if we use it as the 4654 // main-thread's visual viewport offset we need to clamp it ourselves. 4655 // Use GetScrollRangeForUserInputEvents() to do the clamping because this 4656 // the scroll range that APZ will use. 4657 return GetScrollRangeForUserInputEvents().ClampPoint( 4658 pendingUpdate->mVisualScrollOffset); 4659 } 4660 return presShell->GetVisualViewportOffset(); 4661 } 4662 return GetScrollPosition(); 4663 } 4664 4665 bool ScrollContainerFrame::SetVisualViewportOffset(const nsPoint& aOffset, 4666 bool aRepaint) { 4667 MOZ_ASSERT(mIsRoot); 4668 AutoWeakFrame weakFrame(this); 4669 AutoScrollbarRepaintSuppression repaintSuppression(this, weakFrame, 4670 !aRepaint); 4671 4672 bool retVal = 4673 PresShell()->SetVisualViewportOffset(aOffset, GetScrollPosition()); 4674 if (!weakFrame.IsAlive()) { 4675 return false; 4676 } 4677 return retVal; 4678 } 4679 4680 nsRect ScrollContainerFrame::GetVisualOptimalViewingRect() const { 4681 auto* presShell = PresShell(); 4682 nsRect rect = mScrollPort; 4683 if (mIsRoot && presShell->IsVisualViewportSizeSet() && 4684 presShell->IsVisualViewportOffsetSet()) { 4685 rect = nsRect(mScrollPort.TopLeft() - GetScrollPosition() + 4686 presShell->GetVisualViewportOffset(), 4687 presShell->GetVisualViewportSize()); 4688 } 4689 // NOTE: We intentionally resolve scroll-padding percentages against the 4690 // scrollport even when the visual viewport is set, see 4691 // https://github.com/w3c/csswg-drafts/issues/4393. 4692 rect.Deflate(GetScrollPadding()); 4693 return rect; 4694 } 4695 4696 static void AdjustDestinationForWholeDelta(const nsIntPoint& aDelta, 4697 const nsRect& aScrollRange, 4698 nsPoint& aPoint) { 4699 if (aDelta.x < 0) { 4700 aPoint.x = aScrollRange.X(); 4701 } else if (aDelta.x > 0) { 4702 aPoint.x = aScrollRange.XMost(); 4703 } 4704 if (aDelta.y < 0) { 4705 aPoint.y = aScrollRange.Y(); 4706 } else if (aDelta.y > 0) { 4707 aPoint.y = aScrollRange.YMost(); 4708 } 4709 } 4710 4711 /** 4712 * Calculate lower/upper scrollBy range in given direction. 4713 * @param aDelta specifies scrollBy direction, if 0 then range will be 0 size 4714 * @param aPos desired destination in AppUnits 4715 * @param aNeg/PosTolerance defines relative range distance 4716 * below and above of aPos point 4717 * @param aMultiplier used for conversion of tolerance into appUnis 4718 */ 4719 static void CalcRangeForScrollBy(int32_t aDelta, nscoord aPos, 4720 float aNegTolerance, float aPosTolerance, 4721 nscoord aMultiplier, nscoord* aLower, 4722 nscoord* aUpper) { 4723 if (!aDelta) { 4724 *aLower = *aUpper = aPos; 4725 return; 4726 } 4727 *aLower = aPos - NSToCoordRound(aMultiplier * 4728 (aDelta > 0 ? aNegTolerance : aPosTolerance)); 4729 *aUpper = aPos + NSToCoordRound(aMultiplier * 4730 (aDelta > 0 ? aPosTolerance : aNegTolerance)); 4731 } 4732 4733 void ScrollContainerFrame::ScrollBy(nsIntPoint aDelta, ScrollUnit aUnit, 4734 ScrollMode aMode, nsIntPoint* aOverflow, 4735 ScrollOrigin aOrigin, 4736 ScrollMomentum aMomentum, 4737 ScrollSnapFlags aSnapFlags) { 4738 // When a smooth scroll is being processed on a frame, mouse wheel and 4739 // trackpad momentum scroll event updates must notcancel the SMOOTH or 4740 // SMOOTH_MSD scroll animations, enabling Javascript that depends on them to 4741 // be responsive without forcing the user to wait for the fling animations to 4742 // completely stop. 4743 switch (aMomentum) { 4744 case NOT_MOMENTUM: 4745 mIgnoreMomentumScroll = false; 4746 break; 4747 case SYNTHESIZED_MOMENTUM_EVENT: 4748 if (mIgnoreMomentumScroll) { 4749 return; 4750 } 4751 break; 4752 } 4753 4754 if (mAsyncSmoothMSDScroll != nullptr) { 4755 // When CSSOM-View scroll-behavior smooth scrolling is interrupted, 4756 // the scroll is not completed to avoid non-smooth snapping to the 4757 // prior smooth scroll's destination. 4758 mDestination = GetScrollPosition(); 4759 } 4760 4761 nsSize deltaMultiplier; 4762 float negativeTolerance; 4763 float positiveTolerance; 4764 if (aOrigin == ScrollOrigin::NotSpecified) { 4765 aOrigin = ScrollOrigin::Other; 4766 } 4767 bool isGenericOrigin = (aOrigin == ScrollOrigin::Other); 4768 4769 bool askApzToDoTheScroll = false; 4770 if ((aSnapFlags == ScrollSnapFlags::Disabled || !NeedsScrollSnap()) && 4771 gfxPlatform::UseDesktopZoomingScrollbars() && 4772 nsLayoutUtils::AsyncPanZoomEnabled(this) && 4773 !nsLayoutUtils::ShouldDisableApzForElement(GetContent()) && 4774 (WantAsyncScroll() || mZoomableByAPZ) && 4775 CanApzScrollInTheseDirections(DirectionsInDelta(aDelta))) { 4776 askApzToDoTheScroll = true; 4777 } 4778 4779 switch (aUnit) { 4780 case ScrollUnit::DEVICE_PIXELS: { 4781 nscoord appUnitsPerDevPixel = PresContext()->AppUnitsPerDevPixel(); 4782 deltaMultiplier = nsSize(appUnitsPerDevPixel, appUnitsPerDevPixel); 4783 if (isGenericOrigin) { 4784 aOrigin = ScrollOrigin::Pixels; 4785 } 4786 negativeTolerance = positiveTolerance = 0.5f; 4787 break; 4788 } 4789 case ScrollUnit::LINES: { 4790 deltaMultiplier = GetLineScrollAmount(); 4791 if (isGenericOrigin) { 4792 aOrigin = ScrollOrigin::Lines; 4793 } 4794 negativeTolerance = positiveTolerance = 0.1f; 4795 break; 4796 } 4797 case ScrollUnit::PAGES: { 4798 deltaMultiplier = GetPageScrollAmount(); 4799 if (isGenericOrigin) { 4800 aOrigin = ScrollOrigin::Pages; 4801 } 4802 negativeTolerance = 0.05f; 4803 positiveTolerance = 0; 4804 break; 4805 } 4806 case ScrollUnit::WHOLE: { 4807 if (askApzToDoTheScroll) { 4808 MOZ_ASSERT(aDelta.x >= -1 && aDelta.x <= 1 && aDelta.y >= -1 && 4809 aDelta.y <= 1); 4810 deltaMultiplier = GetScrollRangeForUserInputEvents().Size(); 4811 break; 4812 } else { 4813 nsPoint pos = GetScrollPosition(); 4814 AdjustDestinationForWholeDelta(aDelta, GetLayoutScrollRange(), pos); 4815 ScrollToWithOrigin( 4816 pos, nullptr /* range */, 4817 ScrollOperationParams{aMode, ScrollOrigin::Other, aSnapFlags, 4818 ScrollTriggeredByScript::No}); 4819 // 'this' might be destroyed here 4820 if (aOverflow) { 4821 *aOverflow = nsIntPoint(0, 0); 4822 } 4823 return; 4824 } 4825 } 4826 default: 4827 NS_ERROR("Invalid scroll mode"); 4828 return; 4829 } 4830 4831 if (askApzToDoTheScroll) { 4832 // Stop suppressing displayport while the page is still loading. 4833 if (MOZ_UNLIKELY(PresShell()->IsDocumentLoading())) { 4834 PresShell()->SuppressDisplayport(false); 4835 } 4836 4837 nsPoint delta( 4838 NSCoordSaturatingNonnegativeMultiply(aDelta.x, deltaMultiplier.width), 4839 NSCoordSaturatingNonnegativeMultiply(aDelta.y, deltaMultiplier.height)); 4840 4841 AppendScrollUpdate( 4842 ScrollPositionUpdate::NewPureRelativeScroll(aOrigin, aMode, delta)); 4843 4844 nsIContent* content = GetContent(); 4845 if (!DisplayPortUtils::HasNonMinimalNonZeroDisplayPort(content)) { 4846 if (MOZ_LOG_TEST(sDisplayportLog, LogLevel::Debug)) { 4847 mozilla::layers::ScrollableLayerGuid::ViewID viewID = 4848 mozilla::layers::ScrollableLayerGuid::NULL_SCROLL_ID; 4849 nsLayoutUtils::FindIDFor(content, &viewID); 4850 MOZ_LOG( 4851 sDisplayportLog, LogLevel::Debug, 4852 ("ScrollBy setting displayport on scrollId=%" PRIu64 "\n", viewID)); 4853 } 4854 4855 DisplayPortUtils::CalculateAndSetDisplayPortMargins( 4856 GetScrollTargetFrame(), DisplayPortUtils::RepaintMode::Repaint); 4857 nsIFrame* frame = do_QueryFrame(GetScrollTargetFrame()); 4858 DisplayPortUtils::SetZeroMarginDisplayPortOnAsyncScrollableAncestors( 4859 frame); 4860 } 4861 4862 SchedulePaint(); 4863 return; 4864 } 4865 4866 nsPoint newPos(NSCoordSaturatingAdd(mDestination.x, 4867 NSCoordSaturatingNonnegativeMultiply( 4868 aDelta.x, deltaMultiplier.width)), 4869 NSCoordSaturatingAdd(mDestination.y, 4870 NSCoordSaturatingNonnegativeMultiply( 4871 aDelta.y, deltaMultiplier.height))); 4872 4873 Maybe<SnapDestination> snapDestination; 4874 if (aSnapFlags != ScrollSnapFlags::Disabled) { 4875 if (NeedsScrollSnap()) { 4876 nscoord appUnitsPerDevPixel = PresContext()->AppUnitsPerDevPixel(); 4877 deltaMultiplier = nsSize(appUnitsPerDevPixel, appUnitsPerDevPixel); 4878 negativeTolerance = 0.1f; 4879 positiveTolerance = 0; 4880 ScrollUnit snapUnit = aUnit; 4881 if (aOrigin == ScrollOrigin::MouseWheel) { 4882 // When using a clicky scroll wheel, snap point selection works the same 4883 // as keyboard up/down/left/right navigation, but with varying amounts 4884 // of scroll delta. 4885 snapUnit = ScrollUnit::LINES; 4886 } 4887 snapDestination = GetSnapPointForDestination(snapUnit, aSnapFlags, 4888 mDestination, newPos); 4889 if (snapDestination) { 4890 newPos = snapDestination->mPosition; 4891 } 4892 } 4893 } 4894 4895 // Calculate desired range values. 4896 nscoord rangeLowerX, rangeUpperX, rangeLowerY, rangeUpperY; 4897 CalcRangeForScrollBy(aDelta.x, newPos.x, negativeTolerance, positiveTolerance, 4898 deltaMultiplier.width, &rangeLowerX, &rangeUpperX); 4899 CalcRangeForScrollBy(aDelta.y, newPos.y, negativeTolerance, positiveTolerance, 4900 deltaMultiplier.height, &rangeLowerY, &rangeUpperY); 4901 nsRect range(rangeLowerX, rangeLowerY, rangeUpperX - rangeLowerX, 4902 rangeUpperY - rangeLowerY); 4903 AutoWeakFrame weakFrame(this); 4904 ScrollToWithOrigin( 4905 newPos, &range, 4906 snapDestination 4907 ? ScrollOperationParams{aMode, aOrigin, 4908 std::move(snapDestination->mTargetIds)} 4909 : ScrollOperationParams{aMode, aOrigin}); 4910 if (!weakFrame.IsAlive()) { 4911 return; 4912 } 4913 4914 if (aOverflow) { 4915 nsPoint clampAmount = newPos - mDestination; 4916 float appUnitsPerDevPixel = PresContext()->AppUnitsPerDevPixel(); 4917 *aOverflow = 4918 nsIntPoint(NSAppUnitsToIntPixels(clampAmount.x, appUnitsPerDevPixel), 4919 NSAppUnitsToIntPixels(clampAmount.y, appUnitsPerDevPixel)); 4920 } 4921 4922 if (aUnit == ScrollUnit::DEVICE_PIXELS && 4923 !nsLayoutUtils::AsyncPanZoomEnabled(this)) { 4924 // When APZ is disabled, we must track the velocity 4925 // on the main thread; otherwise, the APZC will manage this. 4926 mVelocityQueue.Sample(GetScrollPosition()); 4927 } 4928 } 4929 4930 void ScrollContainerFrame::ScrollByCSSPixelsInternal( 4931 const CSSPoint& aDelta, ScrollMode aMode, ScrollSnapFlags aSnapFlags) { 4932 nsPoint current = GetScrollPosition(); 4933 // `current` value above might be a value which was aligned to in 4934 // layer-pixels, so starting from such points will make the difference between 4935 // the given position in script (e.g. scrollTo) and the aligned position 4936 // larger, in the worst case the difference can be observed in CSS pixels. 4937 // To avoid it, we use the current position in CSS pixels as the start 4938 // position. Hopefully it exactly matches the position where it was given by 4939 // the previous scrolling operation, but there may be some edge cases where 4940 // the current position in CSS pixels differs from the given position, the 4941 // cases should be fixed in bug 1556685. 4942 CSSPoint currentCSSPixels; 4943 if (StaticPrefs::layout_disable_pixel_alignment()) { 4944 currentCSSPixels = GetScrollPositionCSSPixels(); 4945 } else { 4946 currentCSSPixels = GetRoundedScrollPositionCSSPixels(); 4947 } 4948 nsPoint pt = CSSPoint::ToAppUnits(currentCSSPixels + aDelta); 4949 4950 nscoord halfPixel = nsPresContext::CSSPixelsToAppUnits(0.5f); 4951 nsRect range(pt.x - halfPixel, pt.y - halfPixel, 2 * halfPixel - 1, 4952 2 * halfPixel - 1); 4953 // XXX I don't think the following blocks are needed anymore, now that 4954 // ScrollToImpl simply tries to scroll an integer number of layer 4955 // pixels from the current position 4956 if (aDelta.x == 0.0f) { 4957 pt.x = current.x; 4958 range.x = pt.x; 4959 range.width = 0; 4960 } 4961 if (aDelta.y == 0.0f) { 4962 pt.y = current.y; 4963 range.y = pt.y; 4964 range.height = 0; 4965 } 4966 ScrollToWithOrigin( 4967 pt, &range, 4968 ScrollOperationParams{aMode, ScrollOrigin::Relative, aSnapFlags, 4969 ScrollTriggeredByScript::Yes}); 4970 // 'this' might be destroyed here 4971 } 4972 4973 void ScrollContainerFrame::ScrollSnap(ScrollMode aMode) { 4974 float flingSensitivity = 4975 StaticPrefs::layout_css_scroll_snap_prediction_sensitivity(); 4976 int maxVelocity = 4977 StaticPrefs::layout_css_scroll_snap_prediction_max_velocity(); 4978 maxVelocity = nsPresContext::CSSPixelsToAppUnits(maxVelocity); 4979 int maxOffset = maxVelocity * flingSensitivity; 4980 nsPoint velocity = mVelocityQueue.GetVelocity(); 4981 // Multiply each component individually to avoid integer multiply 4982 nsPoint predictedOffset = 4983 nsPoint(velocity.x * flingSensitivity, velocity.y * flingSensitivity); 4984 predictedOffset.Clamp(maxOffset); 4985 nsPoint pos = GetScrollPosition(); 4986 nsPoint destinationPos = pos + predictedOffset; 4987 ScrollSnap(destinationPos, aMode); 4988 } 4989 4990 void ScrollContainerFrame::ScrollSnap(const nsPoint& aDestination, 4991 ScrollMode aMode) { 4992 nsRect scrollRange = GetLayoutScrollRange(); 4993 nsPoint pos = GetScrollPosition(); 4994 nsPoint destination = scrollRange.ClampPoint(aDestination); 4995 ScrollSnapFlags snapFlags = ScrollSnapFlags::IntendedEndPosition; 4996 if (mVelocityQueue.GetVelocity() != nsPoint()) { 4997 snapFlags |= ScrollSnapFlags::IntendedDirection; 4998 } 4999 5000 // Bug 1776624 : Consider using mDestination as |aStartPos| argument for this 5001 // GetSnapPointForDestination call, this function call is the only one call 5002 // site using `GetScrollPosition()` as |aStartPos|. 5003 if (auto snapDestination = GetSnapPointForDestination( 5004 ScrollUnit::DEVICE_PIXELS, snapFlags, pos, destination)) { 5005 // Bail out if there's no scroll position change to do a workaround for bug 5006 // 1665932 (even if the __layout__ scroll position is unchanged, the 5007 // corresponding scroll offset update will change the __visual__ scroll 5008 // offset in APZ). 5009 if (snapDestination->mPosition == destination) { 5010 return; 5011 } 5012 destination = snapDestination->mPosition; 5013 ScrollToWithOrigin( 5014 destination, nullptr /* range */, 5015 ScrollOperationParams{aMode, ScrollOrigin::Other, 5016 std::move(snapDestination->mTargetIds)}); 5017 } 5018 } 5019 5020 nsSize ScrollContainerFrame::GetLineScrollAmount() const { 5021 RefPtr<nsFontMetrics> fm = 5022 nsLayoutUtils::GetInflatedFontMetricsForFrame(this); 5023 NS_ASSERTION(fm, "FontMetrics is null, assuming fontHeight == 1 appunit"); 5024 int32_t appUnitsPerDevPixel = PresContext()->AppUnitsPerDevPixel(); 5025 nscoord minScrollAmountInAppUnits = 5026 std::max(1, StaticPrefs::mousewheel_min_line_scroll_amount()) * 5027 appUnitsPerDevPixel; 5028 nscoord horizontalAmount = fm ? fm->AveCharWidth() : 0; 5029 nscoord verticalAmount = fm ? fm->MaxHeight() : 0; 5030 return nsSize(std::max(horizontalAmount, minScrollAmountInAppUnits), 5031 std::max(verticalAmount, minScrollAmountInAppUnits)); 5032 } 5033 5034 /** 5035 * Compute the scrollport size excluding any fixed-pos and sticky-pos (that are 5036 * stuck) headers and footers. A header or footer is an box that spans that 5037 * entire width of the viewport and touches the top (or bottom, respectively) of 5038 * the viewport. We also want to consider fixed/sticky elements that stack or 5039 * overlap to effectively create a larger header or footer. Headers and footers 5040 * that cover more than a third of the the viewport are ignored since they 5041 * probably aren't true headers and footers and we don't want to restrict 5042 * scrolling too much in such cases. This is a bit conservative --- some 5043 * pages use elements as headers or footers that don't span the entire width 5044 * of the viewport --- but it should be a good start. 5045 * 5046 * If aViewportFrame is non-null then the scroll frame is the root scroll 5047 * frame and we should consider fixed-pos items. 5048 */ 5049 struct TopAndBottom { 5050 TopAndBottom(nscoord aTop, nscoord aBottom) : top(aTop), bottom(aBottom) {} 5051 5052 nscoord top, bottom; 5053 }; 5054 struct TopComparator { 5055 bool Equals(const TopAndBottom& A, const TopAndBottom& B) const { 5056 return A.top == B.top; 5057 } 5058 bool LessThan(const TopAndBottom& A, const TopAndBottom& B) const { 5059 return A.top < B.top; 5060 } 5061 }; 5062 struct ReverseBottomComparator { 5063 bool Equals(const TopAndBottom& A, const TopAndBottom& B) const { 5064 return A.bottom == B.bottom; 5065 } 5066 bool LessThan(const TopAndBottom& A, const TopAndBottom& B) const { 5067 return A.bottom > B.bottom; 5068 } 5069 }; 5070 5071 static void AddToListIfHeaderFooter(nsIFrame* aFrame, 5072 nsIFrame* aScrollPortFrame, 5073 const nsRect& aScrollPort, 5074 nsTArray<TopAndBottom>& aList) { 5075 nsRect r = aFrame->GetRectRelativeToSelf(); 5076 r = nsLayoutUtils::TransformFrameRectToAncestor(aFrame, r, aScrollPortFrame); 5077 r = r.Intersect(aScrollPort); 5078 if ((r.width >= aScrollPort.width / 2 || 5079 r.width >= NSIntPixelsToAppUnits(800, AppUnitsPerCSSPixel())) && 5080 r.height <= aScrollPort.height / 3) { 5081 aList.AppendElement(TopAndBottom(r.y, r.YMost())); 5082 } 5083 } 5084 5085 StickyScrollContainer& ScrollContainerFrame::EnsureStickyContainer() { 5086 if (!mStickyContainer) { 5087 mStickyContainer = MakeUnique<StickyScrollContainer>(this); 5088 } 5089 return *mStickyContainer; 5090 } 5091 5092 static nsSize GetScrollPortSizeExcludingHeadersAndFooters( 5093 ScrollContainerFrame* aScrollFrame, nsIFrame* aViewportFrame, 5094 const nsRect& aScrollPort) { 5095 AutoTArray<TopAndBottom, 10> list; 5096 if (aViewportFrame) { 5097 for (nsIFrame* f : aViewportFrame->GetChildList(FrameChildListID::Fixed)) { 5098 AddToListIfHeaderFooter(f, aViewportFrame, aScrollPort, list); 5099 } 5100 } 5101 5102 // Add sticky frames that are currently in "fixed" positions 5103 if (auto* ssc = aScrollFrame->GetStickyContainer()) { 5104 for (nsIFrame* f : ssc->GetFrames().IterFromShallowest()) { 5105 // If it's acting like fixed position. 5106 if (ssc->IsStuckInYDirection(f)) { 5107 AddToListIfHeaderFooter(f, aScrollFrame, aScrollPort, list); 5108 } 5109 } 5110 } 5111 5112 list.Sort(TopComparator()); 5113 nscoord headerBottom = 0; 5114 for (uint32_t i = 0; i < list.Length(); ++i) { 5115 if (list[i].top <= headerBottom) { 5116 headerBottom = std::max(headerBottom, list[i].bottom); 5117 } 5118 } 5119 5120 list.Sort(ReverseBottomComparator()); 5121 nscoord footerTop = aScrollPort.height; 5122 for (uint32_t i = 0; i < list.Length(); ++i) { 5123 if (list[i].bottom >= footerTop) { 5124 footerTop = std::min(footerTop, list[i].top); 5125 } 5126 } 5127 5128 headerBottom = std::min(aScrollPort.height / 3, headerBottom); 5129 footerTop = std::max(aScrollPort.height - aScrollPort.height / 3, footerTop); 5130 5131 return nsSize(aScrollPort.width, footerTop - headerBottom); 5132 } 5133 5134 nsSize ScrollContainerFrame::GetPageScrollAmount() const { 5135 nsSize effectiveScrollPortSize; 5136 5137 if (GetVisualViewportSize() != mScrollPort.Size()) { 5138 // We want to use the visual viewport size if one is set. 5139 // The headers/footers adjustment is too complicated to do if there is a 5140 // visual viewport that differs from the layout viewport, this is probably 5141 // okay. 5142 effectiveScrollPortSize = GetVisualViewportSize(); 5143 } else { 5144 // Reduce effective scrollport height by the height of any 5145 // fixed-pos/sticky-pos headers or footers 5146 effectiveScrollPortSize = GetScrollPortSizeExcludingHeadersAndFooters( 5147 const_cast<ScrollContainerFrame*>(this), 5148 mIsRoot ? PresShell()->GetRootFrame() : nullptr, mScrollPort); 5149 } 5150 5151 nsSize lineScrollAmount = GetLineScrollAmount(); 5152 const int32_t maxOverlapPercent = std::clamp( 5153 StaticPrefs::toolkit_scrollbox_pagescroll_maxOverlapPercent(), 0, 80); 5154 const int32_t maxOverlapLines = 5155 std::max(StaticPrefs::toolkit_scrollbox_pagescroll_maxOverlapLines(), 0); 5156 5157 // The page increment is the size of the page, minus some overlap. 5158 return nsSize( 5159 effectiveScrollPortSize.width - 5160 std::min(effectiveScrollPortSize.width * maxOverlapPercent / 100, 5161 maxOverlapLines * lineScrollAmount.width), 5162 effectiveScrollPortSize.height - 5163 std::min(effectiveScrollPortSize.height * maxOverlapPercent / 100, 5164 maxOverlapLines * lineScrollAmount.height)); 5165 } 5166 5167 /** 5168 * this code is resposible for restoring the scroll position back to some 5169 * saved position. if the user has not moved the scroll position manually 5170 * we keep scrolling down until we get to our original position. keep in 5171 * mind that content could incrementally be coming in. we only want to stop 5172 * when we reach our new position. 5173 */ 5174 void ScrollContainerFrame::ScrollToRestoredPosition() { 5175 if (!NeedRestorePosition()) { 5176 return; 5177 } 5178 // make sure our scroll position did not change for where we last put 5179 // it. if it does then the user must have moved it, and we no longer 5180 // need to restore. 5181 // 5182 // In the RTL case, we check whether the scroll position changed using the 5183 // logical scroll position, but we scroll to the physical scroll position in 5184 // all cases 5185 5186 // The layout offset we want to restore is the same as the visual offset 5187 // (for now, may change in bug 1499210), but clamped to the layout scroll 5188 // range (which can be a subset of the visual scroll range). 5189 // Note that we can't do the clamping when initializing mRestorePos in 5190 // RestoreState(), since the scrollable rect (which the clamping depends 5191 // on) can change over the course of the restoration process. 5192 nsPoint layoutRestorePos = GetLayoutScrollRange().ClampPoint(mRestorePos); 5193 nsPoint visualRestorePos = GetVisualScrollRange().ClampPoint(mRestorePos); 5194 5195 // Continue restoring until both the layout and visual scroll positions 5196 // reach the destination. (Note that the two can only be different for 5197 // the root content document's root scroll frame, and when zoomed in). 5198 // This is necessary to avoid situations where the two offsets get stuck 5199 // at different values and nothing reconciles them (see bug 1519621 comment 5200 // 8). 5201 nsPoint logicalLayoutScrollPos = GetLogicalScrollPosition(); 5202 5203 SCROLLRESTORE_LOG( 5204 "%p: ScrollToRestoredPosition (mRestorePos=%s, mLastPos=%s, " 5205 "layoutRestorePos=%s, visualRestorePos=%s, " 5206 "logicalLayoutScrollPos=%s, " 5207 "GetLogicalVisualViewportOffset()=%s)\n", 5208 this, ToString(mRestorePos).c_str(), ToString(mLastPos).c_str(), 5209 ToString(layoutRestorePos).c_str(), ToString(visualRestorePos).c_str(), 5210 ToString(logicalLayoutScrollPos).c_str(), 5211 ToString(GetLogicalVisualViewportOffset()).c_str()); 5212 5213 // if we didn't move, we still need to restore 5214 if (GetLogicalVisualViewportOffset() == mLastPos || 5215 logicalLayoutScrollPos == mLastPos) { 5216 // if our desired position is different to the scroll position, scroll. 5217 // remember that we could be incrementally loading so we may enter 5218 // and scroll many times. 5219 if (mRestorePos != mLastPos /* GetLogicalVisualViewportOffset() */ || 5220 layoutRestorePos != logicalLayoutScrollPos) { 5221 LoadingState state = GetPageLoadingState(); 5222 if (state == LoadingState::Stopped && !IsSubtreeDirty()) { 5223 return; 5224 } 5225 nsPoint visualScrollToPos = visualRestorePos; 5226 nsPoint layoutScrollToPos = layoutRestorePos; 5227 if (!IsPhysicalLTR()) { 5228 // convert from logical to physical scroll position 5229 visualScrollToPos.x -= 5230 (GetVisualViewportSize().width - mScrolledFrame->GetRect().width); 5231 layoutScrollToPos.x -= 5232 (GetVisualViewportSize().width - mScrolledFrame->GetRect().width); 5233 } 5234 AutoWeakFrame weakFrame(this); 5235 // It's very important to pass ScrollOrigin::Restore here, so 5236 // ScrollToWithOrigin won't clear out mRestorePos. 5237 ScrollToWithOrigin( 5238 layoutScrollToPos, nullptr, 5239 ScrollOperationParams{ScrollMode::Instant, ScrollOrigin::Restore}); 5240 if (!weakFrame.IsAlive()) { 5241 return; 5242 } 5243 if (mIsRoot) { 5244 PresShell()->ScrollToVisual(visualScrollToPos, FrameMetrics::eRestore, 5245 ScrollMode::Instant); 5246 } 5247 if (state == LoadingState::Loading || IsSubtreeDirty()) { 5248 // If we're trying to do a history scroll restore, then we want to 5249 // keep trying this until we succeed, because the page can be loading 5250 // incrementally. So re-get the scroll position for the next iteration, 5251 // it might not be exactly equal to mRestorePos due to rounding and 5252 // clamping. 5253 mLastPos = GetLogicalVisualViewportOffset(); 5254 return; 5255 } 5256 } 5257 // If we get here, either we reached the desired position (mLastPos == 5258 // mRestorePos) or we're not trying to do a history scroll restore, so 5259 // we can stop after the scroll attempt above. 5260 mRestorePos.y = -1; 5261 mLastPos.x = -1; 5262 mLastPos.y = -1; 5263 } else { 5264 // user moved the position, so we won't need to restore 5265 mLastPos.x = -1; 5266 mLastPos.y = -1; 5267 } 5268 } 5269 5270 ScrollContainerFrame::LoadingState ScrollContainerFrame::GetPageLoadingState() { 5271 bool loadCompleted = false, stopped = false; 5272 nsCOMPtr<nsIDocShell> ds = GetContent()->GetComposedDoc()->GetDocShell(); 5273 if (ds) { 5274 nsCOMPtr<nsIDocumentViewer> viewer; 5275 ds->GetDocViewer(getter_AddRefs(viewer)); 5276 if (viewer) { 5277 loadCompleted = viewer->GetLoadCompleted(); 5278 stopped = viewer->GetIsStopped(); 5279 } 5280 } 5281 return loadCompleted 5282 ? (stopped ? LoadingState::Stopped : LoadingState::Loaded) 5283 : LoadingState::Loading; 5284 } 5285 5286 PhysicalAxes ScrollContainerFrame::GetOverflowAxes() const { 5287 nsSize scrollportSize = mScrollPort.Size(); 5288 nsSize childSize = GetScrolledRect().Size(); 5289 5290 PhysicalAxes result; 5291 5292 if (childSize.height > scrollportSize.height) { 5293 result += PhysicalAxis::Vertical; 5294 } 5295 5296 if (childSize.width > scrollportSize.width) { 5297 result += PhysicalAxis::Horizontal; 5298 } 5299 5300 return result; 5301 } 5302 5303 nsresult ScrollContainerFrame::FireScrollPortEvent() { 5304 mAsyncScrollPortEvent.Forget(); 5305 5306 // TODO(emilio): why do we need the whole WillPaintObserver infrastructure and 5307 // can't use AddScriptRunner & co? I guess it made sense when we used 5308 // WillPaintObserver for scroll events too, or when this used to flush. 5309 // 5310 // Should we remove this? 5311 5312 PhysicalAxes overflowAxes = GetOverflowAxes(); 5313 5314 bool newVerticalOverflow = overflowAxes.contains(PhysicalAxis::Vertical); 5315 bool vertChanged = mVerticalOverflow != newVerticalOverflow; 5316 5317 bool newHorizontalOverflow = overflowAxes.contains(PhysicalAxis::Horizontal); 5318 bool horizChanged = mHorizontalOverflow != newHorizontalOverflow; 5319 5320 if (!vertChanged && !horizChanged) { 5321 return NS_OK; 5322 } 5323 5324 // If both either overflowed or underflowed then we dispatch only one 5325 // DOM event. 5326 bool both = vertChanged && horizChanged && 5327 newVerticalOverflow == newHorizontalOverflow; 5328 InternalScrollPortEvent::OrientType orient; 5329 if (both) { 5330 orient = InternalScrollPortEvent::eBoth; 5331 mHorizontalOverflow = newHorizontalOverflow; 5332 mVerticalOverflow = newVerticalOverflow; 5333 } else if (vertChanged) { 5334 orient = InternalScrollPortEvent::eVertical; 5335 mVerticalOverflow = newVerticalOverflow; 5336 if (horizChanged) { 5337 // We need to dispatch a separate horizontal DOM event. Do that the next 5338 // time around since dispatching the vertical DOM event might destroy 5339 // the frame. 5340 PostOverflowEvent(); 5341 } 5342 } else { 5343 orient = InternalScrollPortEvent::eHorizontal; 5344 mHorizontalOverflow = newHorizontalOverflow; 5345 } 5346 5347 InternalScrollPortEvent event( 5348 true, 5349 (orient == InternalScrollPortEvent::eHorizontal ? mHorizontalOverflow 5350 : mVerticalOverflow) 5351 ? eScrollPortOverflow 5352 : eScrollPortUnderflow, 5353 nullptr); 5354 event.mOrient = orient; 5355 5356 RefPtr<nsIContent> content = GetContent(); 5357 RefPtr<nsPresContext> presContext = PresContext(); 5358 return EventDispatcher::Dispatch(content, presContext, &event); 5359 } 5360 5361 void ScrollContainerFrame::PostScrollEndEvent() { 5362 if (mScrollEndEvent) { 5363 return; 5364 } 5365 5366 // The ScrollEndEvent constructor registers itself. 5367 mScrollEndEvent = new ScrollEndEvent(this); 5368 } 5369 5370 void ScrollContainerFrame::FireScrollEndEvent() { 5371 MOZ_ASSERT(mScrollEndEvent); 5372 mScrollEndEvent->Revoke(); 5373 mScrollEndEvent = nullptr; 5374 5375 RefPtr<nsPresContext> presContext = PresContext(); 5376 nsEventStatus status = nsEventStatus_eIgnore; 5377 WidgetGUIEvent event(true, eScrollend, nullptr); 5378 event.mFlags.mBubbles = mIsRoot; 5379 event.mFlags.mCancelable = false; 5380 RefPtr<nsINode> target = 5381 mIsRoot ? static_cast<nsINode*>(presContext->Document()) : GetContent(); 5382 EventDispatcher::Dispatch(target, presContext, &event, nullptr, &status); 5383 } 5384 5385 void ScrollContainerFrame::ReloadChildFrames() { 5386 mScrolledFrame = nullptr; 5387 mHScrollbarBox = nullptr; 5388 mVScrollbarBox = nullptr; 5389 mScrollCornerBox = nullptr; 5390 mResizerBox = nullptr; 5391 5392 for (nsIFrame* frame : PrincipalChildList()) { 5393 nsIContent* content = frame->GetContent(); 5394 if (content == GetContent()) { 5395 NS_ASSERTION(!mScrolledFrame, "Already found the scrolled frame"); 5396 mScrolledFrame = frame; 5397 } else if (content == mVScrollbarContent) { 5398 NS_ASSERTION(!mVScrollbarBox, "Found multiple vertical scrollbars?"); 5399 mVScrollbarBox = do_QueryFrame(frame); 5400 MOZ_ASSERT(mVScrollbarBox, "Not a scrollbar?"); 5401 } else if (content == mHScrollbarContent) { 5402 NS_ASSERTION(!mHScrollbarBox, "Found multiple horizontal scrollbars?"); 5403 mHScrollbarBox = do_QueryFrame(frame); 5404 MOZ_ASSERT(mHScrollbarBox, "Not a scrollbar?"); 5405 } else if (content == mResizerContent) { 5406 NS_ASSERTION(!mResizerBox, "Found multiple resizers"); 5407 mResizerBox = frame; 5408 } else if (content == mScrollCornerContent) { 5409 NS_ASSERTION(!mScrollCornerBox, "Found multiple scrollcorners"); 5410 mScrollCornerBox = frame; 5411 } 5412 } 5413 } 5414 5415 already_AddRefed<Element> ScrollContainerFrame::MakeScrollbar( 5416 NodeInfo* aNodeInfo, bool aVertical, AnonymousContentKey& aKey) { 5417 MOZ_ASSERT(aNodeInfo); 5418 MOZ_ASSERT( 5419 aNodeInfo->Equals(nsGkAtoms::scrollbar, nullptr, kNameSpaceID_XUL)); 5420 5421 aKey = AnonymousContentKey::Type_Scrollbar; 5422 if (aVertical) { 5423 aKey |= AnonymousContentKey::Flag_Vertical; 5424 } 5425 5426 RefPtr<Element> e; 5427 NS_TrustedNewXULElement(getter_AddRefs(e), do_AddRef(aNodeInfo)); 5428 5429 #ifdef DEBUG 5430 // Scrollbars can get restyled by theme changes. Whether such a restyle 5431 // will actually reconstruct them correctly if it involves a frame 5432 // reconstruct... I don't know. :( 5433 e->SetProperty(nsGkAtoms::restylableAnonymousNode, 5434 reinterpret_cast<void*>(true)); 5435 #endif // DEBUG 5436 5437 if (aVertical) { 5438 e->SetAttr(kNameSpaceID_None, nsGkAtoms::vertical, u"true"_ns, false); 5439 } 5440 5441 if (mIsRoot) { 5442 e->SetProperty(nsGkAtoms::docLevelNativeAnonymousContent, 5443 reinterpret_cast<void*>(true)); 5444 e->SetAttr(kNameSpaceID_None, nsGkAtoms::root, u"true"_ns, false); 5445 5446 // Don't bother making style caching take [root] styles into account. 5447 aKey = AnonymousContentKey::None; 5448 } 5449 5450 return e.forget(); 5451 } 5452 5453 auto ScrollContainerFrame::GetCurrentAnonymousContent() const 5454 -> EnumSet<AnonymousContentType> { 5455 EnumSet<AnonymousContentType> result; 5456 if (mHScrollbarContent) { 5457 result += AnonymousContentType::HorizontalScrollbar; 5458 } 5459 if (mVScrollbarContent) { 5460 result += AnonymousContentType::VerticalScrollbar; 5461 } 5462 if (mResizerContent) { 5463 result += AnonymousContentType::Resizer; 5464 } 5465 return result; 5466 } 5467 5468 auto ScrollContainerFrame::GetNeededAnonymousContent() const 5469 -> EnumSet<AnonymousContentType> { 5470 nsPresContext* pc = PresContext(); 5471 5472 // Don't create scrollbars if we're an SVG document being used as an image, 5473 // or if we're printing/print previewing. 5474 // (In the printing case, we allow scrollbars if this is the child of the 5475 // viewport & paginated scrolling is enabled, because then we must be the 5476 // scroll frame for the print preview window, & that does need scrollbars.) 5477 if (pc->Document()->IsBeingUsedAsImage() || 5478 (!pc->IsDynamic() && !(mIsRoot && pc->HasPaginatedScrolling()))) { 5479 return {}; 5480 } 5481 5482 EnumSet<AnonymousContentType> result; 5483 // If we're the scrollframe for the root, then we want to construct our 5484 // scrollbar frames no matter what. That way later dynamic changes to 5485 // propagated overflow styles will show or hide scrollbars on the viewport 5486 // without requiring frame reconstruction of the viewport (good!). 5487 // 5488 // TODO(emilio): Figure out if we can remove this special-case now that we 5489 // have more targeted optimizations. 5490 if (mIsRoot) { 5491 result += AnonymousContentType::HorizontalScrollbar; 5492 result += AnonymousContentType::VerticalScrollbar; 5493 // If scrollbar-width is none, don't generate scrollbars. 5494 } else if (StyleUIReset()->ScrollbarWidth() != StyleScrollbarWidth::None) { 5495 ScrollStyles styles = GetScrollStyles(); 5496 if (styles.mHorizontal != StyleOverflow::Hidden) { 5497 result += AnonymousContentType::HorizontalScrollbar; 5498 } 5499 if (styles.mVertical != StyleOverflow::Hidden) { 5500 result += AnonymousContentType::VerticalScrollbar; 5501 } 5502 } 5503 5504 // Check if the frame is resizable. Note: 5505 // "The effect of the resize property on generated content is undefined. 5506 // Implementations should not apply the resize property to generated 5507 // content." [1] 5508 // For info on what is generated content, see [2]. 5509 // [1]: https://drafts.csswg.org/css-ui/#resize 5510 // [2]: https://www.w3.org/TR/CSS2/generate.html#content 5511 auto resizeStyle = StyleDisplay()->mResize; 5512 if (resizeStyle != StyleResize::None && 5513 !HasAnyStateBits(NS_FRAME_GENERATED_CONTENT)) { 5514 result += AnonymousContentType::Resizer; 5515 } 5516 5517 return result; 5518 } 5519 5520 nsresult ScrollContainerFrame::CreateAnonymousContent( 5521 nsTArray<ContentInfo>& aElements) { 5522 nsPresContext* presContext = PresContext(); 5523 nsNodeInfoManager* nodeInfoManager = 5524 presContext->Document()->NodeInfoManager(); 5525 5526 auto neededAnonContent = GetNeededAnonymousContent(); 5527 if (neededAnonContent.isEmpty()) { 5528 return NS_OK; 5529 } 5530 5531 { 5532 RefPtr<NodeInfo> nodeInfo = nodeInfoManager->GetNodeInfo( 5533 nsGkAtoms::scrollbar, nullptr, kNameSpaceID_XUL, nsINode::ELEMENT_NODE); 5534 NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY); 5535 5536 if (neededAnonContent.contains(AnonymousContentType::HorizontalScrollbar)) { 5537 AnonymousContentKey key; 5538 mHScrollbarContent = MakeScrollbar(nodeInfo, /* aVertical */ false, key); 5539 aElements.AppendElement(ContentInfo(mHScrollbarContent, key)); 5540 } 5541 5542 if (neededAnonContent.contains(AnonymousContentType::VerticalScrollbar)) { 5543 AnonymousContentKey key; 5544 mVScrollbarContent = MakeScrollbar(nodeInfo, /* aVertical */ true, key); 5545 aElements.AppendElement(ContentInfo(mVScrollbarContent, key)); 5546 } 5547 } 5548 5549 if (neededAnonContent.contains(AnonymousContentType::Resizer)) { 5550 MOZ_ASSERT(!mIsRoot, "Root scroll frame shouldn't be resizable"); 5551 5552 RefPtr<NodeInfo> nodeInfo; 5553 nodeInfo = nodeInfoManager->GetNodeInfo( 5554 nsGkAtoms::resizer, nullptr, kNameSpaceID_XUL, nsINode::ELEMENT_NODE); 5555 NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY); 5556 5557 NS_TrustedNewXULElement(getter_AddRefs(mResizerContent), nodeInfo.forget()); 5558 5559 nsAutoString dir; 5560 switch (StyleDisplay()->mResize) { 5561 case StyleResize::Horizontal: 5562 if (IsScrollbarOnRight()) { 5563 dir.AssignLiteral("right"); 5564 } else { 5565 dir.AssignLiteral("left"); 5566 } 5567 break; 5568 case StyleResize::Vertical: 5569 dir.AssignLiteral("bottom"); 5570 if (!IsScrollbarOnRight()) { 5571 mResizerContent->SetAttr(kNameSpaceID_None, nsGkAtoms::flip, u""_ns, 5572 false); 5573 } 5574 break; 5575 case StyleResize::Both: 5576 if (IsScrollbarOnRight()) { 5577 dir.AssignLiteral("bottomright"); 5578 } else { 5579 dir.AssignLiteral("bottomleft"); 5580 } 5581 break; 5582 default: 5583 NS_WARNING("only resizable types should have resizers"); 5584 } 5585 mResizerContent->SetAttr(kNameSpaceID_None, nsGkAtoms::dir, dir, false); 5586 aElements.AppendElement(mResizerContent); 5587 } 5588 5589 if (neededAnonContent.contains(AnonymousContentType::HorizontalScrollbar) && 5590 neededAnonContent.contains(AnonymousContentType::VerticalScrollbar)) { 5591 AnonymousContentKey key = AnonymousContentKey::Type_ScrollCorner; 5592 5593 RefPtr<NodeInfo> nodeInfo = 5594 nodeInfoManager->GetNodeInfo(nsGkAtoms::scrollcorner, nullptr, 5595 kNameSpaceID_XUL, nsINode::ELEMENT_NODE); 5596 NS_TrustedNewXULElement(getter_AddRefs(mScrollCornerContent), 5597 nodeInfo.forget()); 5598 if (mIsRoot) { 5599 mScrollCornerContent->SetProperty( 5600 nsGkAtoms::docLevelNativeAnonymousContent, 5601 reinterpret_cast<void*>(true)); 5602 mScrollCornerContent->SetAttr(kNameSpaceID_None, nsGkAtoms::root, 5603 u"true"_ns, false); 5604 5605 // Don't bother making style caching take [root="true"] styles into 5606 // account. 5607 key = AnonymousContentKey::None; 5608 } 5609 aElements.AppendElement(ContentInfo(mScrollCornerContent, key)); 5610 } 5611 return NS_OK; 5612 } 5613 5614 void ScrollContainerFrame::AppendAnonymousContentTo( 5615 nsTArray<nsIContent*>& aElements, uint32_t aFilter) { 5616 if (mHScrollbarContent) { 5617 aElements.AppendElement(mHScrollbarContent); 5618 } 5619 5620 if (mVScrollbarContent) { 5621 aElements.AppendElement(mVScrollbarContent); 5622 } 5623 5624 if (mScrollCornerContent) { 5625 aElements.AppendElement(mScrollCornerContent); 5626 } 5627 5628 if (mResizerContent) { 5629 aElements.AppendElement(mResizerContent); 5630 } 5631 } 5632 5633 void ScrollContainerFrame::DidSetComputedStyle( 5634 ComputedStyle* aOldComputedStyle) { 5635 nsContainerFrame::DidSetComputedStyle(aOldComputedStyle); 5636 if (aOldComputedStyle && !mIsRoot && 5637 StyleDisplay()->mScrollSnapType != 5638 aOldComputedStyle->StyleDisplay()->mScrollSnapType) { 5639 PostPendingResnap(); 5640 } 5641 } 5642 5643 void ScrollContainerFrame::RemoveObservers() { 5644 if (mAsyncScroll) { 5645 mAsyncScroll->RemoveObserver(); 5646 mAsyncScroll = nullptr; 5647 } 5648 if (mAsyncSmoothMSDScroll) { 5649 mAsyncSmoothMSDScroll->RemoveObserver(); 5650 mAsyncSmoothMSDScroll = nullptr; 5651 } 5652 } 5653 5654 void ScrollContainerFrame::ActivityOccurred() { 5655 if (mScrollbarActivity && 5656 (mHasHorizontalScrollbar || mHasVerticalScrollbar)) { 5657 RefPtr<ScrollbarActivity> scrollbarActivity(mScrollbarActivity); 5658 scrollbarActivity->ActivityOccurred(); 5659 } 5660 } 5661 5662 /** 5663 * Called when we want to update the scrollbar position, either because 5664 * scrolling happened or the user moved the scrollbar position and we need to 5665 * undo that (e.g., when the user clicks to scroll and we're using smooth 5666 * scrolling, so we need to put the thumb back to its initial position for the 5667 * start of the smooth sequence). 5668 */ 5669 void ScrollContainerFrame::UpdateScrollbarPosition() { 5670 mFrameIsUpdatingScrollbar = true; 5671 5672 nsPoint pt = GetScrollPosition(); 5673 nsRect scrollRange = GetVisualScrollRange(); 5674 5675 if (gfxPlatform::UseDesktopZoomingScrollbars()) { 5676 pt = GetVisualViewportOffset(); 5677 scrollRange = GetScrollRangeForUserInputEvents(); 5678 } 5679 5680 if (mVScrollbarBox && mVScrollbarBox->SetCurPos(CSSPixel::FromAppUnitsRounded( 5681 pt.y - scrollRange.y))) { 5682 ActivityOccurred(); 5683 } 5684 if (mHScrollbarBox && mHScrollbarBox->SetCurPos(CSSPixel::FromAppUnitsRounded( 5685 pt.x - scrollRange.x))) { 5686 ActivityOccurred(); 5687 } 5688 5689 mFrameIsUpdatingScrollbar = false; 5690 } 5691 5692 void ScrollContainerFrame::ScrollbarCurPosChanged(bool aDoScroll) { 5693 // Attribute changes on the scrollbars happen in one of three ways: 5694 // 1) The scrollbar changed the attribute in response to some user event 5695 // 2) We changed the attribute in response to a ScrollPositionDidChange 5696 // callback from the scrolling view 5697 // 3) We changed the attribute to adjust the scrollbars for the start 5698 // of a smooth scroll operation 5699 // 5700 // In cases 2 and 3 we do not need to scroll because we're just 5701 // updating our scrollbar. 5702 if (mFrameIsUpdatingScrollbar) { 5703 return; 5704 } 5705 5706 nsRect scrollRange = GetVisualScrollRange(); 5707 nsPoint current = GetScrollPosition() - scrollRange.TopLeft(); 5708 if (gfxPlatform::UseDesktopZoomingScrollbars()) { 5709 scrollRange = GetScrollRangeForUserInputEvents(); 5710 current = GetVisualViewportOffset() - scrollRange.TopLeft(); 5711 } 5712 5713 nsPoint dest = current; 5714 nsRect allowedRange(current, nsSize()); 5715 if (mHScrollbarBox) { 5716 dest.x = CSSPixel::ToAppUnits(mHScrollbarBox->GetCurPos()); 5717 if (dest.x) { 5718 const nscoord halfPixel = AppUnitsPerCSSPixel() / 2; 5719 // Any nscoord value that would round to the attribute value when 5720 // converted to CSS pixels is allowed. 5721 allowedRange.x = dest.x - halfPixel; 5722 allowedRange.width = halfPixel * 2 - 1; 5723 } 5724 } 5725 if (mVScrollbarBox) { 5726 dest.y = CSSPixel::ToAppUnits(mVScrollbarBox->GetCurPos()); 5727 if (dest.y) { 5728 const nscoord halfPixel = AppUnitsPerCSSPixel() / 2; 5729 // Any nscoord value that would round to the attribute value when 5730 // converted to CSS pixels is allowed. 5731 allowedRange.y = dest.y - halfPixel; 5732 allowedRange.height = halfPixel * 2 - 1; 5733 } 5734 } 5735 current += scrollRange.TopLeft(); 5736 dest += scrollRange.TopLeft(); 5737 allowedRange += scrollRange.TopLeft(); 5738 5739 // Don't try to scroll if we're already at an acceptable place. 5740 // Don't call Contains here since Contains returns false when the point is 5741 // on the bottom or right edge of the rectangle. 5742 if (allowedRange.ClampPoint(current) == current) { 5743 return; 5744 } 5745 5746 if (aDoScroll) { 5747 ScrollToWithOrigin( 5748 dest, &allowedRange, 5749 ScrollOperationParams{ScrollMode::Instant, ScrollOrigin::Scrollbars}); 5750 } 5751 // 'this' might be destroyed here 5752 } 5753 5754 /* ============= Scroll events ========== */ 5755 5756 ScrollContainerFrame::ScrollEvent::ScrollEvent(ScrollContainerFrame* aHelper) 5757 : Runnable("ScrollContainerFrame::ScrollEvent"), mHelper(aHelper) { 5758 mHelper->PresShell()->PostScrollEvent(this); 5759 } 5760 5761 // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230, bug 1535398) 5762 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP 5763 ScrollContainerFrame::ScrollEvent::Run() { 5764 if (mHelper) { 5765 mHelper->FireScrollEvent(); 5766 } 5767 return NS_OK; 5768 } 5769 5770 ScrollContainerFrame::ScrollEndEvent::ScrollEndEvent( 5771 ScrollContainerFrame* aHelper) 5772 : Runnable("ScrollContainerFrame::ScrollEndEvent"), mHelper(aHelper) { 5773 mHelper->PresShell()->PostScrollEvent(this); 5774 } 5775 5776 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP 5777 ScrollContainerFrame::ScrollEndEvent::Run() { 5778 if (mHelper) { 5779 mHelper->FireScrollEndEvent(); 5780 } 5781 return NS_OK; 5782 } 5783 5784 void ScrollContainerFrame::FireScrollEvent() { 5785 RefPtr<nsIContent> content = GetContent(); 5786 RefPtr<nsPresContext> presContext = PresContext(); 5787 AUTO_PROFILER_MARKER_DOCSHELL("FireScrollEvent", GRAPHICS, 5788 presContext->GetDocShell()); 5789 5790 MOZ_ASSERT(mScrollEvent); 5791 mScrollEvent->Revoke(); 5792 mScrollEvent = nullptr; 5793 5794 bool oldProcessing = mProcessingScrollEvent; 5795 AutoWeakFrame weakFrame(this); 5796 auto RestoreProcessingScrollEvent = mozilla::MakeScopeExit([&] { 5797 if (weakFrame.IsAlive()) { // Otherwise `this` will be dead too. 5798 mProcessingScrollEvent = oldProcessing; 5799 } 5800 }); 5801 5802 mProcessingScrollEvent = true; 5803 5804 WidgetGUIEvent event(true, eScroll, nullptr); 5805 nsEventStatus status = nsEventStatus_eIgnore; 5806 // Fire viewport scroll events at the document (where they 5807 // will bubble to the window) 5808 mozilla::layers::ScrollLinkedEffectDetector detector( 5809 content->GetComposedDoc(), 5810 presContext->RefreshDriver()->MostRecentRefresh()); 5811 if (mIsRoot) { 5812 if (RefPtr<Document> doc = content->GetUncomposedDoc()) { 5813 EventDispatcher::Dispatch(doc, presContext, &event, nullptr, &status); 5814 } 5815 } else { 5816 // scroll events fired at elements don't bubble (although scroll events 5817 // fired at documents do, to the window) 5818 event.mFlags.mBubbles = false; 5819 EventDispatcher::Dispatch(content, presContext, &event, nullptr, &status); 5820 } 5821 } 5822 5823 void ScrollContainerFrame::PostScrollEvent() { 5824 if (mScrollEvent) { 5825 return; 5826 } 5827 5828 // The ScrollEvent constructor registers itself. 5829 mScrollEvent = new ScrollEvent(this); 5830 } 5831 5832 // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230, bug 1535398) 5833 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP 5834 ScrollContainerFrame::AsyncScrollPortEvent::Run() { 5835 return mHelper ? mHelper->FireScrollPortEvent() : NS_OK; 5836 } 5837 5838 void ScrollContainerFrame::PostOverflowEvent() { 5839 if (mAsyncScrollPortEvent.IsPending()) { 5840 return; 5841 } 5842 5843 if (!nsContentUtils::IsChromeDoc(PresContext()->Document())) { 5844 return; 5845 } 5846 5847 PhysicalAxes overflowAxes = GetOverflowAxes(); 5848 5849 bool newVerticalOverflow = overflowAxes.contains(PhysicalAxis::Vertical); 5850 bool vertChanged = mVerticalOverflow != newVerticalOverflow; 5851 5852 bool newHorizontalOverflow = overflowAxes.contains(PhysicalAxis::Horizontal); 5853 bool horizChanged = mHorizontalOverflow != newHorizontalOverflow; 5854 5855 if (!vertChanged && !horizChanged) { 5856 return; 5857 } 5858 5859 nsRootPresContext* rpc = PresContext()->GetRootPresContext(); 5860 if (!rpc) { 5861 return; 5862 } 5863 5864 mAsyncScrollPortEvent = new AsyncScrollPortEvent(this); 5865 rpc->AddWillPaintObserver(mAsyncScrollPortEvent.get()); 5866 } 5867 5868 nsIFrame* ScrollContainerFrame::GetFrameForStyle() const { 5869 if (mIsRoot) { 5870 if (auto* rootFrame = 5871 PresContext()->FrameConstructor()->GetRootElementStyleFrame()) { 5872 return rootFrame; 5873 } 5874 } 5875 return const_cast<ScrollContainerFrame*>(this); 5876 } 5877 5878 bool ScrollContainerFrame::NeedsScrollSnap() const { 5879 nsIFrame* scrollSnapFrame = GetFrameForStyle(); 5880 if (!scrollSnapFrame) { 5881 return false; 5882 } 5883 return scrollSnapFrame->StyleDisplay()->mScrollSnapType.strictness != 5884 StyleScrollSnapStrictness::None; 5885 } 5886 5887 nsSize ScrollContainerFrame::GetSnapportSize() const { 5888 nsRect snapport = GetScrollPortRect(); 5889 nsMargin scrollPadding = GetScrollPadding(); 5890 snapport.Deflate(scrollPadding); 5891 return snapport.Size(); 5892 } 5893 5894 bool ScrollContainerFrame::IsScrollbarOnRight() const { 5895 // The position of the scrollbar in top-level windows depends on the pref 5896 // layout.scrollbar.side. For non-top-level elements, it depends only on the 5897 // directionaliy of the element (equivalent to a value of "1" for the pref). 5898 if (!mIsRoot) { 5899 return IsPhysicalLTR(); 5900 } 5901 switch (StaticPrefs::layout_scrollbar_side()) { 5902 default: 5903 case 0: // UI directionality 5904 return StaticPrefs::bidi_direction() == IBMBIDI_TEXTDIRECTION_LTR; 5905 case 1: // Document / content directionality 5906 return IsPhysicalLTR(); 5907 case 2: // Always right 5908 return true; 5909 case 3: // Always left 5910 return false; 5911 } 5912 } 5913 5914 bool ScrollContainerFrame::IsScrollingActive() const { 5915 const nsStyleDisplay* disp = StyleDisplay(); 5916 if (disp->mWillChange.bits & StyleWillChangeBits::SCROLL) { 5917 return true; 5918 } 5919 5920 nsIContent* content = GetContent(); 5921 return mHasBeenScrolledRecently || IsAlwaysActive() || 5922 DisplayPortUtils::HasDisplayPort(content); 5923 } 5924 5925 void ScrollContainerFrame::FinishReflowForScrollbar( 5926 nsScrollbarFrame* aScrollbar, nscoord aMinXY, nscoord aMaxXY, 5927 nscoord aCurPosXY, nscoord aPageIncrement) { 5928 // Scrollbars assume zero is the minimum position, so translate for them. 5929 bool changed = false; 5930 changed |= 5931 aScrollbar->SetCurPos(CSSPixel::FromAppUnitsRounded(aCurPosXY - aMinXY)); 5932 changed |= aScrollbar->SetEnabled(aMaxXY != aMinXY); 5933 changed |= 5934 aScrollbar->SetMaxPos(CSSPixel::FromAppUnitsRounded(aMaxXY - aMinXY)); 5935 changed |= aScrollbar->SetPageIncrement( 5936 CSSPixel::FromAppUnitsRounded(aPageIncrement)); 5937 if (changed) { 5938 ActivityOccurred(); 5939 } 5940 } 5941 5942 class MOZ_RAII ScrollContainerFrame::AutoMinimumScaleSizeChangeDetector final { 5943 public: 5944 explicit AutoMinimumScaleSizeChangeDetector( 5945 ScrollContainerFrame* aScrollFrame) 5946 : mHelper(aScrollFrame) { 5947 MOZ_ASSERT(mHelper); 5948 MOZ_ASSERT(mHelper->mIsRoot); 5949 5950 mPreviousMinimumScaleSize = aScrollFrame->mMinimumScaleSize; 5951 mPreviousIsUsingMinimumScaleSize = aScrollFrame->mIsUsingMinimumScaleSize; 5952 } 5953 ~AutoMinimumScaleSizeChangeDetector() { 5954 if (mPreviousMinimumScaleSize != mHelper->mMinimumScaleSize || 5955 mPreviousIsUsingMinimumScaleSize != mHelper->mIsUsingMinimumScaleSize) { 5956 mHelper->mMinimumScaleSizeChanged = true; 5957 } 5958 } 5959 5960 private: 5961 ScrollContainerFrame* mHelper; 5962 5963 nsSize mPreviousMinimumScaleSize; 5964 bool mPreviousIsUsingMinimumScaleSize; 5965 }; 5966 5967 nsSize ScrollContainerFrame::TrueOuterSize( 5968 nsDisplayListBuilder* aBuilder) const { 5969 if (!PresShell()->UsesMobileViewportSizing()) { 5970 return GetSize(); 5971 } 5972 5973 RefPtr<MobileViewportManager> manager = 5974 PresShell()->GetMobileViewportManager(); 5975 MOZ_ASSERT(manager); 5976 5977 LayoutDeviceIntSize displaySize = manager->DisplaySize(); 5978 5979 MOZ_ASSERT(aBuilder); 5980 // In case of WebRender, we expand the outer size to include the dynamic 5981 // toolbar area here. 5982 // In case of non WebRender, we expand the size dynamically in 5983 // MoveScrollbarForLayerMargin in AsyncCompositionManager.cpp. 5984 WebRenderLayerManager* layerManager = aBuilder->GetWidgetLayerManager(); 5985 if (layerManager) { 5986 displaySize.height += ViewAs<LayoutDevicePixel>( 5987 PresContext()->GetDynamicToolbarMaxHeight(), 5988 PixelCastJustification::LayoutDeviceIsScreenForBounds); 5989 } 5990 5991 return LayoutDeviceSize::ToAppUnits(displaySize, 5992 PresContext()->AppUnitsPerDevPixel()); 5993 } 5994 5995 void ScrollContainerFrame::UpdateMinimumScaleSize( 5996 const nsRect& aScrollableOverflow, const nsSize& aICBSize) { 5997 MOZ_ASSERT(mIsRoot); 5998 5999 AutoMinimumScaleSizeChangeDetector minimumScaleSizeChangeDetector(this); 6000 6001 mIsUsingMinimumScaleSize = false; 6002 6003 if (!PresShell()->UsesMobileViewportSizing()) { 6004 return; 6005 } 6006 6007 nsPresContext* pc = PresContext(); 6008 MOZ_ASSERT(pc->IsRootContentDocumentCrossProcess(), 6009 "The pres context should be for the root content document"); 6010 6011 RefPtr<MobileViewportManager> manager = 6012 PresShell()->GetMobileViewportManager(); 6013 MOZ_ASSERT(manager); 6014 6015 ScreenIntSize displaySize = ViewAs<ScreenPixel>( 6016 manager->DisplaySize(), 6017 PixelCastJustification::LayoutDeviceIsScreenForBounds); 6018 if (displaySize.width == 0 || displaySize.height == 0) { 6019 return; 6020 } 6021 if (aScrollableOverflow.IsEmpty()) { 6022 // Bail if the scrollable overflow rect is empty, as we're going to be 6023 // dividing by it. 6024 return; 6025 } 6026 6027 Document* doc = pc->Document(); 6028 MOZ_ASSERT(doc, "The document should be valid"); 6029 if (doc->GetFullscreenElement()) { 6030 // Don't use the minimum scale size in the case of fullscreen state. 6031 // FIXME: 1508177: We will no longer need this. 6032 return; 6033 } 6034 6035 nsViewportInfo viewportInfo = doc->GetViewportInfo(displaySize); 6036 if (!viewportInfo.IsZoomAllowed()) { 6037 // Don't apply the minimum scale size if user-scalable=no is specified. 6038 return; 6039 } 6040 6041 // The intrinsic minimum scale is the scale that fits the entire content 6042 // width into the visual viewport. 6043 CSSToScreenScale intrinsicMinScale( 6044 displaySize.width / CSSRect::FromAppUnits(aScrollableOverflow).XMost()); 6045 6046 // The scale used to compute the minimum-scale size is the larger of the 6047 // intrinsic minimum and the min-scale from the meta viewport tag. 6048 CSSToScreenScale minScale = 6049 std::max(intrinsicMinScale, viewportInfo.GetMinZoom()); 6050 6051 // The minimum-scale size is the size of the visual viewport when zoomed 6052 // to be the minimum scale. 6053 mMinimumScaleSize = CSSSize::ToAppUnits(ScreenSize(displaySize) / minScale); 6054 6055 // Ensure the minimum-scale size is never smaller than the ICB size. 6056 // That could happen if a page has a meta viewport tag with large explicitly 6057 // specified viewport dimensions (making the ICB large) and also a large 6058 // minimum scale (making the min-scale size small). 6059 mMinimumScaleSize = Max(aICBSize, mMinimumScaleSize); 6060 6061 mIsUsingMinimumScaleSize = true; 6062 } 6063 6064 bool ScrollContainerFrame::ReflowFinished() { 6065 mPostedReflowCallback = false; 6066 6067 TryScheduleScrollAnimations(); 6068 6069 if (mIsRoot) { 6070 if (mMinimumScaleSizeChanged && PresShell()->UsesMobileViewportSizing() && 6071 !PresShell()->IsResolutionUpdatedByApz()) { 6072 RefPtr<MobileViewportManager> manager = 6073 PresShell()->GetMobileViewportManager(); 6074 MOZ_ASSERT(manager); 6075 6076 manager->ShrinkToDisplaySizeIfNeeded(); 6077 mMinimumScaleSizeChanged = false; 6078 } 6079 6080 if (!UsesOverlayScrollbars()) { 6081 // Layout scrollbars may have added or removed during reflow, so let's 6082 // update the visual viewport accordingly. Note that this may be a no-op 6083 // because we might have recomputed the visual viewport size during the 6084 // reflow itself, just before laying out the fixed-pos items. But there 6085 // might be cases where that code doesn't run, so this is a sort of 6086 // backstop to ensure we do that recomputation. 6087 if (RefPtr<MobileViewportManager> manager = 6088 PresShell()->GetMobileViewportManager()) { 6089 manager->UpdateVisualViewportSizeForPotentialScrollbarChange(); 6090 } 6091 } 6092 6093 #if defined(MOZ_WIDGET_ANDROID) 6094 const bool hasVerticalOverflow = 6095 GetOverflowAxes().contains(PhysicalAxis::Vertical) && 6096 GetScrollStyles().mVertical != StyleOverflow::Hidden; 6097 if (!mFirstReflow && mHasVerticalOverflowForDynamicToolbar && 6098 !hasVerticalOverflow) { 6099 PresShell()->MaybeNotifyShowDynamicToolbar(); 6100 } 6101 mHasVerticalOverflowForDynamicToolbar = hasVerticalOverflow; 6102 #endif // defined(MOZ_WIDGET_ANDROID) 6103 } 6104 6105 bool doScroll = true; 6106 if (IsSubtreeDirty()) { 6107 // We will get another call after the next reflow and scrolling 6108 // later is less janky. 6109 doScroll = false; 6110 } 6111 6112 if (mFirstReflow) { 6113 nsPoint currentScrollPos = GetScrollPosition(); 6114 if (!mScrollUpdates.IsEmpty() && 6115 mScrollUpdates.LastElement().GetOrigin() == ScrollOrigin::None && 6116 currentScrollPos != nsPoint()) { 6117 // With frame reconstructions, the reconstructed frame may have a nonzero 6118 // scroll position by the end of the reflow, but without going through 6119 // RestoreState. In particular this can happen with RTL XUL scrollframes, 6120 // see https://bugzilla.mozilla.org/show_bug.cgi?id=1664638#c14. 6121 // Upon construction, the ScrollContainerFrame constructor will have 6122 // inserted a ScrollPositionUpdate into mScrollUpdates with origin None 6123 // and a zero scroll position, but here we update that to hold the correct 6124 // scroll position. Otherwise APZ may end up resetting the scroll position 6125 // to zero incorrectly. If we ever hit this codepath, it must be on a 6126 // reflow immediately following the scrollframe construction, so there 6127 // should be exactly one ScrollPositionUpdate in mScrollUpdates. 6128 MOZ_ASSERT(mScrollUpdates.Length() == 1); 6129 MOZ_ASSERT(mScrollUpdates.LastElement().GetGeneration() == 6130 mScrollGeneration); 6131 MOZ_ASSERT(mScrollUpdates.LastElement().GetDestination() == CSSPoint()); 6132 SCROLLRESTORE_LOG("%p: updating initial SPU to pos %s\n", this, 6133 ToString(currentScrollPos).c_str()); 6134 mScrollUpdates.Clear(); 6135 AppendScrollUpdate( 6136 ScrollPositionUpdate::NewScrollframe(currentScrollPos)); 6137 } 6138 6139 mFirstReflow = false; 6140 } 6141 6142 nsAutoScriptBlocker scriptBlocker; 6143 6144 if (mReclampVVOffsetInReflowFinished) { 6145 MOZ_ASSERT(mIsRoot && PresShell()->IsVisualViewportOffsetSet()); 6146 mReclampVVOffsetInReflowFinished = false; 6147 AutoWeakFrame weakFrame(this); 6148 PresShell()->SetVisualViewportOffset(PresShell()->GetVisualViewportOffset(), 6149 GetScrollPosition()); 6150 NS_ENSURE_TRUE(weakFrame.IsAlive(), false); 6151 } 6152 6153 if (doScroll) { 6154 ScrollToRestoredPosition(); 6155 6156 // Clamp current scroll position to new bounds. Normally this won't 6157 // do anything. 6158 nsPoint currentScrollPos = GetScrollPosition(); 6159 ScrollToImpl(currentScrollPos, nsRect(currentScrollPos, nsSize(0, 0)), 6160 ScrollOrigin::Clamp); 6161 if (ScrollAnimationState().isEmpty()) { 6162 // We need to have mDestination track the current scroll position, 6163 // in case it falls outside the new reflow area. mDestination is used 6164 // by ScrollBy as its starting position. 6165 mDestination = GetScrollPosition(); 6166 } 6167 } 6168 6169 if (!mUpdateScrollbarAttributes) { 6170 return false; 6171 } 6172 mUpdateScrollbarAttributes = false; 6173 6174 // Update scrollbar attributes. 6175 if (mMayHaveDirtyFixedChildren) { 6176 mMayHaveDirtyFixedChildren = false; 6177 nsIFrame* parentFrame = GetParent(); 6178 for (nsIFrame* fixedChild = 6179 parentFrame->GetChildList(FrameChildListID::Fixed).FirstChild(); 6180 fixedChild; fixedChild = fixedChild->GetNextSibling()) { 6181 // force a reflow of the fixed child 6182 PresShell()->FrameNeedsReflow(fixedChild, IntrinsicDirty::None, 6183 NS_FRAME_HAS_DIRTY_CHILDREN); 6184 } 6185 } 6186 6187 // Suppress handling of the curpos attribute changes we make here. 6188 NS_ASSERTION(!mFrameIsUpdatingScrollbar, "We shouldn't be reentering here"); 6189 mFrameIsUpdatingScrollbar = true; 6190 6191 // Note, in some cases this may get deleted while finishing reflow 6192 // for scrollbars. XXXmats is this still true now that we have a script 6193 // blocker in this scope? (if not, remove the weak frame checks below). 6194 if (mVScrollbarBox || mHScrollbarBox) { 6195 nsSize visualViewportSize = GetVisualViewportSize(); 6196 nsRect scrollRange = GetVisualScrollRange(); 6197 nsPoint scrollPos = GetScrollPosition(); 6198 nsSize lineScrollAmount = GetLineScrollAmount(); 6199 6200 if (gfxPlatform::UseDesktopZoomingScrollbars()) { 6201 scrollRange = GetScrollRangeForUserInputEvents(); 6202 scrollPos = GetVisualViewportOffset(); 6203 } 6204 6205 // If modifying the logic here, be sure to modify the corresponding 6206 // compositor-side calculation in ScrollThumbUtils::ApplyTransformForAxis(). 6207 AutoWeakFrame weakFrame(this); 6208 if (mVScrollbarBox) { 6209 const double kScrollMultiplier = 6210 StaticPrefs::toolkit_scrollbox_verticalScrollDistance(); 6211 nscoord increment = lineScrollAmount.height * kScrollMultiplier; 6212 // We normally use (visualViewportSize.height - increment) for height of 6213 // page scrolling. However, it is too small when increment is very large. 6214 // (If increment is larger than visualViewportSize.height, direction of 6215 // scrolling will be opposite). To avoid it, we use 6216 // (float(visualViewportSize.height) * 0.8) as lower bound value of height 6217 // of page scrolling. (bug 383267) 6218 // XXX shouldn't we use GetPageScrollAmount here? 6219 nscoord pageincrement = nscoord(visualViewportSize.height - increment); 6220 nscoord pageincrementMin = 6221 nscoord(float(visualViewportSize.height) * 0.8); 6222 FinishReflowForScrollbar(mVScrollbarBox, scrollRange.y, 6223 scrollRange.YMost(), scrollPos.y, 6224 std::max(pageincrement, pageincrementMin)); 6225 } 6226 if (mHScrollbarBox) { 6227 FinishReflowForScrollbar(mHScrollbarBox, scrollRange.x, 6228 scrollRange.XMost(), scrollPos.x, 6229 nscoord(float(visualViewportSize.width) * 0.8)); 6230 } 6231 NS_ENSURE_TRUE(weakFrame.IsAlive(), false); 6232 } 6233 6234 mFrameIsUpdatingScrollbar = false; 6235 // We used to rely on the curpos attribute changes above to scroll the 6236 // view. However, for scrolling to the left of the viewport, we 6237 // rescale the curpos attribute, which means that operations like 6238 // resizing the window while it is scrolled all the way to the left 6239 // hold the curpos attribute constant at 0 while still requiring 6240 // scrolling. So we suppress the effect of the changes above with 6241 // mFrameIsUpdatingScrollbar and call CurPosAttributeChanged here. 6242 // (It actually even works some of the time without this, thanks to 6243 // nsSliderFrame::AttributeChanged's handling of maxpos, but not when 6244 // we hide the scrollbar on a large size change, such as 6245 // maximization.) 6246 if (!mHScrollbarBox && !mVScrollbarBox) { 6247 return false; 6248 } 6249 ScrollbarCurPosChanged(doScroll); 6250 return doScroll; 6251 } 6252 6253 void ScrollContainerFrame::ReflowCallbackCanceled() { 6254 mPostedReflowCallback = false; 6255 } 6256 6257 bool ScrollContainerFrame::ComputeCustomOverflow( 6258 OverflowAreas& aOverflowAreas) { 6259 ScrollStyles ss = GetScrollStyles(); 6260 6261 // Reflow when the change in overflow leads to one of our scrollbars 6262 // changing or might require repositioning the scrolled content due to 6263 // reduced extents. 6264 nsRect scrolledRect = GetScrolledRect(); 6265 ScrollDirections overflowChange = 6266 GetOverflowChange(scrolledRect, mPrevScrolledRect); 6267 mPrevScrolledRect = scrolledRect; 6268 6269 bool needReflow = false; 6270 nsPoint scrollPosition = GetScrollPosition(); 6271 if (overflowChange.contains(ScrollDirection::eHorizontal)) { 6272 if (ss.mHorizontal != StyleOverflow::Hidden || scrollPosition.x || 6273 // If we are in minimum-scale size mode, we need to do a reflow to 6274 // re-compute the minimum-scale size. 6275 mIsUsingMinimumScaleSize) { 6276 needReflow = true; 6277 } 6278 } 6279 if (overflowChange.contains(ScrollDirection::eVertical)) { 6280 if (ss.mVertical != StyleOverflow::Hidden || scrollPosition.y) { 6281 needReflow = true; 6282 } 6283 } 6284 6285 if (needReflow) { 6286 // If there are scrollbars, or we're not at the beginning of the pane, 6287 // the scroll position may change. In this case, mark the frame as 6288 // needing reflow. Don't use NS_FRAME_IS_DIRTY as dirty as that means 6289 // we have to reflow the frame and all its descendants, and we don't 6290 // have to do that here. Only this frame needs to be reflowed. 6291 PresShell()->FrameNeedsReflow(this, IntrinsicDirty::None, 6292 NS_FRAME_HAS_DIRTY_CHILDREN); 6293 // Ensure that next time ScrollContainerFrame::Reflow runs, we don't skip 6294 // updating the scrollbars. (Because the overflow area of the scrolled 6295 // frame has probably just been updated, Reflow won't see it change.) 6296 mSkippedScrollbarLayout = true; 6297 return false; // reflowing will update overflow 6298 } 6299 PostOverflowEvent(); 6300 return nsContainerFrame::ComputeCustomOverflow(aOverflowAreas); 6301 } 6302 6303 void ScrollContainerFrame::UpdateSticky() { 6304 if (mStickyContainer) { 6305 mStickyContainer->UpdatePositions(GetScrollPosition(), this); 6306 } 6307 } 6308 6309 void ScrollContainerFrame::UpdatePrevScrolledRect() { 6310 // The layout scroll range is determinated by the scrolled rect and the scroll 6311 // port, so if the scrolled rect is updated, we may have to schedule the 6312 // associated scroll-driven animations' restyles. 6313 nsRect currScrolledRect = GetScrolledRect(); 6314 if (!currScrolledRect.IsEqualEdges(mPrevScrolledRect)) { 6315 mMayScheduleScrollAnimations = true; 6316 } 6317 mPrevScrolledRect = currScrolledRect; 6318 } 6319 6320 void ScrollContainerFrame::AdjustScrollbarRectForResizer( 6321 nsIFrame* aFrame, nsPresContext* aPresContext, nsRect& aRect, 6322 bool aHasResizer, ScrollDirection aDirection) { 6323 if ((aDirection == ScrollDirection::eVertical ? aRect.width : aRect.height) == 6324 0) { 6325 return; 6326 } 6327 6328 // if a content resizer is present, use its size. Otherwise, check if the 6329 // widget has a resizer. 6330 nsRect resizerRect; 6331 if (aHasResizer) { 6332 resizerRect = mResizerBox->GetRect(); 6333 } else { 6334 nsPoint offset; 6335 nsIWidget* widget = aFrame->GetNearestWidget(offset); 6336 LayoutDeviceIntRect widgetRect; 6337 if (!widget || !widget->ShowsResizeIndicator(&widgetRect)) { 6338 return; 6339 } 6340 6341 resizerRect = 6342 nsRect(aPresContext->DevPixelsToAppUnits(widgetRect.x) - offset.x, 6343 aPresContext->DevPixelsToAppUnits(widgetRect.y) - offset.y, 6344 aPresContext->DevPixelsToAppUnits(widgetRect.width), 6345 aPresContext->DevPixelsToAppUnits(widgetRect.height)); 6346 } 6347 6348 if (resizerRect.Contains(aRect.BottomRight() - nsPoint(1, 1))) { 6349 switch (aDirection) { 6350 case ScrollDirection::eVertical: 6351 aRect.height = std::max(0, resizerRect.y - aRect.y); 6352 break; 6353 case ScrollDirection::eHorizontal: 6354 aRect.width = std::max(0, resizerRect.x - aRect.x); 6355 break; 6356 } 6357 } else if (resizerRect.Contains(aRect.BottomLeft() + nsPoint(1, -1))) { 6358 switch (aDirection) { 6359 case ScrollDirection::eVertical: 6360 aRect.height = std::max(0, resizerRect.y - aRect.y); 6361 break; 6362 case ScrollDirection::eHorizontal: { 6363 nscoord xmost = aRect.XMost(); 6364 aRect.x = std::max(aRect.x, resizerRect.XMost()); 6365 aRect.width = xmost - aRect.x; 6366 break; 6367 } 6368 } 6369 } 6370 } 6371 6372 static void AdjustOverlappingScrollbars(nsRect& aVRect, nsRect& aHRect) { 6373 if (aVRect.IsEmpty() || aHRect.IsEmpty()) { 6374 return; 6375 } 6376 6377 const nsRect oldVRect = aVRect; 6378 const nsRect oldHRect = aHRect; 6379 if (oldVRect.Contains(oldHRect.BottomRight() - nsPoint(1, 1))) { 6380 aHRect.width = std::max(0, oldVRect.x - oldHRect.x); 6381 } else if (oldVRect.Contains(oldHRect.BottomLeft() - nsPoint(0, 1))) { 6382 nscoord overlap = std::min(oldHRect.width, oldVRect.XMost() - oldHRect.x); 6383 aHRect.x += overlap; 6384 aHRect.width -= overlap; 6385 } 6386 if (oldHRect.Contains(oldVRect.BottomRight() - nsPoint(1, 1))) { 6387 aVRect.height = std::max(0, oldHRect.y - oldVRect.y); 6388 } 6389 } 6390 6391 void ScrollContainerFrame::LayoutScrollbarPartAtRect( 6392 const ScrollReflowInput& aState, ReflowInput& aKidReflowInput, 6393 const nsRect& aRect) { 6394 nsPresContext* pc = PresContext(); 6395 nsIFrame* kid = aKidReflowInput.mFrame; 6396 const auto wm = kid->GetWritingMode(); 6397 ReflowOutput desiredSize(wm); 6398 MOZ_ASSERT(!wm.IsVertical(), 6399 "Scrollbar parts should have writing-mode: initial"); 6400 MOZ_ASSERT(!wm.IsInlineReversed(), 6401 "Scrollbar parts should have writing-mode: initial"); 6402 // XXX Maybe get a meaningful container size or something. Shouldn't matter 6403 // given our asserts above. 6404 const nsSize containerSize; 6405 aKidReflowInput.SetComputedISize(aRect.Width()); 6406 aKidReflowInput.SetComputedBSize(aRect.Height()); 6407 6408 const LogicalPoint pos(wm, aRect.TopLeft(), containerSize); 6409 const auto flags = ReflowChildFlags::Default; 6410 nsReflowStatus status; 6411 ReflowOutput kidDesiredSize(wm); 6412 ReflowChild(kid, pc, kidDesiredSize, aKidReflowInput, wm, pos, containerSize, 6413 flags, status); 6414 FinishReflowChild(kid, pc, kidDesiredSize, &aKidReflowInput, wm, pos, 6415 containerSize, flags); 6416 } 6417 6418 void ScrollContainerFrame::LayoutScrollbars(ScrollReflowInput& aState, 6419 const nsRect& aInsideBorderArea, 6420 const nsRect& aOldScrollPort) { 6421 const bool scrollbarOnLeft = !IsScrollbarOnRight(); 6422 const bool overlayScrollbars = UsesOverlayScrollbars(); 6423 const bool overlayScrollBarsOnRoot = overlayScrollbars && mIsRoot; 6424 const bool showVScrollbar = mVScrollbarBox && mHasVerticalScrollbar; 6425 const bool showHScrollbar = mHScrollbarBox && mHasHorizontalScrollbar; 6426 6427 nsSize compositionSize = mScrollPort.Size(); 6428 if (overlayScrollBarsOnRoot) { 6429 compositionSize = nsLayoutUtils::CalculateCompositionSizeForFrame( 6430 this, false, &compositionSize); 6431 } 6432 6433 nsPresContext* presContext = mScrolledFrame->PresContext(); 6434 nsRect vRect; 6435 if (showVScrollbar) { 6436 vRect.height = 6437 overlayScrollBarsOnRoot ? compositionSize.height : mScrollPort.height; 6438 vRect.y = mScrollPort.y; 6439 if (scrollbarOnLeft) { 6440 vRect.width = mScrollPort.x - aInsideBorderArea.x; 6441 vRect.x = aInsideBorderArea.x; 6442 } else { 6443 vRect.width = aInsideBorderArea.XMost() - mScrollPort.XMost(); 6444 vRect.x = mScrollPort.x + compositionSize.width; 6445 } 6446 if (overlayScrollbars || mOnlyNeedVScrollbarToScrollVVInsideLV) { 6447 const nscoord width = aState.VScrollbarPrefWidth(); 6448 // There is no space reserved for the layout scrollbar, it is currently 6449 // not visible because it is positioned just outside the scrollport. But 6450 // we know that it needs to be made visible so we shift it back in. 6451 vRect.width += width; 6452 if (!scrollbarOnLeft) { 6453 vRect.x -= width; 6454 } 6455 } 6456 } 6457 6458 nsRect hRect; 6459 if (showHScrollbar) { 6460 hRect.width = 6461 overlayScrollBarsOnRoot ? compositionSize.width : mScrollPort.width; 6462 hRect.x = mScrollPort.x; 6463 hRect.height = aInsideBorderArea.YMost() - mScrollPort.YMost(); 6464 hRect.y = mScrollPort.y + compositionSize.height; 6465 6466 if (overlayScrollbars || mOnlyNeedHScrollbarToScrollVVInsideLV) { 6467 const nscoord height = aState.HScrollbarPrefHeight(); 6468 hRect.height += height; 6469 // There is no space reserved for the layout scrollbar, it is currently 6470 // not visible because it is positioned just outside the scrollport. But 6471 // we know that it needs to be made visible so we shift it back in. 6472 hRect.y -= height; 6473 } 6474 } 6475 6476 const bool hasVisualOnlyScrollbarsOnBothDirections = 6477 !overlayScrollbars && showHScrollbar && 6478 mOnlyNeedHScrollbarToScrollVVInsideLV && showVScrollbar && 6479 mOnlyNeedVScrollbarToScrollVVInsideLV; 6480 nsPresContext* pc = PresContext(); 6481 6482 // place the scrollcorner 6483 if (mScrollCornerBox) { 6484 nsRect r(0, 0, 0, 0); 6485 if (scrollbarOnLeft) { 6486 // scrollbar (if any) on left 6487 r.width = showVScrollbar ? mScrollPort.x - aInsideBorderArea.x : 0; 6488 r.x = aInsideBorderArea.x; 6489 } else { 6490 // scrollbar (if any) on right 6491 r.width = 6492 showVScrollbar ? aInsideBorderArea.XMost() - mScrollPort.XMost() : 0; 6493 r.x = aInsideBorderArea.XMost() - r.width; 6494 } 6495 NS_ASSERTION(r.width >= 0, "Scroll area should be inside client rect"); 6496 6497 if (showHScrollbar) { 6498 // scrollbar (if any) on bottom 6499 // Note we don't support the horizontal scrollbar at the top side. 6500 r.height = aInsideBorderArea.YMost() - mScrollPort.YMost(); 6501 NS_ASSERTION(r.height >= 0, "Scroll area should be inside client rect"); 6502 } 6503 r.y = aInsideBorderArea.YMost() - r.height; 6504 6505 // If we have layout scrollbars and both scrollbars are present and both are 6506 // only needed to scroll the VV inside the LV then we need a scrollcorner 6507 // but the above calculation will result in an empty rect, so adjust it. 6508 if (r.IsEmpty() && hasVisualOnlyScrollbarsOnBothDirections) { 6509 r.width = vRect.width; 6510 r.height = hRect.height; 6511 r.x = scrollbarOnLeft ? mScrollPort.x : mScrollPort.XMost() - r.width; 6512 r.y = mScrollPort.YMost() - r.height; 6513 } 6514 6515 ReflowInput scrollCornerRI( 6516 pc, aState.mReflowInput, mScrollCornerBox, 6517 LogicalSize(mScrollCornerBox->GetWritingMode(), r.Size())); 6518 LayoutScrollbarPartAtRect(aState, scrollCornerRI, r); 6519 } 6520 6521 if (mResizerBox) { 6522 // If a resizer is present, get its size. 6523 // 6524 // TODO(emilio): Should this really account for scrollbar-width? 6525 auto scrollbarWidth = nsLayoutUtils::StyleForScrollbar(this) 6526 ->StyleUIReset() 6527 ->ScrollbarWidth(); 6528 const nscoord scrollbarSize = 6529 GetNonOverlayScrollbarSize(pc, scrollbarWidth); 6530 ReflowInput resizerRI(pc, aState.mReflowInput, mResizerBox, 6531 LogicalSize(mResizerBox->GetWritingMode())); 6532 nsSize resizerMinSize = {resizerRI.ComputedMinWidth(), 6533 resizerRI.ComputedMinHeight()}; 6534 6535 nsRect r; 6536 r.width = std::max(std::max(r.width, scrollbarSize), resizerMinSize.width); 6537 r.x = scrollbarOnLeft ? aInsideBorderArea.x 6538 : aInsideBorderArea.XMost() - r.width; 6539 r.height = 6540 std::max(std::max(r.height, scrollbarSize), resizerMinSize.height); 6541 r.y = aInsideBorderArea.YMost() - r.height; 6542 6543 LayoutScrollbarPartAtRect(aState, resizerRI, r); 6544 } 6545 6546 // Note that AdjustScrollbarRectForResizer has to be called after the 6547 // resizer has been laid out immediately above this because it gets the rect 6548 // of the resizer frame. 6549 if (mVScrollbarBox) { 6550 AdjustScrollbarRectForResizer(this, presContext, vRect, mResizerBox, 6551 ScrollDirection::eVertical); 6552 } 6553 if (mHScrollbarBox) { 6554 AdjustScrollbarRectForResizer(this, presContext, hRect, mResizerBox, 6555 ScrollDirection::eHorizontal); 6556 } 6557 6558 // Layout scrollbars can overlap at this point if they are both present and 6559 // both only needed to scroll the VV inside the LV. 6560 if (!LookAndFeel::GetInt(LookAndFeel::IntID::AllowOverlayScrollbarsOverlap) || 6561 hasVisualOnlyScrollbarsOnBothDirections) { 6562 AdjustOverlappingScrollbars(vRect, hRect); 6563 } 6564 if (mVScrollbarBox) { 6565 ReflowInput vScrollbarRI( 6566 pc, aState.mReflowInput, mVScrollbarBox, 6567 LogicalSize(mVScrollbarBox->GetWritingMode(), vRect.Size())); 6568 LayoutScrollbarPartAtRect(aState, vScrollbarRI, vRect); 6569 } 6570 if (mHScrollbarBox) { 6571 ReflowInput hScrollbarRI( 6572 pc, aState.mReflowInput, mHScrollbarBox, 6573 LogicalSize(mHScrollbarBox->GetWritingMode(), hRect.Size())); 6574 LayoutScrollbarPartAtRect(aState, hScrollbarRI, hRect); 6575 } 6576 6577 // may need to update fixed position children of the viewport, 6578 // if the client area changed size because of an incremental 6579 // reflow of a descendant. (If the outer frame is dirty, the fixed 6580 // children will be re-laid out anyway) 6581 if (aOldScrollPort.Size() != mScrollPort.Size() && 6582 !HasAnyStateBits(NS_FRAME_IS_DIRTY) && mIsRoot) { 6583 mMayHaveDirtyFixedChildren = true; 6584 } 6585 6586 // post reflow callback to modify scrollbar attributes 6587 mUpdateScrollbarAttributes = true; 6588 if (!mPostedReflowCallback) { 6589 PresShell()->PostReflowCallback(this); 6590 mPostedReflowCallback = true; 6591 } 6592 } 6593 6594 static void ReduceRadii(nscoord aXBorder, nscoord aYBorder, nsSize& aRadius) { 6595 // In order to ensure that the inside edge of the border has no 6596 // curvature, we need at least one of its radii to be zero. 6597 if (aRadius.width <= aXBorder || aRadius.height <= aYBorder) { 6598 return; 6599 } 6600 6601 // For any corner where we reduce the radii, preserve the corner's shape. 6602 double ratio = std::max(double(aXBorder) / aRadius.width, 6603 double(aYBorder) / aRadius.height); 6604 aRadius.width *= ratio; 6605 aRadius.height *= ratio; 6606 } 6607 6608 /** 6609 * Implement an override for nsIFrame::GetBorderRadii to ensure that 6610 * the clipping region for the border radius does not clip the scrollbars. 6611 * 6612 * In other words, we require that the border radius be reduced until the 6613 * inner border radius at the inner edge of the border is 0 wherever we 6614 * have scrollbars. 6615 */ 6616 bool ScrollContainerFrame::GetBorderRadii(const nsSize& aFrameSize, 6617 const nsSize& aBorderArea, 6618 Sides aSkipSides, 6619 nsRectCornerRadii& aRadii) const { 6620 if (!nsContainerFrame::GetBorderRadii(aFrameSize, aBorderArea, aSkipSides, 6621 aRadii)) { 6622 return false; 6623 } 6624 6625 // Since we can use GetActualScrollbarSizes (rather than 6626 // GetDesiredScrollbarSizes) since this doesn't affect reflow, we 6627 // probably should. 6628 nsMargin sb = GetActualScrollbarSizes(); 6629 nsMargin border = GetUsedBorder(); 6630 6631 if (sb.left > 0 || sb.top > 0) { 6632 ReduceRadii(border.left, border.top, aRadii.TopLeft()); 6633 } 6634 if (sb.top > 0 || sb.right > 0) { 6635 ReduceRadii(border.right, border.top, aRadii.TopRight()); 6636 } 6637 if (sb.right > 0 || sb.bottom > 0) { 6638 ReduceRadii(border.right, border.bottom, aRadii.BottomRight()); 6639 } 6640 if (sb.bottom > 0 || sb.left > 0) { 6641 ReduceRadii(border.left, border.bottom, aRadii.BottomLeft()); 6642 } 6643 return true; 6644 } 6645 6646 static nscoord SnapCoord(nscoord aCoord, double aRes, 6647 nscoord aAppUnitsPerPixel) { 6648 if (StaticPrefs::layout_disable_pixel_alignment()) { 6649 return aCoord; 6650 } 6651 double snappedToLayerPixels = NS_round((aRes * aCoord) / aAppUnitsPerPixel); 6652 return NSToCoordRoundWithClamp(snappedToLayerPixels * aAppUnitsPerPixel / 6653 aRes); 6654 } 6655 6656 nsRect ScrollContainerFrame::GetScrolledRect() const { 6657 nsRect result = GetUnsnappedScrolledRectInternal( 6658 mScrolledFrame->ScrollableOverflowRect(), mScrollPort.Size()); 6659 6660 #if 0 6661 // This happens often enough. 6662 if (result.width < mScrollPort.width || result.height < mScrollPort.height) { 6663 NS_WARNING("Scrolled rect smaller than scrollport?"); 6664 } 6665 #endif 6666 6667 // Expand / contract the result by up to half a layer pixel so that scrolling 6668 // to the right / bottom edge does not change the layer pixel alignment of 6669 // the scrolled contents. 6670 6671 if (result.x == 0 && result.y == 0 && result.width == mScrollPort.width && 6672 result.height == mScrollPort.height) { 6673 // The edges that we would snap are already aligned with the scroll port, 6674 // so we can skip all the work below. 6675 return result; 6676 } 6677 6678 // For that, we first convert the scroll port and the scrolled rect to rects 6679 // relative to the reference frame, since that's the space where painting does 6680 // snapping. 6681 nsSize visualViewportSize = GetVisualViewportSize(); 6682 const nsIFrame* referenceFrame = 6683 mReferenceFrameDuringPainting 6684 ? mReferenceFrameDuringPainting 6685 : nsLayoutUtils::GetReferenceFrame( 6686 const_cast<ScrollContainerFrame*>(this)); 6687 nsPoint toReferenceFrame = GetOffsetToCrossDoc(referenceFrame); 6688 nsRect scrollPort(mScrollPort.TopLeft() + toReferenceFrame, 6689 visualViewportSize); 6690 nsRect scrolledRect = result + scrollPort.TopLeft(); 6691 6692 if (scrollPort.Overflows() || scrolledRect.Overflows()) { 6693 return result; 6694 } 6695 6696 // Now, snap the bottom right corner of both of these rects. 6697 // We snap to layer pixels, so we need to respect the layer's scale. 6698 nscoord appUnitsPerDevPixel = 6699 mScrolledFrame->PresContext()->AppUnitsPerDevPixel(); 6700 MatrixScales scale = GetPaintedLayerScaleForFrame( 6701 mScrolledFrame, /* aIncludeCSSTransform = */ false); 6702 if (scale.xScale == 0 || scale.yScale == 0) { 6703 scale = MatrixScales(); 6704 } 6705 6706 // Compute bounds for the scroll position, and computed the snapped scrolled 6707 // rect from the scroll position bounds. 6708 nscoord snappedScrolledAreaBottom = 6709 SnapCoord(scrolledRect.YMost(), scale.yScale, appUnitsPerDevPixel); 6710 nscoord snappedScrollPortBottom = 6711 SnapCoord(scrollPort.YMost(), scale.yScale, appUnitsPerDevPixel); 6712 nscoord maximumScrollOffsetY = 6713 snappedScrolledAreaBottom - snappedScrollPortBottom; 6714 result.SetBottomEdge(scrollPort.height + maximumScrollOffsetY); 6715 6716 if (GetScrolledFrameDir() == StyleDirection::Ltr) { 6717 nscoord snappedScrolledAreaRight = 6718 SnapCoord(scrolledRect.XMost(), scale.xScale, appUnitsPerDevPixel); 6719 nscoord snappedScrollPortRight = 6720 SnapCoord(scrollPort.XMost(), scale.xScale, appUnitsPerDevPixel); 6721 nscoord maximumScrollOffsetX = 6722 snappedScrolledAreaRight - snappedScrollPortRight; 6723 result.SetRightEdge(scrollPort.width + maximumScrollOffsetX); 6724 } else { 6725 // In RTL, the scrolled area's right edge is at scrollPort.XMost(), 6726 // and the scrolled area's x position is zero or negative. We want 6727 // the right edge to stay flush with the scroll port, so we snap the 6728 // left edge. 6729 nscoord snappedScrolledAreaLeft = 6730 SnapCoord(scrolledRect.x, scale.xScale, appUnitsPerDevPixel); 6731 nscoord snappedScrollPortLeft = 6732 SnapCoord(scrollPort.x, scale.xScale, appUnitsPerDevPixel); 6733 nscoord minimumScrollOffsetX = 6734 snappedScrolledAreaLeft - snappedScrollPortLeft; 6735 result.SetLeftEdge(minimumScrollOffsetX); 6736 } 6737 6738 return result; 6739 } 6740 6741 nsRect ScrollContainerFrame::GetScrollPortRectAccountingForMaxDynamicToolbar() 6742 const { 6743 auto rect = mScrollPort; 6744 if (mIsRoot && PresContext()->HasDynamicToolbar()) { 6745 rect.SizeTo(nsLayoutUtils::ExpandHeightForDynamicToolbar(PresContext(), 6746 rect.Size())); 6747 } 6748 return rect; 6749 } 6750 6751 StyleDirection ScrollContainerFrame::GetScrolledFrameDir() const { 6752 return GetScrolledFrameDir(mScrolledFrame); 6753 } 6754 6755 StyleDirection ScrollContainerFrame::GetScrolledFrameDir( 6756 const nsIFrame* aScrolledFrame) { 6757 // If the scrolled frame has unicode-bidi: plaintext, the paragraph 6758 // direction set by the text content overrides the direction of the frame 6759 if (aScrolledFrame->StyleTextReset()->mUnicodeBidi == 6760 StyleUnicodeBidi::Plaintext) { 6761 if (nsIFrame* child = aScrolledFrame->PrincipalChildList().FirstChild()) { 6762 return nsBidiPresUtils::ParagraphDirection(child) == 6763 intl::BidiDirection::LTR 6764 ? StyleDirection::Ltr 6765 : StyleDirection::Rtl; 6766 } 6767 } 6768 return aScrolledFrame->GetWritingMode().IsBidiLTR() ? StyleDirection::Ltr 6769 : StyleDirection::Rtl; 6770 } 6771 6772 auto ScrollContainerFrame::ComputePerAxisScrollDirections( 6773 const nsIFrame* aScrolledFrame) -> PerAxisScrollDirections { 6774 auto wm = aScrolledFrame->GetWritingMode(); 6775 auto dir = GetScrolledFrameDir(aScrolledFrame); 6776 wm.SetDirectionFromBidiLevel(dir == StyleDirection::Rtl 6777 ? intl::BidiEmbeddingLevel::RTL() 6778 : intl::BidiEmbeddingLevel::LTR()); 6779 bool scrollToRight = wm.IsPhysicalLTR(); 6780 bool scrollToBottom = 6781 !wm.IsVertical() || wm.GetInlineDir() == WritingMode::InlineDir::TTB; 6782 if (aScrolledFrame->IsFlexContainerFrame()) { 6783 // In a flex container, the children flow (and overflow) along the flex 6784 // container's main axis and cross axis. These are analogous to the 6785 // inline/block axes, and by default they correspond exactly to those axes; 6786 // but the flex container's CSS (e.g. flex-direction: column-reverse) may 6787 // have swapped and/or reversed them, and we need to account for that here. 6788 const FlexboxAxisInfo info(aScrolledFrame); 6789 const bool isMainAxisVertical = info.mIsRowOriented == wm.IsVertical(); 6790 if (info.mIsMainAxisReversed) { 6791 if (isMainAxisVertical) { 6792 scrollToBottom = !scrollToBottom; 6793 } else { 6794 scrollToRight = !scrollToRight; 6795 } 6796 } 6797 if (info.mIsCrossAxisReversed) { 6798 if (isMainAxisVertical) { 6799 scrollToRight = !scrollToRight; 6800 } else { 6801 scrollToBottom = !scrollToBottom; 6802 } 6803 } 6804 } 6805 return {scrollToRight, scrollToBottom}; 6806 } 6807 6808 nsRect ScrollContainerFrame::GetUnsnappedScrolledRectInternal( 6809 const nsRect& aScrolledOverflowArea, const nsSize& aScrollPortSize) const { 6810 nscoord x1 = aScrolledOverflowArea.x, x2 = aScrolledOverflowArea.XMost(), 6811 y1 = aScrolledOverflowArea.y, y2 = aScrolledOverflowArea.YMost(); 6812 auto dirs = ComputePerAxisScrollDirections(mScrolledFrame); 6813 // Clamp the horizontal start-edge (x1 or x2, depending whether the logical 6814 // axis that corresponds to horizontal progresses from left-to-right or 6815 // right-to-left). 6816 if (dirs.mToRight) { 6817 if (x1 < 0) { 6818 x1 = 0; 6819 } 6820 } else { 6821 if (x2 > aScrollPortSize.width) { 6822 x2 = aScrollPortSize.width; 6823 } 6824 // When the scrolled frame chooses a size larger than its available width 6825 // (because its padding alone is larger than the available width), we need 6826 // to keep the start-edge of the scroll frame anchored to the start-edge of 6827 // the scrollport. 6828 // When the scrolled frame is RTL, this means moving it in our left-based 6829 // coordinate system, so we need to compensate for its extra width here by 6830 // effectively repositioning the frame. 6831 nscoord extraWidth = 6832 std::max(0, mScrolledFrame->GetSize().width - aScrollPortSize.width); 6833 x2 += extraWidth; 6834 } 6835 6836 // Similarly, clamp the vertical start-edge (y1 or y2, depending whether the 6837 // logical axis that corresponds to vertical progresses from top-to-bottom or 6838 // buttom-to-top). 6839 if (dirs.mToBottom) { 6840 if (y1 < 0) { 6841 y1 = 0; 6842 } 6843 } else { 6844 if (y2 > aScrollPortSize.height) { 6845 y2 = aScrollPortSize.height; 6846 } 6847 nscoord extraHeight = 6848 std::max(0, mScrolledFrame->GetSize().height - aScrollPortSize.height); 6849 y2 += extraHeight; 6850 } 6851 6852 return nsRect(x1, y1, x2 - x1, y2 - y1); 6853 } 6854 6855 nsMargin ScrollContainerFrame::GetActualScrollbarSizes( 6856 ScrollbarSizesOptions aOptions /* = ScrollbarSizesOptions::NONE */) const { 6857 nsRect r = GetPaddingRectRelativeToSelf(); 6858 6859 nsMargin m(mScrollPort.y - r.y, r.XMost() - mScrollPort.XMost(), 6860 r.YMost() - mScrollPort.YMost(), mScrollPort.x - r.x); 6861 6862 if (aOptions == ScrollbarSizesOptions::INCLUDE_VISUAL_VIEWPORT_SCROLLBARS && 6863 !UsesOverlayScrollbars()) { 6864 // If we are using layout scrollbars and they only exist to scroll the 6865 // visual viewport then they do not take up any layout space (so the 6866 // scrollport is the same as the padding rect) but they do cover everything 6867 // below them so some callers may want to include this special type of 6868 // scrollbars in the returned value. 6869 if (mHScrollbarBox && mHasHorizontalScrollbar && 6870 mOnlyNeedHScrollbarToScrollVVInsideLV) { 6871 m.bottom += mHScrollbarBox->GetRect().height; 6872 } 6873 if (mVScrollbarBox && mHasVerticalScrollbar && 6874 mOnlyNeedVScrollbarToScrollVVInsideLV) { 6875 if (IsScrollbarOnRight()) { 6876 m.right += mVScrollbarBox->GetRect().width; 6877 } else { 6878 m.left += mVScrollbarBox->GetRect().width; 6879 } 6880 } 6881 } 6882 6883 return m; 6884 } 6885 6886 void ScrollContainerFrame::SetScrollbarVisibility(nsIFrame* aScrollbar, 6887 bool aVisible) { 6888 nsScrollbarFrame* scrollbar = do_QueryFrame(aScrollbar); 6889 if (scrollbar) { 6890 // See if we have a mediator. 6891 nsIScrollbarMediator* mediator = scrollbar->GetScrollbarMediator(); 6892 if (mediator) { 6893 // Inform the mediator of the visibility change. 6894 mediator->VisibilityChanged(aVisible); 6895 } 6896 } 6897 } 6898 6899 bool ScrollContainerFrame::IsLastScrollUpdateAnimating() const { 6900 if (!mScrollUpdates.IsEmpty()) { 6901 switch (mScrollUpdates.LastElement().GetMode()) { 6902 case ScrollMode::Smooth: 6903 case ScrollMode::SmoothMsd: 6904 return true; 6905 case ScrollMode::Instant: 6906 case ScrollMode::Normal: 6907 break; 6908 } 6909 } 6910 return false; 6911 } 6912 6913 bool ScrollContainerFrame::IsLastScrollUpdateTriggeredByScriptAnimating() 6914 const { 6915 if (!mScrollUpdates.IsEmpty()) { 6916 const ScrollPositionUpdate& lastUpdate = mScrollUpdates.LastElement(); 6917 if (lastUpdate.WasTriggeredByScript() && 6918 (mScrollUpdates.LastElement().GetMode() == ScrollMode::Smooth || 6919 mScrollUpdates.LastElement().GetMode() == ScrollMode::SmoothMsd)) { 6920 return true; 6921 } 6922 } 6923 return false; 6924 } 6925 6926 EnumSet<ScrollContainerFrame::AnimationState> 6927 ScrollContainerFrame::ScrollAnimationState() const { 6928 EnumSet<AnimationState> retval; 6929 if (IsApzAnimationInProgress()) { 6930 retval += AnimationState::APZInProgress; 6931 if (mCurrentAPZScrollAnimationType == 6932 APZScrollAnimationType::TriggeredByScript) { 6933 retval += AnimationState::TriggeredByScript; 6934 } 6935 } 6936 6937 if (mApzAnimationRequested) { 6938 retval += AnimationState::APZRequested; 6939 if (mApzAnimationTriggeredByScriptRequested) { 6940 retval += AnimationState::TriggeredByScript; 6941 } 6942 } 6943 6944 if (IsLastScrollUpdateAnimating()) { 6945 retval += AnimationState::APZPending; 6946 if (IsLastScrollUpdateTriggeredByScriptAnimating()) { 6947 retval += AnimationState::TriggeredByScript; 6948 } 6949 } 6950 if (mAsyncScroll) { 6951 retval += AnimationState::MainThread; 6952 if (mAsyncScroll->WasTriggeredByScript()) { 6953 retval += AnimationState::TriggeredByScript; 6954 } 6955 } 6956 6957 if (mAsyncSmoothMSDScroll) { 6958 retval += AnimationState::MainThread; 6959 if (mAsyncSmoothMSDScroll->WasTriggeredByScript()) { 6960 retval += AnimationState::TriggeredByScript; 6961 } 6962 } 6963 return retval; 6964 } 6965 6966 void ScrollContainerFrame::ResetScrollInfoIfNeeded( 6967 const MainThreadScrollGeneration& aGeneration, 6968 const APZScrollGeneration& aGenerationOnApz, 6969 APZScrollAnimationType aAPZScrollAnimationType, 6970 InScrollingGesture aInScrollingGesture) { 6971 if (aGeneration == mScrollGeneration) { 6972 mLastScrollOrigin = ScrollOrigin::None; 6973 mApzAnimationRequested = false; 6974 mApzAnimationTriggeredByScriptRequested = false; 6975 } 6976 6977 mScrollGenerationOnApz = aGenerationOnApz; 6978 // We can reset this regardless of scroll generation, as this is only set 6979 // here, as a response to APZ requesting a repaint. 6980 mCurrentAPZScrollAnimationType = aAPZScrollAnimationType; 6981 6982 mInScrollingGesture = aInScrollingGesture; 6983 } 6984 6985 UniquePtr<PresState> ScrollContainerFrame::SaveState() { 6986 nsIScrollbarMediator* mediator = do_QueryFrame(GetScrolledFrame()); 6987 if (mediator) { 6988 // child handles its own scroll state, so don't bother saving state here 6989 return nullptr; 6990 } 6991 6992 // Don't store a scroll state if we never have been scrolled or restored 6993 // a previous scroll state, and we're not in the middle of a smooth scroll. 6994 auto scrollAnimationState = ScrollAnimationState(); 6995 bool isScrollAnimating = 6996 scrollAnimationState.contains(AnimationState::MainThread) || 6997 scrollAnimationState.contains(AnimationState::APZPending) || 6998 scrollAnimationState.contains(AnimationState::APZRequested); 6999 if (!mHasBeenScrolled && !mDidHistoryRestore && !isScrollAnimating) { 7000 return nullptr; 7001 } 7002 7003 UniquePtr<PresState> state = NewPresState(); 7004 bool allowScrollOriginDowngrade = 7005 !nsLayoutUtils::CanScrollOriginClobberApz(mLastScrollOrigin) || 7006 mAllowScrollOriginDowngrade; 7007 // Save mRestorePos instead of our actual current scroll position, if it's 7008 // valid and we haven't moved since the last update of mLastPos (same check 7009 // that ScrollToRestoredPosition uses). This ensures if a reframe occurs 7010 // while we're in the process of loading content to scroll to a restored 7011 // position, we'll keep trying after the reframe. Similarly, if we're in the 7012 // middle of a smooth scroll, store the destination so that when we restore 7013 // we'll jump straight to the end of the scroll animation, rather than 7014 // effectively dropping it. Note that the mRestorePos will override the 7015 // smooth scroll destination if both are present. 7016 nsPoint pt = GetLogicalVisualViewportOffset(); 7017 if (isScrollAnimating) { 7018 pt = mDestination; 7019 allowScrollOriginDowngrade = false; 7020 } 7021 SCROLLRESTORE_LOG("%p: SaveState, pt=%s, mLastPos=%s, mRestorePos=%s\n", this, 7022 ToString(pt).c_str(), ToString(mLastPos).c_str(), 7023 ToString(mRestorePos).c_str()); 7024 if (mRestorePos.y != -1 && pt == mLastPos) { 7025 pt = mRestorePos; 7026 } 7027 state->scrollState() = pt; 7028 state->allowScrollOriginDowngrade() = allowScrollOriginDowngrade; 7029 if (mIsRoot) { 7030 // Only save resolution properties for root scroll frames 7031 state->resolution() = PresShell()->GetResolution(); 7032 } 7033 return state; 7034 } 7035 7036 NS_IMETHODIMP ScrollContainerFrame::RestoreState(PresState* aState) { 7037 mRestorePos = aState->scrollState(); 7038 MOZ_ASSERT(mLastScrollOrigin == ScrollOrigin::None); 7039 mAllowScrollOriginDowngrade = aState->allowScrollOriginDowngrade(); 7040 // When restoring state, we promote mLastScrollOrigin to a stronger value 7041 // from the default of eNone, to restore the behaviour that existed when 7042 // the state was saved. If mLastScrollOrigin was a weaker value previously, 7043 // then mAllowScrollOriginDowngrade will be true, and so the combination of 7044 // mAllowScrollOriginDowngrade and the stronger mLastScrollOrigin will allow 7045 // the same types of scrolls as before. It might be possible to also just 7046 // save and restore the mAllowScrollOriginDowngrade and mLastScrollOrigin 7047 // values directly without this sort of fiddling. Something to try in the 7048 // future or if we tinker with this code more. 7049 mLastScrollOrigin = ScrollOrigin::Other; 7050 mDidHistoryRestore = true; 7051 mLastPos = mScrolledFrame ? GetLogicalVisualViewportOffset() : nsPoint(0, 0); 7052 SCROLLRESTORE_LOG("%p: RestoreState, set mRestorePos=%s mLastPos=%s\n", this, 7053 ToString(mRestorePos).c_str(), ToString(mLastPos).c_str()); 7054 7055 // Resolution properties should only exist on root scroll frames. 7056 MOZ_ASSERT(mIsRoot || aState->resolution() == 1.0); 7057 7058 if (mIsRoot) { 7059 PresShell()->SetResolutionAndScaleTo( 7060 aState->resolution(), ResolutionChangeOrigin::MainThreadRestore); 7061 } 7062 return NS_OK; 7063 } 7064 7065 void ScrollContainerFrame::PostScrolledAreaEvent() { 7066 if (mScrolledAreaEvent.IsPending()) { 7067 return; 7068 } 7069 mScrolledAreaEvent = new ScrolledAreaEvent(this); 7070 nsContentUtils::AddScriptRunner(mScrolledAreaEvent.get()); 7071 } 7072 7073 //////////////////////////////////////////////////////////////////////////////// 7074 // ScrolledArea change event dispatch 7075 7076 // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230, bug 1535398) 7077 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP 7078 ScrollContainerFrame::ScrolledAreaEvent::Run() { 7079 if (mHelper) { 7080 mHelper->FireScrolledAreaEvent(); 7081 } 7082 return NS_OK; 7083 } 7084 7085 void ScrollContainerFrame::FireScrolledAreaEvent() { 7086 mScrolledAreaEvent.Forget(); 7087 7088 InternalScrollAreaEvent event(true, eScrolledAreaChanged, nullptr); 7089 RefPtr<nsPresContext> presContext = PresContext(); 7090 nsIContent* content = GetContent(); 7091 7092 event.mArea = mScrolledFrame->ScrollableOverflowRectRelativeToParent(); 7093 if (RefPtr<Document> doc = content->GetUncomposedDoc()) { 7094 EventDispatcher::Dispatch(doc, presContext, &event, nullptr); 7095 } 7096 } 7097 7098 ScrollDirections ScrollContainerFrame::GetAvailableScrollingDirections() const { 7099 nscoord oneDevPixel = 7100 GetScrolledFrame()->PresContext()->AppUnitsPerDevPixel(); 7101 ScrollDirections directions; 7102 nsRect scrollRange = GetScrollRange(); 7103 if (scrollRange.width >= oneDevPixel) { 7104 directions += ScrollDirection::eHorizontal; 7105 } 7106 if (scrollRange.height >= oneDevPixel) { 7107 directions += ScrollDirection::eVertical; 7108 } 7109 return directions; 7110 } 7111 7112 nsRect ScrollContainerFrame::GetScrollRangeForUserInputEvents() const { 7113 // This function computes a scroll range based on a scrolled rect and scroll 7114 // port defined as follows: 7115 // scrollable rect = overflow:hidden ? layout viewport : scrollable rect 7116 // scroll port = have visual viewport ? visual viewport : layout viewport 7117 // The results in the same notion of scroll range that APZ uses (the combined 7118 // effect of FrameMetrics::CalculateScrollRange() and 7119 // nsLayoutUtils::CalculateScrollableRectForFrame). 7120 7121 ScrollStyles ss = GetScrollStyles(); 7122 7123 nsPoint scrollPos = GetScrollPosition(); 7124 7125 nsRect scrolledRect = GetScrolledRect(); 7126 if (StyleOverflow::Hidden == ss.mHorizontal) { 7127 scrolledRect.width = mScrollPort.width; 7128 scrolledRect.x = scrollPos.x; 7129 } 7130 if (StyleOverflow::Hidden == ss.mVertical) { 7131 scrolledRect.height = mScrollPort.height; 7132 scrolledRect.y = scrollPos.y; 7133 } 7134 7135 nsSize scrollPort = GetVisualViewportSize(); 7136 7137 nsRect scrollRange = scrolledRect; 7138 scrollRange.width = std::max(scrolledRect.width - scrollPort.width, 0); 7139 scrollRange.height = std::max(scrolledRect.height - scrollPort.height, 0); 7140 7141 return scrollRange; 7142 } 7143 7144 ScrollDirections 7145 ScrollContainerFrame::GetAvailableScrollingDirectionsForUserInputEvents() 7146 const { 7147 nsRect scrollRange = GetScrollRangeForUserInputEvents(); 7148 7149 // We check if there is at least one half of a screen pixel of scroll range to 7150 // roughly match what apz does when it checks if the change in scroll position 7151 // in screen pixels round to zero or not. 7152 // (https://searchfox.org/mozilla-central/rev/2f09184ec781a2667feec87499d4b81b32b6c48e/gfx/layers/apz/src/AsyncPanZoomController.cpp#3210) 7153 // This isn't quite half a screen pixel, it doesn't take into account CSS 7154 // transforms, but should be good enough. 7155 float halfScreenPixel = 7156 GetScrolledFrame()->PresContext()->AppUnitsPerDevPixel() / 7157 (PresShell()->GetCumulativeResolution() * 2.f); 7158 ScrollDirections directions; 7159 if (scrollRange.width >= halfScreenPixel) { 7160 directions += ScrollDirection::eHorizontal; 7161 } 7162 if (scrollRange.height >= halfScreenPixel) { 7163 directions += ScrollDirection::eVertical; 7164 } 7165 return directions; 7166 } 7167 7168 /** 7169 * Append scroll positions for valid snap positions into |aSnapInfo| if 7170 * applicable. 7171 */ 7172 static void AppendScrollPositionsForSnap( 7173 const nsIFrame* aFrame, const nsIFrame* aScrolledFrame, 7174 const nsRect& aScrolledRect, const nsMargin& aScrollPadding, 7175 const nsRect& aScrollRange, WritingMode aWritingModeOnScroller, 7176 ScrollSnapInfo& aSnapInfo, 7177 ScrollContainerFrame::SnapTargetSet* aSnapTargets) { 7178 ScrollSnapTargetId targetId = ScrollSnapUtils::GetTargetIdFor(aFrame); 7179 7180 nsRect snapArea = 7181 ScrollSnapUtils::GetSnapAreaFor(aFrame, aScrolledFrame, aScrolledRect); 7182 // Use the writing-mode on the target element if the snap area is larger than 7183 // the snapport. 7184 // https://drafts.csswg.org/css-scroll-snap/#snap-scope 7185 WritingMode writingMode = ScrollSnapUtils::NeedsToRespectTargetWritingMode( 7186 snapArea.Size(), aSnapInfo.mSnapportSize) 7187 ? aFrame->GetWritingMode() 7188 : aWritingModeOnScroller; 7189 7190 // These snap range shouldn't be involved with scroll-margin since we just 7191 // need the visible range of the target element. 7192 if (snapArea.width > aSnapInfo.mSnapportSize.width) { 7193 aSnapInfo.mXRangeWiderThanSnapport.AppendElement( 7194 ScrollSnapInfo::ScrollSnapRange(snapArea, ScrollDirection::eHorizontal, 7195 targetId)); 7196 } 7197 if (snapArea.height > aSnapInfo.mSnapportSize.height) { 7198 aSnapInfo.mYRangeWiderThanSnapport.AppendElement( 7199 ScrollSnapInfo::ScrollSnapRange(snapArea, ScrollDirection::eVertical, 7200 targetId)); 7201 } 7202 7203 // Shift target rect position by the scroll padding to get the padded 7204 // position thus we don't need to take account scroll-padding values in 7205 // ScrollSnapUtils::GetSnapPointForDestination() when it gets called from 7206 // the compositor thread. 7207 snapArea.y -= aScrollPadding.top; 7208 snapArea.x -= aScrollPadding.left; 7209 7210 LogicalRect logicalTargetRect(writingMode, snapArea, aSnapInfo.mSnapportSize); 7211 LogicalSize logicalSnapportRect(writingMode, aSnapInfo.mSnapportSize); 7212 LogicalRect logicalScrollRange(aWritingModeOnScroller, aScrollRange, 7213 // The origin of this logical coordinate system 7214 // what we need here is (0, 0), so we use an 7215 // empty size. 7216 nsSize()); 7217 logicalScrollRange = logicalScrollRange.ConvertTo( 7218 writingMode, aWritingModeOnScroller, nsSize()); 7219 7220 Maybe<nscoord> blockDirectionPosition; 7221 const nsStyleDisplay* styleDisplay = aFrame->StyleDisplay(); 7222 nscoord containerBSize = logicalSnapportRect.BSize(writingMode); 7223 switch (styleDisplay->mScrollSnapAlign.block) { 7224 case StyleScrollSnapAlignKeyword::None: 7225 break; 7226 case StyleScrollSnapAlignKeyword::Start: { 7227 nscoord candidate = std::clamp(logicalTargetRect.BStart(writingMode), 7228 logicalScrollRange.BStart(writingMode), 7229 logicalScrollRange.BEnd(writingMode)); 7230 blockDirectionPosition.emplace(writingMode.IsVerticalRL() ? -candidate 7231 : candidate); 7232 break; 7233 } 7234 case StyleScrollSnapAlignKeyword::End: { 7235 nscoord candidate = std::clamp( 7236 // What we need here is the scroll position instead of the snap 7237 // position itself, so we need, for example, the top edge of the 7238 // scroll port on horizontal-tb when the frame is positioned at 7239 // the bottom edge of the scroll port. For this reason we subtract 7240 // containerBSize from BEnd of the target and clamp it inside the 7241 // scrollable range. 7242 logicalTargetRect.BEnd(writingMode) - containerBSize, 7243 logicalScrollRange.BStart(writingMode), 7244 logicalScrollRange.BEnd(writingMode)); 7245 blockDirectionPosition.emplace(writingMode.IsVerticalRL() ? -candidate 7246 : candidate); 7247 break; 7248 } 7249 case StyleScrollSnapAlignKeyword::Center: { 7250 nscoord targetCenter = (logicalTargetRect.BStart(writingMode) + 7251 logicalTargetRect.BEnd(writingMode)) / 7252 2; 7253 nscoord halfSnapportSize = containerBSize / 2; 7254 // Get the center of the target to align with the center of the snapport 7255 // depending on direction. 7256 nscoord candidate = std::clamp(targetCenter - halfSnapportSize, 7257 logicalScrollRange.BStart(writingMode), 7258 logicalScrollRange.BEnd(writingMode)); 7259 blockDirectionPosition.emplace(writingMode.IsVerticalRL() ? -candidate 7260 : candidate); 7261 break; 7262 } 7263 } 7264 7265 Maybe<nscoord> inlineDirectionPosition; 7266 nscoord containerISize = logicalSnapportRect.ISize(writingMode); 7267 switch (styleDisplay->mScrollSnapAlign.inline_) { 7268 case StyleScrollSnapAlignKeyword::None: 7269 break; 7270 case StyleScrollSnapAlignKeyword::Start: { 7271 nscoord candidate = std::clamp(logicalTargetRect.IStart(writingMode), 7272 logicalScrollRange.IStart(writingMode), 7273 logicalScrollRange.IEnd(writingMode)); 7274 inlineDirectionPosition.emplace( 7275 writingMode.IsInlineReversed() ? -candidate : candidate); 7276 break; 7277 } 7278 case StyleScrollSnapAlignKeyword::End: { 7279 nscoord candidate = std::clamp( 7280 // Same as above BEnd case, we subtract containerISize. 7281 // 7282 // Note that the logical scroll range is mapped to [0, x] range even 7283 // if it's in RTL contents. So for example, if the physical range is 7284 // [-200, 0], it's mapped to [0, 200], i.e. IStart() is 0, IEnd() is 7285 // 200. So we can just use std::clamp with the same arguments in both 7286 // RTL/LTR cases. 7287 logicalTargetRect.IEnd(writingMode) - containerISize, 7288 logicalScrollRange.IStart(writingMode), 7289 logicalScrollRange.IEnd(writingMode)); 7290 inlineDirectionPosition.emplace( 7291 writingMode.IsInlineReversed() ? -candidate : candidate); 7292 break; 7293 } 7294 case StyleScrollSnapAlignKeyword::Center: { 7295 nscoord targetCenter = (logicalTargetRect.IStart(writingMode) + 7296 logicalTargetRect.IEnd(writingMode)) / 7297 2; 7298 nscoord halfSnapportSize = containerISize / 2; 7299 // Get the center of the target to align with the center of the snapport 7300 // depending on direction. 7301 nscoord candidate = std::clamp(targetCenter - halfSnapportSize, 7302 logicalScrollRange.IStart(writingMode), 7303 logicalScrollRange.IEnd(writingMode)); 7304 inlineDirectionPosition.emplace( 7305 writingMode.IsInlineReversed() ? -candidate : candidate); 7306 break; 7307 } 7308 } 7309 7310 if (blockDirectionPosition || inlineDirectionPosition) { 7311 aSnapInfo.mSnapTargets.AppendElement( 7312 writingMode.IsVertical() 7313 ? ScrollSnapInfo::SnapTarget( 7314 std::move(blockDirectionPosition), 7315 std::move(inlineDirectionPosition), std::move(snapArea), 7316 styleDisplay->mScrollSnapStop, targetId) 7317 : ScrollSnapInfo::SnapTarget( 7318 std::move(inlineDirectionPosition), 7319 std::move(blockDirectionPosition), std::move(snapArea), 7320 styleDisplay->mScrollSnapStop, targetId)); 7321 if (aSnapTargets) { 7322 aSnapTargets->EnsureInserted(aFrame->GetContent()); 7323 } 7324 } 7325 } 7326 7327 /** 7328 * Collect the scroll positions corresponding to snap positions of frames in the 7329 * subtree rooted at |aFrame|, relative to |aScrolledFrame|, into |aSnapInfo|. 7330 */ 7331 static void CollectScrollPositionsForSnap( 7332 nsIFrame* aFrame, nsIFrame* aScrolledFrame, const nsRect& aScrolledRect, 7333 const nsMargin& aScrollPadding, const nsRect& aScrollRange, 7334 WritingMode aWritingModeOnScroller, ScrollSnapInfo& aSnapInfo, 7335 ScrollContainerFrame::SnapTargetSet* aSnapTargets) { 7336 // Snap positions only affect the nearest ancestor scroll container on the 7337 // element's containing block chain. 7338 ScrollContainerFrame* sf = do_QueryFrame(aFrame); 7339 if (sf) { 7340 return; 7341 } 7342 7343 for (const auto& childList : aFrame->ChildLists()) { 7344 for (nsIFrame* f : childList.mList) { 7345 const nsStyleDisplay* styleDisplay = f->StyleDisplay(); 7346 if (styleDisplay->mScrollSnapAlign.inline_ != 7347 StyleScrollSnapAlignKeyword::None || 7348 styleDisplay->mScrollSnapAlign.block != 7349 StyleScrollSnapAlignKeyword::None) { 7350 AppendScrollPositionsForSnap( 7351 f, aScrolledFrame, aScrolledRect, aScrollPadding, aScrollRange, 7352 aWritingModeOnScroller, aSnapInfo, aSnapTargets); 7353 } 7354 CollectScrollPositionsForSnap( 7355 f, aScrolledFrame, aScrolledRect, aScrollPadding, aScrollRange, 7356 aWritingModeOnScroller, aSnapInfo, aSnapTargets); 7357 } 7358 } 7359 } 7360 7361 static nscoord ResolveScrollPaddingStyleValue( 7362 const StyleRect<mozilla::NonNegativeLengthPercentageOrAuto>& 7363 aScrollPaddingStyle, 7364 Side aSide, const nsSize& aScrollPortSize) { 7365 if (aScrollPaddingStyle.Get(aSide).IsAuto()) { 7366 // https://drafts.csswg.org/css-scroll-snap-1/#valdef-scroll-padding-auto 7367 return 0; 7368 } 7369 7370 nscoord percentageBasis; 7371 switch (aSide) { 7372 case eSideTop: 7373 case eSideBottom: 7374 percentageBasis = aScrollPortSize.height; 7375 break; 7376 case eSideLeft: 7377 case eSideRight: 7378 percentageBasis = aScrollPortSize.width; 7379 break; 7380 } 7381 7382 return aScrollPaddingStyle.Get(aSide).AsLengthPercentage().Resolve( 7383 percentageBasis); 7384 } 7385 7386 static nsMargin ResolveScrollPaddingStyle( 7387 const StyleRect<mozilla::NonNegativeLengthPercentageOrAuto>& 7388 aScrollPaddingStyle, 7389 const nsSize& aScrollPortSize) { 7390 return nsMargin(ResolveScrollPaddingStyleValue(aScrollPaddingStyle, eSideTop, 7391 aScrollPortSize), 7392 ResolveScrollPaddingStyleValue(aScrollPaddingStyle, 7393 eSideRight, aScrollPortSize), 7394 ResolveScrollPaddingStyleValue(aScrollPaddingStyle, 7395 eSideBottom, aScrollPortSize), 7396 ResolveScrollPaddingStyleValue(aScrollPaddingStyle, eSideLeft, 7397 aScrollPortSize)); 7398 } 7399 7400 nsMargin ScrollContainerFrame::GetScrollPadding() const { 7401 nsIFrame* styleFrame = GetFrameForStyle(); 7402 if (!styleFrame) { 7403 return nsMargin(); 7404 } 7405 7406 // The spec says percentage values are relative to the scroll port size. 7407 // https://drafts.csswg.org/css-scroll-snap-1/#scroll-padding 7408 return ResolveScrollPaddingStyle(styleFrame->StylePadding()->mScrollPadding, 7409 GetScrollPortRect().Size()); 7410 } 7411 7412 ScrollSnapInfo ScrollContainerFrame::ComputeScrollSnapInfo() { 7413 ScrollSnapInfo result; 7414 7415 nsIFrame* scrollSnapFrame = GetFrameForStyle(); 7416 if (!scrollSnapFrame) { 7417 return result; 7418 } 7419 7420 const nsStyleDisplay* disp = scrollSnapFrame->StyleDisplay(); 7421 if (disp->mScrollSnapType.strictness == StyleScrollSnapStrictness::None) { 7422 // We won't be snapping, short-circuit the computation. 7423 return result; 7424 } 7425 7426 WritingMode writingMode = GetWritingMode(); 7427 result.InitializeScrollSnapStrictness(writingMode, disp); 7428 7429 result.mSnapportSize = GetSnapportSize(); 7430 if (result.mSnapportSize.IsEmpty()) { 7431 // Ignore any target snap points if the snapport is empty. 7432 return result; 7433 } 7434 7435 CollectScrollPositionsForSnap( 7436 mScrolledFrame, mScrolledFrame, GetScrolledRect(), GetScrollPadding(), 7437 GetLayoutScrollRange(), writingMode, result, &mSnapTargets); 7438 return result; 7439 } 7440 7441 ScrollSnapInfo ScrollContainerFrame::GetScrollSnapInfo() { 7442 // TODO(botond): Should we cache it? 7443 return ComputeScrollSnapInfo(); 7444 } 7445 7446 Maybe<SnapDestination> ScrollContainerFrame::GetSnapPointForDestination( 7447 ScrollUnit aUnit, ScrollSnapFlags aFlags, const nsPoint& aStartPos, 7448 const nsPoint& aDestination) { 7449 // We can release the strong references for the previous snap target 7450 // elements here since calling this ComputeScrollSnapInfo means we are going 7451 // to evaluate new snap points, thus there's no chance to generating 7452 // nsIContent instances in between this function call and the function call 7453 // for the (re-)evaluation. 7454 mSnapTargets.Clear(); 7455 return ScrollSnapUtils::GetSnapPointForDestination( 7456 ComputeScrollSnapInfo(), aUnit, aFlags, GetLayoutScrollRange(), aStartPos, 7457 aDestination); 7458 } 7459 7460 Maybe<SnapDestination> ScrollContainerFrame::GetSnapPointForResnap() { 7461 nsIContent* focusedContent = 7462 GetContent()->GetComposedDoc()->GetUnretargetedFocusedContent(); 7463 7464 // While we are reconstructing this scroll container, we might be in the 7465 // process of restoring the scroll position, we need to respect it. 7466 nsPoint currentOrRestorePos = 7467 NeedRestorePosition() ? mRestorePos : GetScrollPosition(); 7468 return ScrollSnapUtils::GetSnapPointForResnap( 7469 ComputeScrollSnapInfo(), GetLayoutScrollRange(), currentOrRestorePos, 7470 mLastSnapTargetIds, focusedContent); 7471 } 7472 7473 bool ScrollContainerFrame::NeedsResnap() { 7474 return GetSnapPointForResnap().isSome(); 7475 } 7476 7477 void ScrollContainerFrame::SetLastSnapTargetIds( 7478 UniquePtr<ScrollSnapTargetIds> aIds) { 7479 if (!aIds) { 7480 mLastSnapTargetIds = nullptr; 7481 return; 7482 } 7483 7484 // This SetLastSnapTargetIds gets called asynchronously so that there's a race 7485 // condition something like; 7486 // 1) an async scroll operation triggered snapping to a point on an element 7487 // 2) during the async scroll operation, the element got removed from this 7488 // scroll container 7489 // 3) re-snapping triggered 7490 // 4) this SetLastSnapTargetIds got called 7491 // In such case |aIds| are stale, we shouldn't use it. 7492 for (const auto* idList : {&aIds->mIdsOnX, &aIds->mIdsOnY}) { 7493 for (const auto id : *idList) { 7494 if (!mSnapTargets.Contains(reinterpret_cast<nsIContent*>(id))) { 7495 mLastSnapTargetIds = nullptr; 7496 return; 7497 } 7498 } 7499 } 7500 7501 mLastSnapTargetIds = std::move(aIds); 7502 } 7503 7504 bool ScrollContainerFrame::IsLastSnappedTarget(const nsIFrame* aFrame) const { 7505 ScrollSnapTargetId id = ScrollSnapUtils::GetTargetIdFor(aFrame); 7506 MOZ_ASSERT(id != ScrollSnapTargetId::None, 7507 "This function is supposed to be called for contents"); 7508 7509 if (!mLastSnapTargetIds) { 7510 return false; 7511 } 7512 7513 return mLastSnapTargetIds->mIdsOnX.Contains(id) || 7514 mLastSnapTargetIds->mIdsOnY.Contains(id); 7515 } 7516 7517 void ScrollContainerFrame::TryResnap() { 7518 // If there's any async scroll is running or we are processing pan/touch 7519 // gestures or scroll thumb dragging, don't clobber the scroll. 7520 if (!ScrollAnimationState().isEmpty() || 7521 mInScrollingGesture == InScrollingGesture::Yes) { 7522 return; 7523 } 7524 7525 // Same as in GetSnapPointForDestination, We can release the strong references 7526 // for the previous snap targets here. 7527 mSnapTargets.Clear(); 7528 if (auto snapDestination = GetSnapPointForResnap()) { 7529 // We are going to re-snap so that we need to clobber scroll anchoring. 7530 mAnchor.UserScrolled(); 7531 7532 // Snap to the nearest snap position if exists. 7533 ScrollToWithOrigin( 7534 snapDestination->mPosition, nullptr /* range */, 7535 ScrollOperationParams{ 7536 IsSmoothScroll(ScrollBehavior::Auto) ? ScrollMode::SmoothMsd 7537 : ScrollMode::Instant, 7538 ScrollOrigin::Other, std::move(snapDestination->mTargetIds)}); 7539 } 7540 } 7541 7542 void ScrollContainerFrame::PostPendingResnapIfNeeded(const nsIFrame* aFrame) { 7543 if (!IsLastSnappedTarget(aFrame)) { 7544 return; 7545 } 7546 7547 PostPendingResnap(); 7548 } 7549 7550 void ScrollContainerFrame::PostPendingResnap() { 7551 PresShell()->PostPendingScrollResnap(this); 7552 } 7553 7554 ScrollContainerFrame::PhysicalScrollSnapAlign 7555 ScrollContainerFrame::GetScrollSnapAlignFor(const nsIFrame* aFrame) const { 7556 StyleScrollSnapAlignKeyword alignForY = StyleScrollSnapAlignKeyword::None; 7557 StyleScrollSnapAlignKeyword alignForX = StyleScrollSnapAlignKeyword::None; 7558 7559 nsIFrame* styleFrame = GetFrameForStyle(); 7560 if (!styleFrame) { 7561 return {alignForX, alignForY}; 7562 } 7563 7564 if (styleFrame->StyleDisplay()->mScrollSnapType.strictness == 7565 StyleScrollSnapStrictness::None) { 7566 return {alignForX, alignForY}; 7567 } 7568 7569 const nsStyleDisplay* styleDisplay = aFrame->StyleDisplay(); 7570 if (styleDisplay->mScrollSnapAlign.inline_ == 7571 StyleScrollSnapAlignKeyword::None && 7572 styleDisplay->mScrollSnapAlign.block == 7573 StyleScrollSnapAlignKeyword::None) { 7574 return {alignForX, alignForY}; 7575 } 7576 7577 nsSize snapAreaSize = 7578 ScrollSnapUtils::GetSnapAreaFor(aFrame, mScrolledFrame, GetScrolledRect()) 7579 .Size(); 7580 const WritingMode writingMode = 7581 ScrollSnapUtils::NeedsToRespectTargetWritingMode(snapAreaSize, 7582 GetSnapportSize()) 7583 ? aFrame->GetWritingMode() 7584 : styleFrame->GetWritingMode(); 7585 7586 switch (styleFrame->StyleDisplay()->mScrollSnapType.axis) { 7587 case StyleScrollSnapAxis::X: 7588 alignForX = writingMode.IsVertical() 7589 ? styleDisplay->mScrollSnapAlign.block 7590 : styleDisplay->mScrollSnapAlign.inline_; 7591 break; 7592 case StyleScrollSnapAxis::Y: 7593 alignForY = writingMode.IsVertical() 7594 ? styleDisplay->mScrollSnapAlign.inline_ 7595 : styleDisplay->mScrollSnapAlign.block; 7596 break; 7597 case StyleScrollSnapAxis::Block: 7598 if (writingMode.IsVertical()) { 7599 alignForX = styleDisplay->mScrollSnapAlign.block; 7600 } else { 7601 alignForY = styleDisplay->mScrollSnapAlign.block; 7602 } 7603 break; 7604 case StyleScrollSnapAxis::Inline: 7605 if (writingMode.IsVertical()) { 7606 alignForY = styleDisplay->mScrollSnapAlign.inline_; 7607 } else { 7608 alignForX = styleDisplay->mScrollSnapAlign.inline_; 7609 } 7610 break; 7611 case StyleScrollSnapAxis::Both: 7612 if (writingMode.IsVertical()) { 7613 alignForX = styleDisplay->mScrollSnapAlign.block; 7614 alignForY = styleDisplay->mScrollSnapAlign.inline_; 7615 } else { 7616 alignForX = styleDisplay->mScrollSnapAlign.inline_; 7617 alignForY = styleDisplay->mScrollSnapAlign.block; 7618 } 7619 break; 7620 } 7621 7622 return {alignForX, alignForY}; 7623 } 7624 7625 bool ScrollContainerFrame::UsesOverlayScrollbars() const { 7626 return PresContext()->UseOverlayScrollbars(); 7627 } 7628 7629 bool ScrollContainerFrame::DragScroll(WidgetEvent* aEvent) { 7630 // Dragging is allowed while within a 20 pixel border. Note that device pixels 7631 // are used so that the same margin is used even when zoomed in or out. 7632 nscoord margin = 20 * PresContext()->AppUnitsPerDevPixel(); 7633 7634 // Don't drag scroll for small scrollareas. 7635 if (mScrollPort.width < margin * 2 || mScrollPort.height < margin * 2) { 7636 return false; 7637 } 7638 7639 // If willScroll is computed as false, then the frame is already scrolled as 7640 // far as it can go in both directions. Return false so that an ancestor 7641 // scrollframe can scroll instead. 7642 bool willScroll = false; 7643 nsPoint pnt = 7644 nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, RelativeTo{this}); 7645 nsPoint scrollPoint = GetScrollPosition(); 7646 nsRect rangeRect = GetLayoutScrollRange(); 7647 7648 // Only drag scroll when a scrollbar is present. 7649 nsPoint offset; 7650 if (mHasHorizontalScrollbar) { 7651 if (pnt.x >= mScrollPort.x && pnt.x <= mScrollPort.x + margin) { 7652 offset.x = -margin; 7653 if (scrollPoint.x > 0) { 7654 willScroll = true; 7655 } 7656 } else if (pnt.x >= mScrollPort.XMost() - margin && 7657 pnt.x <= mScrollPort.XMost()) { 7658 offset.x = margin; 7659 if (scrollPoint.x < rangeRect.width) { 7660 willScroll = true; 7661 } 7662 } 7663 } 7664 7665 if (mHasVerticalScrollbar) { 7666 if (pnt.y >= mScrollPort.y && pnt.y <= mScrollPort.y + margin) { 7667 offset.y = -margin; 7668 if (scrollPoint.y > 0) { 7669 willScroll = true; 7670 } 7671 } else if (pnt.y >= mScrollPort.YMost() - margin && 7672 pnt.y <= mScrollPort.YMost()) { 7673 offset.y = margin; 7674 if (scrollPoint.y < rangeRect.height) { 7675 willScroll = true; 7676 } 7677 } 7678 } 7679 7680 if (offset.x || offset.y) { 7681 ScrollToWithOrigin( 7682 GetScrollPosition() + offset, nullptr /* range */, 7683 ScrollOperationParams{ScrollMode::Normal, ScrollOrigin::Other}); 7684 } 7685 7686 return willScroll; 7687 } 7688 7689 static nsSliderFrame* GetSliderFrame(nsIFrame* aScrollbarFrame) { 7690 if (!aScrollbarFrame) { 7691 return nullptr; 7692 } 7693 7694 for (const auto& childList : aScrollbarFrame->ChildLists()) { 7695 for (nsIFrame* frame : childList.mList) { 7696 if (nsSliderFrame* sliderFrame = do_QueryFrame(frame)) { 7697 return sliderFrame; 7698 } 7699 } 7700 } 7701 return nullptr; 7702 } 7703 7704 static void AsyncScrollbarDragInitiated(uint64_t aDragBlockId, 7705 nsIFrame* aScrollbar) { 7706 if (nsSliderFrame* sliderFrame = GetSliderFrame(aScrollbar)) { 7707 sliderFrame->AsyncScrollbarDragInitiated(aDragBlockId); 7708 } 7709 } 7710 7711 void ScrollContainerFrame::AsyncScrollbarDragInitiated( 7712 uint64_t aDragBlockId, ScrollDirection aDirection) { 7713 switch (aDirection) { 7714 case ScrollDirection::eVertical: 7715 ::AsyncScrollbarDragInitiated(aDragBlockId, mVScrollbarBox); 7716 break; 7717 case ScrollDirection::eHorizontal: 7718 ::AsyncScrollbarDragInitiated(aDragBlockId, mHScrollbarBox); 7719 break; 7720 } 7721 } 7722 7723 static void AsyncScrollbarDragRejected(nsIFrame* aScrollbar) { 7724 if (nsSliderFrame* sliderFrame = GetSliderFrame(aScrollbar)) { 7725 sliderFrame->AsyncScrollbarDragRejected(); 7726 } 7727 } 7728 7729 void ScrollContainerFrame::AsyncScrollbarDragRejected() { 7730 // We don't get told which scrollbar requested the async drag, 7731 // so we notify both. 7732 ::AsyncScrollbarDragRejected(mHScrollbarBox); 7733 ::AsyncScrollbarDragRejected(mVScrollbarBox); 7734 } 7735 7736 void ScrollContainerFrame::ApzSmoothScrollTo( 7737 const nsPoint& aDestination, ScrollMode aMode, ScrollOrigin aOrigin, 7738 ScrollTriggeredByScript aTriggeredByScript, 7739 UniquePtr<ScrollSnapTargetIds> aSnapTargetIds, 7740 ViewportType aViewportToScroll) { 7741 if (mApzSmoothScrollDestination == Some(aDestination)) { 7742 // If we already sent APZ a smooth-scroll request to this 7743 // destination (i.e. it was the last request 7744 // we sent), then don't send another one because it is redundant. 7745 // This is to avoid a scenario where pages do repeated scrollBy 7746 // calls, incrementing the generation counter, and blocking APZ from 7747 // syncing the scroll offset back to the main thread. 7748 // Note that if we get two smooth-scroll requests to the same 7749 // destination with some other scroll in between, 7750 // mApzSmoothScrollDestination will get reset to Nothing() and so 7751 // we shouldn't have the problem where this check discards a 7752 // legitimate smooth-scroll. 7753 return; 7754 } 7755 7756 // The animation will be handled in the compositor, pass the 7757 // information needed to start the animation and skip the main-thread 7758 // animation for this scroll. 7759 MOZ_ASSERT(aOrigin != ScrollOrigin::None); 7760 mApzSmoothScrollDestination = Some(aDestination); 7761 AppendScrollUpdate(ScrollPositionUpdate::NewSmoothScroll( 7762 aMode, aOrigin, aDestination, aTriggeredByScript, 7763 std::move(aSnapTargetIds), aViewportToScroll)); 7764 7765 nsIContent* content = GetContent(); 7766 if (!DisplayPortUtils::HasNonMinimalNonZeroDisplayPort(content)) { 7767 // If this frame doesn't have a displayport then there won't be an 7768 // APZC instance for it and so there won't be anything to process 7769 // this smooth scroll request. We should set a displayport on this 7770 // frame to force an APZC which can handle the request. 7771 if (MOZ_LOG_TEST(sDisplayportLog, LogLevel::Debug)) { 7772 mozilla::layers::ScrollableLayerGuid::ViewID viewID = 7773 mozilla::layers::ScrollableLayerGuid::NULL_SCROLL_ID; 7774 nsLayoutUtils::FindIDFor(content, &viewID); 7775 MOZ_LOG( 7776 sDisplayportLog, LogLevel::Debug, 7777 ("ApzSmoothScrollTo setting displayport on scrollId=%" PRIu64 "\n", 7778 viewID)); 7779 } 7780 7781 DisplayPortUtils::CalculateAndSetDisplayPortMargins( 7782 GetScrollTargetFrame(), DisplayPortUtils::RepaintMode::Repaint); 7783 nsIFrame* frame = do_QueryFrame(GetScrollTargetFrame()); 7784 DisplayPortUtils::SetZeroMarginDisplayPortOnAsyncScrollableAncestors(frame); 7785 } 7786 7787 // Schedule a paint to ensure that the frame metrics get updated on 7788 // the compositor thread. 7789 SchedulePaint(); 7790 } 7791 7792 bool ScrollContainerFrame::CanApzScrollInTheseDirections( 7793 ScrollDirections aDirections) { 7794 ScrollStyles styles = GetScrollStyles(); 7795 if (aDirections.contains(ScrollDirection::eHorizontal) && 7796 styles.mHorizontal == StyleOverflow::Hidden) { 7797 return false; 7798 } 7799 if (aDirections.contains(ScrollDirection::eVertical) && 7800 styles.mVertical == StyleOverflow::Hidden) { 7801 return false; 7802 } 7803 return true; 7804 } 7805 7806 bool ScrollContainerFrame::SmoothScrollVisual( 7807 const nsPoint& aVisualViewportOffset, 7808 FrameMetrics::ScrollOffsetUpdateType aUpdateType, ScrollMode aMode) { 7809 MOZ_ASSERT(aMode == ScrollMode::Smooth || aMode == ScrollMode::SmoothMsd); 7810 7811 bool canDoApzSmoothScroll = 7812 nsLayoutUtils::AsyncPanZoomEnabled(this) && WantAsyncScroll(); 7813 if (!canDoApzSmoothScroll) { 7814 return false; 7815 } 7816 7817 // Stop suppressing displayport while the page is still loading. 7818 if (MOZ_UNLIKELY(PresShell()->IsDocumentLoading())) { 7819 PresShell()->SuppressDisplayport(false); 7820 } 7821 7822 // Clamp the destination to the visual scroll range. 7823 // There is a potential issue here, where |mDestination| is usually 7824 // clamped to the layout scroll range, and so e.g. a subsequent 7825 // window.scrollBy() may have an undesired effect. However, as this function 7826 // is only called internally, this should not be a problem in practice. 7827 // If it turns out to be, the fix would be: 7828 // - add a new "destination" field that doesn't have to be clamped to 7829 // the layout scroll range 7830 // - clamp mDestination to the layout scroll range here 7831 // - make sure ComputeScrollMetadata() picks up the former as the 7832 // smooth scroll destination to send to APZ. 7833 mDestination = GetVisualScrollRange().ClampPoint(aVisualViewportOffset); 7834 7835 UniquePtr<ScrollSnapTargetIds> snapTargetIds; 7836 // Perform the scroll. 7837 ApzSmoothScrollTo(mDestination, aMode, 7838 aUpdateType == FrameMetrics::eRestore 7839 ? ScrollOrigin::Restore 7840 : ScrollOrigin::Other, 7841 ScrollTriggeredByScript::No, std::move(snapTargetIds), 7842 ViewportType::Visual); 7843 return true; 7844 } 7845 7846 bool ScrollContainerFrame::IsSmoothScroll(dom::ScrollBehavior aBehavior) const { 7847 if (aBehavior == dom::ScrollBehavior::Instant) { 7848 return false; 7849 } 7850 7851 // The user smooth scrolling preference should be honored for any requested 7852 // smooth scrolls. A requested smooth scroll when smooth scrolling is 7853 // disabled should be equivalent to an instant scroll. This is not enforced 7854 // for the <scrollbox> XUL element to allow for the browser chrome to 7855 // override this behavior when toolkit.scrollbox.smoothScroll is enabled. 7856 if (!GetContent()->IsXULElement(nsGkAtoms::scrollbox)) { 7857 if (!nsLayoutUtils::IsSmoothScrollingEnabled()) { 7858 return false; 7859 } 7860 } else { 7861 if (!StaticPrefs::toolkit_scrollbox_smoothScroll()) { 7862 return false; 7863 } 7864 } 7865 7866 if (aBehavior == dom::ScrollBehavior::Smooth) { 7867 return true; 7868 } 7869 7870 nsIFrame* styleFrame = GetFrameForStyle(); 7871 if (!styleFrame) { 7872 return false; 7873 } 7874 return (aBehavior == dom::ScrollBehavior::Auto && 7875 styleFrame->StyleDisplay()->mScrollBehavior == 7876 StyleScrollBehavior::Smooth); 7877 } 7878 7879 ScrollMode ScrollContainerFrame::ScrollModeForScrollBehavior( 7880 dom::ScrollBehavior aBehavior) const { 7881 if (!IsSmoothScroll(aBehavior)) { 7882 return ScrollMode::Instant; 7883 } 7884 7885 return StaticPrefs::layout_css_scroll_behavior_same_physics_as_user_input() 7886 ? ScrollMode::Smooth 7887 : ScrollMode::SmoothMsd; 7888 } 7889 7890 nsTArray<ScrollPositionUpdate> ScrollContainerFrame::GetScrollUpdates() const { 7891 return mScrollUpdates.Clone(); 7892 } 7893 7894 void ScrollContainerFrame::AppendScrollUpdate( 7895 const ScrollPositionUpdate& aUpdate) { 7896 mScrollGeneration = aUpdate.GetGeneration(); 7897 mScrollUpdates.AppendElement(aUpdate); 7898 } 7899 7900 void ScrollContainerFrame::ScheduleScrollAnimations() { 7901 auto* rd = PresContext()->RefreshDriver(); 7902 MOZ_ASSERT(rd); 7903 // This schedules UpdateAnimationsAndSendEvents in the HTML loop. 7904 rd->EnsureAnimationUpdate(); 7905 } 7906 7907 nsSize ScrollContainerFrame::GetSizeForWindowInnerSize() const { 7908 MOZ_ASSERT(mIsRoot); 7909 7910 return mIsUsingMinimumScaleSize ? mMinimumScaleSize 7911 : PresContext()->GetVisibleArea().Size(); 7912 }