Bug 1502059 - Don't always cancel a scroll animation when we have a relative scroll offset update. r=botond
authorRyan Hunt <rhunt@eqrion.net>
Thu, 25 Oct 2018 17:21:29 -0500
changeset 503085 b04f3425ee0361c2dde602efd1465f38ec72ef97
parent 503033 e28fa79bc2f94ca3b72456b47353f2e2dda8da1a
child 503086 2590832f34408f2ece66fb5d6a688d141d1df5bd
push id1905
push userffxbld-merge
push dateMon, 21 Jan 2019 12:33:13 +0000
treeherdermozilla-release@c2fca1944d8c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbotond
bugs1502059
milestone65.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1502059 - Don't always cancel a scroll animation when we have a relative scroll offset update. r=botond Differential Revision: https://phabricator.services.mozilla.com/D9871
gfx/layers/FrameMetrics.h
gfx/layers/apz/src/AsyncPanZoomAnimation.h
gfx/layers/apz/src/AsyncPanZoomController.cpp
gfx/layers/apz/src/AutoscrollAnimation.h
gfx/layers/apz/src/GenericScrollAnimation.cpp
gfx/layers/apz/src/GenericScrollAnimation.h
layout/generic/ScrollAnimationBezierPhysics.cpp
layout/generic/ScrollAnimationBezierPhysics.h
layout/generic/ScrollAnimationMSDPhysics.cpp
layout/generic/ScrollAnimationMSDPhysics.h
layout/generic/ScrollAnimationPhysics.h
--- a/gfx/layers/FrameMetrics.h
+++ b/gfx/layers/FrameMetrics.h
@@ -271,24 +271,38 @@ public:
 
   void ApplySmoothScrollUpdateFrom(const FrameMetrics& aOther)
   {
     mSmoothScrollOffset = aOther.mSmoothScrollOffset;
     mScrollGeneration = aOther.mScrollGeneration;
     mDoSmoothScroll = aOther.mDoSmoothScroll;
   }
 
-  void ApplyRelativeScrollUpdateFrom(const FrameMetrics& aOther)
+  /**
+   * Applies the relative scroll offset update contained in aOther to the
+   * scroll offset contained in this. The scroll delta is clamped to the
+   * scrollable region.
+   *
+   * @returns The clamped scroll offset delta that was applied
+   */
+  CSSPoint ApplyRelativeScrollUpdateFrom(const FrameMetrics& aOther)
   {
     MOZ_ASSERT(aOther.IsRelative());
+    CSSPoint origin = mScrollOffset;
     CSSPoint delta = (aOther.mScrollOffset - aOther.mBaseScrollOffset);
     ClampAndSetScrollOffset(mScrollOffset + delta);
     mScrollGeneration = aOther.mScrollGeneration;
+    return mScrollOffset - origin;
   }
 
+  /**
+   * Applies the relative scroll offset update contained in aOther to the
+   * smooth scroll destination offset contained in this. The scroll delta is
+   * clamped to the scrollable region.
+   */
   void ApplyRelativeSmoothScrollUpdateFrom(const FrameMetrics& aOther)
   {
     MOZ_ASSERT(aOther.IsRelative());
     CSSPoint delta = (aOther.mSmoothScrollOffset - aOther.mBaseScrollOffset);
     ClampAndSetSmoothScrollOffset(mScrollOffset + delta);
     mScrollGeneration = aOther.mScrollGeneration;
     mDoSmoothScroll = aOther.mDoSmoothScroll;
   }
--- a/gfx/layers/apz/src/AsyncPanZoomAnimation.h
+++ b/gfx/layers/apz/src/AsyncPanZoomAnimation.h
@@ -26,16 +26,29 @@ class AsyncPanZoomAnimation {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AsyncPanZoomAnimation)
 
 public:
   explicit AsyncPanZoomAnimation() = default;
 
   virtual bool DoSample(FrameMetrics& aFrameMetrics,
                         const TimeDuration& aDelta) = 0;
 
+  /**
+   * Attempt to apply a translation to the animation in response to content
+   * providing a relative scroll offset update.
+   *
+   * @param aShiftDelta the amount to translate the animation in app units
+   * @returns Whether the animation was able to translate. If false, the
+   *    animation must be canceled.
+   */
+  virtual bool ApplyContentShift(const CSSPoint& aShiftDelta)
+  {
+    return false;
+  }
+
   bool Sample(FrameMetrics& aFrameMetrics,
               const TimeDuration& aDelta) {
     // In some situations, particularly when handoff is involved, it's possible
     // for |aDelta| to be negative on the first call to sample. Ignore such a
     // sample here, to avoid each derived class having to deal with this case.
     if (aDelta.ToMilliseconds() <= 0) {
       return true;
     }
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -4375,48 +4375,56 @@ void AsyncPanZoomController::NotifyLayer
     if (scrollOffsetUpdated) {
       // Send an acknowledgement with the new scroll generation so that any
       // repaint requests later in this function go through.
       // Because of the scroll generation update, any inflight paint requests are
       // going to be ignored by layout, and so mExpectedGeckoMetrics
       // becomes incorrect for the purposes of calculating the LD transform. To
       // correct this we need to update mExpectedGeckoMetrics to be the
       // last thing we know was painted by Gecko.
+      Maybe<CSSPoint> relativeDelta;
       if (gfxPrefs::APZRelativeUpdate() && aLayerMetrics.IsRelative()) {
         APZC_LOG("%p relative updating scroll offset from %s by %s\n", this,
           ToString(Metrics().GetScrollOffset()).c_str(),
           ToString(aLayerMetrics.GetScrollOffset() - aLayerMetrics.GetBaseScrollOffset()).c_str());
 
         // It's possible that the main thread has ignored an APZ scroll offset
         // update for the pending relative scroll that we have just received.
         // When this happens, we need to send a new scroll offset update with
         // the combined scroll offset or else the main thread may have an
         // incorrect scroll offset for a period of time.
         if (Metrics().HasPendingScroll(aLayerMetrics)) {
           needContentRepaint = true;
           userAction = true;
         }
 
-        Metrics().ApplyRelativeScrollUpdateFrom(aLayerMetrics);
+        relativeDelta = Some(Metrics().ApplyRelativeScrollUpdateFrom(aLayerMetrics));
       } else {
         APZC_LOG("%p updating scroll offset from %s to %s\n", this,
           ToString(Metrics().GetScrollOffset()).c_str(),
           ToString(aLayerMetrics.GetScrollOffset()).c_str());
         Metrics().ApplyScrollUpdateFrom(aLayerMetrics);
       }
       Metrics().RecalculateViewportOffset();
 
       mCompositedLayoutViewport = Metrics().GetViewport();
       mCompositedScrollOffset = Metrics().GetScrollOffset();
       mExpectedGeckoMetrics = aLayerMetrics;
 
-      // Cancel the animation (which might also trigger a repaint request)
-      // after we update the scroll offset above. Otherwise we can be left
-      // in a state where things are out of sync.
-      CancelAnimation();
+      // If we have applied a relative scroll update and a scroll animation is
+      // happening, attempt to apply a content shift and preserve the
+      // animation.
+      if (!mAnimation ||
+          relativeDelta.isNothing() ||
+          !mAnimation->ApplyContentShift(relativeDelta.value())) {
+        // Cancel the animation (which might also trigger a repaint request)
+        // after we update the scroll offset above. Otherwise we can be left
+        // in a state where things are out of sync.
+        CancelAnimation();
+      }
 
       // Since the scroll offset has changed, we need to recompute the
       // displayport margins and send them to layout. Otherwise there might be
       // scenarios where for example we scroll from the top of a page (where the
       // top displayport margin is zero) to the bottom of a page, which will
       // result in a displayport that doesn't extend upwards at all.
       // Note that even if the CancelAnimation call above requested a repaint
       // this is fine because we already have repaint request deduplication.
--- a/gfx/layers/apz/src/AutoscrollAnimation.h
+++ b/gfx/layers/apz/src/AutoscrollAnimation.h
@@ -17,16 +17,23 @@ class AsyncPanZoomController;
 class AutoscrollAnimation : public AsyncPanZoomAnimation
 {
 public:
   AutoscrollAnimation(AsyncPanZoomController& aApzc,
                       const ScreenPoint& aAnchorLocation);
 
   bool DoSample(FrameMetrics& aFrameMetrics, const TimeDuration& aDelta) override;
 
+  bool ApplyContentShift(const CSSPoint& aShiftDelta) override
+  {
+    // Autoscroll works using screen space coordinates, so there's no work we
+    // need to do to handle a content shift
+    return true;
+  }
+
   void Cancel(CancelAnimationFlags aFlags) override;
 private:
   AsyncPanZoomController& mApzc;
   ScreenPoint mAnchorLocation;
 };
 
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/apz/src/GenericScrollAnimation.cpp
+++ b/gfx/layers/apz/src/GenericScrollAnimation.cpp
@@ -104,10 +104,17 @@ GenericScrollAnimation::DoSample(FrameMe
     // Nothing more to do - end the animation.
     return false;
   }
 
   mApzc.ScrollBy(adjustedOffset / zoom);
   return !finished;
 }
 
+bool
+GenericScrollAnimation::ApplyContentShift(const CSSPoint& aShiftDelta)
+{
+  mAnimationPhysics->ApplyContentShift(aShiftDelta);
+  return true;
+}
+
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/apz/src/GenericScrollAnimation.h
+++ b/gfx/layers/apz/src/GenericScrollAnimation.h
@@ -23,16 +23,18 @@ class GenericScrollAnimation
 {
 public:
   GenericScrollAnimation(AsyncPanZoomController& aApzc,
                          const nsPoint& aInitialPosition,
                          const ScrollAnimationBezierPhysicsSettings& aSettings);
 
   bool DoSample(FrameMetrics& aFrameMetrics, const TimeDuration& aDelta) override;
 
+  bool ApplyContentShift(const CSSPoint& aShiftDelta) override;
+
   void UpdateDelta(TimeStamp aTime,
                    const nsPoint& aDelta,
                    const nsSize& aCurrentVelocity);
   void UpdateDestination(TimeStamp aTime,
                          const nsPoint& aDestination,
                          const nsSize& aCurrentVelocity);
 
   CSSPoint GetDestination() const {
--- a/layout/generic/ScrollAnimationBezierPhysics.cpp
+++ b/layout/generic/ScrollAnimationBezierPhysics.cpp
@@ -48,16 +48,24 @@ ScrollAnimationBezierPhysics::Update(con
   mDestination = aDestination;
   InitTimingFunction(mTimingFunctionX, mStartPos.x, currentVelocity.width,
                      aDestination.x);
   InitTimingFunction(mTimingFunctionY, mStartPos.y, currentVelocity.height,
                      aDestination.y);
   mIsFirstIteration = false;
 }
 
+void
+ScrollAnimationBezierPhysics::ApplyContentShift(const CSSPoint& aShiftDelta)
+{
+  nsPoint shiftDelta = CSSPoint::ToAppUnits(aShiftDelta);
+  mStartPos += shiftDelta;
+  mDestination += shiftDelta;
+}
+
 TimeDuration
 ScrollAnimationBezierPhysics::ComputeDuration(const TimeStamp& aTime)
 {
   // Average last 3 delta durations (rounding errors up to 2ms are negligible for us)
   int32_t eventsDeltaMs = (aTime - mPrevEventTime[2]).ToMilliseconds() / 3;
   mPrevEventTime[2] = mPrevEventTime[1];
   mPrevEventTime[1] = mPrevEventTime[0];
   mPrevEventTime[0] = aTime;
--- a/layout/generic/ScrollAnimationBezierPhysics.h
+++ b/layout/generic/ScrollAnimationBezierPhysics.h
@@ -30,16 +30,18 @@ class ScrollAnimationBezierPhysics final
 public:
   explicit ScrollAnimationBezierPhysics(const nsPoint& aStartPos,
                                         const ScrollAnimationBezierPhysicsSettings& aSettings);
 
   void Update(const TimeStamp& aTime,
               const nsPoint& aDestination,
               const nsSize& aCurrentVelocity) override;
 
+  void ApplyContentShift(const CSSPoint& aShiftDelta) override;
+
   // Get the velocity at a point in time in nscoords/sec.
   nsSize VelocityAt(const TimeStamp& aTime) override;
 
   // Returns the expected scroll position at a given point in time, in app
   // units, relative to the scroll frame.
   nsPoint PositionAt(const TimeStamp& aTime) override;
 
   bool IsFinished(const TimeStamp& aTime) override {
--- a/layout/generic/ScrollAnimationMSDPhysics.cpp
+++ b/layout/generic/ScrollAnimationMSDPhysics.cpp
@@ -43,16 +43,24 @@ ScrollAnimationMSDPhysics::Update(const 
   mDestination = aDestination;
   mModelX = AxisPhysicsMSDModel(mStartPos.x, aDestination.x,
                                 aCurrentVelocity.width, springConstant, 1);
   mModelY = AxisPhysicsMSDModel(mStartPos.y, aDestination.y,
                                 aCurrentVelocity.height, springConstant, 1);
   mIsFirstIteration = false;
 }
 
+void
+ScrollAnimationMSDPhysics::ApplyContentShift(const CSSPoint& aShiftDelta)
+{
+  nsPoint shiftDelta = CSSPoint::ToAppUnits(aShiftDelta);
+  mStartPos += shiftDelta;
+  mDestination += shiftDelta;
+}
+
 double
 ScrollAnimationMSDPhysics::ComputeSpringConstant(const TimeStamp& aTime)
 {
   if (!mPreviousEventTime) {
     mPreviousEventTime = aTime;
     mPreviousDelta = TimeDuration();
     return gfxPrefs::SmoothScrollMSDPhysicsMotionBeginSpringConstant();
   }
--- a/layout/generic/ScrollAnimationMSDPhysics.h
+++ b/layout/generic/ScrollAnimationMSDPhysics.h
@@ -20,16 +20,18 @@ public:
   typedef mozilla::layers::AxisPhysicsMSDModel AxisPhysicsMSDModel;
 
   explicit ScrollAnimationMSDPhysics(const nsPoint& aStartPos);
 
   void Update(const TimeStamp& aTime,
               const nsPoint& aDestination,
               const nsSize& aCurrentVelocity) override;
 
+  void ApplyContentShift(const CSSPoint& aShiftDelta) override;
+
   // Get the velocity at a point in time in nscoords/sec.
   nsSize VelocityAt(const TimeStamp& aTime) override;
 
   // Returns the expected scroll position at a given point in time, in app
   // units, relative to the scroll frame.
   nsPoint PositionAt(const TimeStamp& aTime) override;
 
   bool IsFinished(const TimeStamp& aTime) override {
--- a/layout/generic/ScrollAnimationPhysics.h
+++ b/layout/generic/ScrollAnimationPhysics.h
@@ -14,16 +14,18 @@ namespace mozilla {
 
 class ScrollAnimationPhysics
 {
 public:
   virtual void Update(const TimeStamp& aTime,
                       const nsPoint& aDestination,
                       const nsSize& aCurrentVelocity) = 0;
 
+  virtual void ApplyContentShift(const CSSPoint& aShiftDelta) = 0;
+
   // Get the velocity at a point in time in nscoords/sec.
   virtual nsSize VelocityAt(const TimeStamp& aTime) = 0;
 
   // Returns the expected scroll position at a given point in time, in app
   // units, relative to the scroll frame.
   virtual nsPoint PositionAt(const TimeStamp& aTime) = 0;
 
   virtual bool IsFinished(const TimeStamp& aTime) = 0;