Bug 1259296 - Scroll snap in the compositor in response to wheel events. r=kats
MozReview-Commit-ID: 9fOlssstgvR
--- 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,