Bug 945584: Part 6 - Implementation of scroll snapping (v10 Patch), r=roc, r=kats

- Implemented CSS scroll snapping (http://dev.w3.org/csswg/css-snappoints/)
This commit is contained in:
Kearwood (Kip) Gilbert 2015-02-19 15:53:30 -08:00
parent 2879261784
commit 957ced20f0
22 changed files with 679 additions and 25 deletions

View File

@ -2417,13 +2417,16 @@ EventStateManager::DoScrollText(nsIScrollableFrame* aScrollableFrame,
actualDevPixelScrollAmount.y = 0;
}
nsIScrollableFrame::ScrollSnapMode snapMode = nsIScrollableFrame::DISABLE_SNAP;
nsIAtom* origin = nullptr;
switch (aEvent->deltaMode) {
case nsIDOMWheelEvent::DOM_DELTA_LINE:
origin = nsGkAtoms::mouseWheel;
snapMode = nsIScrollableFrame::ENABLE_SNAP;
break;
case nsIDOMWheelEvent::DOM_DELTA_PAGE:
origin = nsGkAtoms::pages;
snapMode = nsIScrollableFrame::ENABLE_SNAP;
break;
case nsIDOMWheelEvent::DOM_DELTA_PIXEL:
origin = nsGkAtoms::pixels;
@ -2483,7 +2486,7 @@ EventStateManager::DoScrollText(nsIScrollableFrame* aScrollableFrame,
nsIntPoint overflow;
aScrollableFrame->ScrollBy(actualDevPixelScrollAmount,
nsIScrollableFrame::DEVICE_PIXELS,
mode, &overflow, origin, momentum);
mode, &overflow, origin, momentum, snapMode);
if (!scrollFrameWeak.IsAlive()) {
// If the scroll causes changing the layout, we can think that the event
@ -2984,6 +2987,13 @@ EventStateManager::PostHandleEvent(nsPresContext* aPresContext,
{
MOZ_ASSERT(aEvent->mFlags.mIsTrusted);
ScrollbarsForWheel::MayInactivate();
WidgetWheelEvent* wheelEvent = aEvent->AsWheelEvent();
nsIScrollableFrame* scrollTarget =
ComputeScrollTarget(aTargetFrame, wheelEvent,
COMPUTE_DEFAULT_ACTION_TARGET);
if (scrollTarget) {
scrollTarget->ScrollSnap();
}
}
break;
case NS_WHEEL_WHEEL:

View File

@ -513,6 +513,7 @@ child:
// The following methods correspond to functions on the GeckoContentController
// interface in gfx/layers/apz/public/GeckoContentController.h. Refer to documentation
// in that file for these functions.
RequestFlingSnap(ViewID aScrollID, CSSPoint aDestination);
AcknowledgeScrollUpdate(ViewID aScrollId, uint32_t aScrollGeneration);
HandleDoubleTap(CSSPoint aPoint, Modifiers aModifiers, ScrollableLayerGuid aGuid);
HandleSingleTap(CSSPoint aPoint, Modifiers aModifiers, ScrollableLayerGuid aGuid);

View File

@ -2036,6 +2036,14 @@ TabChild::RecvUpdateFrame(const FrameMetrics& aFrameMetrics)
return TabChildBase::UpdateFrameHandler(aFrameMetrics);
}
bool
TabChild::RecvRequestFlingSnap(const FrameMetrics::ViewID& aScrollId,
const mozilla::CSSPoint& aDestination)
{
APZCCallbackHelper::RequestFlingSnap(aScrollId, aDestination);
return true;
}
bool
TabChild::RecvAcknowledgeScrollUpdate(const ViewID& aScrollId,
const uint32_t& aScrollGeneration)

View File

@ -319,6 +319,8 @@ public:
const ScreenOrientation& orientation,
const nsIntPoint& chromeDisp) MOZ_OVERRIDE;
virtual bool RecvUpdateFrame(const layers::FrameMetrics& aFrameMetrics) MOZ_OVERRIDE;
virtual bool RecvRequestFlingSnap(const ViewID& aScrollId,
const CSSPoint& aDestination) MOZ_OVERRIDE;
virtual bool RecvAcknowledgeScrollUpdate(const ViewID& aScrollId,
const uint32_t& aScrollGeneration) MOZ_OVERRIDE;
virtual bool RecvHandleDoubleTap(const CSSPoint& aPoint,

View File

@ -922,6 +922,15 @@ TabParent::UIResolutionChanged()
}
}
void
TabParent::RequestFlingSnap(const FrameMetrics::ViewID& aScrollId,
const mozilla::CSSPoint& aDestination)
{
if (!mIsDestroyed) {
unused << SendRequestFlingSnap(aScrollId, aDestination);
}
}
void
TabParent::AcknowledgeScrollUpdate(const ViewID& aScrollId, const uint32_t& aScrollGeneration)
{

View File

@ -245,6 +245,8 @@ public:
const nsIntPoint& chromeDisp);
void UpdateFrame(const layers::FrameMetrics& aFrameMetrics);
void UIResolutionChanged();
void RequestFlingSnap(const FrameMetrics::ViewID& aScrollId,
const mozilla::CSSPoint& aDestination);
void AcknowledgeScrollUpdate(const ViewID& aScrollId, const uint32_t& aScrollGeneration);
void HandleDoubleTap(const CSSPoint& aPoint,
Modifiers aModifiers,

View File

@ -30,6 +30,14 @@ public:
*/
virtual void RequestContentRepaint(const FrameMetrics& aFrameMetrics) = 0;
/**
* Requests handling of a scroll snapping at the end of a fling gesture for
* the scrollable frame with the given scroll id. aDestination specifies the
* expected landing position of the fling if no snapping were to be performed.
*/
virtual void RequestFlingSnap(const FrameMetrics::ViewID& aScrollId,
const mozilla::CSSPoint& aDestination) = 0;
/**
* Acknowledges the recipt of a scroll offset update for the scrollable
* frame with the given scroll id. This is used to maintain consistency

View File

@ -1980,9 +1980,37 @@ void AsyncPanZoomController::AcceptFling(const ParentLayerPoint& aVelocity,
mX.SetVelocity(mX.GetVelocity() + aVelocity.x);
mY.SetVelocity(mY.GetVelocity() + aVelocity.y);
SetState(FLING);
StartAnimation(new FlingAnimation(*this,
FlingAnimation *fling = new FlingAnimation(*this,
aOverscrollHandoffChain,
!aHandoff)); // only apply acceleration if this is an initial fling
!aHandoff); // only apply acceleration if this is an initial fling
float friction = gfxPrefs::APZFlingFriction();
ParentLayerPoint velocity(mX.GetVelocity(), mY.GetVelocity());
ParentLayerPoint predictedDelta;
// "-velocity / log(1.0 - friction)" is the integral of the deceleration
// curve modeled for flings in the "Axis" class.
if (velocity.x != 0.0f) {
predictedDelta.x = -velocity.x / log(1.0 - friction);
}
if (velocity.y != 0.0f) {
predictedDelta.y = -velocity.y / log(1.0 - friction);
}
CSSPoint predictedDestination = mFrameMetrics.GetScrollOffset() + predictedDelta / mFrameMetrics.GetZoom();
nsRefPtr<GeckoContentController> controller = GetGeckoContentController();
if (controller) {
APZC_LOG("%p fling snapping. friction: %f velocity: %f, %f "
"predictedDelta: %f, %f position: %f, %f "
"predictedDestination: %f, %f\n",
this, friction, velocity.x, velocity.y, (float)predictedDelta.x,
(float)predictedDelta.y, (float)mFrameMetrics.GetScrollOffset().x,
(float)mFrameMetrics.GetScrollOffset().y,
(float)predictedDestination.x, (float)predictedDestination.y);
controller->RequestFlingSnap(mFrameMetrics.GetScrollId(),
predictedDestination);
}
StartAnimation(fling);
}
bool AsyncPanZoomController::AttemptFling(ParentLayerPoint aVelocity,

View File

@ -264,6 +264,46 @@ APZCCallbackHelper::GetOrCreateScrollIdentifiers(nsIContent* aContent,
return utils && (utils->GetPresShellId(aPresShellIdOut) == NS_OK);
}
class FlingSnapEvent : public nsRunnable
{
typedef mozilla::layers::FrameMetrics::ViewID ViewID;
public:
FlingSnapEvent(const ViewID& aScrollId,
const mozilla::CSSPoint& aDestination)
: mScrollId(aScrollId)
, mDestination(aDestination)
{
}
NS_IMETHOD Run() {
MOZ_ASSERT(NS_IsMainThread());
nsIScrollableFrame* sf = nsLayoutUtils::FindScrollableFrameFor(mScrollId);
if (sf) {
sf->FlingSnap(mDestination);
}
return NS_OK;
}
protected:
ViewID mScrollId;
mozilla::CSSPoint mDestination;
};
void
APZCCallbackHelper::RequestFlingSnap(const FrameMetrics::ViewID& aScrollId,
const mozilla::CSSPoint& aDestination)
{
nsCOMPtr<nsIRunnable> r1 = new FlingSnapEvent(aScrollId, aDestination);
if (!NS_IsMainThread()) {
NS_DispatchToMainThread(r1);
} else {
r1->Run();
}
}
class AcknowledgeScrollUpdateEvent : public nsRunnable
{
typedef mozilla::layers::FrameMetrics::ViewID ViewID;

View File

@ -81,6 +81,14 @@ public:
uint32_t* aPresShellIdOut,
FrameMetrics::ViewID* aViewIdOut);
/* Tell layout to perform scroll snapping for the scrollable frame with the
* given scroll id. aDestination specifies the expected landing position of
* a current fling or scrolling animation that should be used to select
* the scroll snap point.
*/
static void RequestFlingSnap(const FrameMetrics::ViewID& aScrollId,
const mozilla::CSSPoint& aDestination);
/* Tell layout that we received the scroll offset update for the given view ID, so
that it accepts future scroll offset updates from APZ. */
static void AcknowledgeScrollUpdate(const FrameMetrics::ViewID& aScrollId,

View File

@ -80,6 +80,13 @@ ChromeProcessController::PostDelayedTask(Task* aTask, int aDelayMs)
MessageLoop::current()->PostDelayedTask(FROM_HERE, aTask, aDelayMs);
}
void
ChromeProcessController::RequestFlingSnap(const FrameMetrics::ViewID& aScrollId,
const mozilla::CSSPoint& aDestination)
{
APZCCallbackHelper::RequestFlingSnap(aScrollId, aDestination);
}
void
ChromeProcessController::AcknowledgeScrollUpdate(const FrameMetrics::ViewID& aScrollId,
const uint32_t& aScrollGeneration)

View File

@ -37,6 +37,8 @@ public:
// GeckoContentController interface
virtual void RequestContentRepaint(const FrameMetrics& aFrameMetrics) MOZ_OVERRIDE;
virtual void PostDelayedTask(Task* aTask, int aDelayMs) MOZ_OVERRIDE;
virtual void RequestFlingSnap(const FrameMetrics::ViewID& aScrollId,
const mozilla::CSSPoint& aDestination) MOZ_OVERRIDE;
virtual void AcknowledgeScrollUpdate(const FrameMetrics::ViewID& aScrollId,
const uint32_t& aScrollGeneration) MOZ_OVERRIDE;

View File

@ -61,6 +61,7 @@ private:
class MockContentController : public GeckoContentController {
public:
MOCK_METHOD1(RequestContentRepaint, void(const FrameMetrics&));
MOCK_METHOD2(RequestFlingSnap, void(const FrameMetrics::ViewID& aScrollId, const mozilla::CSSPoint& aDestination));
MOCK_METHOD2(AcknowledgeScrollUpdate, void(const FrameMetrics::ViewID&, const uint32_t& aScrollGeneration));
MOCK_METHOD3(HandleDoubleTap, void(const CSSPoint&, Modifiers, const ScrollableLayerGuid&));
MOCK_METHOD3(HandleSingleTap, void(const CSSPoint&, Modifiers, const ScrollableLayerGuid&));

View File

@ -2392,7 +2392,8 @@ PresShell::ScrollPage(bool aForward)
nsIScrollableFrame::PAGES,
nsIScrollableFrame::SMOOTH,
nullptr, nullptr,
nsIScrollableFrame::NOT_MOMENTUM);
nsIScrollableFrame::NOT_MOMENTUM,
nsIScrollableFrame::ENABLE_SNAP);
}
return NS_OK;
}
@ -2409,7 +2410,8 @@ PresShell::ScrollLine(bool aForward)
nsIScrollableFrame::LINES,
nsIScrollableFrame::SMOOTH,
nullptr, nullptr,
nsIScrollableFrame::NOT_MOMENTUM);
nsIScrollableFrame::NOT_MOMENTUM,
nsIScrollableFrame::ENABLE_SNAP);
}
return NS_OK;
}
@ -2426,7 +2428,8 @@ PresShell::ScrollCharacter(bool aRight)
nsIScrollableFrame::LINES,
nsIScrollableFrame::SMOOTH,
nullptr, nullptr,
nsIScrollableFrame::NOT_MOMENTUM);
nsIScrollableFrame::NOT_MOMENTUM,
nsIScrollableFrame::ENABLE_SNAP);
}
return NS_OK;
}
@ -2441,7 +2444,8 @@ PresShell::CompleteScroll(bool aForward)
nsIScrollableFrame::WHOLE,
nsIScrollableFrame::SMOOTH,
nullptr, nullptr,
nsIScrollableFrame::NOT_MOMENTUM);
nsIScrollableFrame::NOT_MOMENTUM,
nsIScrollableFrame::ENABLE_SNAP);
}
return NS_OK;
}

View File

@ -1899,6 +1899,7 @@ ScrollFrameHelper::ScrollFrameHelper(nsContainerFrame* aOuter,
, mIgnoreMomentumScroll(false)
, mScaleToResolution(false)
, mTransformingByAPZ(false)
, mVelocityQueue(aOuter->PresContext())
{
if (LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars) != 0) {
mScrollbarActivity = new ScrollbarActivity(do_QueryFrame(aOuter));
@ -2069,8 +2070,16 @@ void
ScrollFrameHelper::ScrollToWithOrigin(nsPoint aScrollPosition,
nsIScrollableFrame::ScrollMode aMode,
nsIAtom *aOrigin,
const nsRect* aRange)
const nsRect* aRange,
nsIScrollableFrame::ScrollSnapMode aSnap)
{
if (aSnap == nsIScrollableFrame::ENABLE_SNAP) {
GetSnapPointForDestination(nsIScrollableFrame::DEVICE_PIXELS,
mDestination,
aScrollPosition);
}
nsRect scrollRange = GetScrollRangeForClamping();
mDestination = scrollRange.ClampPoint(aScrollPosition);
@ -2094,6 +2103,9 @@ ScrollFrameHelper::ScrollToWithOrigin(nsPoint aScrollPosition,
if (aMode == nsIScrollableFrame::SMOOTH_MSD) {
mIgnoreMomentumScroll = true;
if (!mAsyncSmoothMSDScroll) {
nsPoint sv = mVelocityQueue.GetVelocity();
currentVelocity.width = sv.x;
currentVelocity.height = sv.y;
if (mAsyncScroll) {
if (mAsyncScroll->mIsSmoothScroll) {
currentVelocity = mAsyncScroll->VelocityAt(now);
@ -3327,7 +3339,8 @@ ScrollFrameHelper::ScrollBy(nsIntPoint aDelta,
nsIScrollableFrame::ScrollMode aMode,
nsIntPoint* aOverflow,
nsIAtom *aOrigin,
nsIScrollableFrame::ScrollMomentum aMomentum)
nsIScrollableFrame::ScrollMomentum aMomentum,
nsIScrollableFrame::ScrollSnapMode aSnap)
{
// When a smooth scroll is being processed on a frame, mouse wheel and trackpad
// momentum scroll event updates must notcancel the SMOOTH or SMOOTH_MSD
@ -3390,6 +3403,9 @@ ScrollFrameHelper::ScrollBy(nsIntPoint aDelta,
nsPoint pos = GetScrollPosition();
AdjustForWholeDelta(aDelta.x, &pos.x);
AdjustForWholeDelta(aDelta.y, &pos.y);
if (aSnap == nsIScrollableFrame::ENABLE_SNAP) {
GetSnapPointForDestination(aUnit, mDestination, pos);
}
ScrollTo(pos, aMode);
// 'this' might be destroyed here
if (aOverflow) {
@ -3402,8 +3418,27 @@ ScrollFrameHelper::ScrollBy(nsIntPoint aDelta,
return;
}
nsPoint newPos = mDestination +
nsPoint(aDelta.x*deltaMultiplier.width, aDelta.y*deltaMultiplier.height);
nsPoint newPos = mDestination + nsPoint(aDelta.x*deltaMultiplier.width, aDelta.y*deltaMultiplier.height);
if (aSnap == nsIScrollableFrame::ENABLE_SNAP) {
ScrollbarStyles styles = GetScrollbarStylesFromFrame();
if (styles.mScrollSnapTypeY != NS_STYLE_SCROLL_SNAP_TYPE_NONE ||
styles.mScrollSnapTypeX != NS_STYLE_SCROLL_SNAP_TYPE_NONE) {
nscoord appUnitsPerDevPixel = mOuter->PresContext()->AppUnitsPerDevPixel();
deltaMultiplier = nsSize(appUnitsPerDevPixel, appUnitsPerDevPixel);
negativeTolerance = 0.1f;
positiveTolerance = 0;
nsIScrollableFrame::ScrollUnit snapUnit = aUnit;
if (aOrigin == nsGkAtoms::mouseWheel) {
// When using a clicky scroll wheel, snap point selection works the same
// as keyboard up/down/left/right navigation, but with varying amounts
// of scroll delta.
snapUnit = nsIScrollableFrame::LINES;
}
GetSnapPointForDestination(snapUnit, mDestination, newPos);
}
}
// Calculate desired range values.
nscoord rangeLowerX, rangeUpperX, rangeLowerY, rangeUpperY;
CalcRangeForScrollBy(aDelta.x, newPos.x, negativeTolerance, positiveTolerance,
@ -3427,6 +3462,49 @@ ScrollFrameHelper::ScrollBy(nsIntPoint aDelta,
NSAppUnitsToIntPixels(clampAmount.x, appUnitsPerDevPixel),
NSAppUnitsToIntPixels(clampAmount.y, appUnitsPerDevPixel));
}
if (aUnit == nsIScrollableFrame::DEVICE_PIXELS &&
!gfxPrefs::AsyncPanZoomEnabled()) {
// When APZ is disabled, we must track the velocity
// on the main thread; otherwise, the APZC will manage this.
mVelocityQueue.Sample(GetScrollPosition());
}
}
void
ScrollFrameHelper::ScrollSnap()
{
float flingSensitivity = gfxPrefs::ScrollSnapPredictionSensitivity();
int maxVelocity = gfxPrefs::ScrollSnapPredictionMaxVelocity();
maxVelocity = nsPresContext::CSSPixelsToAppUnits(maxVelocity);
int maxOffset = maxVelocity * flingSensitivity;
nsPoint velocity = mVelocityQueue.GetVelocity();
// Multiply each component individually to avoid integer multiply
nsPoint predictedOffset = nsPoint(velocity.x * flingSensitivity,
velocity.y * flingSensitivity);
predictedOffset.Clamp(maxOffset);
nsPoint pos = GetScrollPosition();
nsPoint destinationPos = pos + predictedOffset;
ScrollSnap(destinationPos);
}
void
ScrollFrameHelper::FlingSnap(const mozilla::CSSPoint& aDestination)
{
ScrollSnap(CSSPoint::ToAppUnits(aDestination));
}
void
ScrollFrameHelper::ScrollSnap(const nsPoint &aDestination)
{
nsRect scrollRange = GetScrollRangeForClamping();
nsPoint pos = GetScrollPosition();
nsPoint snapDestination = scrollRange.ClampPoint(aDestination);
if (GetSnapPointForDestination(nsIScrollableFrame::DEVICE_PIXELS,
pos,
snapDestination)) {
ScrollTo(snapDestination, nsIScrollableFrame::SMOOTH_MSD);
}
}
nsSize
@ -5196,3 +5274,343 @@ nsIScrollableFrame::GetPerceivedScrollingDirections() const
}
return directions;
}
/**
* Stores candidate snapping edges.
*/
class SnappingEdgeCallback {
public:
virtual void AddHorizontalEdge(nscoord aEdge) = 0;
virtual void AddVerticalEdge(nscoord aEdge) = 0;
virtual void AddHorizontalEdgeInterval(const nsRect &aScrollRange,
nscoord aInterval,
nscoord aOffset) = 0;
virtual void AddVerticalEdgeInterval(const nsRect &aScrollRange,
nscoord aInterval,
nscoord aOffset) = 0;
};
/**
* Keeps track of the current best edge to snap to. The criteria for
* adding an edge depends on the scrolling unit.
*/
class CalcSnapPoints : public SnappingEdgeCallback {
public:
CalcSnapPoints(nsIScrollableFrame::ScrollUnit aUnit,
const nsPoint& aDestination,
const nsPoint& aStartPos);
virtual void AddHorizontalEdge(nscoord aEdge) MOZ_OVERRIDE;
virtual void AddVerticalEdge(nscoord aEdge) MOZ_OVERRIDE;
virtual void AddHorizontalEdgeInterval(const nsRect &aScrollRange,
nscoord aInterval, nscoord aOffset)
MOZ_OVERRIDE;
virtual void AddVerticalEdgeInterval(const nsRect &aScrollRange,
nscoord aInterval, nscoord aOffset)
MOZ_OVERRIDE;
void AddEdge(nscoord aEdge,
nscoord aDestination,
nscoord aStartPos,
nscoord aScrollingDirection,
nscoord* aBestEdge,
bool* aEdgeFound);
void AddEdgeInterval(nscoord aInterval,
nscoord aMinPos,
nscoord aMaxPos,
nscoord aOffset,
nscoord aDestination,
nscoord aStartPos,
nscoord aScrollingDirection,
nscoord* aBestEdge,
bool* aEdgeFound);
nsPoint GetBestEdge() const;
protected:
nsIScrollableFrame::ScrollUnit mUnit;
nsPoint mDestination; // gives the position after scrolling but before snapping
nsPoint mStartPos; // gives the position before scrolling
nsIntPoint mScrollingDirection; // always -1, 0, or 1
nsPoint mBestEdge; // keeps track of the position of the current best edge
bool mHorizontalEdgeFound; // true if mBestEdge.x is storing a valid horizontal edge
bool mVerticalEdgeFound; // true if mBestEdge.y is storing a valid vertical edge
};
CalcSnapPoints::CalcSnapPoints(nsIScrollableFrame::ScrollUnit aUnit,
const nsPoint& aDestination,
const nsPoint& aStartPos)
{
mUnit = aUnit;
mDestination = aDestination;
mStartPos = aStartPos;
nsPoint direction = aDestination - aStartPos;
mScrollingDirection = nsIntPoint(0,0);
if (direction.x < 0) {
mScrollingDirection.x = -1;
}
if (direction.x > 0) {
mScrollingDirection.x = 1;
}
if (direction.y < 0) {
mScrollingDirection.y = -1;
}
if (direction.y > 0) {
mScrollingDirection.y = 1;
}
mBestEdge = aDestination;
mHorizontalEdgeFound = false;
mVerticalEdgeFound = false;
}
nsPoint
CalcSnapPoints::GetBestEdge() const
{
return nsPoint(mVerticalEdgeFound ? mBestEdge.x : mStartPos.x,
mHorizontalEdgeFound ? mBestEdge.y : mStartPos.y);
}
void
CalcSnapPoints::AddHorizontalEdge(nscoord aEdge)
{
AddEdge(aEdge, mDestination.y, mStartPos.y, mScrollingDirection.y, &mBestEdge.y,
&mHorizontalEdgeFound);
}
void
CalcSnapPoints::AddVerticalEdge(nscoord aEdge)
{
AddEdge(aEdge, mDestination.x, mStartPos.x, mScrollingDirection.x, &mBestEdge.x,
&mVerticalEdgeFound);
}
void
CalcSnapPoints::AddHorizontalEdgeInterval(const nsRect &aScrollRange,
nscoord aInterval, nscoord aOffset)
{
AddEdgeInterval(aInterval, aScrollRange.y, aScrollRange.YMost(), aOffset,
mDestination.y, mStartPos.y, mScrollingDirection.y,
&mBestEdge.y, &mHorizontalEdgeFound);
}
void
CalcSnapPoints::AddVerticalEdgeInterval(const nsRect &aScrollRange,
nscoord aInterval, nscoord aOffset)
{
AddEdgeInterval(aInterval, aScrollRange.x, aScrollRange.XMost(), aOffset,
mDestination.x, mStartPos.x, mScrollingDirection.x,
&mBestEdge.x, &mVerticalEdgeFound);
}
void
CalcSnapPoints::AddEdge(nscoord aEdge, nscoord aDestination, nscoord aStartPos,
nscoord aScrollingDirection, nscoord* aBestEdge,
bool *aEdgeFound)
{
// nsIScrollableFrame::DEVICE_PIXELS indicates that we are releasing a drag
// gesture or any other user input event that sets an absolute scroll
// position. In this case, scroll snapping is expected to travel in any
// direction. Otherwise, we will restrict the direction of the scroll
// snapping movement based on aScrollingDirection.
if (mUnit != nsIScrollableFrame::DEVICE_PIXELS) {
// Unless DEVICE_PIXELS, we only want to snap to points ahead of the
// direction we are scrolling
if (aScrollingDirection == 0) {
// The scroll direction is neutral - will not hit a snap point.
return;
}
// nsIScrollableFrame::WHOLE indicates that we are navigating to "home" or
// "end". In this case, we will always select the first or last snap point
// regardless of the direction of the scroll. Otherwise, we will select
// scroll snapping points only in the direction specified by
// aScrollingDirection.
if (mUnit != nsIScrollableFrame::WHOLE) {
// Direction of the edge from the current position (before scrolling) in
// the direction of scrolling
nscoord direction = (aEdge - aStartPos) * aScrollingDirection;
if (direction <= 0) {
// The edge is not in the direction we are scrolling, skip it.
return;
}
}
}
if (!*aEdgeFound) {
*aBestEdge = aEdge;
*aEdgeFound = true;
return;
}
if (mUnit == nsIScrollableFrame::DEVICE_PIXELS ||
mUnit == nsIScrollableFrame::LINES) {
if (std::abs(aEdge - aDestination) < std::abs(*aBestEdge - aDestination)) {
*aBestEdge = aEdge;
}
} else if (mUnit == nsIScrollableFrame::PAGES) {
// distance to the edge from the scrolling destination in the direction of scrolling
nscoord overshoot = (aEdge - aDestination) * aScrollingDirection;
// distance to the current best edge from the scrolling destination in the direction of scrolling
nscoord curOvershoot = (*aBestEdge - aDestination) * aScrollingDirection;
// edges between the current position and the scrolling destination are favoured
// to preserve context
if (overshoot < 0 && (overshoot > curOvershoot || curOvershoot >= 0)) {
*aBestEdge = aEdge;
}
// if there are no edges between the current position and the scrolling destination
// the closest edge beyond the destination is used
if (overshoot > 0 && overshoot < curOvershoot) {
*aBestEdge = aEdge;
}
} else if (mUnit == nsIScrollableFrame::WHOLE) {
// the edge closest to the top/bottom/left/right is used, depending on scrolling direction
if (aScrollingDirection > 0 && aEdge > *aBestEdge) {
*aBestEdge = aEdge;
} else if (aScrollingDirection < 0 && aEdge < *aBestEdge) {
*aBestEdge = aEdge;
}
} else {
NS_ERROR("Invalid scroll mode");
return;
}
}
void
CalcSnapPoints::AddEdgeInterval(nscoord aInterval, nscoord aMinPos,
nscoord aMaxPos, nscoord aOffset,
nscoord aDestination, nscoord aStartPos,
nscoord aScrollingDirection,
nscoord* aBestEdge, bool *aEdgeFound)
{
if (aInterval == 0) {
// When interval is 0, there are no scroll snap points.
// Avoid division by zero and bail.
return;
}
// The only possible candidate interval snap points are the edges immediately
// surrounding aDestination.
// aDestination must be clamped to the scroll
// range in order to handle cases where the best matching snap point would
// result in scrolling out of bounds. This clamping must be prior to
// selecting the two interval edges.
nscoord clamped = std::max(std::min(aDestination, aMaxPos), aMinPos);
// Add each edge in the interval immediately before aTarget and after aTarget
// Do not add edges that are out of range.
nscoord r = (clamped + aOffset) % aInterval;
if (r < aMinPos) {
r += aInterval;
}
nscoord edge = clamped - r;
if (edge >= aMinPos && edge <= aMaxPos) {
AddEdge(edge, aDestination, aStartPos, aScrollingDirection, aBestEdge,
aEdgeFound);
}
edge += aInterval;
if (edge >= aMinPos && edge <= aMaxPos) {
AddEdge(edge, aDestination, aStartPos, aScrollingDirection, aBestEdge,
aEdgeFound);
}
}
static void
ScrollSnapHelper(SnappingEdgeCallback& aCallback, nsIFrame* aFrame,
nsIFrame* aScrolledFrame,
const nsPoint &aScrollSnapDestination) {
nsIFrame::ChildListIterator childLists(aFrame);
for (; !childLists.IsDone(); childLists.Next()) {
nsFrameList::Enumerator childFrames(childLists.CurrentList());
for (; !childFrames.AtEnd(); childFrames.Next()) {
nsIFrame* f = childFrames.get();
const nsStyleDisplay* styleDisplay = f->StyleDisplay();
size_t coordCount = styleDisplay->mScrollSnapCoordinate.Length();
if (coordCount) {
nsRect frameRect = f->GetRect();
nsPoint offset = f->GetOffsetTo(aScrolledFrame);
nsRect edgesRect = nsRect(offset, frameRect.Size());
for (size_t coordNum = 0; coordNum < coordCount; coordNum++) {
const nsStyleBackground::Position &coordPosition =
f->StyleDisplay()->mScrollSnapCoordinate[coordNum];
nsPoint coordPoint = edgesRect.TopLeft() - aScrollSnapDestination;
coordPoint += nsPoint(coordPosition.mXPosition.mLength,
coordPosition.mYPosition.mLength);
if (coordPosition.mXPosition.mHasPercent) {
coordPoint.x += NSToCoordRound(coordPosition.mXPosition.mPercent *
frameRect.width);
}
if (coordPosition.mYPosition.mHasPercent) {
coordPoint.y += NSToCoordRound(coordPosition.mYPosition.mPercent *
frameRect.height);
}
aCallback.AddVerticalEdge(coordPoint.x);
aCallback.AddHorizontalEdge(coordPoint.y);
}
}
ScrollSnapHelper(aCallback, f, aScrolledFrame, aScrollSnapDestination);
}
}
}
bool
ScrollFrameHelper::GetSnapPointForDestination(nsIScrollableFrame::ScrollUnit aUnit,
nsPoint aStartPos,
nsPoint &aDestination)
{
ScrollbarStyles styles = GetScrollbarStylesFromFrame();
if (styles.mScrollSnapTypeY == NS_STYLE_SCROLL_SNAP_TYPE_NONE &&
styles.mScrollSnapTypeX == NS_STYLE_SCROLL_SNAP_TYPE_NONE) {
return false;
}
nsSize scrollPortSize = mScrollPort.Size();
nsRect scrollRange = GetScrollRangeForClamping();
nsPoint destPos = nsPoint(styles.mScrollSnapDestinationX.mLength,
styles.mScrollSnapDestinationY.mLength);
if (styles.mScrollSnapDestinationX.mHasPercent) {
destPos.x += NSToCoordFloorClamped(styles.mScrollSnapDestinationX.mPercent
* scrollPortSize.width);
}
if (styles.mScrollSnapDestinationY.mHasPercent) {
destPos.y += NSToCoordFloorClamped(styles.mScrollSnapDestinationY.mPercent
* scrollPortSize.height);
}
CalcSnapPoints calcSnapPoints(aUnit, aDestination, aStartPos);
if (styles.mScrollSnapPointsX.GetUnit() != eStyleUnit_None) {
nscoord interval = nsRuleNode::ComputeCoordPercentCalc(styles.mScrollSnapPointsX,
scrollPortSize.width);
calcSnapPoints.AddVerticalEdgeInterval(scrollRange, interval, destPos.x);
}
if (styles.mScrollSnapPointsY.GetUnit() != eStyleUnit_None) {
nscoord interval = nsRuleNode::ComputeCoordPercentCalc(styles.mScrollSnapPointsY,
scrollPortSize.width);
calcSnapPoints.AddHorizontalEdgeInterval(scrollRange, interval, destPos.y);
}
ScrollSnapHelper(calcSnapPoints, mScrolledFrame, mScrolledFrame, destPos);
bool snapped = false;
nsPoint finalPos = calcSnapPoints.GetBestEdge();
nscoord proximityThreshold =
Preferences::GetInt("layout.css.scroll-snap.proximity-threshold", 0);
proximityThreshold = nsPresContext::CSSPixelsToAppUnits(proximityThreshold);
if (styles.mScrollSnapTypeY == NS_STYLE_SCROLL_SNAP_TYPE_PROXIMITY &&
std::abs(aDestination.y - finalPos.y) > proximityThreshold) {
finalPos.y = aDestination.y;
} else {
snapped = true;
}
if (styles.mScrollSnapTypeX == NS_STYLE_SCROLL_SNAP_TYPE_PROXIMITY &&
std::abs(aDestination.x - finalPos.x) > proximityThreshold) {
finalPos.x = aDestination.x;
} else {
snapped = true;
}
if (snapped) {
aDestination = finalPos;
}
return snapped;
}

View File

@ -21,6 +21,7 @@
#include "nsQueryFrame.h"
#include "nsExpirationTracker.h"
#include "TextOverflow.h"
#include "ScrollVelocityQueue.h"
class nsPresContext;
class nsIPresShell;
@ -179,6 +180,9 @@ public:
gfxSize GetResolution() const;
void SetResolution(const gfxSize& aResolution);
void SetResolutionAndScaleTo(const gfxSize& aResolution);
void FlingSnap(const mozilla::CSSPoint& aDestination);
void ScrollSnap();
void ScrollSnap(const nsPoint &aDestination);
protected:
nsRect GetScrollRangeForClamping() const;
@ -195,8 +199,10 @@ public:
* This is a closed-ended range --- aRange.XMost()/aRange.YMost() are allowed.
*/
void ScrollTo(nsPoint aScrollPosition, nsIScrollableFrame::ScrollMode aMode,
const nsRect* aRange = nullptr) {
ScrollToWithOrigin(aScrollPosition, aMode, nsGkAtoms::other, aRange);
const nsRect* aRange = nullptr,
nsIScrollableFrame::ScrollSnapMode aSnap = nsIScrollableFrame::DISABLE_SNAP) {
ScrollToWithOrigin(aScrollPosition, aMode, nsGkAtoms::other, aRange,
aSnap);
}
/**
* @note This method might destroy the frame, pres shell and other objects.
@ -222,11 +228,23 @@ public:
void ScrollBy(nsIntPoint aDelta, nsIScrollableFrame::ScrollUnit aUnit,
nsIScrollableFrame::ScrollMode aMode, nsIntPoint* aOverflow,
nsIAtom* aOrigin = nullptr,
nsIScrollableFrame::ScrollMomentum aMomentum = nsIScrollableFrame::NOT_MOMENTUM);
nsIScrollableFrame::ScrollMomentum aMomentum = nsIScrollableFrame::NOT_MOMENTUM,
nsIScrollableFrame::ScrollSnapMode aSnap = nsIScrollableFrame::DISABLE_SNAP);
/**
* @note This method might destroy the frame, pres shell and other objects.
*/
void ScrollToRestoredPosition();
/**
* GetSnapPointForDestination determines which point to snap to after
* scrolling. aStartPos gives the position before scrolling and aDestination
* gives the position after scrolling, with no snapping. Behaviour is
* dependent on the value of aUnit.
* Returns true if a suitable snap point could be found and aDestination has
* been updated to a valid snapping position.
*/
bool GetSnapPointForDestination(nsIScrollableFrame::ScrollUnit aUnit,
nsPoint aStartPos,
nsPoint &aDestination);
nsSize GetLineScrollAmount() const;
nsSize GetPageScrollAmount() const;
@ -492,6 +510,8 @@ public:
// (as best as we can tell on the main thread, anyway).
bool mTransformingByAPZ:1;
mozilla::layout::ScrollVelocityQueue mVelocityQueue;
protected:
/**
* @note This method might destroy the frame, pres shell and other objects.
@ -499,7 +519,8 @@ protected:
void ScrollToWithOrigin(nsPoint aScrollPosition,
nsIScrollableFrame::ScrollMode aMode,
nsIAtom *aOrigin, // nullptr indicates "other" origin
const nsRect* aRange);
const nsRect* aRange,
nsIScrollableFrame::ScrollSnapMode aSnap = nsIScrollableFrame::DISABLE_SNAP);
void CompleteAsyncScroll(const nsRect &aRange, nsIAtom* aOrigin = nullptr);
@ -687,8 +708,10 @@ public:
* @note This method might destroy the frame, pres shell and other objects.
*/
virtual void ScrollTo(nsPoint aScrollPosition, ScrollMode aMode,
const nsRect* aRange = nullptr) MOZ_OVERRIDE {
mHelper.ScrollTo(aScrollPosition, aMode, aRange);
const nsRect* aRange = nullptr,
nsIScrollableFrame::ScrollSnapMode aSnap = nsIScrollableFrame::DISABLE_SNAP)
MOZ_OVERRIDE {
mHelper.ScrollTo(aScrollPosition, aMode, aRange, aSnap);
}
/**
* @note This method might destroy the frame, pres shell and other objects.
@ -713,9 +736,16 @@ public:
*/
virtual void ScrollBy(nsIntPoint aDelta, ScrollUnit aUnit, ScrollMode aMode,
nsIntPoint* aOverflow, nsIAtom* aOrigin = nullptr,
nsIScrollableFrame::ScrollMomentum aMomentum = nsIScrollableFrame::NOT_MOMENTUM)
nsIScrollableFrame::ScrollMomentum aMomentum = nsIScrollableFrame::NOT_MOMENTUM,
nsIScrollableFrame::ScrollSnapMode aSnap = nsIScrollableFrame::DISABLE_SNAP)
MOZ_OVERRIDE {
mHelper.ScrollBy(aDelta, aUnit, aMode, aOverflow, aOrigin, aMomentum);
mHelper.ScrollBy(aDelta, aUnit, aMode, aOverflow, aOrigin, aMomentum, aSnap);
}
virtual void FlingSnap(const mozilla::CSSPoint& aDestination) MOZ_OVERRIDE {
mHelper.FlingSnap(aDestination);
}
virtual void ScrollSnap() MOZ_OVERRIDE {
mHelper.ScrollSnap();
}
/**
* @note This method might destroy the frame, pres shell and other objects.
@ -1059,8 +1089,9 @@ public:
* @note This method might destroy the frame, pres shell and other objects.
*/
virtual void ScrollTo(nsPoint aScrollPosition, ScrollMode aMode,
const nsRect* aRange = nullptr) MOZ_OVERRIDE {
mHelper.ScrollTo(aScrollPosition, aMode, aRange);
const nsRect* aRange = nullptr,
ScrollSnapMode aSnap = nsIScrollableFrame::DISABLE_SNAP) MOZ_OVERRIDE {
mHelper.ScrollTo(aScrollPosition, aMode, aRange, aSnap);
}
/**
* @note This method might destroy the frame, pres shell and other objects.
@ -1082,9 +1113,16 @@ public:
*/
virtual void ScrollBy(nsIntPoint aDelta, ScrollUnit aUnit, ScrollMode aMode,
nsIntPoint* aOverflow, nsIAtom* aOrigin = nullptr,
nsIScrollableFrame::ScrollMomentum aMomentum = nsIScrollableFrame::NOT_MOMENTUM)
nsIScrollableFrame::ScrollMomentum aMomentum = nsIScrollableFrame::NOT_MOMENTUM,
nsIScrollableFrame::ScrollSnapMode aSnap = nsIScrollableFrame::DISABLE_SNAP)
MOZ_OVERRIDE {
mHelper.ScrollBy(aDelta, aUnit, aMode, aOverflow, aOrigin, aMomentum);
mHelper.ScrollBy(aDelta, aUnit, aMode, aOverflow, aOrigin, aMomentum, aSnap);
}
virtual void FlingSnap(const mozilla::CSSPoint& aDestination) MOZ_OVERRIDE {
mHelper.FlingSnap(aDestination);
}
virtual void ScrollSnap() MOZ_OVERRIDE {
mHelper.ScrollSnap();
}
/**
* @note This method might destroy the frame, pres shell and other objects.

View File

@ -207,6 +207,13 @@ public:
* been started since the last actual user input.
*/
enum ScrollMomentum { NOT_MOMENTUM, SYNTHESIZED_MOMENTUM_EVENT };
/**
* When set to ENABLE_SNAP, additional scrolling will be performed after the
* scroll operation to maintain the constraints set by CSS Scroll snapping.
* The additional scrolling may include asynchronous smooth scrolls that
* continue to animate after the initial scroll position has been set.
*/
enum ScrollSnapMode { DISABLE_SNAP, ENABLE_SNAP };
/**
* @note This method might destroy the frame, pres shell and other objects.
* Clamps aScrollPosition to GetScrollRange and sets the scroll position
@ -217,7 +224,8 @@ public:
* The choosen point will be as close as possible to aScrollPosition.
*/
virtual void ScrollTo(nsPoint aScrollPosition, ScrollMode aMode,
const nsRect* aRange = nullptr) = 0;
const nsRect* aRange = nullptr,
ScrollSnapMode aSnap = DISABLE_SNAP) = 0;
/**
* @note This method might destroy the frame, pres shell and other objects.
* Scrolls to a particular position in integer CSS pixels.
@ -271,7 +279,27 @@ public:
virtual void ScrollBy(nsIntPoint aDelta, ScrollUnit aUnit, ScrollMode aMode,
nsIntPoint* aOverflow = nullptr,
nsIAtom* aOrigin = nullptr,
ScrollMomentum aMomentum = NOT_MOMENTUM) = 0;
ScrollMomentum aMomentum = NOT_MOMENTUM,
ScrollSnapMode aSnap = DISABLE_SNAP) = 0;
/**
* Perform scroll snapping, possibly resulting in a smooth scroll to
* maintain the scroll snap position constraints. A predicted landing
* position determined by the APZC is used to select the best matching
* snap point, allowing touchscreen fling gestures to navigate between
* snap points.
* @param aDestination The desired landing position of the fling, which
* is used to select the best matching snap point.
*/
virtual void FlingSnap(const mozilla::CSSPoint& aDestination) = 0;
/**
* Perform scroll snapping, possibly resulting in a smooth scroll to
* maintain the scroll snap position constraints. Velocity sampled from
* main thread scrolling is used to determine best matching snap point
* when called after a fling gesture on a trackpad or mouse wheel.
*/
virtual void ScrollSnap() = 0;
/**
* @note This method might destroy the frame, pres shell and other objects.
* This tells the scroll frame to try scrolling to the scroll

View File

@ -101,6 +101,24 @@ public:
}
}
virtual void RequestFlingSnap(const FrameMetrics::ViewID& aScrollId,
const mozilla::CSSPoint& aDestination) MOZ_OVERRIDE
{
if (MessageLoop::current() != mUILoop) {
// We have to send this message from the "UI thread" (main
// thread).
mUILoop->PostTask(
FROM_HERE,
NewRunnableMethod(this, &RemoteContentController::RequestFlingSnap,
aScrollId, aDestination));
return;
}
if (mRenderFrame) {
TabParent* browser = TabParent::GetFrom(mRenderFrame->Manager());
browser->RequestFlingSnap(aScrollId, aDestination);
}
}
virtual void AcknowledgeScrollUpdate(const FrameMetrics::ViewID& aScrollId,
const uint32_t& aScrollGeneration) MOZ_OVERRIDE
{

View File

@ -102,6 +102,13 @@ APZCCallbackHandler::RequestContentRepaint(const FrameMetrics& aFrameMetrics)
}
}
void
APZCCallbackHandler::RequestFlingSnap(const FrameMetrics::ViewID& aScrollId,
const mozilla::CSSPoint& aDestination)
{
APZCCallbackHelper::RequestFlingSnap(aScrollId, aDestination);
}
void
APZCCallbackHandler::AcknowledgeScrollUpdate(const FrameMetrics::ViewID& aScrollId,
const uint32_t& aScrollGeneration)

View File

@ -44,6 +44,8 @@ public:
public: // GeckoContentController methods
void RequestContentRepaint(const mozilla::layers::FrameMetrics& aFrameMetrics) MOZ_OVERRIDE;
void RequestFlingSnap(const mozilla::layers::FrameMetrics::ViewID& aScrollId,
const mozilla::CSSPoint& aDestination) MOZ_OVERRIDE;
void AcknowledgeScrollUpdate(const mozilla::layers::FrameMetrics::ViewID& aScrollId,
const uint32_t& aScrollGeneration) MOZ_OVERRIDE;
void HandleDoubleTap(const mozilla::CSSPoint& aPoint, Modifiers aModifiers,

View File

@ -187,6 +187,17 @@ APZController::RequestContentRepaint(const FrameMetrics& aFrameMetrics)
}
}
void
APZController::RequestFlingSnap(const FrameMetrics::ViewID& aScrollId,
const mozilla::CSSPoint& aDestination)
{
#ifdef DEBUG_CONTROLLER
WinUtils::Log("APZController::RequestFlingSnap scrollid=%I64d destination: %lu %lu",
aScrollId, aDestination.x, aDestination.y);
#endif
mozilla::layers::APZCCallbackHelper::RequestFlingSnap(aScrollId, aDestination);
}
void
APZController::AcknowledgeScrollUpdate(const FrameMetrics::ViewID& aScrollId,
const uint32_t& aScrollGeneration)

View File

@ -37,6 +37,8 @@ public:
// GeckoContentController interface
virtual void RequestContentRepaint(const FrameMetrics& aFrameMetrics);
virtual void RequestFlingSnap(const FrameMetrics::ViewID& aScrollId,
const mozilla::CSSPoint& aDestination);
virtual void AcknowledgeScrollUpdate(const FrameMetrics::ViewID& aScrollId, const uint32_t& aScrollGeneration);
virtual void HandleDoubleTap(const mozilla::CSSPoint& aPoint,
Modifiers aModifiers,