Bug 1259296 - Scroll snap in the compositor in response to wheel events. r=kats draft
authorBotond Ballo <botond@mozilla.com>
Mon, 04 Apr 2016 17:43:21 -0400
changeset 348679 fbdee753c4618cbc1a7c79e3ec07b7479f93a05d
parent 347060 5e9c5c43202fb48c5cad5ab95b05fd0bc1be021e
child 348680 a9468a25f1b937c49c5f801069d2cdc2cd610bdd
push id14875
push userbballo@mozilla.com
push dateThu, 07 Apr 2016 21:45:38 +0000
reviewerskats
bugs1259296
milestone48.0a1
Bug 1259296 - Scroll snap in the compositor in response to wheel events. r=kats MozReview-Commit-ID: 9fOlssstgvR
gfx/layers/apz/src/AsyncPanZoomController.cpp
gfx/layers/apz/src/AsyncPanZoomController.h
widget/InputData.h
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -1794,16 +1794,21 @@ 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: {
       ScreenPoint distance = ToScreenCoordinates(
         ParentLayerPoint(fabs(delta.x), fabs(delta.y)), aEvent.mLocalOrigin);
 
       CancelAnimation();
 
       OverscrollHandoffState handoffState(
@@ -1825,34 +1830,43 @@ 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 (mState != WHEEL_SCROLL) {
-        CancelAnimation();
-        SetState(WHEEL_SCROLL);
-
-        nsPoint initialPosition = CSSPoint::ToAppUnits(mFrameMetrics.GetScrollOffset());
-        StartAnimation(new WheelScrollAnimation(
-          *this, initialPosition, aEvent.mDeltaType));
+      if (scrollSnapping) {
+        // 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));
       }
-
-      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
@@ -3885,34 +3899,42 @@ void AsyncPanZoomController::ShareCompos
       // so the content process know which APZC sent this shared FrameMetrics.
       if (!compositor->SendSharedCompositorFrameMetrics(mem, handle, mLayersId, mAPZCId)) {
         APZC_LOG("%p failed to share FrameMetrics with content process.", this);
       }
     }
   }
 }
 
-void AsyncPanZoomController::ScrollSnapNear(const CSSPoint& aDestination) {
+Maybe<CSSPoint> AsyncPanZoomController::FindSnapPointNear(
+    const CSSPoint& aDestination, nsIScrollableFrame::ScrollUnit aUnit) {
   mMonitor.AssertCurrentThreadIn();
   APZC_LOG("%p scroll snapping near %s\n", this, Stringify(aDestination).c_str());
   CSSRect scrollRange = mFrameMetrics.CalculateScrollRange();
   if (Maybe<nsPoint> snapPoint = ScrollSnapUtils::GetSnapPointForDestination(
           mScrollMetadata.GetSnapInfo(),
-          nsIScrollableFrame::DEVICE_PIXELS,
+          aUnit,
           CSSSize::ToAppUnits(mFrameMetrics.CalculateCompositedSizeInCssPixels()),
           CSSRect::ToAppUnits(scrollRange),
           CSSPoint::ToAppUnits(mFrameMetrics.GetScrollOffset()),
           CSSPoint::ToAppUnits(aDestination))) {
     CSSPoint cssSnapPoint = CSSPoint::FromAppUnits(snapPoint.ref());
     // GetSnapPointForDestination() can produce a destination that's outside
     // of the scroll frame's scroll range. Clamp it here (this matches the
     // behaviour of the main-thread code path, which clamps it in
     // nsGfxScrollFrame::ScrollTo()).
-    cssSnapPoint = scrollRange.ClampPoint(cssSnapPoint);
-    SmoothScrollTo(cssSnapPoint);
+    return Some(scrollRange.ClampPoint(cssSnapPoint));
+  }
+  return Nothing();
+}
+
+void AsyncPanZoomController::ScrollSnapNear(const CSSPoint& aDestination) {
+  if (Maybe<CSSPoint> snapPoint =
+        FindSnapPointNear(aDestination, nsIScrollableFrame::DEVICE_PIXELS)) {
+    SmoothScrollTo(*snapPoint);
   }
 }
 
 void AsyncPanZoomController::ScrollSnap() {
   ReentrantMonitorAutoEnter lock(mMonitor);
   ScrollSnapNear(mFrameMetrics.GetScrollOffset());
 }
 
@@ -3944,10 +3966,34 @@ void AsyncPanZoomController::ScrollSnapT
              (float)predictedDelta.y, (float)mFrameMetrics.GetScrollOffset().x,
              (float)mFrameMetrics.GetScrollOffset().y,
              (float)predictedDestination.x, (float)predictedDestination.y);
 
     ScrollSnapNear(predictedDestination);
   }
 }
 
+bool AsyncPanZoomController::MaybeAdjustDeltaForScrollSnapping(
+    ParentLayerPoint& aDelta, const ScrollWheelInput& aEvent)
+{
+  // 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));
+  nsIScrollableFrame::ScrollUnit unit =
+      ScrollWheelInput::ScrollUnitForDeltaType(aEvent.mDeltaType);
+
+  if (Maybe<CSSPoint> snapPoint = FindSnapPointNear(destination, unit)) {
+    aDelta = (*snapPoint - scrollOffset) * zoom;
+    return true;
+  }
+  return false;
+}
+
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/apz/src/AsyncPanZoomController.h
+++ b/gfx/layers/apz/src/AsyncPanZoomController.h
@@ -20,16 +20,17 @@
 #include "mozilla/Atomics.h"
 #include "InputData.h"
 #include "Axis.h"
 #include "InputQueue.h"
 #include "APZUtils.h"
 #include "Layers.h"                     // for Layer::ScrollDirection
 #include "LayersTypes.h"
 #include "mozilla/gfx/Matrix.h"
+#include "nsIScrollableFrame.h"
 #include "nsRegion.h"
 #include "PotentialCheckerboardDurationTracker.h"
 
 #include "base/message_loop.h"
 
 namespace mozilla {
 
 namespace ipc {
@@ -638,25 +639,16 @@ protected:
   static AxisLockMode GetAxisLockMode();
 
   // Helper function for OnSingleTapUp() and OnSingleTapConfirmed().
   nsEventStatus GenerateSingleTap(const ScreenIntPoint& aPoint, mozilla::Modifiers aModifiers);
 
   // Common processing at the end of a touch block.
   void OnTouchEndOrCancel();
 
-  // 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();
-
-  // Helper function for ScrollSnap() and ScrollSnapToDestination().
-  void ScrollSnapNear(const CSSPoint& aDestination);
-
   uint64_t mLayersId;
   RefPtr<CompositorBridgeParent> mCompositorBridgeParent;
 
   /* Access to the following two fields is protected by the mRefPtrMonitor,
      since they are accessed on the UI thread but can be cleared on the
      compositor thread. */
   RefPtr<GeckoContentController> mGeckoContentController;
   RefPtr<GestureEventListener> mGestureEventListener;
@@ -732,17 +724,16 @@ private:
   // to allow panning by moving multiple fingers (thus moving the focus point).
   ParentLayerPoint mLastZoomFocus;
 
   RefPtr<AsyncPanZoomAnimation> mAnimation;
 
   friend class Axis;
 
 
-
   /* ===================================================================
    * The functions and members in this section are used to manage
    * the state that tracks what this APZC is doing with the input events.
    */
 protected:
   enum PanZoomState {
     NOTHING,                  /* no touch-start events received */
     FLING,                    /* all touches removed, but we're still scrolling page */
@@ -1125,14 +1116,43 @@ private:
   // This is created when this APZC instance is first included as part of a
   // composite. If a checkerboard event takes place, this is destroyed at the
   // end of the event, and a new one is created on the next composite.
   UniquePtr<CheckerboardEvent> mCheckerboardEvent;
   // This is used to track the total amount of time that we could reasonably
   // be checkerboarding. Combined with other info, this allows us to meaningfully
   // say how frequently users actually encounter checkerboarding.
   PotentialCheckerboardDurationTracker mPotentialCheckerboardTracker;
+
+
+  /* ===================================================================
+   * 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 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();
+
+  // Snap to a snap position nearby the provided destination, if appropriate.
+  void ScrollSnapNear(const CSSPoint& aDestination);
+
+  // Find a snap point near |aDestination| that we should snap to.
+  // Returns the snap point if one was found, or an empty Maybe otherwise.
+  // |aUnit| affects the snapping behaviour (see ScrollSnapUtils::
+  // GetSnapPointForDestination). It should generally be determined by the
+  // type of event that's triggering the scroll.
+  Maybe<CSSPoint> FindSnapPointNear(const CSSPoint& aDestination,
+                                    nsIScrollableFrame::ScrollUnit aUnit);
 };
 
 } // namespace layers
 } // namespace mozilla
 
 #endif // mozilla_layers_PanZoomController_h
--- a/widget/InputData.h
+++ b/widget/InputData.h
@@ -1,18 +1,19 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef InputData_h__
 #define InputData_h__
 
+#include "nsDebug.h"
 #include "nsIDOMWheelEvent.h"
-#include "nsDebug.h"
+#include "nsIScrollableFrame.h"
 #include "nsPoint.h"
 #include "nsTArray.h"
 #include "Units.h"
 #include "mozilla/EventForwards.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/gfx/MatrixFwd.h"
 
 template<class E> struct already_AddRefed;
@@ -572,16 +573,32 @@ public:
       case nsIDOMWheelEvent::DOM_DELTA_PIXEL:
         return SCROLLDELTA_PIXEL;
       default:
         MOZ_CRASH();
     }
     return SCROLLDELTA_LINE;
   }
 
+  static nsIScrollableFrame::ScrollUnit
+  ScrollUnitForDeltaType(ScrollDeltaType aDeltaType)
+  {
+    switch (aDeltaType) {
+    case SCROLLDELTA_LINE:
+      return nsIScrollableFrame::LINES;
+    case SCROLLDELTA_PAGE:
+      return nsIScrollableFrame::PAGES;
+    case SCROLLDELTA_PIXEL:
+      return nsIScrollableFrame::DEVICE_PIXELS;
+    default:
+      MOZ_CRASH();
+    }
+    return nsIScrollableFrame::LINES;
+  }
+
   enum ScrollMode
   {
     SCROLLMODE_INSTANT,
     SCROLLMODE_SMOOTH
   };
 
   ScrollWheelInput(uint32_t aTime,
                    TimeStamp aTimeStamp,