Bug 1259296 - Scroll snap in the compositor in response to wheel events. r=kats
authorBotond Ballo <botond@mozilla.com>
Mon, 04 Apr 2016 17:43:21 -0400
changeset 332141 dc5ba1c7e59f1cea940c71ff69b5ee1358c7e7b0
parent 332140 11edf75d48c6b3d2c4ada21841f027367777b230
child 332142 4334ce2047bec8deeffefc35680a711e225316d4
push id1146
push userCallek@gmail.com
push dateMon, 25 Jul 2016 16:35:44 +0000
treeherdermozilla-release@a55778f9cd5a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskats
bugs1259296
milestone48.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 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
@@ -1814,16 +1814,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(
@@ -1845,34 +1850,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
@@ -3932,34 +3946,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());
 }
 
@@ -3991,10 +4013,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 {
@@ -617,25 +618,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;
@@ -758,17 +750,16 @@ public:
 
   /**
    * Returns the same transform as GetCurrentAsyncTransform(), but includes
    * any transform due to axis over-scroll.
    */
   AsyncTransformComponentMatrix GetCurrentAsyncTransformWithOverscroll(AsyncMode aMode) const;
 
 
-
   /* ===================================================================
    * 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 */
@@ -1151,14 +1142,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,