tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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, &copyOfVisibleRect,
   4141                                    &copyOfDirtyRect,
   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 }