Bug 1650502 - Plumb whether an async APZ animation is in progress via RepaintRequest. r=botond draft
authorEmilio Cobos Álvarez <emilio@crisal.io>
Fri, 31 Jul 2020 22:33:01 +0200
changeset 3100953 8306d2ee3017f53f68c9de49eb8f5cceb1c7dfc0
parent 3100952 93a7707a13010fb90b4aa4459d3966ea4bc80080
child 3100954 bae83ed1f7c2a79756562b72b6085dbf2f43911f
push id577951
push useremilio@crisal.io
push dateSun, 02 Aug 2020 08:27:23 +0000
treeherdertry@bae83ed1f7c2 [default view] [failures only]
reviewersbotond
bugs1650502
milestone81.0a1
Bug 1650502 - Plumb whether an async APZ animation is in progress via RepaintRequest. r=botond And store that information in the scroll frame for the purposes of avoiding scroll anchoring and scroll restoration. Differential Revision: https://phabricator.services.mozilla.com/D85157 Depends on D85156
gfx/layers/RepaintRequest.h
gfx/layers/apz/src/AsyncPanZoomController.cpp
gfx/layers/apz/util/APZCCallbackHelper.cpp
gfx/layers/apz/util/APZCCallbackHelper.h
gfx/layers/ipc/LayersMessageUtils.h
layout/generic/ScrollAnchorContainer.cpp
layout/generic/nsGfxScrollFrame.cpp
layout/generic/nsGfxScrollFrame.h
layout/generic/nsIScrollableFrame.h
--- a/gfx/layers/RepaintRequest.h
+++ b/gfx/layers/RepaintRequest.h
@@ -52,35 +52,38 @@ struct RepaintRequest {
         mScrollGeneration(0),
         mDisplayPortMargins(0, 0, 0, 0),
         mPresShellId(-1),
         mLayoutViewport(0, 0, 0, 0),
         mExtraResolution(),
         mPaintRequestTime(),
         mScrollUpdateType(eNone),
         mIsRootContent(false),
+        mIsAnimationInProgress(false),
         mIsScrollInfoLayer(false) {}
 
   RepaintRequest(const FrameMetrics& aOther,
-                 const ScrollOffsetUpdateType aScrollUpdateType)
+                 const ScrollOffsetUpdateType aScrollUpdateType,
+                 bool aIsAnimationInProgress)
       : mScrollId(aOther.GetScrollId()),
         mPresShellResolution(aOther.GetPresShellResolution()),
         mCompositionBounds(aOther.GetCompositionBounds()),
         mCumulativeResolution(aOther.GetCumulativeResolution()),
         mDevPixelsPerCSSPixel(aOther.GetDevPixelsPerCSSPixel()),
         mScrollOffset(aOther.GetScrollOffset()),
         mZoom(aOther.GetZoom()),
         mScrollGeneration(aOther.GetScrollGeneration()),
         mDisplayPortMargins(aOther.GetDisplayPortMargins()),
         mPresShellId(aOther.GetPresShellId()),
         mLayoutViewport(aOther.GetLayoutViewport()),
         mExtraResolution(aOther.GetExtraResolution()),
         mPaintRequestTime(aOther.GetPaintRequestTime()),
         mScrollUpdateType(aScrollUpdateType),
         mIsRootContent(aOther.IsRootContent()),
+        mIsAnimationInProgress(aIsAnimationInProgress),
         mIsScrollInfoLayer(aOther.IsScrollInfoLayer()) {}
 
   // Default copy ctor and operator= are fine
 
   bool operator==(const RepaintRequest& aOther) const {
     // Put mScrollId at the top since it's the most likely one to fail.
     return mScrollId == aOther.mScrollId &&
            mPresShellResolution == aOther.mPresShellResolution &&
@@ -92,16 +95,17 @@ struct RepaintRequest {
            mScrollGeneration == aOther.mScrollGeneration &&
            mDisplayPortMargins == aOther.mDisplayPortMargins &&
            mPresShellId == aOther.mPresShellId &&
            mLayoutViewport.IsEqualEdges(aOther.mLayoutViewport) &&
            mExtraResolution == aOther.mExtraResolution &&
            mPaintRequestTime == aOther.mPaintRequestTime &&
            mScrollUpdateType == aOther.mScrollUpdateType &&
            mIsRootContent == aOther.mIsRootContent &&
+           mIsAnimationInProgress == aOther.mIsAnimationInProgress &&
            mIsScrollInfoLayer == aOther.mIsScrollInfoLayer;
   }
 
   bool operator!=(const RepaintRequest& aOther) const {
     return !operator==(aOther);
   }
 
   CSSToScreenScale2D DisplayportPixelsPerCSSPixel() const {
@@ -142,16 +146,18 @@ struct RepaintRequest {
   const LayoutDeviceToLayerScale2D& GetCumulativeResolution() const {
     return mCumulativeResolution;
   }
 
   const CSSToLayoutDeviceScale& GetDevPixelsPerCSSPixel() const {
     return mDevPixelsPerCSSPixel;
   }
 
+  bool IsAnimationInProgress() const { return mIsAnimationInProgress; }
+
   bool IsRootContent() const { return mIsRootContent; }
 
   const CSSPoint& GetScrollOffset() const { return mScrollOffset; }
 
   const CSSToParentLayerScale2D& GetZoom() const { return mZoom; }
 
   ScrollOffsetUpdateType GetScrollUpdateType() const {
     return mScrollUpdateType;
@@ -175,16 +181,20 @@ struct RepaintRequest {
     return mExtraResolution;
   }
 
   const TimeStamp& GetPaintRequestTime() const { return mPaintRequestTime; }
 
   bool IsScrollInfoLayer() const { return mIsScrollInfoLayer; }
 
  protected:
+  void SetIsAnimationInProgress(bool aInProgress) {
+    mIsAnimationInProgress = aInProgress;
+  }
+
   void SetIsRootContent(bool aIsRootContent) {
     mIsRootContent = aIsRootContent;
   }
 
   void SetIsScrollInfoLayer(bool aIsScrollInfoLayer) {
     mIsScrollInfoLayer = aIsScrollInfoLayer;
   }
 
@@ -275,16 +285,19 @@ struct RepaintRequest {
   TimeStamp mPaintRequestTime;
 
   // The type of repaint request this represents.
   ScrollOffsetUpdateType mScrollUpdateType;
 
   // Whether or not this is the root scroll frame for the root content document.
   bool mIsRootContent : 1;
 
+  // Whether or not we are in the middle of a scroll animation.
+  bool mIsAnimationInProgress : 1;
+
   // True if this scroll frame is a scroll info layer. A scroll info layer is
   // not layerized and its content cannot be truly async-scrolled, but its
   // metrics are still sent to and updated by the compositor, with the updates
   // being reflected on the next paint rather than the next composite.
   bool mIsScrollInfoLayer : 1;
 };
 
 }  // namespace layers
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -3977,33 +3977,36 @@ void AsyncPanZoomController::RequestCont
     const FrameMetrics& aFrameMetrics, const ParentLayerPoint& aVelocity,
     RepaintUpdateType aUpdateType) {
   RefPtr<GeckoContentController> controller = GetGeckoContentController();
   if (!controller) {
     return;
   }
   MOZ_ASSERT(controller->IsRepaintThread());
 
-  RepaintRequest request(aFrameMetrics, aUpdateType);
+  const bool isAnimationInProgress = !!mAnimation;
+  RepaintRequest request(aFrameMetrics, aUpdateType, isAnimationInProgress);
 
   // If we're trying to paint what we already think is painted, discard this
   // request since it's a pointless paint.
   if (request.GetDisplayPortMargins().WithinEpsilonOf(
           mLastPaintRequestMetrics.GetDisplayPortMargins(), EPSILON) &&
       request.GetScrollOffset().WithinEpsilonOf(
           mLastPaintRequestMetrics.GetScrollOffset(), EPSILON) &&
       request.GetPresShellResolution() ==
           mLastPaintRequestMetrics.GetPresShellResolution() &&
       request.GetZoom() == mLastPaintRequestMetrics.GetZoom() &&
       request.GetLayoutViewport().WithinEpsilonOf(
           mLastPaintRequestMetrics.GetLayoutViewport(), EPSILON) &&
       request.GetScrollGeneration() ==
           mLastPaintRequestMetrics.GetScrollGeneration() &&
       request.GetScrollUpdateType() ==
-          mLastPaintRequestMetrics.GetScrollUpdateType()) {
+          mLastPaintRequestMetrics.GetScrollUpdateType() &&
+      request.IsAnimationInProgress() ==
+          mLastPaintRequestMetrics.IsAnimationInProgress()) {
     return;
   }
 
   APZC_LOGV_FM(aFrameMetrics, "%p requesting content repaint", this);
   {  // scope lock
     MutexAutoLock lock(mCheckerboardEventLock);
     if (mCheckerboardEvent && mCheckerboardEvent->IsRecordingTrace()) {
       std::stringstream info;
--- a/gfx/layers/apz/util/APZCCallbackHelper.cpp
+++ b/gfx/layers/apz/util/APZCCallbackHelper.cpp
@@ -159,17 +159,18 @@ static CSSPoint ScrollFrameTo(nsIScrolla
  * display port.
  */
 static ScreenMargin ScrollFrame(nsIContent* aContent,
                                 const RepaintRequest& aRequest) {
   // Scroll the window to the desired spot
   nsIScrollableFrame* sf =
       nsLayoutUtils::FindScrollableFrameFor(aRequest.GetScrollId());
   if (sf) {
-    sf->ResetScrollInfoIfGeneration(aRequest.GetScrollGeneration());
+    sf->ResetScrollInfoIfNeeded(aRequest.GetScrollGeneration(),
+                                aRequest.IsAnimationInProgress());
     sf->SetScrollableByAPZ(!aRequest.IsScrollInfoLayer());
     if (sf->IsRootScrollFrameOfDocument()) {
       if (!APZCCallbackHelper::IsScrollInProgress(sf)) {
         if (RefPtr<PresShell> presShell = GetPresShell(aContent)) {
           APZCCH_LOG("Setting VV offset to %s on presShell %p\n",
                      Stringify(aRequest.GetScrollOffset()).c_str(),
                      presShell.get());
           if (presShell->SetVisualViewportOffset(
@@ -829,20 +830,20 @@ void APZCCallbackHelper::NotifyFlushComp
   nsCOMPtr<nsIObserverService> observerService =
       mozilla::services::GetObserverService();
   MOZ_ASSERT(observerService);
   observerService->NotifyObservers(nullptr, "apz-repaints-flushed", nullptr);
 }
 
 /* static */
 bool APZCCallbackHelper::IsScrollInProgress(nsIScrollableFrame* aFrame) {
-  return aFrame->IsProcessingAsyncScroll() ||
-         nsLayoutUtils::CanScrollOriginClobberApz(aFrame->LastScrollOrigin()) ||
-         aFrame->LastSmoothScrollOrigin() != ScrollOrigin::None ||
-         aFrame->GetRelativeOffset().isSome();
+  using IncludeApzAnimation = nsIScrollableFrame::IncludeApzAnimation;
+
+  return aFrame->IsScrollAnimating(IncludeApzAnimation::No) ||
+         nsLayoutUtils::CanScrollOriginClobberApz(aFrame->LastScrollOrigin());
 }
 
 /* static */
 void APZCCallbackHelper::NotifyAsyncScrollbarDragInitiated(
     uint64_t aDragBlockId, const ScrollableLayerGuid::ViewID& aScrollId,
     ScrollDirection aDirection) {
   MOZ_ASSERT(NS_IsMainThread());
   if (nsIScrollableFrame* scrollFrame =
--- a/gfx/layers/apz/util/APZCCallbackHelper.h
+++ b/gfx/layers/apz/util/APZCCallbackHelper.h
@@ -174,18 +174,21 @@ class APZCCallbackHelper {
 
   /* Adjust the display-port margins by the difference between the requested
    * scroll offset and the resulting scroll offset after setting the requested
    * value. */
   static ScreenMargin AdjustDisplayPortForScrollDelta(ScreenMargin aMargins,
                                                       ScreenPoint aScrollDelta);
 
   /*
-   * Check if the scrollable frame is currently in the middle of an async
-   * or smooth scroll. We want to discard certain scroll input if this is
+   * Check if the scrollable frame is currently in the middle of a main thread
+   * async or smooth scroll, or has already requested some other apz scroll that
+   * hasn't been acknowledged by apz.
+   *
+   * We want to discard apz updates to the main-thread scroll offset if this is
    * true to prevent clobbering higher priority origins.
    */
   static bool IsScrollInProgress(nsIScrollableFrame* aFrame);
 
   /* Notify content of the progress of a pinch gesture that APZ won't do
    * zooming for (because the apz.allow_zooming pref is false). This function
    * will dispatch appropriate WidgetSimpleGestureEvent events to gecko.
    */
--- a/gfx/layers/ipc/LayersMessageUtils.h
+++ b/gfx/layers/ipc/LayersMessageUtils.h
@@ -273,16 +273,17 @@ struct ParamTraits<mozilla::layers::Repa
     WriteParam(aMsg, aParam.mScrollGeneration);
     WriteParam(aMsg, aParam.mDisplayPortMargins);
     WriteParam(aMsg, aParam.mPresShellId);
     WriteParam(aMsg, aParam.mLayoutViewport);
     WriteParam(aMsg, aParam.mExtraResolution);
     WriteParam(aMsg, aParam.mPaintRequestTime);
     WriteParam(aMsg, aParam.mScrollUpdateType);
     WriteParam(aMsg, aParam.mIsRootContent);
+    WriteParam(aMsg, aParam.mIsAnimationInProgress);
     WriteParam(aMsg, aParam.mIsScrollInfoLayer);
   }
 
   static bool Read(const Message* aMsg, PickleIterator* aIter,
                    paramType* aResult) {
     return (ReadParam(aMsg, aIter, &aResult->mScrollId) &&
             ReadParam(aMsg, aIter, &aResult->mPresShellResolution) &&
             ReadParam(aMsg, aIter, &aResult->mCompositionBounds) &&
@@ -295,16 +296,18 @@ struct ParamTraits<mozilla::layers::Repa
             ReadParam(aMsg, aIter, &aResult->mPresShellId) &&
             ReadParam(aMsg, aIter, &aResult->mLayoutViewport) &&
             ReadParam(aMsg, aIter, &aResult->mExtraResolution) &&
             ReadParam(aMsg, aIter, &aResult->mPaintRequestTime) &&
             ReadParam(aMsg, aIter, &aResult->mScrollUpdateType) &&
             ReadBoolForBitfield(aMsg, aIter, aResult,
                                 &paramType::SetIsRootContent) &&
             ReadBoolForBitfield(aMsg, aIter, aResult,
+                                &paramType::SetIsAnimationInProgress) &&
+            ReadBoolForBitfield(aMsg, aIter, aResult,
                                 &paramType::SetIsScrollInfoLayer));
   }
 };
 
 template <>
 struct ParamTraits<nsSize> {
   typedef nsSize paramType;
 
--- a/layout/generic/ScrollAnchorContainer.cpp
+++ b/layout/generic/ScrollAnchorContainer.cpp
@@ -413,29 +413,27 @@ void ScrollAnchorContainer::InvalidateAn
 void ScrollAnchorContainer::Destroy() {
   InvalidateAnchor(ScheduleSelection::No);
 }
 
 void ScrollAnchorContainer::ApplyAdjustments() {
   if (!mAnchorNode || mAnchorNodeIsDirty || mDisabled ||
       mScrollFrame->HasPendingScrollRestoration() ||
       mScrollFrame->IsProcessingScrollEvent() ||
-      mScrollFrame->IsProcessingAsyncScroll() ||
-      mScrollFrame->mApzSmoothScrollDestination.isSome() ||
+      mScrollFrame->IsScrollAnimating() ||
       mScrollFrame->GetScrollPosition() == nsPoint()) {
     ANCHOR_LOG(
         "Ignoring post-reflow (anchor=%p, dirty=%d, disabled=%d, "
-        "pendingRestoration=%d, scrollevent=%d, asyncScroll=%d, "
-        "apzSmoothDestination=%d, zeroScrollPos=%d pendingSuppression=%d, "
+        "pendingRestoration=%d, scrollevent=%d, animating=%d, "
+        "zeroScrollPos=%d pendingSuppression=%d, "
         "container=%p).\n",
         mAnchorNode, mAnchorNodeIsDirty, mDisabled,
         mScrollFrame->HasPendingScrollRestoration(),
         mScrollFrame->IsProcessingScrollEvent(),
-        mScrollFrame->IsProcessingAsyncScroll(),
-        mScrollFrame->mApzSmoothScrollDestination.isSome(),
+        mScrollFrame->IsScrollAnimating(),
         mScrollFrame->GetScrollPosition() == nsPoint(),
         mSuppressAnchorAdjustment, this);
     if (mSuppressAnchorAdjustment) {
       mSuppressAnchorAdjustment = false;
       InvalidateAnchor();
     }
     return;
   }
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -2179,16 +2179,17 @@ ScrollFrameHelper::ScrollFrameHelper(nsC
       mTransformingByAPZ(false),
       mScrollableByAPZ(false),
       mZoomableByAPZ(false),
       mHasOutOfFlowContentInsideFilter(false),
       mSuppressScrollbarRepaints(false),
       mIsUsingMinimumScaleSize(false),
       mMinimumScaleSizeChanged(false),
       mProcessingScrollEvent(false),
+      mApzAnimationInProgress(false),
       mVelocityQueue(aOuter->PresContext()) {
   if (LookAndFeel::GetInt(LookAndFeel::IntID::UseOverlayScrollbars) != 0) {
     mScrollbarActivity = new ScrollbarActivity(do_QueryFrame(aOuter));
   }
 
   if (IsAlwaysActive() && StaticPrefs::layers_enable_tiles_AtStartup() &&
       !nsLayoutUtils::UsesAsyncScrolling(mOuter) && mOuter->GetContent()) {
     // If we have tiling but no APZ, then set a 0-margin display port on
@@ -6180,18 +6181,17 @@ bool ScrollFrameHelper::ReflowFinished()
 
   if (doScroll) {
     ScrollToRestoredPosition();
 
     // Clamp current scroll position to new bounds. Normally this won't
     // do anything.
     nsPoint currentScrollPos = GetScrollPosition();
     ScrollToImpl(currentScrollPos, nsRect(currentScrollPos, nsSize(0, 0)));
-    if (!mAsyncScroll && !mAsyncSmoothMSDScroll &&
-        !mApzSmoothScrollDestination) {
+    if (!IsScrollAnimating()) {
       // We need to have mDestination track the current scroll position,
       // in case it falls outside the new reflow area. mDestination is used
       // by ScrollBy as its starting position.
       mDestination = GetScrollPosition();
     }
   }
 
   if (!mUpdateScrollbarAttributes) {
@@ -6821,29 +6821,37 @@ nscoord ScrollFrameHelper::GetCoordAttri
   }
 
   // Only this exact default value is allowed.
   *aRangeStart = aDefaultValue;
   *aRangeLength = 0;
   return aDefaultValue;
 }
 
+bool ScrollFrameHelper::IsScrollAnimating(
+    IncludeApzAnimation aIncludeApz) const {
+  if (aIncludeApz == IncludeApzAnimation::Yes && IsApzAnimationInProgress()) {
+    return true;
+  }
+  return mAsyncScroll || mAsyncSmoothMSDScroll ||
+         LastSmoothScrollOrigin() != ScrollOrigin::None ||
+         mRelativeOffset.isSome();
+}
+
 UniquePtr<PresState> ScrollFrameHelper::SaveState() const {
   nsIScrollbarMediator* mediator = do_QueryFrame(GetScrolledFrame());
   if (mediator) {
     // child handles its own scroll state, so don't bother saving state here
     return nullptr;
   }
 
   // Don't store a scroll state if we never have been scrolled or restored
   // a previous scroll state, and we're not in the middle of a smooth scroll.
-  bool isInSmoothScroll = IsProcessingAsyncScroll() ||
-                          mLastSmoothScrollOrigin != ScrollOrigin::None ||
-                          mRelativeOffset.isSome();
-  if (!mHasBeenScrolled && !mDidHistoryRestore && !isInSmoothScroll) {
+  bool isScrollAnimating = IsScrollAnimating(IncludeApzAnimation::No);
+  if (!mHasBeenScrolled && !mDidHistoryRestore && !isScrollAnimating) {
     return nullptr;
   }
 
   UniquePtr<PresState> state = NewPresState();
   bool allowScrollOriginDowngrade =
       !nsLayoutUtils::CanScrollOriginClobberApz(mLastScrollOrigin) ||
       mAllowScrollOriginDowngrade;
   // Save mRestorePos instead of our actual current scroll position, if it's
@@ -6851,17 +6859,17 @@ UniquePtr<PresState> ScrollFrameHelper::
   // that ScrollToRestoredPosition uses). This ensures if a reframe occurs
   // while we're in the process of loading content to scroll to a restored
   // position, we'll keep trying after the reframe. Similarly, if we're in the
   // middle of a smooth scroll, store the destination so that when we restore
   // we'll jump straight to the end of the scroll animation, rather than
   // effectively dropping it. Note that the mRestorePos will override the
   // smooth scroll destination if both are present.
   nsPoint pt = GetLogicalVisualViewportOffset();
-  if (isInSmoothScroll) {
+  if (isScrollAnimating) {
     pt = mDestination;
     allowScrollOriginDowngrade = false;
   }
   SCROLLRESTORE_LOG("%p: SaveState, pt.y=%d, mLastPos.y=%d, mRestorePos.y=%d\n",
                     this, pt.y, mLastPos.y, mRestorePos.y);
   if (mRestorePos.y != -1 && pt == mLastPos) {
     pt = mRestorePos;
   }
--- a/layout/generic/nsGfxScrollFrame.h
+++ b/layout/generic/nsGfxScrollFrame.h
@@ -371,19 +371,16 @@ class ScrollFrameHelper : public nsIRefl
   bool IsScrollbarOnRight() const;
   bool IsScrollingActive(nsDisplayListBuilder* aBuilder) const;
   bool IsMaybeAsynchronouslyScrolled() const {
     // If this is true, then we'll build an ASR, and that's what we want
     // to know I think.
     return mWillBuildScrollableLayer;
   }
   bool IsMaybeScrollingActive() const;
-  bool IsProcessingAsyncScroll() const {
-    return mAsyncScroll != nullptr || mAsyncSmoothMSDScroll != nullptr;
-  }
   void ResetScrollPositionForLayerPixelAlignment() {
     mScrollPosForLayerPixelAlignment = GetScrollPosition();
   }
 
   bool ComputeCustomOverflow(nsOverflowAreas& aOverflowAreas);
 
   void UpdateSticky();
 
@@ -458,23 +455,32 @@ class ScrollFrameHelper : public nsIRefl
   static void ScrollActivityCallback(nsITimer* aTimer, void* anInstance);
 
   void HandleScrollbarStyleSwitching();
 
   ScrollOrigin LastScrollOrigin() const { return mLastScrollOrigin; }
   ScrollOrigin LastSmoothScrollOrigin() const {
     return mLastSmoothScrollOrigin;
   }
+  bool IsApzAnimationInProgress() const { return mApzAnimationInProgress; }
   uint32_t CurrentScrollGeneration() const { return mScrollGeneration; }
   nsPoint LastScrollDestination() const { return mDestination; }
-  void ResetScrollInfoIfGeneration(uint32_t aGeneration) {
+
+  using IncludeApzAnimation = nsIScrollableFrame::IncludeApzAnimation;
+  bool IsScrollAnimating(IncludeApzAnimation = IncludeApzAnimation::Yes) const;
+
+  void ResetScrollInfoIfNeeded(uint32_t aGeneration,
+                               bool aApzAnimationInProgress) {
     if (aGeneration == mScrollGeneration) {
       mLastScrollOrigin = ScrollOrigin::NotSpecified;
       mLastSmoothScrollOrigin = ScrollOrigin::None;
     }
+    // We can reset this regardless of scroll generation, as this is only set
+    // here, as a response to APZ requesting a repaint.
+    mApzAnimationInProgress = aApzAnimationInProgress;
   }
   Maybe<nsPoint> GetRelativeOffset() const { return mRelativeOffset; }
   bool WantAsyncScroll() const;
   Maybe<mozilla::layers::ScrollMetadata> ComputeScrollMetadata(
       LayerManager* aLayerManager, const nsIFrame* aContainerReferenceFrame,
       const Maybe<ContainerLayerParameters>& aParameters,
       const mozilla::DisplayItemClip* aClip) const;
   void ClipLayerToDisplayPort(
@@ -700,16 +706,23 @@ class ScrollFrameHelper : public nsIRefl
   bool mIsUsingMinimumScaleSize : 1;
 
   // True if the minimum scale size has been changed since the last reflow.
   bool mMinimumScaleSizeChanged : 1;
 
   // True if we're processing an scroll event.
   bool mProcessingScrollEvent : 1;
 
+  // Whether an APZ animation is in progress. Note that this is only set to true
+  // when repainted via APZ, which means that there may be a request for an APZ
+  // animation in flight for example, while this is still false. In order to
+  // answer "is an APZ animation in the process of starting or in progress" you
+  // need to check both mLastSmoothScrollOrigin and this bit.
+  bool mApzAnimationInProgress : 1;
+
   mozilla::layout::ScrollVelocityQueue mVelocityQueue;
 
  protected:
   class AutoScrollbarRepaintSuppression;
   friend class AutoScrollbarRepaintSuppression;
   class AutoScrollbarRepaintSuppression {
    public:
     AutoScrollbarRepaintSuppression(ScrollFrameHelper* aHelper,
@@ -1013,43 +1026,44 @@ class nsHTMLScrollFrame : public nsConta
     return mHelper.IsScrollingActive(aBuilder);
   }
   bool IsMaybeScrollingActive() const final {
     return mHelper.IsMaybeScrollingActive();
   }
   bool IsMaybeAsynchronouslyScrolled() final {
     return mHelper.IsMaybeAsynchronouslyScrolled();
   }
-  bool IsProcessingAsyncScroll() final {
-    return mHelper.IsProcessingAsyncScroll();
-  }
   void ResetScrollPositionForLayerPixelAlignment() final {
     mHelper.ResetScrollPositionForLayerPixelAlignment();
   }
   bool DidHistoryRestore() const final { return mHelper.mDidHistoryRestore; }
   void ClearDidHistoryRestore() final { mHelper.mDidHistoryRestore = false; }
   void MarkEverScrolled() final { mHelper.MarkEverScrolled(); }
   bool IsRectNearlyVisible(const nsRect& aRect) final {
     return mHelper.IsRectNearlyVisible(aRect);
   }
   nsRect ExpandRectToNearlyVisible(const nsRect& aRect) const final {
     return mHelper.ExpandRectToNearlyVisible(aRect);
   }
   ScrollOrigin LastScrollOrigin() final { return mHelper.LastScrollOrigin(); }
   ScrollOrigin LastSmoothScrollOrigin() final {
     return mHelper.LastSmoothScrollOrigin();
   }
+  bool IsScrollAnimating(IncludeApzAnimation aIncludeApz) final {
+    return mHelper.IsScrollAnimating(aIncludeApz);
+  }
   uint32_t CurrentScrollGeneration() final {
     return mHelper.CurrentScrollGeneration();
   }
   nsPoint LastScrollDestination() final {
     return mHelper.LastScrollDestination();
   }
-  void ResetScrollInfoIfGeneration(uint32_t aGeneration) final {
-    mHelper.ResetScrollInfoIfGeneration(aGeneration);
+  void ResetScrollInfoIfNeeded(uint32_t aGeneration,
+                               bool aApzAnimationInProgress) final {
+    mHelper.ResetScrollInfoIfNeeded(aGeneration, aApzAnimationInProgress);
   }
   Maybe<nsPoint> GetRelativeOffset() const final {
     return mHelper.GetRelativeOffset();
   }
   bool WantAsyncScroll() const final { return mHelper.WantAsyncScroll(); }
   mozilla::Maybe<mozilla::layers::ScrollMetadata> ComputeScrollMetadata(
       LayerManager* aLayerManager, const nsIFrame* aContainerReferenceFrame,
       const Maybe<ContainerLayerParameters>& aParameters,
@@ -1488,43 +1502,44 @@ class nsXULScrollFrame final : public ns
     return mHelper.IsScrollingActive(aBuilder);
   }
   bool IsMaybeScrollingActive() const final {
     return mHelper.IsMaybeScrollingActive();
   }
   bool IsMaybeAsynchronouslyScrolled() final {
     return mHelper.IsMaybeAsynchronouslyScrolled();
   }
-  bool IsProcessingAsyncScroll() final {
-    return mHelper.IsProcessingAsyncScroll();
-  }
   void ResetScrollPositionForLayerPixelAlignment() final {
     mHelper.ResetScrollPositionForLayerPixelAlignment();
   }
   bool DidHistoryRestore() const final { return mHelper.mDidHistoryRestore; }
   void ClearDidHistoryRestore() final { mHelper.mDidHistoryRestore = false; }
   void MarkEverScrolled() final { mHelper.MarkEverScrolled(); }
   bool IsRectNearlyVisible(const nsRect& aRect) final {
     return mHelper.IsRectNearlyVisible(aRect);
   }
   nsRect ExpandRectToNearlyVisible(const nsRect& aRect) const final {
     return mHelper.ExpandRectToNearlyVisible(aRect);
   }
   ScrollOrigin LastScrollOrigin() final { return mHelper.LastScrollOrigin(); }
   ScrollOrigin LastSmoothScrollOrigin() final {
     return mHelper.LastSmoothScrollOrigin();
   }
+  bool IsScrollAnimating(IncludeApzAnimation aIncludeApz) final {
+    return mHelper.IsScrollAnimating(aIncludeApz);
+  }
   uint32_t CurrentScrollGeneration() final {
     return mHelper.CurrentScrollGeneration();
   }
   nsPoint LastScrollDestination() final {
     return mHelper.LastScrollDestination();
   }
-  void ResetScrollInfoIfGeneration(uint32_t aGeneration) final {
-    mHelper.ResetScrollInfoIfGeneration(aGeneration);
+  void ResetScrollInfoIfNeeded(uint32_t aGeneration,
+                               bool aApzAnimationInProgress) final {
+    mHelper.ResetScrollInfoIfNeeded(aGeneration, aApzAnimationInProgress);
   }
   Maybe<nsPoint> GetRelativeOffset() const final {
     return mHelper.GetRelativeOffset();
   }
   bool WantAsyncScroll() const final { return mHelper.WantAsyncScroll(); }
   mozilla::Maybe<mozilla::layers::ScrollMetadata> ComputeScrollMetadata(
       LayerManager* aLayerManager, const nsIFrame* aContainerReferenceFrame,
       const Maybe<ContainerLayerParameters>& aParameters,
--- a/layout/generic/nsIScrollableFrame.h
+++ b/layout/generic/nsIScrollableFrame.h
@@ -376,21 +376,16 @@ class nsIScrollableFrame : public nsIScr
   virtual bool IsMaybeAsynchronouslyScrolled() = 0;
 
   /**
    * Same as the above except doesn't take into account will-change budget,
    * which means that it can be called during display list building.
    */
   virtual bool IsMaybeScrollingActive() const = 0;
   /**
-   * Returns true if the scrollframe is currently processing an async
-   * or smooth scroll.
-   */
-  virtual bool IsProcessingAsyncScroll() = 0;
-  /**
    * Call this when the layer(s) induced by active scrolling are being
    * completely redrawn.
    */
   virtual void ResetScrollPositionForLayerPixelAlignment() = 0;
   /**
    * Was the current presentation state for this frame restored from history?
    */
   virtual bool DidHistoryRestore() const = 0;
@@ -431,32 +426,48 @@ class nsIScrollableFrame : public nsIScr
    * replication of the frame metrics.
    *
    * This is set to nullptr to when the compositor thread acknowledges that
    * the smooth scroll has been started.  If the smooth scroll has been stomped
    * by an instant scroll before the smooth scroll could be started by the
    * compositor, this is set to nullptr to clear the smooth scroll.
    */
   virtual ScrollOrigin LastSmoothScrollOrigin() = 0;
+
+  /**
+   * Returns whether there's an async scroll going on.
+   *
+   * The argument allows a subtle distinction that's needed for APZ. When
+   * `IncludeApzAnimation::No` is given, ongoing APZ animations that have
+   * already been synced to the main thread are not included, which is needed so
+   * that APZ can keep syncing the scroll offset properly.
+   */
+  enum class IncludeApzAnimation : bool { No, Yes };
+  virtual bool IsScrollAnimating(
+      IncludeApzAnimation = IncludeApzAnimation::Yes) = 0;
+
   /**
    * Returns the current generation counter for the scroll. This counter
    * increments every time the scroll position is set.
    */
   virtual uint32_t CurrentScrollGeneration() = 0;
   /**
    * LastScrollDestination returns the destination of the most recently
    * requested smooth scroll animation.
    */
   virtual nsPoint LastScrollDestination() = 0;
   /**
    * Clears the "origin of last scroll" property stored in this frame, if
    * the generation counter passed in matches the current scroll generation
-   * counter.
+   * counter, and clears the "origin of last smooth scroll" property if the
+   * generation counter matches. It also resets whether there's an ongoing apz
+   * animation.
    */
-  virtual void ResetScrollInfoIfGeneration(uint32_t aGeneration) = 0;
+  virtual void ResetScrollInfoIfNeeded(uint32_t aGeneration,
+                                       bool aApzAnimationInProgress) = 0;
   /**
    * Relative scrolling offset to be requested of apz.
    */
   virtual Maybe<nsPoint> GetRelativeOffset() const = 0;
   /**
    * Determine whether it is desirable to be able to asynchronously scroll this
    * scroll frame.
    */