Bug 1249040 - Allow wheel scrolls to accumulate in the presence of scroll snapping. r=kats draft
authorBotond Ballo <botond@mozilla.com>
Thu, 07 Apr 2016 17:41:55 -0400
changeset 349476 b9f9bbb7a11a976ff696c28b026d292a8f90d0e1
parent 348681 3dbbf506be2c4fe2ee9b9a04d604b1869ba5e909
child 352776 e897927f1ca89a175e4014264d3dea93353ec891
push id15098
push userbballo@mozilla.com
push dateMon, 11 Apr 2016 17:18:20 +0000
reviewerskats
bugs1249040
milestone48.0a1
Bug 1249040 - Allow wheel scrolls to accumulate in the presence of scroll snapping. r=kats MozReview-Commit-ID: EUyGvkoyu8I
gfx/layers/AxisPhysicsMSDModel.cpp
gfx/layers/AxisPhysicsMSDModel.h
gfx/layers/apz/src/AsyncPanZoomAnimation.h
gfx/layers/apz/src/AsyncPanZoomController.cpp
gfx/layers/apz/src/AsyncPanZoomController.h
gfx/layers/apz/src/WheelScrollAnimation.h
--- a/gfx/layers/AxisPhysicsMSDModel.cpp
+++ b/gfx/layers/AxisPhysicsMSDModel.cpp
@@ -53,17 +53,17 @@ AxisPhysicsMSDModel::Acceleration(const 
   double spring_force = (mDestination - aState.p) * mSpringConstant;
   double damp_force = -aState.v * mDampingRatio * mSpringConstantSqrtXTwo;
 
   return spring_force + damp_force;
 }
 
 
 double
-AxisPhysicsMSDModel::GetDestination()
+AxisPhysicsMSDModel::GetDestination() const
 {
   return mDestination;
 }
 
 void
 AxisPhysicsMSDModel::SetDestination(double aDestination)
 {
   mDestination = aDestination;
--- a/gfx/layers/AxisPhysicsMSDModel.h
+++ b/gfx/layers/AxisPhysicsMSDModel.h
@@ -22,17 +22,17 @@ public:
                       double aInitialVelocity, double aSpringConstant,
                       double aDampingRatio);
 
   ~AxisPhysicsMSDModel();
 
   /**
    * Gets the raw destination of this axis at this moment.
    */
-  double GetDestination();
+  double GetDestination() const;
 
   /**
    * Sets the raw destination of this axis at this moment.
    */
   void SetDestination(double aDestination);
 
   /**
    * Returns true when the position is close to the destination and the
--- a/gfx/layers/apz/src/AsyncPanZoomAnimation.h
+++ b/gfx/layers/apz/src/AsyncPanZoomAnimation.h
@@ -13,16 +13,17 @@
 #include "mozilla/Vector.h"
 #include "FrameMetrics.h"
 #include "nsISupportsImpl.h"
 
 namespace mozilla {
 namespace layers {
 
 class WheelScrollAnimation;
+class SmoothScrollAnimation;
 
 class AsyncPanZoomAnimation {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AsyncPanZoomAnimation)
 
 public:
   explicit AsyncPanZoomAnimation()
   { }
 
@@ -47,16 +48,19 @@ public:
    */
   Vector<Task*> TakeDeferredTasks() {
     return Move(mDeferredTasks);
   }
 
   virtual WheelScrollAnimation* AsWheelScrollAnimation() {
     return nullptr;
   }
+  virtual SmoothScrollAnimation* AsSmoothScrollAnimation() {
+    return nullptr;
+  }
 
   virtual bool WantsRepaints() {
     return true;
   }
 
 protected:
   // Protected destructor, to discourage deletion outside of Release():
   virtual ~AsyncPanZoomAnimation()
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -727,17 +727,17 @@ public:
   }
 
   /**
    * Advances a smooth scroll simulation based on the time passed in |aDelta|.
    * This should be called whenever sampling the content transform for this
    * frame. Returns true if the smooth scroll should be advanced by one frame,
    * or false if the smooth scroll has ended.
    */
-  bool DoSample(FrameMetrics& aFrameMetrics, const TimeDuration& aDelta) {
+  bool DoSample(FrameMetrics& aFrameMetrics, const TimeDuration& aDelta) override {
     nsPoint oneParentLayerPixel =
       CSSPoint::ToAppUnits(ParentLayerPoint(1, 1) / aFrameMetrics.GetZoom());
     if (mXAxisModel.IsFinished(oneParentLayerPixel.x) &&
         mYAxisModel.IsFinished(oneParentLayerPixel.y)) {
       // Set the scroll offset to the exact destination. If we allow the scroll
       // offset to end up being a bit off from the destination, we can get
       // artefacts like "scroll to the next snap point in this direction"
       // scrolling to the snap point we're already supposed to be at.
@@ -824,16 +824,25 @@ public:
     return true;
   }
 
   void SetDestination(const nsPoint& aNewDestination) {
     mXAxisModel.SetDestination(static_cast<int32_t>(aNewDestination.x));
     mYAxisModel.SetDestination(static_cast<int32_t>(aNewDestination.y));
   }
 
+  CSSPoint GetDestination() const {
+    return CSSPoint::FromAppUnits(
+        nsPoint(mXAxisModel.GetDestination(), mYAxisModel.GetDestination()));
+  }
+
+  SmoothScrollAnimation* AsSmoothScrollAnimation() override {
+    return this;
+  }
+
 private:
   AsyncPanZoomController& mApzc;
   AxisPhysicsMSDModel mXAxisModel, mYAxisModel;
 };
 
 /*static*/ void
 AsyncPanZoomController::InitializeGlobalState()
 {
@@ -1802,23 +1811,26 @@ nsEventStatus AsyncPanZoomController::On
   if (delta.x == 0 && delta.y == 0) {
     // Avoid spurious state changes and unnecessary work
     return nsEventStatus_eIgnore;
   }
 
   mozilla::Telemetry::Accumulate(mozilla::Telemetry::SCROLL_INPUT_METHODS,
       (uint32_t) ScrollInputMethodForWheelDeltaType(aEvent.mDeltaType));
 
-  // Wheel events from "clicky" mouse wheels trigger scroll snapping to the
-  // next snap point. Check for this, and adjust the delta to take into
-  // account the snap point.
-  bool scrollSnapping = MaybeAdjustDeltaForScrollSnapping(delta, aEvent);
 
   switch (aEvent.mScrollMode) {
     case ScrollWheelInput::SCROLLMODE_INSTANT: {
+
+      // Wheel events from "clicky" mouse wheels trigger scroll snapping to the
+      // next snap point. Check for this, and adjust the delta to take into
+      // account the snap point.
+      CSSPoint startPosition = mFrameMetrics.GetScrollOffset();
+      MaybeAdjustDeltaForScrollSnapping(aEvent, delta, startPosition);
+
       ScreenPoint distance = ToScreenCoordinates(
         ParentLayerPoint(fabs(delta.x), fabs(delta.y)), aEvent.mLocalOrigin);
 
       CancelAnimation();
 
       OverscrollHandoffState handoffState(
           *mInputQueue->CurrentWheelBlock()->GetOverscrollHandoffChain(),
           distance,
@@ -1838,43 +1850,53 @@ nsEventStatus AsyncPanZoomController::On
     }
 
     case ScrollWheelInput::SCROLLMODE_SMOOTH: {
       // The lock must be held across the entire update operation, so the
       // compositor doesn't end the animation before we get a chance to
       // update it.
       ReentrantMonitorAutoEnter lock(mMonitor);
 
-      if (scrollSnapping) {
-        // If we're scroll snapping use a smooth scroll animation to get
+      // Perform scroll snapping if appropriate.
+      CSSPoint startPosition = mFrameMetrics.GetScrollOffset();
+      // If we're already in a wheel scroll or smooth scroll animation,
+      // the delta is applied to its destination, not to the current
+      // scroll position. Take this into account when finding a snap point.
+      if (mState == WHEEL_SCROLL) {
+        startPosition = mAnimation->AsWheelScrollAnimation()->GetDestination();
+      } else if (mState == SMOOTH_SCROLL) {
+        startPosition = mAnimation->AsSmoothScrollAnimation()->GetDestination();
+      }
+      if (MaybeAdjustDeltaForScrollSnapping(aEvent, delta, startPosition)) {
+        // If we're scroll snapping, use a smooth scroll animation to get
         // the desired physics. Note that SmoothScrollTo() will re-use an
         // existing smooth scroll animation if there is one.
-        CSSPoint snapPoint = mFrameMetrics.GetScrollOffset() + (delta / mFrameMetrics.GetZoom());
-        SmoothScrollTo(snapPoint);
-      } else {
-        // Otherwise, use a wheel scroll animation, also reusing one if possible.
-        if (mState != WHEEL_SCROLL) {
-          CancelAnimation();
-          SetState(WHEEL_SCROLL);
-
-          nsPoint initialPosition = CSSPoint::ToAppUnits(mFrameMetrics.GetScrollOffset());
-          StartAnimation(new WheelScrollAnimation(
-            *this, initialPosition, aEvent.mDeltaType));
-        }
-
-        nsPoint deltaInAppUnits =
-          CSSPoint::ToAppUnits(delta / mFrameMetrics.GetZoom());
-        // Cast velocity from ParentLayerPoints/ms to CSSPoints/ms then convert to
-        // appunits/second
-        nsPoint velocity =
-          CSSPoint::ToAppUnits(CSSPoint(mX.GetVelocity(), mY.GetVelocity())) * 1000.0f;
-
-        WheelScrollAnimation* animation = mAnimation->AsWheelScrollAnimation();
-        animation->Update(aEvent.mTimeStamp, deltaInAppUnits, nsSize(velocity.x, velocity.y));
+        SmoothScrollTo(startPosition);
+        break;
       }
+
+      // Otherwise, use a wheel scroll animation, also reusing one if possible.
+      if (mState != WHEEL_SCROLL) {
+        CancelAnimation();
+        SetState(WHEEL_SCROLL);
+
+        nsPoint initialPosition = CSSPoint::ToAppUnits(mFrameMetrics.GetScrollOffset());
+        StartAnimation(new WheelScrollAnimation(
+          *this, initialPosition, aEvent.mDeltaType));
+      }
+
+      nsPoint deltaInAppUnits =
+        CSSPoint::ToAppUnits(delta / mFrameMetrics.GetZoom());
+      // Cast velocity from ParentLayerPoints/ms to CSSPoints/ms then convert to
+      // appunits/second
+      nsPoint velocity =
+        CSSPoint::ToAppUnits(CSSPoint(mX.GetVelocity(), mY.GetVelocity())) * 1000.0f;
+
+      WheelScrollAnimation* animation = mAnimation->AsWheelScrollAnimation();
+      animation->Update(aEvent.mTimeStamp, deltaInAppUnits, nsSize(velocity.x, velocity.y));
       break;
     }
   }
 
   return nsEventStatus_eConsumeNoDefault;
 }
 
 void
@@ -3975,33 +3997,35 @@ void AsyncPanZoomController::ScrollSnapT
              (float)mFrameMetrics.GetScrollOffset().y,
              (float)predictedDestination.x, (float)predictedDestination.y);
 
     ScrollSnapNear(predictedDestination);
   }
 }
 
 bool AsyncPanZoomController::MaybeAdjustDeltaForScrollSnapping(
-    ParentLayerPoint& aDelta, const ScrollWheelInput& aEvent)
+    const ScrollWheelInput& aEvent,
+    ParentLayerPoint& aDelta,
+    CSSPoint& aStartPosition)
 {
   // Don't scroll snap for pixel scrolls. This matches the main thread
   // behaviour in EventStateManager::DoScrollText().
   if (aEvent.mDeltaType == ScrollWheelInput::SCROLLDELTA_PIXEL) {
     return false;
   }
 
   ReentrantMonitorAutoEnter lock(mMonitor);
-  CSSPoint scrollOffset = mFrameMetrics.GetScrollOffset();
   CSSToParentLayerScale2D zoom = mFrameMetrics.GetZoom();
   CSSPoint destination = mFrameMetrics.CalculateScrollRange().ClampPoint(
-      scrollOffset + (aDelta / zoom));
+      aStartPosition + (aDelta / zoom));
   nsIScrollableFrame::ScrollUnit unit =
       ScrollWheelInput::ScrollUnitForDeltaType(aEvent.mDeltaType);
 
   if (Maybe<CSSPoint> snapPoint = FindSnapPointNear(destination, unit)) {
-    aDelta = (*snapPoint - scrollOffset) * zoom;
+    aDelta = (*snapPoint - aStartPosition) * zoom;
+    aStartPosition = *snapPoint;
     return true;
   }
   return false;
 }
 
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/apz/src/AsyncPanZoomController.h
+++ b/gfx/layers/apz/src/AsyncPanZoomController.h
@@ -1124,19 +1124,23 @@ private:
 
 
   /* ===================================================================
    * The functions in this section are used for CSS scroll snapping.
    */
 
   // If |aEvent| should trigger scroll snapping, adjust |aDelta| to reflect
   // the snapping (that is, make it a delta that will take us to the desired
-  // snap point). Returns true iff. the delta was so adjusted.
-  bool MaybeAdjustDeltaForScrollSnapping(ParentLayerPoint& aDelta,
-                                         const ScrollWheelInput& aEvent);
+  // snap point). The delta is interpreted as being relative to
+  // |aStartPosition|, and if a target snap point is found, |aStartPosition|
+  // is also updated, to the value of the snap point.
+  // Returns true iff. a target snap point was found.
+  bool MaybeAdjustDeltaForScrollSnapping(const ScrollWheelInput& aEvent,
+                                         ParentLayerPoint& aDelta,
+                                         CSSPoint& aStartPosition);
 
   // Snap to a snap position nearby the current scroll position, if appropriate.
   void ScrollSnap();
 
   // Snap to a snap position nearby the destination predicted based on the
   // current velocity, if appropriate.
   void ScrollSnapToDestination();
 
--- a/gfx/layers/apz/src/WheelScrollAnimation.h
+++ b/gfx/layers/apz/src/WheelScrollAnimation.h
@@ -27,16 +27,20 @@ public:
 
   bool DoSample(FrameMetrics& aFrameMetrics, const TimeDuration& aDelta) override;
   void Update(TimeStamp aTime, nsPoint aDelta, const nsSize& aCurrentVelocity);
 
   WheelScrollAnimation* AsWheelScrollAnimation() override {
     return this;
   }
 
+  CSSPoint GetDestination() const {
+    return CSSPoint::FromAppUnits(mFinalDestination);
+  }
+
 private:
   void InitPreferences(TimeStamp aTime);
 
 private:
   AsyncPanZoomController& mApzc;
   nsPoint mFinalDestination;
   ScrollWheelInput::ScrollDeltaType mDeltaType;
 };