Changeset 210560 in webkit


Ignore:
Timestamp:
Jan 10, 2017 2:26:56 PM (7 years ago)
Author:
Wenson Hsieh
Message:

Implement "proximity" scroll snapping
https://bugs.webkit.org/show_bug.cgi?id=135994
<rdar://problem/18162418>

Reviewed by Dean Jackson.

Source/WebCore:

Adds support for proximity scroll snapping. To do this, we introduce scroll offset ranges, a list of scroll
offset ranges that are plumbed alongside the list of scroll snap offsets. Similar to a snap offset, a snap
offset range contains scroll offsets on which scrolling is allowed to come to a rest within a scroll snapping
container. However, unlike normal snap offsets, scrolling may only come to rest within a snap offset range if
the predicted scroll offset already lies within the range. The new algorithm for selecting a target scroll snap
position given a destination offset is now:

  • If the scroll destination lies within a snap offset range, return the scroll destination
  • Otherwise, compute the nearest lower/upper snap offsets and lower/upper snap offset ranges
  • If scrolling ended with no velocity, return the nearest snap offset
  • If scrolling ended with positive velocity, choose the upper snap offset only if there is no snap offset

range in between the scroll destination and the snap offset; else, choose the lower snap offset

  • If scrolling ended with negative velocity, choose the lower snap offset only if there is no snap offset

range in between the scroll destination and the snap offset; else, choose the upper snap offset

The extra rule accounting for scroll offset ranges in between the scroll destination and a potential snap offset
handles the corner case where the user scrolls with momentum very lightly away from a snap offset, such that the
predicted scroll destination is still within proximity of the snap offset. In this case, the regular (mandatory
scroll snapping) behavior would be to snap to the next offset in the direction of momentum scrolling, but
instead, it is more intuitive to return to the original snap position.

We also move scrolling prediction logic into ScrollingMomentumCalculator and adopt the platform
_NSScrollingMomentumCalculator's destinationOrigin property when computing the predicted scroll destination.
Previously, we were simply multiplying by an empirically-derived constant to approximate the scroll destination,
but now that we are supporting proximity scroll snapping, we need more exact scroll destinaton prediction in
order to make sure that scrolling to a snap offset range feels natural.

Tests: tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-then-proximity.html

tiled-drawing/scrolling/scroll-snap/scroll-snap-proximity-mainframe.html
tiled-drawing/scrolling/scroll-snap/scroll-snap-proximity-overflow.html

  • WebCore.xcodeproj/project.pbxproj:
  • page/scrolling/AsyncScrollingCoordinator.cpp:

(WebCore::setStateScrollingNodeSnapOffsetsAsFloat):
(WebCore::AsyncScrollingCoordinator::updateOverflowScrollingNode):
(WebCore::AsyncScrollingCoordinator::updateScrollSnapPropertiesWithFrameView):

Make boilerplate changes to plumb lists of horizontal and vertical snap offset ranges alongside the lists of
horizontal and vertical snap offsets.

  • page/scrolling/AxisScrollSnapOffsets.cpp:

(WebCore::snapOffsetRangesToString):
(WebCore::indicesOfNearestSnapOffsetRanges):
(WebCore::indicesOfNearestSnapOffsets):
(WebCore::adjustAxisSnapOffsetsForScrollExtent):
(WebCore::computeAxisProximitySnapOffsetRanges):
(WebCore::updateSnapOffsetsForScrollableArea):
(WebCore::closestSnapOffset):

Adjust the snap offset selection algorithm to take snap offset ranges into account. See above for more details.
Additionally, augment snap offset update logic to emit snap offset ranges for proximity scroll snapping. To do
this, we run the following steps on the final list of processed snap offsets:

  • Compute the proximity distance, which (for now) is arbitrarily 0.3 * the length or width of the scroll snap

port, depending on whether scroll snapping is taking place in the X or Y axis.

  • For each pair of adjacent snap offsets, if they are more than 2 * proximity distance away from each other,

emit a snap offset range starting from (lower snap offset + proximity distance) and ending on (upper snap
offset + proximity distance).

  • page/scrolling/AxisScrollSnapOffsets.h:

(WebCore::closestSnapOffset): Deleted.

  • page/scrolling/ScrollSnapOffsetsInfo.h:

Introduce ScrollSnapOffsetsInfo, a struct which contains data relevant to scroll snapping. This includes
vertical and horizontal snap offsets, as well as vertical and horizontal snap offset ranges. Snap offset ranges
consist of a vector of ranges of scroll offsets.

  • page/scrolling/ScrollingCoordinator.h:
  • page/scrolling/ScrollingMomentumCalculator.cpp:

(WebCore::projectedInertialScrollDistance):
(WebCore::ScrollingMomentumCalculator::ScrollingMomentumCalculator):
(WebCore::ScrollingMomentumCalculator::setRetargetedScrollOffset):
(WebCore::ScrollingMomentumCalculator::predictedDestinationOffset):
(WebCore::ScrollingMomentumCalculator::create):
(WebCore::ScrollingMomentumCalculator::setPlatformMomentumScrollingPredictionEnabled):
(WebCore::BasicScrollingMomentumCalculator::BasicScrollingMomentumCalculator):
(WebCore::BasicScrollingMomentumCalculator::linearlyInterpolatedOffsetAtProgress):
(WebCore::BasicScrollingMomentumCalculator::initializeInterpolationCoefficientsIfNecessary):
(WebCore::BasicScrollingMomentumCalculator::initializeSnapProgressCurve):

  • page/scrolling/ScrollingMomentumCalculator.h:

(WebCore::ScrollingMomentumCalculator::retargetedScrollOffset):
(WebCore::ScrollingMomentumCalculator::retargetedScrollOffsetDidChange):

Currently, the ScrollingMomentumCalculator is responsible for taking an initial position, initial velocity, and
target position and animating the scroll offset from the initial to target position. Now, we refactor the
ScrollingMomentumCalculator interface to no longer take a target offset upon initialization, and instead compute
the predicted scroll destination given initial position and velocity; clients of the ScrollingMomentumCalculator
then use this predicted scroll destination to compute a retargeted scroll offset and then call
setRetargetedScrollOffset on the calculator, which sets up an animation curve to the new retargeted offset. This
allows both the AppKit-based scrolling momentum calculator and platform-invariant momentum calculator to be used
interchangeably, while still allowing them to compute a destination offset from initial parameters of the
scroll.

  • page/scrolling/ScrollingStateScrollingNode.cpp:

(WebCore::ScrollingStateScrollingNode::ScrollingStateScrollingNode):
(WebCore::ScrollingStateScrollingNode::setHorizontalSnapOffsets):
(WebCore::ScrollingStateScrollingNode::setVerticalSnapOffsets):
(WebCore::ScrollingStateScrollingNode::setHorizontalSnapOffsetRanges):
(WebCore::ScrollingStateScrollingNode::setVerticalSnapOffsetRanges):

  • page/scrolling/ScrollingStateScrollingNode.h:

(WebCore::ScrollingStateScrollingNode::horizontalSnapOffsets):
(WebCore::ScrollingStateScrollingNode::verticalSnapOffsets):
(WebCore::ScrollingStateScrollingNode::horizontalSnapOffsetRanges):
(WebCore::ScrollingStateScrollingNode::verticalSnapOffsetRanges):

  • page/scrolling/ScrollingTreeScrollingNode.cpp:

(WebCore::ScrollingTreeScrollingNode::commitStateBeforeChildren):
(WebCore::ScrollingTreeScrollingNode::dumpProperties):

  • page/scrolling/ScrollingTreeScrollingNode.h:

(WebCore::ScrollingTreeScrollingNode::horizontalSnapOffsets):
(WebCore::ScrollingTreeScrollingNode::verticalSnapOffsets):
(WebCore::ScrollingTreeScrollingNode::horizontalSnapOffsetRanges):
(WebCore::ScrollingTreeScrollingNode::verticalSnapOffsetRanges):

Add more boilerplate support for snap offset ranges.

  • page/scrolling/mac/ScrollingMomentumCalculatorMac.h:
  • page/scrolling/mac/ScrollingMomentumCalculatorMac.mm:

(WebCore::ScrollingMomentumCalculator::create):
(WebCore::ScrollingMomentumCalculator::setPlatformMomentumScrollingPredictionEnabled):
(WebCore::ScrollingMomentumCalculatorMac::ScrollingMomentumCalculatorMac):
(WebCore::ScrollingMomentumCalculatorMac::scrollOffsetAfterElapsedTime):
(WebCore::ScrollingMomentumCalculatorMac::predictedDestinationOffset):
(WebCore::ScrollingMomentumCalculatorMac::retargetedScrollOffsetDidChange):
(WebCore::ScrollingMomentumCalculatorMac::animationDuration):
(WebCore::ScrollingMomentumCalculatorMac::requiresMomentumScrolling):
(WebCore::ScrollingMomentumCalculatorMac::ensurePlatformMomentumCalculator):

Hook into AppKit momentum scroll offset prediction.

  • page/scrolling/mac/ScrollingTreeFrameScrollingNodeMac.mm:

(WebCore::convertToLayoutUnits):
(WebCore::ScrollingTreeFrameScrollingNodeMac::commitStateBeforeChildren):

  • platform/ScrollableArea.cpp:

(WebCore::ScrollableArea::ensureSnapOffsetsInfo):
(WebCore::ScrollableArea::horizontalSnapOffsets):
(WebCore::ScrollableArea::horizontalSnapOffsetRanges):
(WebCore::ScrollableArea::verticalSnapOffsetRanges):
(WebCore::ScrollableArea::verticalSnapOffsets):
(WebCore::ScrollableArea::setHorizontalSnapOffsets):
(WebCore::ScrollableArea::setVerticalSnapOffsets):
(WebCore::ScrollableArea::setHorizontalSnapOffsetRanges):
(WebCore::ScrollableArea::setVerticalSnapOffsetRanges):
(WebCore::ScrollableArea::clearHorizontalSnapOffsets):
(WebCore::ScrollableArea::clearVerticalSnapOffsets):

  • platform/ScrollableArea.h:

(WebCore::ScrollableArea::horizontalSnapOffsets): Deleted.
(WebCore::ScrollableArea::verticalSnapOffsets): Deleted.

  • platform/cocoa/ScrollController.h:
  • platform/cocoa/ScrollController.mm:

(WebCore::ScrollController::processWheelEventForScrollSnap):

Fix an issue where initial scrolling velocity would be set to zero at the end of a drag gesture.

(WebCore::ScrollController::updateScrollSnapState):
(WebCore::ScrollController::updateScrollSnapPoints):
(WebCore::ScrollController::setNearestScrollSnapIndexForAxisAndOffset):

  • platform/cocoa/ScrollSnapAnimatorState.h:

(WebCore::ScrollSnapAnimatorState::snapOffsetsForAxis):
(WebCore::ScrollSnapAnimatorState::snapOffsetRangesForAxis):
(WebCore::ScrollSnapAnimatorState::setSnapOffsetsAndPositionRangesForAxis):
(WebCore::ScrollSnapAnimatorState::setSnapOffsetsForAxis): Deleted.

  • platform/cocoa/ScrollSnapAnimatorState.mm:

(WebCore::ScrollSnapAnimatorState::setupAnimationForState):
(WebCore::ScrollSnapAnimatorState::targetOffsetForStartOffset):
(WebCore::projectedInertialScrollDistance): Deleted.

  • rendering/RenderLayerCompositor.cpp:

(WebCore::RenderLayerCompositor::updateScrollCoordinatedLayer):

  • testing/Internals.cpp:

(WebCore::Internals::setPlatformMomentumScrollingPredictionEnabled):

Add a new hook for layout tests to force scrolling momentum calculators to use the platform-invariant momentum
scrolling prediction heuristic instead of the platform-dependent one.

(WebCore::Internals::scrollSnapOffsets):

  • testing/Internals.h:
  • testing/Internals.idl:

Source/WebKit2:

Adds boilerplate support for plumbing lists of snap offset ranges from the web process to the UI process
alongside the list of snap offsets.

  • Shared/Scrolling/RemoteScrollingCoordinatorTransaction.cpp:

(ArgumentCoder<ScrollingStateScrollingNode>::encode):
(ArgumentCoder<ScrollingStateScrollingNode>::decode):

  • Shared/WebCoreArgumentCoders.cpp:

(IPC::ArgumentCoder<ScrollOffsetRange<float>>::encode):
(IPC::ArgumentCoder<ScrollOffsetRange<float>>::decode):

  • Shared/WebCoreArgumentCoders.h:
  • UIProcess/Scrolling/ios/ScrollingTreeOverflowScrollingNodeIOS.mm:

(-[WKOverflowScrollViewDelegate scrollViewWillEndDragging:withVelocity:targetContentOffset:]):

  • UIProcess/ios/RemoteScrollingCoordinatorProxyIOS.mm:

Adjust mainframe proximity scroll snapping logic to not subtract out the top content inset when there is no
active snap offset (i.e. when snapping rests in a snap offset range). Attempting to subtract out the top inset
in this case caused the scroll offset to jump after ending a drag with no momentum in a snap offset range.

(WebKit::RemoteScrollingCoordinatorProxy::adjustTargetContentOffsetForSnapping):
(WebKit::RemoteScrollingCoordinatorProxy::shouldSnapForMainFrameScrolling):
(WebKit::RemoteScrollingCoordinatorProxy::closestSnapOffsetForMainFrameScrolling):

LayoutTests:

Adds 3 new layout tests for proximity scroll snapping. Also tweaks some existing tests that test scroll snapping
after scrolling with momentum to use the custom heuristic for predicting scroll destination instead of platform
momentum scrolling. This ensures that the results of our layout tests that depend on predicting momentum scroll
destination are consistent across runs.

  • tiled-drawing/scrolling/latched-div-with-scroll-snap.html:
  • tiled-drawing/scrolling/scroll-snap/scroll-snap-iframe.html:
  • tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-2d-overflow.html:
  • tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-borders.html:
  • tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-hidden-scrollbars.html:
  • tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-mainframe-horizontal.html:
  • tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-mainframe-slow-horizontal.html:
  • tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-mainframe-slow-vertical.html:
  • tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-mainframe-vertical-then-horizontal.html:
  • tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-mainframe-vertical.html:
  • tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-overflow-stateless.html:
  • tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-overflow.html:
  • tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-padding.html:
  • tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-rotated.html:

Force these tests to use platform-independent scrolling momentum prediction, by multiplying the last scroll
delta upon release by a constant factor.

  • tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-then-proximity-expected.txt: Added.
  • tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-then-proximity.html: Added.

Tests that after changing scroll-snap-type from mandatory to proximity, swiping downwards no longer snaps the
scroll offset to the second box, but instead leaves the scroll offset somewhere in the middle of the first box.

  • tiled-drawing/scrolling/scroll-snap/scroll-snap-proximity-mainframe-expected.txt: Added.
  • tiled-drawing/scrolling/scroll-snap/scroll-snap-proximity-mainframe.html: Added.

Tests that when scroll-snap-type is proximity in the mainframe, scrolling slightly downwards snaps the scroll
offset back up to the top; scrolling somewhere in the middle of the first box does not snap the scroll offset;
and scrolling near the end of the first box snaps the scroll offset to the second box.

  • tiled-drawing/scrolling/scroll-snap/scroll-snap-proximity-overflow-expected.txt: Added.
  • tiled-drawing/scrolling/scroll-snap/scroll-snap-proximity-overflow.html: Added.

Similar to scroll-snap-proximity-mainframe.html, except for overflow scrolling instead of the mainframe.

  • tiled-drawing/scrolling/scroll-snap/scroll-snap-scrolling-jumps-to-top.html:
Location:
trunk
Files:
6 added
47 edited
1 copied

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r210559 r210560  
     12017-01-10  Wenson Hsieh  <wenson_hsieh@apple.com>
     2
     3        Implement "proximity" scroll snapping
     4        https://bugs.webkit.org/show_bug.cgi?id=135994
     5        <rdar://problem/18162418>
     6
     7        Reviewed by Dean Jackson.
     8
     9        Adds 3 new layout tests for proximity scroll snapping. Also tweaks some existing tests that test scroll snapping
     10        after scrolling with momentum to use the custom heuristic for predicting scroll destination instead of platform
     11        momentum scrolling. This ensures that the results of our layout tests that depend on predicting momentum scroll
     12        destination are consistent across runs.
     13
     14        * tiled-drawing/scrolling/latched-div-with-scroll-snap.html:
     15        * tiled-drawing/scrolling/scroll-snap/scroll-snap-iframe.html:
     16        * tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-2d-overflow.html:
     17        * tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-borders.html:
     18        * tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-hidden-scrollbars.html:
     19        * tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-mainframe-horizontal.html:
     20        * tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-mainframe-slow-horizontal.html:
     21        * tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-mainframe-slow-vertical.html:
     22        * tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-mainframe-vertical-then-horizontal.html:
     23        * tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-mainframe-vertical.html:
     24        * tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-overflow-stateless.html:
     25        * tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-overflow.html:
     26        * tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-padding.html:
     27        * tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-rotated.html:
     28
     29        Force these tests to use platform-independent scrolling momentum prediction, by multiplying the last scroll
     30        delta upon release by a constant factor.
     31
     32        * tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-then-proximity-expected.txt: Added.
     33        * tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-then-proximity.html: Added.
     34
     35        Tests that after changing scroll-snap-type from mandatory to proximity, swiping downwards no longer snaps the
     36        scroll offset to the second box, but instead leaves the scroll offset somewhere in the middle of the first box.
     37
     38        * tiled-drawing/scrolling/scroll-snap/scroll-snap-proximity-mainframe-expected.txt: Added.
     39        * tiled-drawing/scrolling/scroll-snap/scroll-snap-proximity-mainframe.html: Added.
     40
     41        Tests that when scroll-snap-type is proximity in the mainframe, scrolling slightly downwards snaps the scroll
     42        offset back up to the top; scrolling somewhere in the middle of the first box does not snap the scroll offset;
     43        and scrolling near the end of the first box snaps the scroll offset to the second box.
     44
     45        * tiled-drawing/scrolling/scroll-snap/scroll-snap-proximity-overflow-expected.txt: Added.
     46        * tiled-drawing/scrolling/scroll-snap/scroll-snap-proximity-overflow.html: Added.
     47
     48        Similar to scroll-snap-proximity-mainframe.html, except for overflow scrolling instead of the mainframe.
     49
     50        * tiled-drawing/scrolling/scroll-snap/scroll-snap-scrolling-jumps-to-top.html:
     51
    1522017-01-10  Chris Dumez  <cdumez@apple.com>
    253
  • trunk/LayoutTests/tiled-drawing/scrolling/latched-div-with-scroll-snap.html

    r210024 r210560  
    7575                write(`* Swiping ${momentum ? "with" : "without"} momentum in ${element.id} with scroll offset ${element.scrollLeft}`);
    7676                let location = locationInWindowCoordinates(element);
     77                internals.setPlatformMomentumScrollingPredictionEnabled(false);
    7778                eventSender.monitorWheelEvents();
    7879                eventSender.mouseMoveTo(location.x, location.y);
  • trunk/LayoutTests/tiled-drawing/scrolling/scroll-snap/scroll-snap-iframe.html

    r188793 r210560  
    148148            if (window.eventSender) {
    149149                eventSender.monitorWheelEvents();
     150                internals.setPlatformMomentumScrollingPredictionEnabled(false);
    150151                setTimeout(function() { scrollGlideTest('horizontalTarget') }, 0);
    151152            }
  • trunk/LayoutTests/tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-2d-overflow.html

    r210024 r210560  
    124124            if (window.eventSender) {
    125125                eventSender.monitorWheelEvents();
     126                internals.setPlatformMomentumScrollingPredictionEnabled(false);
    126127                setTimeout(scrollGlideTest, 0);
    127128            } else {
  • trunk/LayoutTests/tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-borders.html

    r210024 r210560  
    178178            if (window.eventSender) {
    179179                eventSender.monitorWheelEvents();
     180                internals.setPlatformMomentumScrollingPredictionEnabled(false);
    180181                setTimeout(function() { scrollGlideTest('horizontalTarget') }, 0);
    181182            } else {
  • trunk/LayoutTests/tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-hidden-scrollbars.html

    r210117 r210560  
    4545            }
    4646
     47            internals.setPlatformMomentumScrollingPredictionEnabled(false);
    4748            eventSender.monitorWheelEvents();
    4849            eventSender.mouseMoveTo(250, 250);
  • trunk/LayoutTests/tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-mainframe-horizontal.html

    r210024 r210560  
    9292            if (window.eventSender) {
    9393                eventSender.monitorWheelEvents();
     94                internals.setPlatformMomentumScrollingPredictionEnabled(false);
    9495                setTimeout(scrollGlideTest, 0);
    9596            } else {
  • trunk/LayoutTests/tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-mainframe-slow-horizontal.html

    r210024 r210560  
    9292            if (window.eventSender) {
    9393                eventSender.monitorWheelEvents();
     94                internals.setPlatformMomentumScrollingPredictionEnabled(false);
    9495                setTimeout(scrollGlideTest, 0);
    9596            } else {
  • trunk/LayoutTests/tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-mainframe-slow-vertical.html

    r210024 r210560  
    9191            if (window.eventSender) {
    9292                eventSender.monitorWheelEvents();
     93                internals.setPlatformMomentumScrollingPredictionEnabled(false);
    9394                setTimeout(scrollGlideTest, 0);
    9495            } else {
  • trunk/LayoutTests/tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-mainframe-vertical-then-horizontal.html

    r210024 r210560  
    5353            if (window.eventSender) {
    5454                eventSender.monitorWheelEvents();
     55                internals.setPlatformMomentumScrollingPredictionEnabled(false);
    5556                setTimeout(scrollSnapTest, 0);
    5657            } else {
  • trunk/LayoutTests/tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-mainframe-vertical.html

    r210024 r210560  
    9292            if (window.eventSender) {
    9393                eventSender.monitorWheelEvents();
     94                internals.setPlatformMomentumScrollingPredictionEnabled(false);
    9495                setTimeout(scrollGlideTest, 0);
    9596            } else {
  • trunk/LayoutTests/tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-overflow-stateless.html

    r210024 r210560  
    6969            if (window.eventSender) {
    7070                eventSender.monitorWheelEvents();
     71                internals.setPlatformMomentumScrollingPredictionEnabled(false);
    7172                setTimeout(scrollSnapTest, 0);
    7273            } else {
  • trunk/LayoutTests/tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-overflow.html

    r210024 r210560  
    168168            if (window.eventSender) {
    169169                eventSender.monitorWheelEvents();
     170                internals.setPlatformMomentumScrollingPredictionEnabled(false);
    170171                setTimeout(function() { scrollGlideTest('horizontalTarget') }, 0);
    171172            } else {
  • trunk/LayoutTests/tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-padding.html

    r210024 r210560  
    178178            if (window.eventSender) {
    179179                eventSender.monitorWheelEvents();
     180                internals.setPlatformMomentumScrollingPredictionEnabled(false);
    180181                setTimeout(function() { scrollGlideTest('horizontalTarget') }, 0);
    181182            } else {
  • trunk/LayoutTests/tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-rotated.html

    r210024 r210560  
    170170            if (window.eventSender) {
    171171                eventSender.monitorWheelEvents();
     172                internals.setPlatformMomentumScrollingPredictionEnabled(false);
    172173                setTimeout(function() { scrollGlideTest('horizontalTarget') }, 0);
    173174            } else {
  • trunk/LayoutTests/tiled-drawing/scrolling/scroll-snap/scroll-snap-scrolling-jumps-to-top.html

    r210024 r210560  
    3535            testRunner.waitUntilDone();
    3636            eventSender.monitorWheelEvents();
     37            internals.setPlatformMomentumScrollingPredictionEnabled(false);
    3738            eventSender.mouseMoveTo(container.offsetLeft + container.clientWidth / 2, container.offsetTop + container.clientHeight / 2);
    3839
  • trunk/Source/WebCore/ChangeLog

    r210559 r210560  
     12017-01-10  Wenson Hsieh  <wenson_hsieh@apple.com>
     2
     3        Implement "proximity" scroll snapping
     4        https://bugs.webkit.org/show_bug.cgi?id=135994
     5        <rdar://problem/18162418>
     6
     7        Reviewed by Dean Jackson.
     8
     9        Adds support for proximity scroll snapping. To do this, we introduce scroll offset ranges, a list of scroll
     10        offset ranges that are plumbed alongside the list of scroll snap offsets. Similar to a snap offset, a snap
     11        offset range contains scroll offsets on which scrolling is allowed to come to a rest within a scroll snapping
     12        container. However, unlike normal snap offsets, scrolling may only come to rest within a snap offset range if
     13        the predicted scroll offset already lies within the range. The new algorithm for selecting a target scroll snap
     14        position given a destination offset is now:
     15
     16        -   If the scroll destination lies within a snap offset range, return the scroll destination
     17        -   Otherwise, compute the nearest lower/upper snap offsets and lower/upper snap offset ranges
     18        -   If scrolling ended with no velocity, return the nearest snap offset
     19        -   If scrolling ended with positive velocity, choose the upper snap offset only if there is no snap offset
     20            range in between the scroll destination and the snap offset; else, choose the lower snap offset
     21        -   If scrolling ended with negative velocity, choose the lower snap offset only if there is no snap offset
     22            range in between the scroll destination and the snap offset; else, choose the upper snap offset
     23
     24        The extra rule accounting for scroll offset ranges in between the scroll destination and a potential snap offset
     25        handles the corner case where the user scrolls with momentum very lightly away from a snap offset, such that the
     26        predicted scroll destination is still within proximity of the snap offset. In this case, the regular (mandatory
     27        scroll snapping) behavior would be to snap to the next offset in the direction of momentum scrolling, but
     28        instead, it is more intuitive to return to the original snap position.
     29
     30        We also move scrolling prediction logic into ScrollingMomentumCalculator and adopt the platform
     31        _NSScrollingMomentumCalculator's destinationOrigin property when computing the predicted scroll destination.
     32        Previously, we were simply multiplying by an empirically-derived constant to approximate the scroll destination,
     33        but now that we are supporting proximity scroll snapping, we need more exact scroll destinaton prediction in
     34        order to make sure that scrolling to a snap offset range feels natural.
     35
     36        Tests: tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-then-proximity.html
     37               tiled-drawing/scrolling/scroll-snap/scroll-snap-proximity-mainframe.html
     38               tiled-drawing/scrolling/scroll-snap/scroll-snap-proximity-overflow.html
     39
     40        * WebCore.xcodeproj/project.pbxproj:
     41        * page/scrolling/AsyncScrollingCoordinator.cpp:
     42        (WebCore::setStateScrollingNodeSnapOffsetsAsFloat):
     43        (WebCore::AsyncScrollingCoordinator::updateOverflowScrollingNode):
     44        (WebCore::AsyncScrollingCoordinator::updateScrollSnapPropertiesWithFrameView):
     45
     46        Make boilerplate changes to plumb lists of horizontal and vertical snap offset ranges alongside the lists of
     47        horizontal and vertical snap offsets.
     48
     49        * page/scrolling/AxisScrollSnapOffsets.cpp:
     50        (WebCore::snapOffsetRangesToString):
     51        (WebCore::indicesOfNearestSnapOffsetRanges):
     52        (WebCore::indicesOfNearestSnapOffsets):
     53        (WebCore::adjustAxisSnapOffsetsForScrollExtent):
     54        (WebCore::computeAxisProximitySnapOffsetRanges):
     55        (WebCore::updateSnapOffsetsForScrollableArea):
     56        (WebCore::closestSnapOffset):
     57
     58        Adjust the snap offset selection algorithm to take snap offset ranges into account. See above for more details.
     59        Additionally, augment snap offset update logic to emit snap offset ranges for proximity scroll snapping. To do
     60        this, we run the following steps on the final list of processed snap offsets:
     61        -   Compute the proximity distance, which (for now) is arbitrarily 0.3 * the length or width of the scroll snap
     62            port, depending on whether scroll snapping is taking place in the X or Y axis.
     63        -   For each pair of adjacent snap offsets, if they are more than 2 * proximity distance away from each other,
     64            emit a snap offset range starting from (lower snap offset + proximity distance) and ending on (upper snap
     65            offset + proximity distance).
     66
     67        * page/scrolling/AxisScrollSnapOffsets.h:
     68        (WebCore::closestSnapOffset): Deleted.
     69        * page/scrolling/ScrollSnapOffsetsInfo.h:
     70
     71        Introduce ScrollSnapOffsetsInfo, a struct which contains data relevant to scroll snapping. This includes
     72        vertical and horizontal snap offsets, as well as vertical and horizontal snap offset ranges. Snap offset ranges
     73        consist of a vector of ranges of scroll offsets.
     74
     75        * page/scrolling/ScrollingCoordinator.h:
     76        * page/scrolling/ScrollingMomentumCalculator.cpp:
     77        (WebCore::projectedInertialScrollDistance):
     78        (WebCore::ScrollingMomentumCalculator::ScrollingMomentumCalculator):
     79        (WebCore::ScrollingMomentumCalculator::setRetargetedScrollOffset):
     80        (WebCore::ScrollingMomentumCalculator::predictedDestinationOffset):
     81        (WebCore::ScrollingMomentumCalculator::create):
     82        (WebCore::ScrollingMomentumCalculator::setPlatformMomentumScrollingPredictionEnabled):
     83        (WebCore::BasicScrollingMomentumCalculator::BasicScrollingMomentumCalculator):
     84        (WebCore::BasicScrollingMomentumCalculator::linearlyInterpolatedOffsetAtProgress):
     85        (WebCore::BasicScrollingMomentumCalculator::initializeInterpolationCoefficientsIfNecessary):
     86        (WebCore::BasicScrollingMomentumCalculator::initializeSnapProgressCurve):
     87        * page/scrolling/ScrollingMomentumCalculator.h:
     88        (WebCore::ScrollingMomentumCalculator::retargetedScrollOffset):
     89        (WebCore::ScrollingMomentumCalculator::retargetedScrollOffsetDidChange):
     90
     91        Currently, the ScrollingMomentumCalculator is responsible for taking an initial position, initial velocity, and
     92        target position and animating the scroll offset from the initial to target position. Now, we refactor the
     93        ScrollingMomentumCalculator interface to no longer take a target offset upon initialization, and instead compute
     94        the predicted scroll destination given initial position and velocity; clients of the ScrollingMomentumCalculator
     95        then use this predicted scroll destination to compute a retargeted scroll offset and then call
     96        setRetargetedScrollOffset on the calculator, which sets up an animation curve to the new retargeted offset. This
     97        allows both the AppKit-based scrolling momentum calculator and platform-invariant momentum calculator to be used
     98        interchangeably, while still allowing them to compute a destination offset from initial parameters of the
     99        scroll.
     100
     101        * page/scrolling/ScrollingStateScrollingNode.cpp:
     102        (WebCore::ScrollingStateScrollingNode::ScrollingStateScrollingNode):
     103        (WebCore::ScrollingStateScrollingNode::setHorizontalSnapOffsets):
     104        (WebCore::ScrollingStateScrollingNode::setVerticalSnapOffsets):
     105        (WebCore::ScrollingStateScrollingNode::setHorizontalSnapOffsetRanges):
     106        (WebCore::ScrollingStateScrollingNode::setVerticalSnapOffsetRanges):
     107        * page/scrolling/ScrollingStateScrollingNode.h:
     108        (WebCore::ScrollingStateScrollingNode::horizontalSnapOffsets):
     109        (WebCore::ScrollingStateScrollingNode::verticalSnapOffsets):
     110        (WebCore::ScrollingStateScrollingNode::horizontalSnapOffsetRanges):
     111        (WebCore::ScrollingStateScrollingNode::verticalSnapOffsetRanges):
     112        * page/scrolling/ScrollingTreeScrollingNode.cpp:
     113        (WebCore::ScrollingTreeScrollingNode::commitStateBeforeChildren):
     114        (WebCore::ScrollingTreeScrollingNode::dumpProperties):
     115        * page/scrolling/ScrollingTreeScrollingNode.h:
     116        (WebCore::ScrollingTreeScrollingNode::horizontalSnapOffsets):
     117        (WebCore::ScrollingTreeScrollingNode::verticalSnapOffsets):
     118        (WebCore::ScrollingTreeScrollingNode::horizontalSnapOffsetRanges):
     119        (WebCore::ScrollingTreeScrollingNode::verticalSnapOffsetRanges):
     120
     121        Add more boilerplate support for snap offset ranges.
     122
     123        * page/scrolling/mac/ScrollingMomentumCalculatorMac.h:
     124        * page/scrolling/mac/ScrollingMomentumCalculatorMac.mm:
     125        (WebCore::ScrollingMomentumCalculator::create):
     126        (WebCore::ScrollingMomentumCalculator::setPlatformMomentumScrollingPredictionEnabled):
     127        (WebCore::ScrollingMomentumCalculatorMac::ScrollingMomentumCalculatorMac):
     128        (WebCore::ScrollingMomentumCalculatorMac::scrollOffsetAfterElapsedTime):
     129        (WebCore::ScrollingMomentumCalculatorMac::predictedDestinationOffset):
     130        (WebCore::ScrollingMomentumCalculatorMac::retargetedScrollOffsetDidChange):
     131        (WebCore::ScrollingMomentumCalculatorMac::animationDuration):
     132        (WebCore::ScrollingMomentumCalculatorMac::requiresMomentumScrolling):
     133        (WebCore::ScrollingMomentumCalculatorMac::ensurePlatformMomentumCalculator):
     134
     135        Hook into AppKit momentum scroll offset prediction.
     136
     137        * page/scrolling/mac/ScrollingTreeFrameScrollingNodeMac.mm:
     138        (WebCore::convertToLayoutUnits):
     139        (WebCore::ScrollingTreeFrameScrollingNodeMac::commitStateBeforeChildren):
     140        * platform/ScrollableArea.cpp:
     141        (WebCore::ScrollableArea::ensureSnapOffsetsInfo):
     142        (WebCore::ScrollableArea::horizontalSnapOffsets):
     143        (WebCore::ScrollableArea::horizontalSnapOffsetRanges):
     144        (WebCore::ScrollableArea::verticalSnapOffsetRanges):
     145        (WebCore::ScrollableArea::verticalSnapOffsets):
     146        (WebCore::ScrollableArea::setHorizontalSnapOffsets):
     147        (WebCore::ScrollableArea::setVerticalSnapOffsets):
     148        (WebCore::ScrollableArea::setHorizontalSnapOffsetRanges):
     149        (WebCore::ScrollableArea::setVerticalSnapOffsetRanges):
     150        (WebCore::ScrollableArea::clearHorizontalSnapOffsets):
     151        (WebCore::ScrollableArea::clearVerticalSnapOffsets):
     152        * platform/ScrollableArea.h:
     153        (WebCore::ScrollableArea::horizontalSnapOffsets): Deleted.
     154        (WebCore::ScrollableArea::verticalSnapOffsets): Deleted.
     155        * platform/cocoa/ScrollController.h:
     156        * platform/cocoa/ScrollController.mm:
     157        (WebCore::ScrollController::processWheelEventForScrollSnap):
     158
     159        Fix an issue where initial scrolling velocity would be set to zero at the end of a drag gesture.
     160
     161        (WebCore::ScrollController::updateScrollSnapState):
     162        (WebCore::ScrollController::updateScrollSnapPoints):
     163        (WebCore::ScrollController::setNearestScrollSnapIndexForAxisAndOffset):
     164        * platform/cocoa/ScrollSnapAnimatorState.h:
     165        (WebCore::ScrollSnapAnimatorState::snapOffsetsForAxis):
     166        (WebCore::ScrollSnapAnimatorState::snapOffsetRangesForAxis):
     167        (WebCore::ScrollSnapAnimatorState::setSnapOffsetsAndPositionRangesForAxis):
     168        (WebCore::ScrollSnapAnimatorState::setSnapOffsetsForAxis): Deleted.
     169        * platform/cocoa/ScrollSnapAnimatorState.mm:
     170        (WebCore::ScrollSnapAnimatorState::setupAnimationForState):
     171        (WebCore::ScrollSnapAnimatorState::targetOffsetForStartOffset):
     172        (WebCore::projectedInertialScrollDistance): Deleted.
     173        * rendering/RenderLayerCompositor.cpp:
     174        (WebCore::RenderLayerCompositor::updateScrollCoordinatedLayer):
     175        * testing/Internals.cpp:
     176        (WebCore::Internals::setPlatformMomentumScrollingPredictionEnabled):
     177
     178        Add a new hook for layout tests to force scrolling momentum calculators to use the platform-invariant momentum
     179        scrolling prediction heuristic instead of the platform-dependent one.
     180
     181        (WebCore::Internals::scrollSnapOffsets):
     182        * testing/Internals.h:
     183        * testing/Internals.idl:
     184
    11852017-01-10  Chris Dumez  <cdumez@apple.com>
    2186
  • trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj

    r210555 r210560  
    64886488                F45C231D1995B73B00A6E2E3 /* AxisScrollSnapOffsets.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F45C231B1995B73B00A6E2E3 /* AxisScrollSnapOffsets.cpp */; };
    64896489                F45C231E1995B73B00A6E2E3 /* AxisScrollSnapOffsets.h in Headers */ = {isa = PBXBuildFile; fileRef = F45C231C1995B73B00A6E2E3 /* AxisScrollSnapOffsets.h */; settings = {ATTRIBUTES = (Private, ); }; };
     6490                F46729281E0DE68500ACC3D8 /* ScrollSnapOffsetsInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = F46729251E0DE5AB00ACC3D8 /* ScrollSnapOffsetsInfo.h */; settings = {ATTRIBUTES = (Private, ); }; };
    64906491                F478755419983AFF0024A287 /* ScrollSnapAnimatorState.h in Headers */ = {isa = PBXBuildFile; fileRef = F478755219983AFF0024A287 /* ScrollSnapAnimatorState.h */; settings = {ATTRIBUTES = (Private, ); }; };
    64916492                F478755519983AFF0024A287 /* ScrollSnapAnimatorState.mm in Sources */ = {isa = PBXBuildFile; fileRef = F478755319983AFF0024A287 /* ScrollSnapAnimatorState.mm */; };
     
    1451014511                F45C231B1995B73B00A6E2E3 /* AxisScrollSnapOffsets.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AxisScrollSnapOffsets.cpp; sourceTree = "<group>"; };
    1451114512                F45C231C1995B73B00A6E2E3 /* AxisScrollSnapOffsets.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AxisScrollSnapOffsets.h; sourceTree = "<group>"; };
     14513                F46729251E0DE5AB00ACC3D8 /* ScrollSnapOffsetsInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ScrollSnapOffsetsInfo.h; sourceTree = "<group>"; };
    1451214514                F478755219983AFF0024A287 /* ScrollSnapAnimatorState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ScrollSnapAnimatorState.h; sourceTree = "<group>"; };
    1451314515                F478755319983AFF0024A287 /* ScrollSnapAnimatorState.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ScrollSnapAnimatorState.mm; sourceTree = "<group>"; };
     
    1604016042                                9391A99A1629D6FF00297330 /* ScrollingTreeScrollingNode.cpp */,
    1604116043                                9391A99B1629D70000297330 /* ScrollingTreeScrollingNode.h */,
     16044                                F46729251E0DE5AB00ACC3D8 /* ScrollSnapOffsetsInfo.h */,
    1604216045                                7AAFE8CD19CB8672000F56D8 /* ScrollLatchingState.cpp */,
    1604316046                                7AAFE8CE19CB8672000F56D8 /* ScrollLatchingState.h */,
     
    2618426187                                BC5A86B60C3367E800EEA649 /* JSDOMSelection.h in Headers */,
    2618526188                                C5137CF311A58378004ADB99 /* JSDOMStringList.h in Headers */,
     26189                                F46729281E0DE68500ACC3D8 /* ScrollSnapOffsetsInfo.h in Headers */,
    2618626190                                BC64649811D82349006455B0 /* JSDOMStringMap.h in Headers */,
    2618726191                                7694563D1214D97C0007CBAE /* JSDOMTokenList.h in Headers */,
  • trunk/Source/WebCore/page/scrolling/AsyncScrollingCoordinator.cpp

    r209087 r210560  
    6666}
    6767
    68 static inline void setStateScrollingNodeSnapOffsetsAsFloat(ScrollingStateScrollingNode& node, ScrollEventAxis axis, const Vector<LayoutUnit>* snapOffsets, float deviceScaleFactor)
     68static inline void setStateScrollingNodeSnapOffsetsAsFloat(ScrollingStateScrollingNode& node, ScrollEventAxis axis, const Vector<LayoutUnit>* snapOffsets, const Vector<ScrollOffsetRange<LayoutUnit>>* snapOffsetRanges, float deviceScaleFactor)
    6969{
    7070    // FIXME: Incorporate current page scale factor in snapping to device pixel. Perhaps we should just convert to float here and let UI process do the pixel snapping?
     
    7575            snapOffsetsAsFloat.uncheckedAppend(roundToDevicePixel(offset, deviceScaleFactor, false));
    7676    }
    77     if (axis == ScrollEventAxis::Horizontal)
     77
     78    Vector<ScrollOffsetRange<float>> snapOffsetRangesAsFloat;
     79    if (snapOffsetRanges) {
     80        snapOffsetRangesAsFloat.reserveInitialCapacity(snapOffsetRanges->size());
     81        for (auto& range : *snapOffsetRanges)
     82            snapOffsetRangesAsFloat.uncheckedAppend({ roundToDevicePixel(range.start, deviceScaleFactor, false), roundToDevicePixel(range.end, deviceScaleFactor, false) });
     83    }
     84    if (axis == ScrollEventAxis::Horizontal) {
    7885        node.setHorizontalSnapOffsets(snapOffsetsAsFloat);
    79     else
     86        node.setHorizontalSnapOffsetRanges(snapOffsetRangesAsFloat);
     87    } else {
    8088        node.setVerticalSnapOffsets(snapOffsetsAsFloat);
     89        node.setVerticalSnapOffsetRanges(snapOffsetRangesAsFloat);
     90    }
    8191}
    8292
     
    538548        node->setScrollableAreaSize(scrollingGeometry->scrollableAreaSize);
    539549#if ENABLE(CSS_SCROLL_SNAP)
    540         setStateScrollingNodeSnapOffsetsAsFloat(*node, ScrollEventAxis::Horizontal, &scrollingGeometry->horizontalSnapOffsets, m_page->deviceScaleFactor());
    541         setStateScrollingNodeSnapOffsetsAsFloat(*node, ScrollEventAxis::Vertical, &scrollingGeometry->verticalSnapOffsets, m_page->deviceScaleFactor());
     550        setStateScrollingNodeSnapOffsetsAsFloat(*node, ScrollEventAxis::Horizontal, &scrollingGeometry->horizontalSnapOffsets, &scrollingGeometry->horizontalSnapOffsetRanges, m_page->deviceScaleFactor());
     551        setStateScrollingNodeSnapOffsetsAsFloat(*node, ScrollEventAxis::Vertical, &scrollingGeometry->verticalSnapOffsets, &scrollingGeometry->verticalSnapOffsetRanges, m_page->deviceScaleFactor());
    542552        node->setCurrentHorizontalSnapPointIndex(scrollingGeometry->currentHorizontalSnapPointIndex);
    543553        node->setCurrentVerticalSnapPointIndex(scrollingGeometry->currentVerticalSnapPointIndex);
     
    683693{
    684694    if (auto node = downcast<ScrollingStateFrameScrollingNode>(m_scrollingStateTree->stateNodeForID(frameView.scrollLayerID()))) {
    685         setStateScrollingNodeSnapOffsetsAsFloat(*node, ScrollEventAxis::Horizontal, frameView.horizontalSnapOffsets(), m_page->deviceScaleFactor());
    686         setStateScrollingNodeSnapOffsetsAsFloat(*node, ScrollEventAxis::Vertical, frameView.verticalSnapOffsets(), m_page->deviceScaleFactor());
     695        setStateScrollingNodeSnapOffsetsAsFloat(*node, ScrollEventAxis::Horizontal, frameView.horizontalSnapOffsets(), frameView.horizontalSnapOffsetRanges(), m_page->deviceScaleFactor());
     696        setStateScrollingNodeSnapOffsetsAsFloat(*node, ScrollEventAxis::Vertical, frameView.verticalSnapOffsets(), frameView.verticalSnapOffsetRanges(), m_page->deviceScaleFactor());
    687697        node->setCurrentHorizontalSnapPointIndex(frameView.currentHorizontalSnapPointIndex());
    688698        node->setCurrentVerticalSnapPointIndex(frameView.currentVerticalSnapPointIndex());
  • trunk/Source/WebCore/page/scrolling/AxisScrollSnapOffsets.cpp

    r210024 r210560  
    8585}
    8686
     87static String snapOffsetRangesToString(const Vector<ScrollOffsetRange<LayoutUnit>>& ranges)
     88{
     89    StringBuilder s;
     90    s.append("[ ");
     91    for (auto range : ranges)
     92        s.append(String::format("(%.1f, %.1f) ", range.start.toFloat(), range.end.toFloat()));
     93    s.append("]");
     94    return s.toString();
     95}
     96
    8797static String snapPortOrAreaToString(const LayoutRect& rect)
    8898{
     
    91101
    92102#endif
     103
     104template <typename LayoutType>
     105static void indicesOfNearestSnapOffsetRanges(LayoutType offset, const Vector<ScrollOffsetRange<LayoutType>>& snapOffsetRanges, unsigned& lowerIndex, unsigned& upperIndex)
     106{
     107    if (snapOffsetRanges.isEmpty()) {
     108        lowerIndex = invalidSnapOffsetIndex;
     109        upperIndex = invalidSnapOffsetIndex;
     110        return;
     111    }
     112
     113    int lowerIndexAsInt = -1;
     114    int upperIndexAsInt = snapOffsetRanges.size();
     115    do {
     116        int middleIndex = (lowerIndexAsInt + upperIndexAsInt) / 2;
     117        auto& range = snapOffsetRanges[middleIndex];
     118        if (range.start < offset && offset < range.end) {
     119            lowerIndexAsInt = middleIndex;
     120            upperIndexAsInt = middleIndex;
     121            break;
     122        }
     123
     124        if (offset > range.end)
     125            lowerIndexAsInt = middleIndex;
     126        else
     127            upperIndexAsInt = middleIndex;
     128    } while (lowerIndexAsInt < upperIndexAsInt - 1);
     129
     130    if (offset <= snapOffsetRanges.first().start)
     131        lowerIndex = invalidSnapOffsetIndex;
     132    else
     133        lowerIndex = lowerIndexAsInt;
     134
     135    if (offset >= snapOffsetRanges.last().end)
     136        upperIndex = invalidSnapOffsetIndex;
     137    else
     138        upperIndex = upperIndexAsInt;
     139}
     140
     141template <typename LayoutType>
     142static void indicesOfNearestSnapOffsets(LayoutType offset, const Vector<LayoutType>& snapOffsets, unsigned& lowerIndex, unsigned& upperIndex)
     143{
     144    lowerIndex = 0;
     145    upperIndex = snapOffsets.size() - 1;
     146    while (lowerIndex < upperIndex - 1) {
     147        int middleIndex = (lowerIndex + upperIndex) / 2;
     148        auto middleOffset = snapOffsets[middleIndex];
     149        if (offset == middleOffset) {
     150            upperIndex = middleIndex;
     151            lowerIndex = middleIndex;
     152            break;
     153        }
     154
     155        if (offset > middleOffset)
     156            lowerIndex = middleIndex;
     157        else
     158            upperIndex = middleIndex;
     159    }
     160}
     161
     162static void adjustAxisSnapOffsetsForScrollExtent(Vector<LayoutUnit>& snapOffsets, float maxScrollExtent)
     163{
     164    if (snapOffsets.isEmpty())
     165        return;
     166
     167    std::sort(snapOffsets.begin(), snapOffsets.end());
     168    if (snapOffsets.last() != maxScrollExtent)
     169        snapOffsets.append(maxScrollExtent);
     170    if (snapOffsets.first())
     171        snapOffsets.insert(0, 0);
     172}
     173
     174static void computeAxisProximitySnapOffsetRanges(const Vector<LayoutUnit>& snapOffsets, Vector<ScrollOffsetRange<LayoutUnit>>& offsetRanges, LayoutUnit scrollPortAxisLength)
     175{
     176    // This is an arbitrary choice for what it means to be "in proximity" of a snap offset. We should play around with
     177    // this and see what feels best.
     178    static const float ratioOfScrollPortAxisLengthToBeConsideredForProximity = 0.3;
     179    if (snapOffsets.size() < 2)
     180        return;
     181
     182    // The extra rule accounting for scroll offset ranges in between the scroll destination and a potential snap offset
     183    // handles the corner case where the user scrolls with momentum very lightly away from a snap offset, such that the
     184    // predicted scroll destination is still within proximity of the snap offset. In this case, the regular (mandatory
     185    // scroll snapping) behavior would be to snap to the next offset in the direction of momentum scrolling, but
     186    // instead, it is more intuitive to either return to the original snap position (which we arbitrarily choose here)
     187    // or scroll just outside of the snap offset range. This is another minor behavior tweak that we should play around
     188    // with to see what feels best.
     189    LayoutUnit proximityDistance = ratioOfScrollPortAxisLengthToBeConsideredForProximity * scrollPortAxisLength;
     190    for (size_t index = 1; index < snapOffsets.size(); ++index) {
     191        auto startOffset = snapOffsets[index - 1] + proximityDistance;
     192        auto endOffset = snapOffsets[index] - proximityDistance;
     193        if (startOffset < endOffset)
     194            offsetRanges.append({ startOffset, endOffset });
     195    }
     196}
    93197
    94198void updateSnapOffsetsForScrollableArea(ScrollableArea& scrollableArea, HTMLElement& scrollingElement, const RenderBox& scrollingElementBox, const RenderStyle& scrollingElementStyle)
     
    102206    }
    103207
    104     auto verticalSnapOffsets = std::make_unique<Vector<LayoutUnit>>();
    105     auto horizontalSnapOffsets = std::make_unique<Vector<LayoutUnit>>();
     208    Vector<LayoutUnit> verticalSnapOffsets;
     209    Vector<LayoutUnit> horizontalSnapOffsets;
     210    Vector<ScrollOffsetRange<LayoutUnit>> verticalSnapOffsetRanges;
     211    Vector<ScrollOffsetRange<LayoutUnit>> horizontalSnapOffsetRanges;
    106212    HashSet<float> seenVerticalSnapOffsets;
    107213    HashSet<float> seenHorizontalSnapOffsets;
     
    134240            if (!seenHorizontalSnapOffsets.contains(absoluteScrollOffset)) {
    135241                seenHorizontalSnapOffsets.add(absoluteScrollOffset);
    136                 horizontalSnapOffsets->append(absoluteScrollOffset);
     242                horizontalSnapOffsets.append(absoluteScrollOffset);
    137243            }
    138244        }
     
    141247            if (!seenVerticalSnapOffsets.contains(absoluteScrollOffset)) {
    142248                seenVerticalSnapOffsets.add(absoluteScrollOffset);
    143                 verticalSnapOffsets->append(absoluteScrollOffset);
     249                verticalSnapOffsets.append(absoluteScrollOffset);
    144250            }
    145251        }
    146252    }
    147253
    148     std::sort(horizontalSnapOffsets->begin(), horizontalSnapOffsets->end());
    149     if (horizontalSnapOffsets->size()) {
    150         if (horizontalSnapOffsets->last() != maxScrollLeft)
    151             horizontalSnapOffsets->append(maxScrollLeft);
    152         if (horizontalSnapOffsets->first())
    153             horizontalSnapOffsets->insert(0, 0);
    154 #if !LOG_DISABLED
    155         LOG(Scrolling, " => Computed horizontal scroll snap offsets: %s", snapOffsetsToString(*horizontalSnapOffsets).utf8().data());
    156 #endif
    157         scrollableArea.setHorizontalSnapOffsets(WTFMove(horizontalSnapOffsets));
     254    if (!horizontalSnapOffsets.isEmpty()) {
     255        adjustAxisSnapOffsetsForScrollExtent(horizontalSnapOffsets, maxScrollLeft);
     256#if !LOG_DISABLED
     257        LOG(Scrolling, " => Computed horizontal scroll snap offsets: %s", snapOffsetsToString(horizontalSnapOffsets).utf8().data());
     258        LOG(Scrolling, " => Computed horizontal scroll snap offset ranges: %s", snapOffsetRangesToString(horizontalSnapOffsetRanges).utf8().data());
     259#endif
     260        if (scrollSnapType.strictness == ScrollSnapStrictness::Proximity)
     261            computeAxisProximitySnapOffsetRanges(horizontalSnapOffsets, horizontalSnapOffsetRanges, scrollSnapPort.width());
     262
     263        scrollableArea.setHorizontalSnapOffsets(horizontalSnapOffsets);
     264        scrollableArea.setHorizontalSnapOffsetRanges(horizontalSnapOffsetRanges);
    158265    } else
    159266        scrollableArea.clearHorizontalSnapOffsets();
    160267
    161     std::sort(verticalSnapOffsets->begin(), verticalSnapOffsets->end());
    162     if (verticalSnapOffsets->size()) {
    163         if (verticalSnapOffsets->last() != maxScrollTop)
    164             verticalSnapOffsets->append(maxScrollTop);
    165         if (verticalSnapOffsets->first())
    166             verticalSnapOffsets->insert(0, 0);
    167 #if !LOG_DISABLED
    168         LOG(Scrolling, " => Computed vertical scroll snap offsets: %s", snapOffsetsToString(*verticalSnapOffsets).utf8().data());
    169 #endif
    170         scrollableArea.setVerticalSnapOffsets(WTFMove(verticalSnapOffsets));
     268    if (!verticalSnapOffsets.isEmpty()) {
     269        adjustAxisSnapOffsetsForScrollExtent(verticalSnapOffsets, maxScrollTop);
     270#if !LOG_DISABLED
     271        LOG(Scrolling, " => Computed vertical scroll snap offsets: %s", snapOffsetsToString(verticalSnapOffsets).utf8().data());
     272        LOG(Scrolling, " => Computed vertical scroll snap offset ranges: %s", snapOffsetRangesToString(verticalSnapOffsetRanges).utf8().data());
     273#endif
     274        if (scrollSnapType.strictness == ScrollSnapStrictness::Proximity)
     275            computeAxisProximitySnapOffsetRanges(verticalSnapOffsets, verticalSnapOffsetRanges, scrollSnapPort.height());
     276
     277        scrollableArea.setVerticalSnapOffsets(verticalSnapOffsets);
     278        scrollableArea.setVerticalSnapOffsetRanges(verticalSnapOffsetRanges);
    171279    } else
    172280        scrollableArea.clearVerticalSnapOffsets();
    173281}
    174282
     283template <typename LayoutType>
     284LayoutType closestSnapOffset(const Vector<LayoutType>& snapOffsets, const Vector<ScrollOffsetRange<LayoutType>>& snapOffsetRanges, LayoutType scrollDestination, float velocity, unsigned& activeSnapIndex)
     285{
     286    ASSERT(snapOffsets.size());
     287    activeSnapIndex = 0;
     288
     289    unsigned lowerSnapOffsetRangeIndex;
     290    unsigned upperSnapOffsetRangeIndex;
     291    indicesOfNearestSnapOffsetRanges<LayoutType>(scrollDestination, snapOffsetRanges, lowerSnapOffsetRangeIndex, upperSnapOffsetRangeIndex);
     292    if (lowerSnapOffsetRangeIndex == upperSnapOffsetRangeIndex && upperSnapOffsetRangeIndex != invalidSnapOffsetIndex) {
     293        activeSnapIndex = invalidSnapOffsetIndex;
     294        return scrollDestination;
     295    }
     296
     297    if (scrollDestination <= snapOffsets.first())
     298        return snapOffsets.first();
     299
     300    activeSnapIndex = snapOffsets.size() - 1;
     301    if (scrollDestination >= snapOffsets.last())
     302        return snapOffsets.last();
     303
     304    unsigned lowerIndex;
     305    unsigned upperIndex;
     306    indicesOfNearestSnapOffsets<LayoutType>(scrollDestination, snapOffsets, lowerIndex, upperIndex);
     307    LayoutType lowerSnapPosition = snapOffsets[lowerIndex];
     308    LayoutType upperSnapPosition = snapOffsets[upperIndex];
     309    if (!std::abs(velocity)) {
     310        bool isCloserToLowerSnapPosition = scrollDestination - lowerSnapPosition <= upperSnapPosition - scrollDestination;
     311        activeSnapIndex = isCloserToLowerSnapPosition ? lowerIndex : upperIndex;
     312        return isCloserToLowerSnapPosition ? lowerSnapPosition : upperSnapPosition;
     313    }
     314
     315    // Non-zero velocity indicates a flick gesture. Even if another snap point is closer, we should choose the one in the direction of the flick gesture
     316    // as long as a scroll snap offset range does not lie between the scroll destination and the targeted snap offset.
     317    if (velocity < 0) {
     318        if (lowerSnapOffsetRangeIndex != invalidSnapOffsetIndex && lowerSnapPosition < snapOffsetRanges[lowerSnapOffsetRangeIndex].end) {
     319            activeSnapIndex = upperIndex;
     320            return upperSnapPosition;
     321        }
     322        activeSnapIndex = lowerIndex;
     323        return lowerSnapPosition;
     324    }
     325
     326    if (upperSnapOffsetRangeIndex != invalidSnapOffsetIndex && snapOffsetRanges[upperSnapOffsetRangeIndex].start < upperSnapPosition) {
     327        activeSnapIndex = lowerIndex;
     328        return lowerSnapPosition;
     329    }
     330    activeSnapIndex = upperIndex;
     331    return upperSnapPosition;
     332}
     333
     334LayoutUnit closestSnapOffset(const Vector<LayoutUnit>& snapOffsets, const Vector<ScrollOffsetRange<LayoutUnit>>& snapOffsetRanges, LayoutUnit scrollDestination, float velocity, unsigned& activeSnapIndex)
     335{
     336    return closestSnapOffset<LayoutUnit>(snapOffsets, snapOffsetRanges, scrollDestination, velocity, activeSnapIndex);
     337}
     338
     339float closestSnapOffset(const Vector<float>& snapOffsets, const Vector<ScrollOffsetRange<float>>& snapOffsetRanges, float scrollDestination, float velocity, unsigned& activeSnapIndex)
     340{
     341    return closestSnapOffset<float>(snapOffsets, snapOffsetRanges, scrollDestination, velocity, activeSnapIndex);
     342}
     343
    175344} // namespace WebCore
    176345
  • trunk/Source/WebCore/page/scrolling/AxisScrollSnapOffsets.h

    r208179 r210560  
    2828#if ENABLE(CSS_SCROLL_SNAP)
    2929
     30#include "LayoutUnit.h"
     31#include "ScrollSnapOffsetsInfo.h"
    3032#include "ScrollTypes.h"
    3133#include <wtf/Vector.h>
     
    4042void updateSnapOffsetsForScrollableArea(ScrollableArea&, HTMLElement& scrollingElement, const RenderBox& scrollingElementBox, const RenderStyle& scrollingElementStyle);
    4143
    42 // closestSnapOffset is a templated function that takes in a Vector representing snap offsets as LayoutTypes (e.g. LayoutUnit or float) and
    43 // as well as a VelocityType indicating the velocity (e.g. float, CGFloat, etc.) This function is templated because the UI process will now
    44 // use pixel snapped floats to represent snap offsets rather than LayoutUnits.
    45 template <typename LayoutType, typename VelocityType>
    46 LayoutType closestSnapOffset(const Vector<LayoutType>& snapOffsets, LayoutType scrollDestination, VelocityType velocity, unsigned& activeSnapIndex)
    47 {
    48     ASSERT(snapOffsets.size());
    49     activeSnapIndex = 0;
    50     if (scrollDestination <= snapOffsets.first())
    51         return snapOffsets.first();
    52 
    53     activeSnapIndex = snapOffsets.size() - 1;
    54     if (scrollDestination >= snapOffsets.last())
    55         return snapOffsets.last();
    56 
    57     size_t lowerIndex = 0;
    58     size_t upperIndex = snapOffsets.size() - 1;
    59     while (lowerIndex < upperIndex - 1) {
    60         size_t middleIndex = (lowerIndex + upperIndex) / 2;
    61         if (scrollDestination < snapOffsets[middleIndex])
    62             upperIndex = middleIndex;
    63         else if (scrollDestination > snapOffsets[middleIndex])
    64             lowerIndex = middleIndex;
    65         else {
    66             upperIndex = middleIndex;
    67             lowerIndex = middleIndex;
    68             break;
    69         }
    70     }
    71     LayoutType lowerSnapPosition = snapOffsets[lowerIndex];
    72     LayoutType upperSnapPosition = snapOffsets[upperIndex];
    73     // Nonzero velocity indicates a flick gesture. Even if another snap point is closer, snap to the one in the direction of the flick gesture.
    74     if (velocity) {
    75         activeSnapIndex = (velocity < 0) ? lowerIndex : upperIndex;
    76         return velocity < 0 ? lowerSnapPosition : upperSnapPosition;
    77     }
    78 
    79     bool isCloserToLowerSnapPosition = scrollDestination - lowerSnapPosition <= upperSnapPosition - scrollDestination;
    80     activeSnapIndex = isCloserToLowerSnapPosition ? lowerIndex : upperIndex;
    81     return isCloserToLowerSnapPosition ? lowerSnapPosition : upperSnapPosition;
    82 }
     44const unsigned invalidSnapOffsetIndex = UINT_MAX;
     45WEBCORE_EXPORT LayoutUnit closestSnapOffset(const Vector<LayoutUnit>& snapOffsets, const Vector<ScrollOffsetRange<LayoutUnit>>& snapOffsetRanges, LayoutUnit scrollDestination, float velocity, unsigned& activeSnapIndex);
     46WEBCORE_EXPORT float closestSnapOffset(const Vector<float>& snapOffsets, const Vector<ScrollOffsetRange<float>>& snapOffsetRanges, float scrollDestination, float velocity, unsigned& activeSnapIndex);
    8347
    8448} // namespace WebCore
  • trunk/Source/WebCore/page/scrolling/ScrollSnapOffsetsInfo.h

    r210559 r210560  
    2626#pragma once
    2727
    28 #include "ScrollingMomentumCalculator.h"
    29 #include <wtf/RetainPtr.h>
    30 
    31 #if HAVE(NSSCROLLING_FILTERS)
    32 
    33 @class _NSScrollingMomentumCalculator;
     28#include <wtf/Vector.h>
    3429
    3530namespace WebCore {
    3631
    37 class ScrollingMomentumCalculatorMac final : public ScrollingMomentumCalculator {
    38 public:
    39     ScrollingMomentumCalculatorMac(const FloatSize& viewportSize, const FloatSize& contentSize, const FloatPoint& initialOffset, const FloatPoint& targetOffset, const FloatSize& initialDelta, const FloatSize& initialVelocity);
    40 
    41 private:
    42     FloatPoint scrollOffsetAfterElapsedTime(Seconds) final;
    43     Seconds animationDuration() final;
    44     _NSScrollingMomentumCalculator *ensurePlatformMomentumCalculator();
    45 
    46     RetainPtr<_NSScrollingMomentumCalculator> m_platformMomentumCalculator;
    47     bool m_requiresMomentumScrolling { true };
     32template <typename T>
     33struct ScrollOffsetRange {
     34    T start;
     35    T end;
    4836};
    4937
    50 } // namespace WebCore
     38template <typename T>
     39struct ScrollSnapOffsetsInfo {
     40    Vector<T> horizontalSnapOffsets;
     41    Vector<T> verticalSnapOffsets;
    5142
    52 #endif // HAVE(NSSCROLLING_FILTERS)
     43    // Snap offset ranges represent non-empty ranges of scroll offsets in which scrolling may rest after scroll snapping.
     44    // These are used in two cases: (1) for proximity scroll snapping, where portions of areas between adjacent snap offsets
     45    // may emit snap offset ranges, and (2) in the case where the snap area is larger than the snap port, in which case areas
     46    // where the snap port fits within the snap area are considered to be valid snap positions.
     47    Vector<ScrollOffsetRange<T>> horizontalSnapOffsetRanges;
     48    Vector<ScrollOffsetRange<T>> verticalSnapOffsetRanges;
     49};
     50
     51}; // namespace WebCore
  • trunk/Source/WebCore/page/scrolling/ScrollingCoordinator.h

    r209087 r210560  
    3030#include "LayoutRect.h"
    3131#include "PlatformWheelEvent.h"
     32#include "ScrollSnapOffsetsInfo.h"
    3233#include "ScrollTypes.h"
    3334#include <wtf/Forward.h>
     
    171172        Vector<LayoutUnit> horizontalSnapOffsets;
    172173        Vector<LayoutUnit> verticalSnapOffsets;
     174        Vector<ScrollOffsetRange<LayoutUnit>> horizontalSnapOffsetRanges;
     175        Vector<ScrollOffsetRange<LayoutUnit>> verticalSnapOffsetRanges;
    173176        unsigned currentHorizontalSnapPointIndex;
    174177        unsigned currentVerticalSnapPointIndex;
  • trunk/Source/WebCore/page/scrolling/ScrollingMomentumCalculator.cpp

    r209087 r210560  
    3333
    3434static const Seconds scrollSnapAnimationDuration = 1_s;
    35 
    36 ScrollingMomentumCalculator::ScrollingMomentumCalculator(const FloatSize& viewportSize, const FloatSize& contentSize, const FloatPoint& initialOffset, const FloatPoint& targetOffset, const FloatSize& initialDelta, const FloatSize& initialVelocity)
     35static inline float projectedInertialScrollDistance(float initialWheelDelta)
     36{
     37    // On macOS 10.10 and earlier, we don't have a platform scrolling momentum calculator, so we instead approximate the scroll destination
     38    // by multiplying the initial wheel delta by a constant factor. By running a few experiments (i.e. logging scroll destination and initial
     39    // wheel delta for many scroll gestures) we determined that this is a reasonable way to approximate where scrolling will take us without
     40    // using _NSScrollingMomentumCalculator.
     41    const static double inertialScrollPredictionFactor = 16.7;
     42    return inertialScrollPredictionFactor * initialWheelDelta;
     43}
     44
     45ScrollingMomentumCalculator::ScrollingMomentumCalculator(const FloatSize& viewportSize, const FloatSize& contentSize, const FloatPoint& initialOffset, const FloatSize& initialDelta, const FloatSize& initialVelocity)
    3746    : m_initialDelta(initialDelta)
    3847    , m_initialVelocity(initialVelocity)
    3948    , m_initialScrollOffset(initialOffset.x(), initialOffset.y())
    40     , m_targetScrollOffset(targetOffset.x(), targetOffset.y())
    4149    , m_viewportSize(viewportSize)
    4250    , m_contentSize(contentSize)
     
    4452}
    4553
     54void ScrollingMomentumCalculator::setRetargetedScrollOffset(const FloatSize& target)
     55{
     56    if (m_retargetedScrollOffset && m_retargetedScrollOffset == target)
     57        return;
     58
     59    m_retargetedScrollOffset = target;
     60    retargetedScrollOffsetDidChange();
     61}
     62
     63FloatSize ScrollingMomentumCalculator::predictedDestinationOffset()
     64{
     65    float initialOffsetX = clampTo<float>(m_initialScrollOffset.width() + projectedInertialScrollDistance(m_initialDelta.width()), 0, m_contentSize.width() - m_viewportSize.width());
     66    float initialOffsetY = clampTo<float>(m_initialScrollOffset.height() + projectedInertialScrollDistance(m_initialDelta.height()), 0, m_contentSize.height() - m_viewportSize.height());
     67    return { initialOffsetX, initialOffsetY };
     68}
     69
    4670#if !HAVE(NSSCROLLING_FILTERS)
    4771
    48 std::unique_ptr<ScrollingMomentumCalculator> ScrollingMomentumCalculator::create(const FloatSize& viewportSize, const FloatSize& contentSize, const FloatPoint& initialOffset, const FloatPoint& targetOffset, const FloatSize& initialDelta, const FloatSize& initialVelocity)
    49 {
    50     return std::make_unique<BasicScrollingMomentumCalculator>(viewportSize, contentSize, initialOffset, targetOffset, initialDelta, initialVelocity);
     72std::unique_ptr<ScrollingMomentumCalculator> ScrollingMomentumCalculator::create(const FloatSize& viewportSize, const FloatSize& contentSize, const FloatPoint& initialOffset, const FloatSize& initialDelta, const FloatSize& initialVelocity)
     73{
     74    return std::make_unique<BasicScrollingMomentumCalculator>(viewportSize, contentSize, initialOffset, initialDelta, initialVelocity);
     75}
     76
     77void ScrollingMomentumCalculator::setPlatformMomentumScrollingPredictionEnabled(bool)
     78{
    5179}
    5280
    5381#endif
    5482
    55 BasicScrollingMomentumCalculator::BasicScrollingMomentumCalculator(const FloatSize& viewportSize, const FloatSize& contentSize, const FloatPoint& initialOffset, const FloatPoint& targetOffset, const FloatSize& initialDelta, const FloatSize& initialVelocity)
    56     : ScrollingMomentumCalculator(viewportSize, contentSize, initialOffset, targetOffset, initialDelta, initialVelocity)
    57 {
    58 }
    59 
    60 FloatSize BasicScrollingMomentumCalculator::linearlyInterpolatedOffsetAtProgress(float progress) const
    61 {
    62     return m_initialScrollOffset + progress * (m_targetScrollOffset - m_initialScrollOffset);
     83BasicScrollingMomentumCalculator::BasicScrollingMomentumCalculator(const FloatSize& viewportSize, const FloatSize& contentSize, const FloatPoint& initialOffset, const FloatSize& initialDelta, const FloatSize& initialVelocity)
     84    : ScrollingMomentumCalculator(viewportSize, contentSize, initialOffset, initialDelta, initialVelocity)
     85{
     86}
     87
     88FloatSize BasicScrollingMomentumCalculator::linearlyInterpolatedOffsetAtProgress(float progress)
     89{
     90    return m_initialScrollOffset + progress * (retargetedScrollOffset() - m_initialScrollOffset);
    6391}
    6492
     
    121149    }
    122150
    123     FloatSize startToEndVector = m_targetScrollOffset - m_initialScrollOffset;
     151    FloatSize startToEndVector = retargetedScrollOffset() - m_initialScrollOffset;
    124152    float startToEndDistance = startToEndVector.diagonalLength();
    125153    if (!startToEndDistance) {
     
    141169    m_snapAnimationCurveCoefficients[1] = 3 * (controlVector1 - m_initialScrollOffset);
    142170    m_snapAnimationCurveCoefficients[2] = 3 * (m_initialScrollOffset - 2 * controlVector1 + controlVector2);
    143     m_snapAnimationCurveCoefficients[3] = 3 * (controlVector1 - controlVector2) - m_initialScrollOffset + m_targetScrollOffset;
     171    m_snapAnimationCurveCoefficients[3] = 3 * (controlVector1 - controlVector2) - m_initialScrollOffset + retargetedScrollOffset();
    144172    m_forceLinearAnimationCurve = false;
    145173}
     
    180208    static const float maxScrollSnapInitialProgress = 0.5;
    181209
    182     FloatSize alignmentVector = m_initialDelta * (m_targetScrollOffset - m_initialScrollOffset);
     210    FloatSize alignmentVector = m_initialDelta * (retargetedScrollOffset() - m_initialScrollOffset);
    183211    float initialProgress;
    184212    if (alignmentVector.width() + alignmentVector.height() > 0)
    185         initialProgress = clampTo(m_initialDelta.diagonalLength() / (m_targetScrollOffset - m_initialScrollOffset).diagonalLength(), minScrollSnapInitialProgress, maxScrollSnapInitialProgress);
     213        initialProgress = clampTo(m_initialDelta.diagonalLength() / (retargetedScrollOffset() - m_initialScrollOffset).diagonalLength(), minScrollSnapInitialProgress, maxScrollSnapInitialProgress);
    186214    else
    187215        initialProgress = minScrollSnapInitialProgress;
  • trunk/Source/WebCore/page/scrolling/ScrollingMomentumCalculator.h

    r209087 r210560  
    3131#include "PlatformWheelEvent.h"
    3232#include "ScrollTypes.h"
     33#include <wtf/Optional.h>
    3334#include <wtf/Seconds.h>
    3435
     
    4041class ScrollingMomentumCalculator {
    4142public:
    42     ScrollingMomentumCalculator(const FloatSize& viewportSize, const FloatSize& contentSize, const FloatPoint& initialOffset, const FloatPoint& targetOffset, const FloatSize& initialDelta, const FloatSize& initialVelocity);
    43     static std::unique_ptr<ScrollingMomentumCalculator> create(const FloatSize& viewportSize, const FloatSize& contentSize, const FloatPoint& initialOffset, const FloatPoint& targetOffset, const FloatSize& initialDelta, const FloatSize& initialVelocity);
     43    ScrollingMomentumCalculator(const FloatSize& viewportSize, const FloatSize& contentSize, const FloatPoint& initialOffset, const FloatSize& initialDelta, const FloatSize& initialVelocity);
     44    static std::unique_ptr<ScrollingMomentumCalculator> create(const FloatSize& viewportSize, const FloatSize& contentSize, const FloatPoint& initialOffset, const FloatSize& initialDelta, const FloatSize& initialVelocity);
     45    WEBCORE_EXPORT static void setPlatformMomentumScrollingPredictionEnabled(bool);
    4446    virtual ~ScrollingMomentumCalculator() { }
    4547
    4648    virtual FloatPoint scrollOffsetAfterElapsedTime(Seconds) = 0;
    4749    virtual Seconds animationDuration() = 0;
     50    virtual FloatSize predictedDestinationOffset();
     51    void setRetargetedScrollOffset(const FloatSize&);
    4852
    4953protected:
     54    const FloatSize& retargetedScrollOffset() const { return m_retargetedScrollOffset.value(); }
     55    virtual void retargetedScrollOffsetDidChange() { }
     56
    5057    FloatSize m_initialDelta;
    5158    FloatSize m_initialVelocity;
    5259    FloatSize m_initialScrollOffset;
    53     FloatSize m_targetScrollOffset;
    5460    FloatSize m_viewportSize;
    5561    FloatSize m_contentSize;
     62
     63private:
     64    std::optional<FloatSize> m_retargetedScrollOffset;
    5665};
    5766
    5867class BasicScrollingMomentumCalculator final : public ScrollingMomentumCalculator {
    5968public:
    60     BasicScrollingMomentumCalculator(const FloatSize& viewportSize, const FloatSize& contentSize, const FloatPoint& initialOffset, const FloatPoint& targetOffset, const FloatSize& initialDelta, const FloatSize& initialVelocity);
     69    BasicScrollingMomentumCalculator(const FloatSize& viewportSize, const FloatSize& contentSize, const FloatPoint& initialOffset, const FloatSize& initialDelta, const FloatSize& initialVelocity);
    6170
    6271private:
     
    6675    void initializeSnapProgressCurve();
    6776    float animationProgressAfterElapsedTime(Seconds) const;
    68     FloatSize linearlyInterpolatedOffsetAtProgress(float progress) const;
     77    FloatSize linearlyInterpolatedOffsetAtProgress(float progress);
    6978    FloatSize cubicallyInterpolatedOffsetAtProgress(float progress) const;
    7079
     
    7483    bool m_forceLinearAnimationCurve { false };
    7584    bool m_momentumCalculatorRequiresInitialization { true };
     85    std::optional<FloatSize> m_predictedDestinationOffset;
    7686};
    7787
  • trunk/Source/WebCore/page/scrolling/ScrollingStateScrollingNode.cpp

    r200636 r210560  
    4848    , m_scrollOrigin(stateNode.scrollOrigin())
    4949#if ENABLE(CSS_SCROLL_SNAP)
    50     , m_horizontalSnapOffsets(stateNode.horizontalSnapOffsets())
    51     , m_verticalSnapOffsets(stateNode.verticalSnapOffsets())
     50    , m_snapOffsetsInfo(stateNode.m_snapOffsetsInfo)
    5251#endif
    5352    , m_scrollableAreaParameters(stateNode.scrollableAreaParameters())
     
    109108void ScrollingStateScrollingNode::setHorizontalSnapOffsets(const Vector<float>& snapOffsets)
    110109{
    111     if (m_horizontalSnapOffsets == snapOffsets)
    112         return;
    113 
    114     m_horizontalSnapOffsets = snapOffsets;
     110    if (m_snapOffsetsInfo.horizontalSnapOffsets == snapOffsets)
     111        return;
     112
     113    m_snapOffsetsInfo.horizontalSnapOffsets = snapOffsets;
    115114    setPropertyChanged(HorizontalSnapOffsets);
    116115}
     
    118117void ScrollingStateScrollingNode::setVerticalSnapOffsets(const Vector<float>& snapOffsets)
    119118{
    120     if (m_verticalSnapOffsets == snapOffsets)
    121         return;
    122 
    123     m_verticalSnapOffsets = snapOffsets;
     119    if (m_snapOffsetsInfo.verticalSnapOffsets == snapOffsets)
     120        return;
     121
     122    m_snapOffsetsInfo.verticalSnapOffsets = snapOffsets;
    124123    setPropertyChanged(VerticalSnapOffsets);
     124}
     125
     126void ScrollingStateScrollingNode::setHorizontalSnapOffsetRanges(const Vector<ScrollOffsetRange<float>>& scrollOffsetRanges)
     127{
     128    if (m_snapOffsetsInfo.horizontalSnapOffsetRanges == scrollOffsetRanges)
     129        return;
     130
     131    m_snapOffsetsInfo.horizontalSnapOffsetRanges = scrollOffsetRanges;
     132    setPropertyChanged(HorizontalSnapOffsetRanges);
     133}
     134
     135void ScrollingStateScrollingNode::setVerticalSnapOffsetRanges(const Vector<ScrollOffsetRange<float>>& scrollOffsetRanges)
     136{
     137    if (m_snapOffsetsInfo.verticalSnapOffsetRanges == scrollOffsetRanges)
     138        return;
     139
     140    m_snapOffsetsInfo.verticalSnapOffsetRanges = scrollOffsetRanges;
     141    setPropertyChanged(VerticalSnapOffsetRanges);
    125142}
    126143
  • trunk/Source/WebCore/page/scrolling/ScrollingStateScrollingNode.h

    r208179 r210560  
    2828#if ENABLE(ASYNC_SCROLLING) || USE(COORDINATED_GRAPHICS)
    2929
     30#include "ScrollSnapOffsetsInfo.h"
    3031#include "ScrollTypes.h"
    3132#include "ScrollingCoordinator.h"
     
    5051        HorizontalSnapOffsets,
    5152        VerticalSnapOffsets,
     53        HorizontalSnapOffsetRanges,
     54        VerticalSnapOffsetRanges,
    5255        CurrentHorizontalSnapOffsetIndex,
    5356        CurrentVerticalSnapOffsetIndex,
     
    7275
    7376#if ENABLE(CSS_SCROLL_SNAP)
    74     const Vector<float>& horizontalSnapOffsets() const { return m_horizontalSnapOffsets; }
     77    const Vector<float>& horizontalSnapOffsets() const { return m_snapOffsetsInfo.horizontalSnapOffsets; }
    7578    WEBCORE_EXPORT void setHorizontalSnapOffsets(const Vector<float>&);
    7679
    77     const Vector<float>& verticalSnapOffsets() const { return m_verticalSnapOffsets; }
     80    const Vector<float>& verticalSnapOffsets() const { return m_snapOffsetsInfo.verticalSnapOffsets; }
    7881    WEBCORE_EXPORT void setVerticalSnapOffsets(const Vector<float>&);
     82
     83    const Vector<ScrollOffsetRange<float>>& horizontalSnapOffsetRanges() const { return m_snapOffsetsInfo.horizontalSnapOffsetRanges; }
     84    WEBCORE_EXPORT void setHorizontalSnapOffsetRanges(const Vector<ScrollOffsetRange<float>>&);
     85
     86    const Vector<ScrollOffsetRange<float>>& verticalSnapOffsetRanges() const { return m_snapOffsetsInfo.verticalSnapOffsetRanges; }
     87    WEBCORE_EXPORT void setVerticalSnapOffsetRanges(const Vector<ScrollOffsetRange<float>>&);
    7988
    8089    unsigned currentHorizontalSnapPointIndex() const { return m_currentHorizontalSnapPointIndex; }
     
    109118    IntPoint m_scrollOrigin;
    110119#if ENABLE(CSS_SCROLL_SNAP)
    111     Vector<float> m_horizontalSnapOffsets;
    112     Vector<float> m_verticalSnapOffsets;
     120    ScrollSnapOffsetsInfo<float> m_snapOffsetsInfo;
    113121    unsigned m_currentHorizontalSnapPointIndex { 0 };
    114122    unsigned m_currentVerticalSnapPointIndex { 0 };
  • trunk/Source/WebCore/page/scrolling/ScrollingTreeScrollingNode.cpp

    r208985 r210560  
    7171#if ENABLE(CSS_SCROLL_SNAP)
    7272    if (state.hasChangedProperty(ScrollingStateScrollingNode::HorizontalSnapOffsets))
    73         m_horizontalSnapOffsets = state.horizontalSnapOffsets();
     73        m_snapOffsetsInfo.horizontalSnapOffsets = state.horizontalSnapOffsets();
    7474
    7575    if (state.hasChangedProperty(ScrollingStateScrollingNode::VerticalSnapOffsets))
    76         m_verticalSnapOffsets = state.verticalSnapOffsets();
     76        m_snapOffsetsInfo.verticalSnapOffsets = state.verticalSnapOffsets();
     77
     78    if (state.hasChangedProperty(ScrollingStateScrollingNode::HorizontalSnapOffsetRanges))
     79        m_snapOffsetsInfo.horizontalSnapOffsetRanges = state.horizontalSnapOffsetRanges();
     80
     81    if (state.hasChangedProperty(ScrollingStateScrollingNode::VerticalSnapOffsetRanges))
     82        m_snapOffsetsInfo.verticalSnapOffsetRanges = state.verticalSnapOffsetRanges();
    7783
    7884    if (state.hasChangedProperty(ScrollingStateScrollingNode::CurrentHorizontalSnapOffsetIndex))
     
    140146
    141147#if ENABLE(CSS_SCROLL_SNAP)
    142     if (m_horizontalSnapOffsets.size())
    143         ts.dumpProperty("horizontal snap offsets", m_horizontalSnapOffsets);
     148    if (m_snapOffsetsInfo.horizontalSnapOffsets.size())
     149        ts.dumpProperty("horizontal snap offsets", m_snapOffsetsInfo.horizontalSnapOffsets);
    144150
    145     if (m_verticalSnapOffsets.size())
    146         ts.dumpProperty("horizontal snap offsets", m_verticalSnapOffsets);
     151    if (m_snapOffsetsInfo.verticalSnapOffsets.size())
     152        ts.dumpProperty("horizontal snap offsets", m_snapOffsetsInfo.verticalSnapOffsets);
    147153
    148154    if (m_currentHorizontalSnapPointIndex)
    149         ts.dumpProperty("current horizontal snap point index", m_verticalSnapOffsets);
     155        ts.dumpProperty("current horizontal snap point index", m_currentHorizontalSnapPointIndex);
    150156
    151157    if (m_currentVerticalSnapPointIndex)
  • trunk/Source/WebCore/page/scrolling/ScrollingTreeScrollingNode.h

    r208666 r210560  
    2929
    3030#include "IntRect.h"
     31#include "ScrollSnapOffsetsInfo.h"
    3132#include "ScrollTypes.h"
    3233#include "ScrollingCoordinator.h"
     
    5758
    5859#if ENABLE(CSS_SCROLL_SNAP)
    59     const Vector<float>& horizontalSnapOffsets() const { return m_horizontalSnapOffsets; }
    60     const Vector<float>& verticalSnapOffsets() const { return m_verticalSnapOffsets; }
     60    const Vector<float>& horizontalSnapOffsets() const { return m_snapOffsetsInfo.horizontalSnapOffsets; }
     61    const Vector<float>& verticalSnapOffsets() const { return m_snapOffsetsInfo.verticalSnapOffsets; }
     62    const Vector<ScrollOffsetRange<float>>& horizontalSnapOffsetRanges() const { return m_snapOffsetsInfo.horizontalSnapOffsetRanges; }
     63    const Vector<ScrollOffsetRange<float>>& verticalSnapOffsetRanges() const { return m_snapOffsetsInfo.verticalSnapOffsetRanges; }
    6164    unsigned currentHorizontalSnapPointIndex() const { return m_currentHorizontalSnapPointIndex; }
    6265    unsigned currentVerticalSnapPointIndex() const { return m_currentVerticalSnapPointIndex; }
     
    103106    IntPoint m_scrollOrigin;
    104107#if ENABLE(CSS_SCROLL_SNAP)
    105     Vector<float> m_horizontalSnapOffsets;
    106     Vector<float> m_verticalSnapOffsets;
     108    ScrollSnapOffsetsInfo<float> m_snapOffsetsInfo;
    107109    unsigned m_currentHorizontalSnapPointIndex { 0 };
    108110    unsigned m_currentVerticalSnapPointIndex { 0 };
  • trunk/Source/WebCore/page/scrolling/mac/ScrollingMomentumCalculatorMac.h

    r209477 r210560  
    3737class ScrollingMomentumCalculatorMac final : public ScrollingMomentumCalculator {
    3838public:
    39     ScrollingMomentumCalculatorMac(const FloatSize& viewportSize, const FloatSize& contentSize, const FloatPoint& initialOffset, const FloatPoint& targetOffset, const FloatSize& initialDelta, const FloatSize& initialVelocity);
     39    ScrollingMomentumCalculatorMac(const FloatSize& viewportSize, const FloatSize& contentSize, const FloatPoint& initialOffset, const FloatSize& initialDelta, const FloatSize& initialVelocity);
    4040
    4141private:
    4242    FloatPoint scrollOffsetAfterElapsedTime(Seconds) final;
    4343    Seconds animationDuration() final;
     44    FloatSize predictedDestinationOffset() final;
     45    void retargetedScrollOffsetDidChange() final;
    4446    _NSScrollingMomentumCalculator *ensurePlatformMomentumCalculator();
     47    bool requiresMomentumScrolling();
    4548
    4649    RetainPtr<_NSScrollingMomentumCalculator> m_platformMomentumCalculator;
    47     bool m_requiresMomentumScrolling { true };
     50    std::optional<bool> m_requiresMomentumScrolling;
     51    FloatPoint m_initialDestinationOrigin;
    4852};
    4953
  • trunk/Source/WebCore/page/scrolling/mac/ScrollingMomentumCalculatorMac.mm

    r209477 r210560  
    3333namespace WebCore {
    3434
    35 std::unique_ptr<ScrollingMomentumCalculator> ScrollingMomentumCalculator::create(const FloatSize& viewportSize, const FloatSize& contentSize, const FloatPoint& initialOffset, const FloatPoint& targetOffset, const FloatSize& initialDelta, const FloatSize& initialVelocity)
     35static bool gEnablePlatformMomentumScrollingPrediction = true;
     36
     37std::unique_ptr<ScrollingMomentumCalculator> ScrollingMomentumCalculator::create(const FloatSize& viewportSize, const FloatSize& contentSize, const FloatPoint& initialOffset, const FloatSize& initialDelta, const FloatSize& initialVelocity)
    3638{
    37     return std::make_unique<ScrollingMomentumCalculatorMac>(viewportSize, contentSize, initialOffset, targetOffset, initialDelta, initialVelocity);
     39    return std::make_unique<ScrollingMomentumCalculatorMac>(viewportSize, contentSize, initialOffset, initialDelta, initialVelocity);
    3840}
    3941
    40 ScrollingMomentumCalculatorMac::ScrollingMomentumCalculatorMac(const FloatSize& viewportSize, const FloatSize& contentSize, const FloatPoint& initialOffset, const FloatPoint& targetOffset, const FloatSize& initialDelta, const FloatSize& initialVelocity)
    41     : ScrollingMomentumCalculator(viewportSize, contentSize, initialOffset, targetOffset, initialDelta, initialVelocity)
    42     , m_requiresMomentumScrolling(initialOffset != targetOffset || initialVelocity.area())
     42void ScrollingMomentumCalculator::setPlatformMomentumScrollingPredictionEnabled(bool enabled)
     43{
     44    gEnablePlatformMomentumScrollingPrediction = enabled;
     45}
     46
     47ScrollingMomentumCalculatorMac::ScrollingMomentumCalculatorMac(const FloatSize& viewportSize, const FloatSize& contentSize, const FloatPoint& initialOffset, const FloatSize& initialDelta, const FloatSize& initialVelocity)
     48    : ScrollingMomentumCalculator(viewportSize, contentSize, initialOffset, initialDelta, initialVelocity)
    4349{
    4450}
     
    4652FloatPoint ScrollingMomentumCalculatorMac::scrollOffsetAfterElapsedTime(Seconds elapsedTime)
    4753{
    48     if (!m_requiresMomentumScrolling)
    49         return { m_targetScrollOffset.width(), m_targetScrollOffset.height() };
     54    if (!requiresMomentumScrolling())
     55        return { retargetedScrollOffset().width(), retargetedScrollOffset().height() };
    5056
    5157    return [ensurePlatformMomentumCalculator() positionAfterDuration:elapsedTime.value()];
    5258}
    5359
     60FloatSize ScrollingMomentumCalculatorMac::predictedDestinationOffset()
     61{
     62    if (!gEnablePlatformMomentumScrollingPrediction)
     63        return ScrollingMomentumCalculator::predictedDestinationOffset();
     64
     65    ensurePlatformMomentumCalculator();
     66    return { m_initialDestinationOrigin.x(), m_initialDestinationOrigin.y() };
     67}
     68
     69void ScrollingMomentumCalculatorMac::retargetedScrollOffsetDidChange()
     70{
     71    _NSScrollingMomentumCalculator *calculator = ensurePlatformMomentumCalculator();
     72    calculator.destinationOrigin = NSMakePoint(retargetedScrollOffset().width(), retargetedScrollOffset().height());
     73#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101200
     74    [calculator calculateToReachDestination];
     75#endif
     76}
     77
    5478Seconds ScrollingMomentumCalculatorMac::animationDuration()
    5579{
    56     if (!m_requiresMomentumScrolling)
     80    if (!requiresMomentumScrolling())
    5781        return 0_s;
    5882
    5983    return Seconds([ensurePlatformMomentumCalculator() durationUntilStop]);
     84}
     85
     86bool ScrollingMomentumCalculatorMac::requiresMomentumScrolling()
     87{
     88    if (m_requiresMomentumScrolling == std::nullopt)
     89        m_requiresMomentumScrolling = m_initialScrollOffset != retargetedScrollOffset() || m_initialVelocity.area();
     90    return m_requiresMomentumScrolling.value();
    6091}
    6192
     
    69100    NSPoint velocity = NSMakePoint(m_initialVelocity.width(), m_initialVelocity.height());
    70101    m_platformMomentumCalculator = adoptNS([[_NSScrollingMomentumCalculator alloc] initWithInitialOrigin:origin velocity:velocity documentFrame:contentFrame constrainedClippingOrigin:NSZeroPoint clippingSize:m_viewportSize tolerance:NSMakeSize(1, 1)]);
    71     [m_platformMomentumCalculator setDestinationOrigin:NSMakePoint(m_targetScrollOffset.width(), m_targetScrollOffset.height())];
    72 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101200
    73     [m_platformMomentumCalculator calculateToReachDestination];
    74 #endif
     102    m_initialDestinationOrigin = [m_platformMomentumCalculator destinationOrigin];
    75103    return m_platformMomentumCalculator.get();
    76104}
  • trunk/Source/WebCore/page/scrolling/mac/ScrollingTreeFrameScrollingNodeMac.mm

    r210059 r210560  
    9191    return snapOffsets;
    9292}
     93
     94static inline Vector<ScrollOffsetRange<LayoutUnit>> convertToLayoutUnits(const Vector<ScrollOffsetRange<float>>& snapOffsetRangesAsFloat)
     95{
     96    Vector<ScrollOffsetRange<LayoutUnit>> snapOffsetRanges;
     97    snapOffsetRanges.reserveInitialCapacity(snapOffsetRangesAsFloat.size());
     98    for (auto range : snapOffsetRangesAsFloat)
     99        snapOffsetRanges.uncheckedAppend({ range.start, range.end });
     100
     101    return snapOffsetRanges;
     102}
    93103#endif
    94104
     
    147157
    148158#if ENABLE(CSS_SCROLL_SNAP)
    149     if (scrollingStateNode.hasChangedProperty(ScrollingStateFrameScrollingNode::HorizontalSnapOffsets))
    150         m_scrollController.updateScrollSnapPoints(ScrollEventAxis::Horizontal, convertToLayoutUnits(scrollingStateNode.horizontalSnapOffsets()));
    151 
    152     if (scrollingStateNode.hasChangedProperty(ScrollingStateFrameScrollingNode::VerticalSnapOffsets))
    153         m_scrollController.updateScrollSnapPoints(ScrollEventAxis::Vertical, convertToLayoutUnits(scrollingStateNode.verticalSnapOffsets()));
     159    if (scrollingStateNode.hasChangedProperty(ScrollingStateFrameScrollingNode::HorizontalSnapOffsets) || scrollingStateNode.hasChangedProperty(ScrollingStateFrameScrollingNode::HorizontalSnapOffsetRanges))
     160        m_scrollController.updateScrollSnapPoints(ScrollEventAxis::Horizontal, convertToLayoutUnits(scrollingStateNode.horizontalSnapOffsets()), convertToLayoutUnits(scrollingStateNode.horizontalSnapOffsetRanges()));
     161
     162    if (scrollingStateNode.hasChangedProperty(ScrollingStateFrameScrollingNode::VerticalSnapOffsets) || scrollingStateNode.hasChangedProperty(ScrollingStateFrameScrollingNode::VerticalSnapOffsetRanges))
     163        m_scrollController.updateScrollSnapPoints(ScrollEventAxis::Vertical, convertToLayoutUnits(scrollingStateNode.verticalSnapOffsets()), convertToLayoutUnits(scrollingStateNode.verticalSnapOffsetRanges()));
    154164
    155165    if (scrollingStateNode.hasChangedProperty(ScrollingStateScrollingNode::CurrentHorizontalSnapOffsetIndex))
  • trunk/Source/WebCore/platform/ScrollableArea.cpp

    r210117 r210560  
    4949    virtual ~SameSizeAsScrollableArea();
    5050#if ENABLE(CSS_SCROLL_SNAP)
    51     void* pointers[4];
     51    void* pointers[3];
    5252    unsigned currentIndices[2];
    5353#else
     
    426426
    427427#if ENABLE(CSS_SCROLL_SNAP)
    428 void ScrollableArea::setHorizontalSnapOffsets(std::unique_ptr<Vector<LayoutUnit>> horizontalSnapOffsets)
    429 {
    430     ASSERT(horizontalSnapOffsets);
     428ScrollSnapOffsetsInfo<LayoutUnit>& ScrollableArea::ensureSnapOffsetsInfo()
     429{
     430    if (!m_snapOffsetsInfo)
     431        m_snapOffsetsInfo = std::make_unique<ScrollSnapOffsetsInfo<LayoutUnit>>();
     432    return *m_snapOffsetsInfo;
     433}
     434
     435const Vector<LayoutUnit>* ScrollableArea::horizontalSnapOffsets() const
     436{
     437    if (!m_snapOffsetsInfo)
     438        return nullptr;
     439
     440    return &m_snapOffsetsInfo->horizontalSnapOffsets;
     441}
     442
     443const Vector<ScrollOffsetRange<LayoutUnit>>* ScrollableArea::horizontalSnapOffsetRanges() const
     444{
     445    if (!m_snapOffsetsInfo)
     446        return nullptr;
     447
     448    return &m_snapOffsetsInfo->horizontalSnapOffsetRanges;
     449}
     450
     451const Vector<ScrollOffsetRange<LayoutUnit>>* ScrollableArea::verticalSnapOffsetRanges() const
     452{
     453    if (!m_snapOffsetsInfo)
     454        return nullptr;
     455
     456    return &m_snapOffsetsInfo->verticalSnapOffsetRanges;
     457}
     458
     459const Vector<LayoutUnit>* ScrollableArea::verticalSnapOffsets() const
     460{
     461    if (!m_snapOffsetsInfo)
     462        return nullptr;
     463
     464    return &m_snapOffsetsInfo->verticalSnapOffsets;
     465}
     466
     467void ScrollableArea::setHorizontalSnapOffsets(const Vector<LayoutUnit>& horizontalSnapOffsets)
     468{
    431469    // Consider having a non-empty set of snap offsets as a cue to initialize the ScrollAnimator.
    432     if (horizontalSnapOffsets->size())
     470    if (horizontalSnapOffsets.size())
    433471        scrollAnimator();
    434472
    435     m_horizontalSnapOffsets = WTFMove(horizontalSnapOffsets);
    436 }
    437 
    438 void ScrollableArea::setVerticalSnapOffsets(std::unique_ptr<Vector<LayoutUnit>> verticalSnapOffsets)
    439 {
    440     ASSERT(verticalSnapOffsets);
     473    ensureSnapOffsetsInfo().horizontalSnapOffsets = horizontalSnapOffsets;
     474}
     475
     476void ScrollableArea::setVerticalSnapOffsets(const Vector<LayoutUnit>& verticalSnapOffsets)
     477{
    441478    // Consider having a non-empty set of snap offsets as a cue to initialize the ScrollAnimator.
    442     if (verticalSnapOffsets->size())
     479    if (verticalSnapOffsets.size())
    443480        scrollAnimator();
    444481
    445     m_verticalSnapOffsets = WTFMove(verticalSnapOffsets);
     482    ensureSnapOffsetsInfo().verticalSnapOffsets = verticalSnapOffsets;
     483}
     484
     485void ScrollableArea::setHorizontalSnapOffsetRanges(const Vector<ScrollOffsetRange<LayoutUnit>>& horizontalRanges)
     486{
     487    ensureSnapOffsetsInfo().horizontalSnapOffsetRanges = horizontalRanges;
     488}
     489
     490void ScrollableArea::setVerticalSnapOffsetRanges(const Vector<ScrollOffsetRange<LayoutUnit>>& verticalRanges)
     491{
     492    ensureSnapOffsetsInfo().verticalSnapOffsetRanges = verticalRanges;
    446493}
    447494
    448495void ScrollableArea::clearHorizontalSnapOffsets()
    449496{
    450     m_horizontalSnapOffsets = nullptr;
     497    if (!m_snapOffsetsInfo)
     498        return;
     499
     500    m_snapOffsetsInfo->horizontalSnapOffsets = { };
     501    m_snapOffsetsInfo->horizontalSnapOffsetRanges = { };
    451502    m_currentHorizontalSnapPointIndex = 0;
    452503}
     
    454505void ScrollableArea::clearVerticalSnapOffsets()
    455506{
    456     m_verticalSnapOffsets = nullptr;
     507    if (!m_snapOffsetsInfo)
     508        return;
     509
     510    m_snapOffsetsInfo->verticalSnapOffsets = { };
     511    m_snapOffsetsInfo->verticalSnapOffsetRanges = { };
    457512    m_currentVerticalSnapPointIndex = 0;
    458513}
  • trunk/Source/WebCore/platform/ScrollableArea.h

    r200342 r210560  
    2727#define ScrollableArea_h
    2828
     29#include "ScrollSnapOffsetsInfo.h"
    2930#include "Scrollbar.h"
    3031#include <wtf/Vector.h>
     
    6869
    6970#if ENABLE(CSS_SCROLL_SNAP)
    70     const Vector<LayoutUnit>* horizontalSnapOffsets() const { return m_horizontalSnapOffsets.get(); };
    71     const Vector<LayoutUnit>* verticalSnapOffsets() const { return m_verticalSnapOffsets.get(); };
     71    WEBCORE_EXPORT const Vector<LayoutUnit>* horizontalSnapOffsets() const;
     72    WEBCORE_EXPORT const Vector<LayoutUnit>* verticalSnapOffsets() const;
     73    WEBCORE_EXPORT const Vector<ScrollOffsetRange<LayoutUnit>>* horizontalSnapOffsetRanges() const;
     74    WEBCORE_EXPORT const Vector<ScrollOffsetRange<LayoutUnit>>* verticalSnapOffsetRanges() const;
    7275    virtual void updateSnapOffsets() { };
    73     void setHorizontalSnapOffsets(std::unique_ptr<Vector<LayoutUnit>>);
    74     void setVerticalSnapOffsets(std::unique_ptr<Vector<LayoutUnit>>);
     76    void setHorizontalSnapOffsets(const Vector<LayoutUnit>&);
     77    void setVerticalSnapOffsets(const Vector<LayoutUnit>&);
     78    void setHorizontalSnapOffsetRanges(const Vector<ScrollOffsetRange<LayoutUnit>>&);
     79    void setVerticalSnapOffsetRanges(const Vector<ScrollOffsetRange<LayoutUnit>>&);
    7580    void clearHorizontalSnapOffsets();
    7681    void clearVerticalSnapOffsets();
     
    347352    // scroll of the content.
    348353    virtual void setScrollOffset(const ScrollOffset&) = 0;
     354    ScrollSnapOffsetsInfo<LayoutUnit>& ensureSnapOffsetsInfo();
    349355
    350356    mutable std::unique_ptr<ScrollAnimator> m_scrollAnimator;
     
    353359
    354360#if ENABLE(CSS_SCROLL_SNAP)
    355     std::unique_ptr<Vector<LayoutUnit>> m_horizontalSnapOffsets;
    356     std::unique_ptr<Vector<LayoutUnit>> m_verticalSnapOffsets;
     361    std::unique_ptr<ScrollSnapOffsetsInfo<LayoutUnit>> m_snapOffsetsInfo;
    357362    unsigned m_currentHorizontalSnapPointIndex { 0 };
    358363    unsigned m_currentVerticalSnapPointIndex { 0 };
  • trunk/Source/WebCore/platform/cocoa/ScrollController.h

    r209087 r210560  
    3838#if ENABLE(CSS_SCROLL_SNAP)
    3939#include "ScrollSnapAnimatorState.h"
     40#include "ScrollSnapOffsetsInfo.h"
    4041#endif
    4142
     
    134135
    135136#if ENABLE(CSS_SCROLL_SNAP)
    136     void updateScrollSnapPoints(ScrollEventAxis, const Vector<LayoutUnit>&);
     137    void updateScrollSnapPoints(ScrollEventAxis, const Vector<LayoutUnit>&, const Vector<ScrollOffsetRange<LayoutUnit>>&);
    137138    void setActiveScrollSnapIndexForAxis(ScrollEventAxis, unsigned);
    138139    void setActiveScrollSnapIndicesForOffset(int x, int y);
  • trunk/Source/WebCore/platform/cocoa/ScrollController.mm

    r210024 r210560  
    554554        stopScrollSnapTimer();
    555555        m_scrollSnapState->transitionToUserInteractionState();
     556        m_dragEndedScrollingVelocity = -wheelEvent.scrollingVelocity();
    556557        break;
    557558    case WheelEventStatus::UserScrollEnd:
    558         m_dragEndedScrollingVelocity = -wheelEvent.scrollingVelocity();
    559559        m_scrollSnapState->transitionToSnapAnimationState(m_client.scrollExtent(), m_client.viewportSize(), m_client.pageScaleFactor(), m_client.scrollOffset());
    560560        startScrollSnapTimer();
     
    562562    case WheelEventStatus::InertialScrollBegin:
    563563        m_scrollSnapState->transitionToGlideAnimationState(m_client.scrollExtent(), m_client.viewportSize(), m_client.pageScaleFactor(), m_client.scrollOffset(), m_dragEndedScrollingVelocity, FloatSize(-wheelEvent.deltaX(), -wheelEvent.deltaY()));
     564        m_dragEndedScrollingVelocity = { };
    564565        isInertialScrolling = true;
    565566        break;
     
    582583void ScrollController::updateScrollSnapState(const ScrollableArea& scrollableArea)
    583584{
    584     if (auto* snapOffsets = scrollableArea.horizontalSnapOffsets())
    585         updateScrollSnapPoints(ScrollEventAxis::Horizontal, *snapOffsets);
    586     else
    587         updateScrollSnapPoints(ScrollEventAxis::Horizontal, { });
    588 
    589     if (auto* snapOffsets = scrollableArea.verticalSnapOffsets())
    590         updateScrollSnapPoints(ScrollEventAxis::Vertical, *snapOffsets);
    591     else
    592         updateScrollSnapPoints(ScrollEventAxis::Vertical, { });
    593 }
    594 
    595 void ScrollController::updateScrollSnapPoints(ScrollEventAxis axis, const Vector<LayoutUnit>& snapPoints)
     585    if (auto* snapOffsets = scrollableArea.horizontalSnapOffsets()) {
     586        if (auto* snapOffsetRanges = scrollableArea.horizontalSnapOffsetRanges())
     587            updateScrollSnapPoints(ScrollEventAxis::Horizontal, *snapOffsets, *snapOffsetRanges);
     588        else
     589            updateScrollSnapPoints(ScrollEventAxis::Horizontal, *snapOffsets, { });
     590    } else
     591        updateScrollSnapPoints(ScrollEventAxis::Horizontal, { }, { });
     592
     593    if (auto* snapOffsets = scrollableArea.verticalSnapOffsets()) {
     594        if (auto* snapOffsetRanges = scrollableArea.verticalSnapOffsetRanges())
     595            updateScrollSnapPoints(ScrollEventAxis::Vertical, *snapOffsets, *snapOffsetRanges);
     596        else
     597            updateScrollSnapPoints(ScrollEventAxis::Vertical, *snapOffsets, { });
     598    } else
     599        updateScrollSnapPoints(ScrollEventAxis::Vertical, { }, { });
     600}
     601
     602void ScrollController::updateScrollSnapPoints(ScrollEventAxis axis, const Vector<LayoutUnit>& snapPoints, const Vector<ScrollOffsetRange<LayoutUnit>>& snapRanges)
    596603{
    597604    if (!m_scrollSnapState) {
     
    605612        m_scrollSnapState = nullptr;
    606613    else
    607         m_scrollSnapState->setSnapOffsetsForAxis(axis, snapPoints);
     614        m_scrollSnapState->setSnapOffsetsAndPositionRangesForAxis(axis, snapPoints, snapRanges);
    608615}
    609616
     
    681688
    682689    unsigned activeIndex = 0;
    683     closestSnapOffset<LayoutUnit, float>(snapState.snapOffsetsForAxis(axis), clampedOffset, 0, activeIndex);
     690    closestSnapOffset(snapState.snapOffsetsForAxis(axis), snapState.snapOffsetRangesForAxis(axis), clampedOffset, 0, activeIndex);
    684691
    685692    if (activeIndex == activeScrollSnapIndexForAxis(axis))
  • trunk/Source/WebCore/platform/cocoa/ScrollSnapAnimatorState.h

    r209087 r210560  
    3434#include "LayoutPoint.h"
    3535#include "PlatformWheelEvent.h"
     36#include "ScrollSnapOffsetsInfo.h"
    3637#include "ScrollTypes.h"
    3738#include "ScrollingMomentumCalculator.h"
     
    4950class ScrollSnapAnimatorState {
    5051public:
    51     Vector<LayoutUnit> snapOffsetsForAxis(ScrollEventAxis axis) const
     52    const Vector<LayoutUnit>& snapOffsetsForAxis(ScrollEventAxis axis) const
    5253    {
    5354        return axis == ScrollEventAxis::Horizontal ? m_snapOffsetsX : m_snapOffsetsY;
    5455    }
    5556
    56     void setSnapOffsetsForAxis(ScrollEventAxis axis, const Vector<LayoutUnit>& snapOffsets)
     57    const Vector<ScrollOffsetRange<LayoutUnit>>& snapOffsetRangesForAxis(ScrollEventAxis axis) const
    5758    {
    58         if (axis == ScrollEventAxis::Horizontal)
     59        return axis == ScrollEventAxis::Horizontal ? m_snapOffsetRangesX : m_snapOffsetRangesY;
     60    }
     61
     62    void setSnapOffsetsAndPositionRangesForAxis(ScrollEventAxis axis, const Vector<LayoutUnit>& snapOffsets, const Vector<ScrollOffsetRange<LayoutUnit>>& snapOffsetRanges)
     63    {
     64        if (axis == ScrollEventAxis::Horizontal) {
    5965            m_snapOffsetsX = snapOffsets;
    60         else
     66            m_snapOffsetRangesX = snapOffsetRanges;
     67        } else {
    6168            m_snapOffsetsY = snapOffsets;
     69            m_snapOffsetRangesY = snapOffsetRanges;
     70        }
    6271    }
    6372
     
    8695
    8796private:
    88     float targetOffsetForStartOffset(ScrollEventAxis, float maxScrollOffset, float startOffset, float pageScale, float delta, unsigned& outActiveSnapIndex) const;
     97    float targetOffsetForStartOffset(const Vector<LayoutUnit>& snapOffsets, const Vector<ScrollOffsetRange<LayoutUnit>>& snapOffsetRanges, float maxScrollOffset, float startOffset, float predictedOffset, float pageScale, float delta, unsigned& outActiveSnapIndex) const;
    8998    void teardownAnimationForState(ScrollSnapState);
    9099    void setupAnimationForState(ScrollSnapState, const FloatSize& contentSize, const FloatSize& viewportSize, float pageScale, const FloatPoint& initialOffset, const FloatSize& initialVelocity, const FloatSize& initialDelta);
     
    93102
    94103    Vector<LayoutUnit> m_snapOffsetsX;
     104    Vector<ScrollOffsetRange<LayoutUnit>> m_snapOffsetRangesX;
    95105    unsigned m_activeSnapIndexX { 0 };
    96106    Vector<LayoutUnit> m_snapOffsetsY;
     107    Vector<ScrollOffsetRange<LayoutUnit>> m_snapOffsetRangesY;
    97108    unsigned m_activeSnapIndexY { 0 };
    98109
  • trunk/Source/WebCore/platform/cocoa/ScrollSnapAnimatorState.mm

    r209087 r210560  
    3232namespace WebCore {
    3333
    34 static const float inertialScrollPredictionFactor = 10;
    35 static inline float projectedInertialScrollDistance(float initialWheelDelta)
    36 {
    37     return inertialScrollPredictionFactor * initialWheelDelta;
    38 }
    39 
    4034void ScrollSnapAnimatorState::transitionToSnapAnimationState(const FloatSize& contentSize, const FloatSize& viewportSize, float pageScale, const FloatPoint& initialOffset)
    4135{
     
    5448        return;
    5549
    56     float targetOffsetX = targetOffsetForStartOffset(ScrollEventAxis::Horizontal, contentSize.width() - viewportSize.width(), initialOffset.x(), pageScale, initialDelta.width(), m_activeSnapIndexX);
    57     float targetOffsetY = targetOffsetForStartOffset(ScrollEventAxis::Vertical, contentSize.height() - viewportSize.height(), initialOffset.y(), pageScale, initialDelta.height(), m_activeSnapIndexY);
    58     m_momentumCalculator = ScrollingMomentumCalculator::create(viewportSize, contentSize, initialOffset, FloatPoint(targetOffsetX, targetOffsetY), initialDelta, initialVelocity);
     50    m_momentumCalculator = ScrollingMomentumCalculator::create(viewportSize, contentSize, initialOffset, initialDelta, initialVelocity);
     51    auto predictedScrollTarget = m_momentumCalculator->predictedDestinationOffset();
     52    float targetOffsetX = targetOffsetForStartOffset(m_snapOffsetsX, m_snapOffsetRangesX, contentSize.width() - viewportSize.width(), initialOffset.x(), predictedScrollTarget.width(), pageScale, initialDelta.width(), m_activeSnapIndexX);
     53    float targetOffsetY = targetOffsetForStartOffset(m_snapOffsetsY, m_snapOffsetRangesY, contentSize.height() - viewportSize.height(), initialOffset.y(), predictedScrollTarget.height(), pageScale, initialDelta.height(), m_activeSnapIndexY);
     54    m_momentumCalculator->setRetargetedScrollOffset({ targetOffsetX, targetOffsetY });
    5955    m_startTime = MonotonicTime::now();
    6056    m_currentState = state;
     
    9490}
    9591
    96 float ScrollSnapAnimatorState::targetOffsetForStartOffset(ScrollEventAxis axis, float maxScrollOffset, float startOffset, float pageScale, float initialDelta, unsigned& outActiveSnapIndex) const
     92float ScrollSnapAnimatorState::targetOffsetForStartOffset(const Vector<LayoutUnit>& snapOffsets, const Vector<ScrollOffsetRange<LayoutUnit>>& snapOffsetRanges, float maxScrollOffset, float startOffset, float predictedOffset, float pageScale, float initialDelta, unsigned& outActiveSnapIndex) const
    9793{
    98     auto snapOffsets = snapOffsetsForAxis(axis);
    99     if (!snapOffsets.size()) {
    100         outActiveSnapIndex = 0;
     94    if (snapOffsets.isEmpty()) {
     95        outActiveSnapIndex = invalidSnapOffsetIndex;
    10196        return clampTo<float>(startOffset, 0, maxScrollOffset);
    10297    }
    10398
    104     float projectedDestination = (startOffset + projectedInertialScrollDistance(initialDelta)) / pageScale;
    105     float targetOffset = closestSnapOffset<LayoutUnit, float>(snapOffsets, projectedDestination, initialDelta, outActiveSnapIndex);
    106     targetOffset = clampTo<float>(targetOffset, snapOffsets.first(), snapOffsets.last());
    107     targetOffset = clampTo<float>(targetOffset, 0, maxScrollOffset);
     99    float targetOffset = closestSnapOffset(snapOffsets, snapOffsetRanges, predictedOffset / pageScale, initialDelta, outActiveSnapIndex);
     100    float minimumTargetOffset = std::max<float>(0, snapOffsets.first());
     101    float maximumTargetOffset = std::min<float>(maxScrollOffset, snapOffsets.last());
     102    targetOffset = clampTo<float>(targetOffset, minimumTargetOffset, maximumTargetOffset);
    108103    return pageScale * targetOffset;
    109104}
  • trunk/Source/WebCore/rendering/RenderLayerCompositor.cpp

    r210436 r210560  
    39083908            if (const Vector<LayoutUnit>* offsets = layer.verticalSnapOffsets())
    39093909                scrollingGeometry.verticalSnapOffsets = *offsets;
     3910            if (const Vector<ScrollOffsetRange<LayoutUnit>>* ranges = layer.horizontalSnapOffsetRanges())
     3911                scrollingGeometry.horizontalSnapOffsetRanges = *ranges;
     3912            if (const Vector<ScrollOffsetRange<LayoutUnit>>* ranges = layer.verticalSnapOffsetRanges())
     3913                scrollingGeometry.verticalSnapOffsetRanges = *ranges;
    39103914            scrollingGeometry.currentHorizontalSnapPointIndex = layer.currentHorizontalSnapPointIndex();
    39113915            scrollingGeometry.currentVerticalSnapPointIndex = layer.currentVerticalSnapPointIndex();
  • trunk/Source/WebCore/testing/Internals.cpp

    r210432 r210560  
    118118#include "ScriptedAnimationController.h"
    119119#include "ScrollingCoordinator.h"
     120#include "ScrollingMomentumCalculator.h"
    120121#include "SerializedScriptValue.h"
    121122#include "Settings.h"
     
    33413342}
    33423343   
     3344void Internals::setPlatformMomentumScrollingPredictionEnabled(bool enabled)
     3345{
     3346    ScrollingMomentumCalculator::setPlatformMomentumScrollingPredictionEnabled(enabled);
     3347}
     3348
    33433349ExceptionOr<String> Internals::scrollSnapOffsets(Element& element)
    33443350{
     
    33683374    StringBuilder result;
    33693375
    3370     if (scrollableArea->horizontalSnapOffsets()) {
    3371         result.appendLiteral("horizontal = ");
    3372         appendOffsets(result, *scrollableArea->horizontalSnapOffsets());
    3373     }
    3374 
    3375     if (scrollableArea->verticalSnapOffsets()) {
    3376         if (result.length())
    3377             result.appendLiteral(", ");
    3378 
    3379         result.appendLiteral("vertical = ");
    3380         appendOffsets(result, *scrollableArea->verticalSnapOffsets());
     3376    if (auto* offsets = scrollableArea->horizontalSnapOffsets()) {
     3377        if (offsets->size()) {
     3378            result.appendLiteral("horizontal = ");
     3379            appendOffsets(result, *offsets);
     3380        }
     3381    }
     3382
     3383    if (auto* offsets = scrollableArea->verticalSnapOffsets()) {
     3384        if (offsets->size()) {
     3385            if (result.length())
     3386                result.appendLiteral(", ");
     3387
     3388            result.appendLiteral("vertical = ");
     3389            appendOffsets(result, *offsets);
     3390        }
    33813391    }
    33823392
  • trunk/Source/WebCore/testing/Internals.h

    r210432 r210560  
    480480#if ENABLE(CSS_SCROLL_SNAP)
    481481    ExceptionOr<String> scrollSnapOffsets(Element&);
     482    void setPlatformMomentumScrollingPredictionEnabled(bool);
    482483#endif
    483484
  • trunk/Source/WebCore/testing/Internals.idl

    r210432 r210560  
    457457#if defined(ENABLE_CSS_SCROLL_SNAP) && ENABLE_CSS_SCROLL_SNAP
    458458    [MayThrowException] DOMString scrollSnapOffsets(Element element);
     459    void setPlatformMomentumScrollingPredictionEnabled(boolean enabled);
    459460#endif
    460461
  • trunk/Source/WebKit2/ChangeLog

    r210545 r210560  
     12017-01-10  Wenson Hsieh  <wenson_hsieh@apple.com>
     2
     3        Implement "proximity" scroll snapping
     4        https://bugs.webkit.org/show_bug.cgi?id=135994
     5        <rdar://problem/18162418>
     6
     7        Reviewed by Dean Jackson.
     8
     9        Adds boilerplate support for plumbing lists of snap offset ranges from the web process to the UI process
     10        alongside the list of snap offsets.
     11
     12        * Shared/Scrolling/RemoteScrollingCoordinatorTransaction.cpp:
     13        (ArgumentCoder<ScrollingStateScrollingNode>::encode):
     14        (ArgumentCoder<ScrollingStateScrollingNode>::decode):
     15        * Shared/WebCoreArgumentCoders.cpp:
     16        (IPC::ArgumentCoder<ScrollOffsetRange<float>>::encode):
     17        (IPC::ArgumentCoder<ScrollOffsetRange<float>>::decode):
     18        * Shared/WebCoreArgumentCoders.h:
     19        * UIProcess/Scrolling/ios/ScrollingTreeOverflowScrollingNodeIOS.mm:
     20        (-[WKOverflowScrollViewDelegate scrollViewWillEndDragging:withVelocity:targetContentOffset:]):
     21        * UIProcess/ios/RemoteScrollingCoordinatorProxyIOS.mm:
     22
     23        Adjust mainframe proximity scroll snapping logic to not subtract out the top content inset when there is no
     24        active snap offset (i.e. when snapping rests in a snap offset range). Attempting to subtract out the top inset
     25        in this case caused the scroll offset to jump after ending a drag with no momentum in a snap offset range.
     26
     27        (WebKit::RemoteScrollingCoordinatorProxy::adjustTargetContentOffsetForSnapping):
     28        (WebKit::RemoteScrollingCoordinatorProxy::shouldSnapForMainFrameScrolling):
     29        (WebKit::RemoteScrollingCoordinatorProxy::closestSnapOffsetForMainFrameScrolling):
     30
    1312017-01-10  Zan Dobersek  <zdobersek@igalia.com>
    232
  • trunk/Source/WebKit2/Shared/Scrolling/RemoteScrollingCoordinatorTransaction.cpp

    r208503 r210560  
    128128    SCROLLING_NODE_ENCODE(ScrollingStateScrollingNode::HorizontalSnapOffsets, horizontalSnapOffsets)
    129129    SCROLLING_NODE_ENCODE(ScrollingStateScrollingNode::VerticalSnapOffsets, verticalSnapOffsets)
     130    SCROLLING_NODE_ENCODE(ScrollingStateScrollingNode::HorizontalSnapOffsetRanges, horizontalSnapOffsetRanges)
     131    SCROLLING_NODE_ENCODE(ScrollingStateScrollingNode::VerticalSnapOffsetRanges, verticalSnapOffsetRanges)
    130132    SCROLLING_NODE_ENCODE(ScrollingStateScrollingNode::CurrentHorizontalSnapOffsetIndex, currentHorizontalSnapPointIndex)
    131133    SCROLLING_NODE_ENCODE(ScrollingStateScrollingNode::CurrentVerticalSnapOffsetIndex, currentVerticalSnapPointIndex)
     
    203205    SCROLLING_NODE_DECODE(ScrollingStateScrollingNode::HorizontalSnapOffsets, Vector<float>, setHorizontalSnapOffsets);
    204206    SCROLLING_NODE_DECODE(ScrollingStateScrollingNode::VerticalSnapOffsets, Vector<float>, setVerticalSnapOffsets);
     207    SCROLLING_NODE_DECODE(ScrollingStateScrollingNode::HorizontalSnapOffsetRanges, Vector<ScrollOffsetRange<float>>, setHorizontalSnapOffsetRanges)
     208    SCROLLING_NODE_DECODE(ScrollingStateScrollingNode::VerticalSnapOffsetRanges, Vector<ScrollOffsetRange<float>>, setVerticalSnapOffsetRanges)
    205209    SCROLLING_NODE_DECODE(ScrollingStateScrollingNode::CurrentHorizontalSnapOffsetIndex, unsigned, setCurrentHorizontalSnapPointIndex);
    206210    SCROLLING_NODE_DECODE(ScrollingStateScrollingNode::CurrentVerticalSnapOffsetIndex, unsigned, setCurrentVerticalSnapPointIndex);
  • trunk/Source/WebKit2/Shared/WebCoreArgumentCoders.cpp

    r210360 r210560  
    24732473#endif
    24742474
     2475#if ENABLE(CSS_SCROLL_SNAP)
     2476
     2477void ArgumentCoder<ScrollOffsetRange<float>>::encode(Encoder& encoder, const ScrollOffsetRange<float>& range)
     2478{
     2479    encoder << range.start;
     2480    encoder << range.end;
     2481}
     2482
     2483bool ArgumentCoder<ScrollOffsetRange<float>>::decode(Decoder& decoder, ScrollOffsetRange<float>& range)
     2484{
     2485    float start;
     2486    if (!decoder.decode(start))
     2487        return false;
     2488
     2489    float end;
     2490    if (!decoder.decode(end))
     2491        return false;
     2492
     2493    range.start = start;
     2494    range.end = end;
     2495    return true;
     2496}
     2497
     2498#endif
     2499
    24752500} // namespace IPC
  • trunk/Source/WebKit2/Shared/WebCoreArgumentCoders.h

    r208916 r210560  
    3131#include <WebCore/IndexedDB.h>
    3232#include <WebCore/PaymentHeaders.h>
     33#include <WebCore/ScrollSnapOffsetsInfo.h>
    3334
    3435namespace WTF {
     
    600601#endif
    601602
     603#if ENABLE(CSS_SCROLL_SNAP)
     604
     605template<> struct ArgumentCoder<WebCore::ScrollOffsetRange<float>> {
     606    static void encode(Encoder&, const WebCore::ScrollOffsetRange<float>&);
     607    static bool decode(Decoder&, WebCore::ScrollOffsetRange<float>&);
     608};
     609
     610#endif
     611
    602612} // namespace IPC
    603613
  • trunk/Source/WebKit2/UIProcess/Scrolling/ios/ScrollingTreeOverflowScrollingNodeIOS.mm

    r208841 r210560  
    4040#if ENABLE(CSS_SCROLL_SNAP)
    4141#import <WebCore/AxisScrollSnapOffsets.h>
     42#import <WebCore/ScrollSnapOffsetsInfo.h>
    4243#endif
    4344
     
    8990    if (!_scrollingTreeNode->horizontalSnapOffsets().isEmpty()) {
    9091        unsigned index;
    91         float potentialSnapPosition = closestSnapOffset<float, CGFloat>(_scrollingTreeNode->horizontalSnapOffsets(), horizontalTarget, velocity.x, index);
     92        float potentialSnapPosition = closestSnapOffset(_scrollingTreeNode->horizontalSnapOffsets(), _scrollingTreeNode->horizontalSnapOffsetRanges(), horizontalTarget, velocity.x, index);
    9293        _scrollingTreeNode->setCurrentHorizontalSnapPointIndex(index);
    9394        if (horizontalTarget >= 0 && horizontalTarget <= scrollView.contentSize.width)
     
    9798    if (!_scrollingTreeNode->verticalSnapOffsets().isEmpty()) {
    9899        unsigned index;
    99         float potentialSnapPosition = closestSnapOffset<float, CGFloat>(_scrollingTreeNode->verticalSnapOffsets(), verticalTarget, velocity.y, index);
     100        float potentialSnapPosition = closestSnapOffset(_scrollingTreeNode->verticalSnapOffsets(), _scrollingTreeNode->verticalSnapOffsetRanges(), verticalTarget, velocity.y, index);
    100101        _scrollingTreeNode->setCurrentVerticalSnapPointIndex(index);
    101102        if (verticalTarget >= 0 && verticalTarget <= scrollView.contentSize.height)
  • trunk/Source/WebKit2/UIProcess/ios/RemoteScrollingCoordinatorProxyIOS.mm

    r209967 r210560  
    4040#if ENABLE(CSS_SCROLL_SNAP)
    4141#import <WebCore/AxisScrollSnapOffsets.h>
     42#import <WebCore/ScrollSnapOffsetsInfo.h>
    4243#import <WebCore/ScrollTypes.h>
    4344#import <WebCore/ScrollingTreeFrameScrollingNode.h>
     
    125126    if (shouldSnapForMainFrameScrolling(WebCore::ScrollEventAxis::Vertical)) {
    126127        float potentialSnapPosition = closestSnapOffsetForMainFrameScrolling(WebCore::ScrollEventAxis::Vertical, targetContentOffset->y, velocity.y, m_currentVerticalSnapPointIndex);
    127         potentialSnapPosition -= topInset;
     128        if (m_currentVerticalSnapPointIndex != invalidSnapOffsetIndex)
     129            potentialSnapPosition -= topInset;
     130
    128131        if (targetContentOffset->y > 0 && targetContentOffset->y < maxScrollOffsets.height)
    129132            targetContentOffset->y = std::min<float>(maxScrollOffsets.height, potentialSnapPosition);
     
    143146        const Vector<float>& snapOffsets = axis == ScrollEventAxis::Horizontal ? rootFrame->horizontalSnapOffsets() : rootFrame->verticalSnapOffsets();
    144147        unsigned currentIndex = axis == ScrollEventAxis::Horizontal ? m_currentHorizontalSnapPointIndex : m_currentVerticalSnapPointIndex;
    145         return (snapOffsets.size() > 0) && (currentIndex < snapOffsets.size());
     148        return snapOffsets.size() && (currentIndex < snapOffsets.size() || currentIndex == invalidSnapOffsetIndex);
    146149    }
    147150    return false;
     
    154157    ScrollingTreeFrameScrollingNode* rootFrame = static_cast<ScrollingTreeFrameScrollingNode*>(root);
    155158    const Vector<float>& snapOffsets = axis == ScrollEventAxis::Horizontal ? rootFrame->horizontalSnapOffsets() : rootFrame->verticalSnapOffsets();
     159    const Vector<ScrollOffsetRange<float>>& snapOffsetRanges = axis == ScrollEventAxis::Horizontal ? rootFrame->horizontalSnapOffsetRanges() : rootFrame->verticalSnapOffsetRanges();
    156160
    157161    float scaledScrollDestination = scrollDestination / m_webPageProxy.displayedContentScale();
    158     float rawClosestSnapOffset = closestSnapOffset<float, float>(snapOffsets, scaledScrollDestination, velocity, currentIndex);
     162    float rawClosestSnapOffset = closestSnapOffset(snapOffsets, snapOffsetRanges, scaledScrollDestination, velocity, currentIndex);
    159163    return rawClosestSnapOffset * m_webPageProxy.displayedContentScale();
    160164}
Note: See TracChangeset for help on using the changeset viewer.