Changeset 209070 in webkit


Ignore:
Timestamp:
Nov 29, 2016 8:10:55 AM (7 years ago)
Author:
Wenson Hsieh
Message:

Scroll snapping on Mac should use AppKit animations
https://bugs.webkit.org/show_bug.cgi?id=147261
<rdar://problem/29395293>

Reviewed by Brent Fulgham.

Source/WebCore:

Refactors the scroll snapping animation logic to support arbitrary scrolling momentum calculators and
introduces ScrollingMomentumCalculatorMac, which wraps AppKit's _NSScrollingMomentumCalculator. On macOS El
Capitan and later, we use the platform scrolling momentum calculator and for all other cases, we fall back to
the preexissting platform-invariant momentum calculator.

Previously, the scroll snapping animation logic was shared between the ScrollSnapAnimatorState and
ScrollController -- namely, the ScrollController would update various parameters of the ScrollSnapAnimatorState
and then tell it to compute animation-specific constants and coefficients. After this patch, ScrollController
will no longer directly set the ScrollSnapAnimatorState's member variables. Instead, it will tell the animator
state to transition to a new ScrollSnapState with the necessary parameters, and the ScrollSnapAnimatorState is
responsible for modifying itself accordingly. Furthermore, logic pertaining to computing animated scroll offsets
is now split out into a new ScrollingMomentumCalculator, which may have different platform-dependent
implementations. The correct calculator is initialized via ScrollingMomentumCalculator::create, which currently
returns a ScrollingMomentumCalculatorMac on El Capitan and later, and a BasicScrollingMomentumCalculator
otherwise.

The new abstracted ScrollingMomentumCalculator is initialized with various parameters describing the scrolled
content and viewport, as well as the initial and target scrolling offsets. The momentum calculator is then able
to compute the animated scroll offset at any given elapsed time, as well as the total duration of the snapping
animation. The ScrollController's scroll snap timer uses this information (via the ScrollSnapAnimatorState) to
animate its client's scroll offset during a snap or glide.

Also reenables 8 failing and/or flaky scroll snapping tests and adds a new layout test. This patch addresses
two causes for failures and flakiness in these scroll snapping tests:

  1. When starting or stopping the scroll snap animation timer, we call deferTestsForReason and

removeTestDeferralForReason, respectively. These were actually noops for the first simulated scroll gesture
on each of the failing mainframe scrolling tests due to m_expectsWheelEventTestTrigger being false. This
member variable is updated when AsyncScrollingCoordinator::frameViewLayoutUpdated is invoked, wherein we
call ScrollingStateFrameScrollingNode::setExpectsWheelEventTestTrigger(true) when the test has started
monitoring wheel events. However, if this does not happen before scrolling begins in the test (which is the
case here), then the mainframe scrolling node will not expect a wheel event test trigger even though
eventSender.monitorWheelEvents() has been called. To fix this, we simply make the Page trigger a layout of
the main FrameView when first ensuring the wheel event test trigger on the Page.

  1. The second reason for flakiness affects both overflow and mainframe scrolling. Previously, due to the way

we would wait for multiple momentum scroll events before starting to glide, we would end up starting the
scroll snap timer for a snapping animation, stopping it, and then starting it again for the glide animation.
Thus, if the wheel event test trigger's timer fires right after the scroll snap timer stops and before it
starts again due to a glide animation, it will erroneously think that scroll snapping is complete, even
though it's only just about to begin! Now that we know scrolling velocity when we receive the initial
"momentum begin", we now directly transition the scroll snap state from a snapping state to a gliding state
and no longer stop and start the timer during this transition, which means that the test trigger will be
deferred for at least the entire duration of the scroll snapping animation (starting right after the first
"drag end" wheel event).

Test: tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-vertical-then-horizontal.html

  • WebCore.xcodeproj/project.pbxproj:
  • page/EventHandler.cpp:

(WebCore::handleWheelEventInAppropriateEnclosingBox):
(WebCore::EventHandler::defaultWheelEventHandler):

  • page/Page.cpp:

(WebCore::Page::ensureTestTrigger):

Addresses test failures by forcing the mainframe scrolling node to expect wheel event test triggers.

  • page/WheelEventDeltaFilter.cpp:

(WebCore::WheelEventDeltaFilter::create):
(WebCore::WheelEventDeltaFilter::filteredVelocity):

  • page/WheelEventDeltaFilter.h:
  • page/mac/WheelEventDeltaFilterMac.mm:

(WebCore::WheelEventDeltaFilterMac::updateFromDelta):

Add support for plumbing filtered scrolling velocity over to the ScrollController.

  • page/scrolling/ScrollingMomentumCalculator.cpp: Copied from Source/WebCore/platform/cocoa/ScrollSnapAnimatorState.mm.

(WebCore::ScrollingMomentumCalculator::ScrollingMomentumCalculator):
(WebCore::ScrollingMomentumCalculator::create):

Creates a platform-independent BasicScrollingMomentumCalculator.

(WebCore::BasicScrollingMomentumCalculator::BasicScrollingMomentumCalculator):
(WebCore::BasicScrollingMomentumCalculator::linearlyInterpolatedOffsetAtProgress):
(WebCore::BasicScrollingMomentumCalculator::cubicallyInterpolatedOffsetAtProgress):
(WebCore::BasicScrollingMomentumCalculator::scrollOffsetAfterElapsedTime):
(WebCore::BasicScrollingMomentumCalculator::animationDuration):
(WebCore::BasicScrollingMomentumCalculator::initializeInterpolationCoefficientsIfNecessary):
(WebCore::BasicScrollingMomentumCalculator::initializeSnapProgressCurve):
(WebCore::BasicScrollingMomentumCalculator::animationProgressAfterElapsedTime):

Interpolation logic ported over from ScrollSnapAnimatorState.

  • page/scrolling/ScrollingMomentumCalculator.h: Added.

(WebCore::ScrollingMomentumCalculator::~ScrollingMomentumCalculator):

  • page/scrolling/mac/ScrollingMomentumCalculatorMac.h: Copied from Source/WebCore/page/WheelEventDeltaFilter.h.
  • page/scrolling/mac/ScrollingMomentumCalculatorMac.mm: Added.

(WebCore::ScrollingMomentumCalculator::create):

Creates a ScrollingMomentumCalculatorMac.

(WebCore::ScrollingMomentumCalculatorMac::ScrollingMomentumCalculatorMac):
(WebCore::ScrollingMomentumCalculatorMac::scrollOffsetAfterElapsedTime):
(WebCore::ScrollingMomentumCalculatorMac::animationDuration):
(WebCore::ScrollingMomentumCalculatorMac::ensurePlatformMomentumCalculator):

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

(WebCore::ScrollingTreeFrameScrollingNodeMac::scrollOffset):
(WebCore::ScrollingTreeFrameScrollingNodeMac::viewportSize):
(WebCore::ScrollingTreeFrameScrollingNodeMac::scrollOffsetOnAxis): Deleted.

  • platform/PlatformWheelEvent.h:

(WebCore::PlatformWheelEvent::copyWithDeltasAndVelocity):
(WebCore::PlatformWheelEvent::scrollingVelocity):
(WebCore::PlatformWheelEvent::copyWithDeltas): Deleted.

  • platform/ScrollAnimator.cpp:

(WebCore::ScrollAnimator::scrollOffset):
(WebCore::ScrollAnimator::viewportSize):
(WebCore::ScrollAnimator::scrollOffsetOnAxis): Deleted.

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

(WebCore::otherScrollEventAxis):
(WebCore::ScrollController::ScrollController):
(WebCore::ScrollController::shouldOverrideInertialScrolling):
(WebCore::ScrollController::scheduleStatelessScrollSnap):
(WebCore::ScrollController::statelessSnapTransitionTimerFired):
(WebCore::ScrollController::startDeferringTestsDueToScrollSnapping):
(WebCore::ScrollController::stopDeferringTestsDueToScrollSnapping):
(WebCore::ScrollController::processWheelEventForScrollSnap):
(WebCore::ScrollController::updateScrollSnapState):
(WebCore::ScrollController::updateScrollSnapPoints):

Update the ScrollController's ScrollSnapAnimationState for both vertical and horizontal axes. If both axes lack
any snap points, the pointer to the animation state will be nulled out; otherwise, the animation state will
exist.

(WebCore::ScrollController::startScrollSnapTimer):
(WebCore::ScrollController::stopScrollSnapTimer):
(WebCore::ScrollController::scrollSnapTimerFired):
(WebCore::ScrollController::activeScrollSnapIndexForAxis):
(WebCore::ScrollController::setActiveScrollSnapIndexForAxis):
(WebCore::ScrollController::setNearestScrollSnapIndexForAxisAndOffset):
(WebCore::ScrollController::setActiveScrollSnapIndicesForOffset):
(WebCore::ScrollController::scrollSnapPointState): Deleted.
(WebCore::ScrollController::processWheelEventForScrollSnapOnAxis): Deleted.
(WebCore::ScrollController::shouldOverrideWheelEvent): Deleted.
(WebCore::projectedInertialScrollDistance): Deleted.
(WebCore::ScrollController::beginScrollSnapAnimation): Deleted.
(WebCore::ScrollController::endScrollSnapAnimation): Deleted.
(WebCore::ScrollController::initializeScrollSnapAnimationParameters): Deleted.
(WebCore::ScrollController::isSnappingOnAxis): Deleted.

  • platform/cocoa/ScrollSnapAnimatorState.h:

(WebCore::ScrollSnapAnimatorState::snapOffsetsForAxis):
(WebCore::ScrollSnapAnimatorState::setSnapOffsetsForAxis):
(WebCore::ScrollSnapAnimatorState::currentState):
(WebCore::ScrollSnapAnimatorState::activeSnapIndexForAxis):
(WebCore::ScrollSnapAnimatorState::setActiveSnapIndexForAxis):

  • platform/cocoa/ScrollSnapAnimatorState.mm:

(WebCore::projectedInertialScrollDistance):
(WebCore::ScrollSnapAnimatorState::transitionToSnapAnimationState):
(WebCore::ScrollSnapAnimatorState::transitionToGlideAnimationState):
(WebCore::ScrollSnapAnimatorState::transitionToUserInteractionState):
(WebCore::ScrollSnapAnimatorState::transitionToDestinationReachedState):

These methods are used to update the ScrollSnapAnimationState. These state transitions should (and do)
encapsulate all changes that need to be made to the animation state; in other words, the ScrollController should
no longer be reaching directly into the ScrollSnapAnimatorState to change member variables.

(WebCore::ScrollSnapAnimatorState::setupAnimationForState):
(WebCore::ScrollSnapAnimatorState::teardownAnimationForState):
(WebCore::ScrollSnapAnimatorState::currentAnimatedScrollOffset):
(WebCore::ScrollSnapAnimatorState::targetOffsetForStartOffset):
(WebCore::ScrollSnapAnimatorState::ScrollSnapAnimatorState): Deleted.
(WebCore::ScrollSnapAnimatorState::pushInitialWheelDelta): Deleted.
(WebCore::ScrollSnapAnimatorState::averageInitialWheelDelta): Deleted.
(WebCore::ScrollSnapAnimatorState::clearInitialWheelDeltaWindow): Deleted.
(WebCore::ScrollSnapAnimatorState::isSnapping): Deleted.
(WebCore::ScrollSnapAnimatorState::canReachTargetWithCurrentInitialScrollDelta): Deleted.
(WebCore::ScrollSnapAnimatorState::wheelDeltaTrackingIsInProgress): Deleted.
(WebCore::ScrollSnapAnimatorState::hasFinishedTrackingWheelDeltas): Deleted.
(WebCore::ScrollSnapAnimatorState::interpolatedOffsetAtProgress): Deleted.
(WebCore::ScrollSnapAnimationCurveState::initializeSnapProgressCurve): Deleted.
(WebCore::ScrollSnapAnimationCurveState::initializeInterpolationCoefficientsIfNecessary): Deleted.
(WebCore::ScrollSnapAnimationCurveState::interpolatedPositionAtProgress): Deleted.
(WebCore::ScrollSnapAnimationCurveState::shouldCompleteSnapAnimationImmediatelyAtTime): Deleted.
(WebCore::ScrollSnapAnimationCurveState::animationProgressAtTime): Deleted.

The ScrollSnapAnimatorState now tracks state across both axes. This simplifies coordinating scroll snapping in
both horizontal and vertical axes and fixes the issue of the scroll offset not snapping when performing a scroll
in one direction without momentum, then scrolling with momentum in the other direction in a single gesture.

  • platform/spi/mac/NSScrollingMomentumCalculatorSPI.h: Added.

Source/WebKit2:

Add some logic to plumb filtered wheel velocity over to WebCore in the case of mainframe scrolling. See
WebCore/ChangeLog for more details.

  • WebProcess/WebPage/EventDispatcher.cpp:

(WebKit::EventDispatcher::wheelEvent):

Source/WTF:

Introduce HAVE(NSSCROLLING_FILTERS), which is on for macOS El Capitan and later.

  • wtf/Platform.h:

LayoutTests:

Fixes 8 previously failing scroll snapping tests in the tiled-drawing/scrolling/scroll-snap directory and
removes them from TestExpectations. Also adds a new layout test. See WebCore/ChangeLog for more details.

  • platform/mac-wk2/TestExpectations:
  • tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-2d-overflow-expected.txt:
  • tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-borders-expected.txt:
  • 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-expected.txt: Added.
  • tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-mainframe-vertical-then-horizontal.html: Added.
  • tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-mainframe-vertical.html:
Location:
trunk
Files:
6 added
27 edited
1 copied

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r209069 r209070  
     12016-11-29  Wenson Hsieh  <wenson_hsieh@apple.com>
     2
     3        Scroll snapping on Mac should use AppKit animations
     4        https://bugs.webkit.org/show_bug.cgi?id=147261
     5        <rdar://problem/29395293>
     6
     7        Reviewed by Brent Fulgham.
     8
     9        Fixes 8 previously failing scroll snapping tests in the tiled-drawing/scrolling/scroll-snap directory and
     10        removes them from TestExpectations. Also adds a new layout test. See WebCore/ChangeLog for more details.
     11
     12        * platform/mac-wk2/TestExpectations:
     13        * tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-2d-overflow-expected.txt:
     14        * tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-borders-expected.txt:
     15        * tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-mainframe-slow-horizontal.html:
     16        * tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-mainframe-slow-vertical.html:
     17        * tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-mainframe-vertical-then-horizontal-expected.txt: Added.
     18        * tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-mainframe-vertical-then-horizontal.html: Added.
     19        * tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-mainframe-vertical.html:
     20
    1212016-11-29  Brady Eidson  <beidson@apple.com>
    222
  • trunk/LayoutTests/platform/mac-wk2/TestExpectations

    r208915 r209070  
    265265
    266266webkit.org/b/162505 tiled-drawing/scrolling/latched-div-with-scroll-snap.html [ Pass Failure ]
    267 
    268 webkit.org/b/148405 tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-2d-overflow.html [ Pass Failure ]
    269 
    270 webkit.org/b/148407 tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-mainframe-horizontal.html [ Pass Failure ]
    271267webkit.org/b/148407 tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-mainframe-slow-horizontal.html [ Pass Failure ]
    272268webkit.org/b/148407 tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-mainframe-slow-vertical.html [ Pass Failure ]
    273 webkit.org/b/148407 tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-mainframe-vertical.html [ Pass Failure ]
    274 
    275 webkit.org/b/148408 tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-borders.html [ Pass Failure ]
    276 webkit.org/b/148408 tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-overflow.html [ Pass Failure ]
    277 webkit.org/b/148408 tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-rotated.html [ Pass Failure ]
    278 webkit.org/b/148408 tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-padding.html [ Pass Failure ]
    279 webkit.org/b/148408 tiled-drawing/scrolling/scroll-snap/scroll-snap-iframe.html [ Pass Failure ]
     269
    280270webkit.org/b/148408 tiled-drawing/scrolling/root-overflow-with-mousewheel.html [ Pass Failure Timeout ]
    281271
  • trunk/LayoutTests/tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-2d-overflow-expected.txt

    r197571 r209070  
    11PASS div successfully scrolled diagonally.
    22PASS div successfully snapped diagonally.
    3 FAIL div did not honor 2D snap points. (single axis scroll followed by flick on other axis)
     3PASS div successfully snapped after dragging along one axis and then scrolling in the other.
    44PASS successfullyParsed is true
    55
  • trunk/LayoutTests/tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-borders-expected.txt

    r197571 r209070  
    55PASS div honored snap points.
    66Testing scroll-snap glide for verticalTarget:
    7 FAIL div did not honor snap points. Expected 300, but got 50
     7PASS div scrolled to next window.
    88Testing scroll-snap snap for verticalTarget:
    9 FAIL div did not snap back to proper location for verticalTarget. Expected 50, but got 0
     9PASS div honored snap points.
    1010PASS successfullyParsed is true
    1111
  • trunk/LayoutTests/tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-mainframe-slow-horizontal.html

    r188793 r209070  
    3434            // The div should have snapped back to the previous position
    3535            if (divTarget.scrollLeft != divScrollPositionBeforeSnap)
    36                 testFailed("div did not snap back to proper location.");
     36                testFailed(`div did not snap back to proper location. ${divTarget.scrollLeft} vs. ${divScrollPositionBeforeSnap}`);
    3737            else
    3838                testPassed("div honored snap points.");
     
    6464                testPassed("div scrolled to next window.");
    6565            else
    66                 testFailed("div did not honor snap points.");
     66                testFailed(`div did not honor snap points. ${divTarget.scrollLeft} vs. ${window.innerWidth}`);
    6767
    6868            setTimeout(scrollSnapTest, 0);
  • trunk/LayoutTests/tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-mainframe-slow-vertical.html

    r188793 r209070  
    3434            // The div should have snapped back to the previous position
    3535            if (divTarget.scrollTop != divScrollPositionBeforeSnap)
    36                 testFailed("div did not snap back to proper location.");
     36                testFailed(`div did not snap back to proper location. (${divTarget.scrollTop} vs. ${divScrollPositionBeforeSnap})`);
    3737            else
    3838                testPassed("div honored snap points.");
     
    6464                testPassed("div scrolled to next window.");
    6565            else
    66                 testFailed("div did not honor snap points.");
     66                testFailed(`div did not honor snap points. (${divTarget.scrollTop} vs. ${window.innerHeight})`);
    6767
    6868            setTimeout(scrollSnapTest, 0);
     
    8989
    9090        function onLoad() {
    91 
    9291            if (window.eventSender) {
    9392                eventSender.monitorWheelEvents();
  • trunk/LayoutTests/tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-mainframe-vertical.html

    r188793 r209070  
    3434            // The div should have snapped back to the previous position
    3535            if (divTarget.scrollTop != divScrollPositionBeforeSnap)
    36                 testFailed("div did not snap back to proper location.");
     36                testFailed(`div did not snap back to proper location. (${divTarget.scrollTop} vs. ${divScrollPositionBeforeSnap})`);
    3737            else
    3838                testPassed("div honored snap points.");
     
    6464                testPassed("div scrolled to next window.");
    6565            else
    66                 testFailed("div did not honor snap points.");
     66                testFailed(`div did not honor snap points. (${divTarget.scrollTop} vs. ${window.innerHeight})`);
    6767
    6868            setTimeout(scrollSnapTest, 0);
  • trunk/Source/WTF/ChangeLog

    r209058 r209070  
     12016-11-29  Wenson Hsieh  <wenson_hsieh@apple.com>
     2
     3        Scroll snapping on Mac should use AppKit animations
     4        https://bugs.webkit.org/show_bug.cgi?id=147261
     5        <rdar://problem/29395293>
     6
     7        Reviewed by Brent Fulgham.
     8
     9        Introduce HAVE(NSSCROLLING_FILTERS), which is on for macOS El Capitan and later.
     10
     11        * wtf/Platform.h:
     12
    1132016-11-28  Darin Adler  <darin@apple.com>
    214
  • trunk/Source/WTF/wtf/Platform.h

    r208761 r209070  
    552552#endif
    553553
     554#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101100
     555#define HAVE_NSSCROLLING_FILTERS 1
     556#else
     557#define HAVE_NSSCROLLING_FILTERS 0
     558#endif
     559
    554560/* OS X defines a series of platform macros for debugging. */
    555561/* Some of them are really annoying because they use common names (e.g. check()). */
  • trunk/Source/WebCore/ChangeLog

    r209069 r209070  
     12016-11-29  Wenson Hsieh  <wenson_hsieh@apple.com>
     2
     3        Scroll snapping on Mac should use AppKit animations
     4        https://bugs.webkit.org/show_bug.cgi?id=147261
     5        <rdar://problem/29395293>
     6
     7        Reviewed by Brent Fulgham.
     8
     9        Refactors the scroll snapping animation logic to support arbitrary scrolling momentum calculators and
     10        introduces ScrollingMomentumCalculatorMac, which wraps AppKit's _NSScrollingMomentumCalculator. On macOS El
     11        Capitan and later, we use the platform scrolling momentum calculator and for all other cases, we fall back to
     12        the preexissting platform-invariant momentum calculator.
     13
     14        Previously, the scroll snapping animation logic was shared between the ScrollSnapAnimatorState and
     15        ScrollController -- namely, the ScrollController would update various parameters of the ScrollSnapAnimatorState
     16        and then tell it to compute animation-specific constants and coefficients. After this patch, ScrollController
     17        will no longer directly set the ScrollSnapAnimatorState's member variables. Instead, it will tell the animator
     18        state to transition to a new ScrollSnapState with the necessary parameters, and the ScrollSnapAnimatorState is
     19        responsible for modifying itself accordingly. Furthermore, logic pertaining to computing animated scroll offsets
     20        is now split out into a new ScrollingMomentumCalculator, which may have different platform-dependent
     21        implementations. The correct calculator is initialized via ScrollingMomentumCalculator::create, which currently
     22        returns a ScrollingMomentumCalculatorMac on El Capitan and later, and a BasicScrollingMomentumCalculator
     23        otherwise.
     24
     25        The new abstracted ScrollingMomentumCalculator is initialized with various parameters describing the scrolled
     26        content and viewport, as well as the initial and target scrolling offsets. The momentum calculator is then able
     27        to compute the animated scroll offset at any given elapsed time, as well as the total duration of the snapping
     28        animation. The ScrollController's scroll snap timer uses this information (via the ScrollSnapAnimatorState) to
     29        animate its client's scroll offset during a snap or glide.
     30
     31        Also reenables 8 failing and/or flaky scroll snapping tests and adds a new layout test. This patch addresses
     32        two causes for failures and flakiness in these scroll snapping tests:
     33
     34        1.  When starting or stopping the scroll snap animation timer, we call deferTestsForReason and
     35            removeTestDeferralForReason, respectively. These were actually noops for the first simulated scroll gesture
     36            on each of the failing mainframe scrolling tests due to m_expectsWheelEventTestTrigger being false. This
     37            member variable is updated when AsyncScrollingCoordinator::frameViewLayoutUpdated is invoked, wherein we
     38            call ScrollingStateFrameScrollingNode::setExpectsWheelEventTestTrigger(true) when the test has started
     39            monitoring wheel events. However, if this does not happen before scrolling begins in the test (which is the
     40            case here), then the mainframe scrolling node will not expect a wheel event test trigger even though
     41            eventSender.monitorWheelEvents() has been called. To fix this, we simply make the Page trigger a layout of
     42            the main FrameView when first ensuring the wheel event test trigger on the Page.
     43
     44        2.  The second reason for flakiness affects both overflow and mainframe scrolling. Previously, due to the way
     45            we would wait for multiple momentum scroll events before starting to glide, we would end up starting the
     46            scroll snap timer for a snapping animation, stopping it, and then starting it again for the glide animation.
     47            Thus, if the wheel event test trigger's timer fires right after the scroll snap timer stops and before it
     48            starts again due to a glide animation, it will erroneously think that scroll snapping is complete, even
     49            though it's only just about to begin! Now that we know scrolling velocity when we receive the initial
     50            "momentum begin", we now directly transition the scroll snap state from a snapping state to a gliding state
     51            and no longer stop and start the timer during this transition, which means that the test trigger will be
     52            deferred for at least the entire duration of the scroll snapping animation (starting right after the first
     53            "drag end" wheel event).
     54
     55        Test: tiled-drawing/scrolling/scroll-snap/scroll-snap-mandatory-vertical-then-horizontal.html
     56
     57        * WebCore.xcodeproj/project.pbxproj:
     58        * page/EventHandler.cpp:
     59        (WebCore::handleWheelEventInAppropriateEnclosingBox):
     60        (WebCore::EventHandler::defaultWheelEventHandler):
     61        * page/Page.cpp:
     62        (WebCore::Page::ensureTestTrigger):
     63
     64        Addresses test failures by forcing the mainframe scrolling node to expect wheel event test triggers.
     65
     66        * page/WheelEventDeltaFilter.cpp:
     67        (WebCore::WheelEventDeltaFilter::create):
     68        (WebCore::WheelEventDeltaFilter::filteredVelocity):
     69        * page/WheelEventDeltaFilter.h:
     70        * page/mac/WheelEventDeltaFilterMac.mm:
     71        (WebCore::WheelEventDeltaFilterMac::updateFromDelta):
     72
     73        Add support for plumbing filtered scrolling velocity over to the ScrollController.
     74
     75        * page/scrolling/ScrollingMomentumCalculator.cpp: Copied from Source/WebCore/platform/cocoa/ScrollSnapAnimatorState.mm.
     76        (WebCore::ScrollingMomentumCalculator::ScrollingMomentumCalculator):
     77        (WebCore::ScrollingMomentumCalculator::create):
     78
     79        Creates a platform-independent BasicScrollingMomentumCalculator.
     80
     81        (WebCore::BasicScrollingMomentumCalculator::BasicScrollingMomentumCalculator):
     82        (WebCore::BasicScrollingMomentumCalculator::linearlyInterpolatedOffsetAtProgress):
     83        (WebCore::BasicScrollingMomentumCalculator::cubicallyInterpolatedOffsetAtProgress):
     84        (WebCore::BasicScrollingMomentumCalculator::scrollOffsetAfterElapsedTime):
     85        (WebCore::BasicScrollingMomentumCalculator::animationDuration):
     86        (WebCore::BasicScrollingMomentumCalculator::initializeInterpolationCoefficientsIfNecessary):
     87        (WebCore::BasicScrollingMomentumCalculator::initializeSnapProgressCurve):
     88        (WebCore::BasicScrollingMomentumCalculator::animationProgressAfterElapsedTime):
     89
     90        Interpolation logic ported over from ScrollSnapAnimatorState.
     91
     92        * page/scrolling/ScrollingMomentumCalculator.h: Added.
     93        (WebCore::ScrollingMomentumCalculator::~ScrollingMomentumCalculator):
     94        * page/scrolling/mac/ScrollingMomentumCalculatorMac.h: Copied from Source/WebCore/page/WheelEventDeltaFilter.h.
     95        * page/scrolling/mac/ScrollingMomentumCalculatorMac.mm: Added.
     96        (WebCore::ScrollingMomentumCalculator::create):
     97
     98        Creates a ScrollingMomentumCalculatorMac.
     99
     100        (WebCore::ScrollingMomentumCalculatorMac::ScrollingMomentumCalculatorMac):
     101        (WebCore::ScrollingMomentumCalculatorMac::scrollOffsetAfterElapsedTime):
     102        (WebCore::ScrollingMomentumCalculatorMac::animationDuration):
     103        (WebCore::ScrollingMomentumCalculatorMac::ensurePlatformMomentumCalculator):
     104        * page/scrolling/mac/ScrollingTreeFrameScrollingNodeMac.h:
     105        * page/scrolling/mac/ScrollingTreeFrameScrollingNodeMac.mm:
     106        (WebCore::ScrollingTreeFrameScrollingNodeMac::scrollOffset):
     107        (WebCore::ScrollingTreeFrameScrollingNodeMac::viewportSize):
     108        (WebCore::ScrollingTreeFrameScrollingNodeMac::scrollOffsetOnAxis): Deleted.
     109        * platform/PlatformWheelEvent.h:
     110        (WebCore::PlatformWheelEvent::copyWithDeltasAndVelocity):
     111        (WebCore::PlatformWheelEvent::scrollingVelocity):
     112        (WebCore::PlatformWheelEvent::copyWithDeltas): Deleted.
     113        * platform/ScrollAnimator.cpp:
     114        (WebCore::ScrollAnimator::scrollOffset):
     115        (WebCore::ScrollAnimator::viewportSize):
     116        (WebCore::ScrollAnimator::scrollOffsetOnAxis): Deleted.
     117        * platform/ScrollAnimator.h:
     118        * platform/cocoa/ScrollController.h:
     119        * platform/cocoa/ScrollController.mm:
     120        (WebCore::otherScrollEventAxis):
     121        (WebCore::ScrollController::ScrollController):
     122        (WebCore::ScrollController::shouldOverrideInertialScrolling):
     123        (WebCore::ScrollController::scheduleStatelessScrollSnap):
     124        (WebCore::ScrollController::statelessSnapTransitionTimerFired):
     125        (WebCore::ScrollController::startDeferringTestsDueToScrollSnapping):
     126        (WebCore::ScrollController::stopDeferringTestsDueToScrollSnapping):
     127        (WebCore::ScrollController::processWheelEventForScrollSnap):
     128        (WebCore::ScrollController::updateScrollSnapState):
     129        (WebCore::ScrollController::updateScrollSnapPoints):
     130
     131        Update the ScrollController's ScrollSnapAnimationState for both vertical and horizontal axes. If both axes lack
     132        any snap points, the pointer to the animation state will be nulled out; otherwise, the animation state will
     133        exist.
     134
     135        (WebCore::ScrollController::startScrollSnapTimer):
     136        (WebCore::ScrollController::stopScrollSnapTimer):
     137        (WebCore::ScrollController::scrollSnapTimerFired):
     138        (WebCore::ScrollController::activeScrollSnapIndexForAxis):
     139        (WebCore::ScrollController::setActiveScrollSnapIndexForAxis):
     140        (WebCore::ScrollController::setNearestScrollSnapIndexForAxisAndOffset):
     141        (WebCore::ScrollController::setActiveScrollSnapIndicesForOffset):
     142        (WebCore::ScrollController::scrollSnapPointState): Deleted.
     143        (WebCore::ScrollController::processWheelEventForScrollSnapOnAxis): Deleted.
     144        (WebCore::ScrollController::shouldOverrideWheelEvent): Deleted.
     145        (WebCore::projectedInertialScrollDistance): Deleted.
     146        (WebCore::ScrollController::beginScrollSnapAnimation): Deleted.
     147        (WebCore::ScrollController::endScrollSnapAnimation): Deleted.
     148        (WebCore::ScrollController::initializeScrollSnapAnimationParameters): Deleted.
     149        (WebCore::ScrollController::isSnappingOnAxis): Deleted.
     150        * platform/cocoa/ScrollSnapAnimatorState.h:
     151        (WebCore::ScrollSnapAnimatorState::snapOffsetsForAxis):
     152        (WebCore::ScrollSnapAnimatorState::setSnapOffsetsForAxis):
     153        (WebCore::ScrollSnapAnimatorState::currentState):
     154        (WebCore::ScrollSnapAnimatorState::activeSnapIndexForAxis):
     155        (WebCore::ScrollSnapAnimatorState::setActiveSnapIndexForAxis):
     156        * platform/cocoa/ScrollSnapAnimatorState.mm:
     157        (WebCore::projectedInertialScrollDistance):
     158        (WebCore::ScrollSnapAnimatorState::transitionToSnapAnimationState):
     159        (WebCore::ScrollSnapAnimatorState::transitionToGlideAnimationState):
     160        (WebCore::ScrollSnapAnimatorState::transitionToUserInteractionState):
     161        (WebCore::ScrollSnapAnimatorState::transitionToDestinationReachedState):
     162
     163        These methods are used to update the ScrollSnapAnimationState. These state transitions should (and do)
     164        encapsulate all changes that need to be made to the animation state; in other words, the ScrollController should
     165        no longer be reaching directly into the ScrollSnapAnimatorState to change member variables.
     166
     167        (WebCore::ScrollSnapAnimatorState::setupAnimationForState):
     168        (WebCore::ScrollSnapAnimatorState::teardownAnimationForState):
     169        (WebCore::ScrollSnapAnimatorState::currentAnimatedScrollOffset):
     170        (WebCore::ScrollSnapAnimatorState::targetOffsetForStartOffset):
     171        (WebCore::ScrollSnapAnimatorState::ScrollSnapAnimatorState): Deleted.
     172        (WebCore::ScrollSnapAnimatorState::pushInitialWheelDelta): Deleted.
     173        (WebCore::ScrollSnapAnimatorState::averageInitialWheelDelta): Deleted.
     174        (WebCore::ScrollSnapAnimatorState::clearInitialWheelDeltaWindow): Deleted.
     175        (WebCore::ScrollSnapAnimatorState::isSnapping): Deleted.
     176        (WebCore::ScrollSnapAnimatorState::canReachTargetWithCurrentInitialScrollDelta): Deleted.
     177        (WebCore::ScrollSnapAnimatorState::wheelDeltaTrackingIsInProgress): Deleted.
     178        (WebCore::ScrollSnapAnimatorState::hasFinishedTrackingWheelDeltas): Deleted.
     179        (WebCore::ScrollSnapAnimatorState::interpolatedOffsetAtProgress): Deleted.
     180        (WebCore::ScrollSnapAnimationCurveState::initializeSnapProgressCurve): Deleted.
     181        (WebCore::ScrollSnapAnimationCurveState::initializeInterpolationCoefficientsIfNecessary): Deleted.
     182        (WebCore::ScrollSnapAnimationCurveState::interpolatedPositionAtProgress): Deleted.
     183        (WebCore::ScrollSnapAnimationCurveState::shouldCompleteSnapAnimationImmediatelyAtTime): Deleted.
     184        (WebCore::ScrollSnapAnimationCurveState::animationProgressAtTime): Deleted.
     185
     186        The ScrollSnapAnimatorState now tracks state across both axes. This simplifies coordinating scroll snapping in
     187        both horizontal and vertical axes and fixes the issue of the scroll offset not snapping when performing a scroll
     188        in one direction without momentum, then scrolling with momentum in the other direction in a single gesture.
     189
     190        * platform/spi/mac/NSScrollingMomentumCalculatorSPI.h: Added.
     191
    11922016-11-29  Brady Eidson  <beidson@apple.com>
    2193
  • trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj

    r208993 r209070  
    21602160                517B25A91CC82B2A0061C011 /* IDBConnectionProxy.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 517B25A71CC820320061C011 /* IDBConnectionProxy.cpp */; };
    21612161                517B25AA1CC82B2A0061C011 /* IDBConnectionProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = 517B25A81CC820320061C011 /* IDBConnectionProxy.h */; settings = {ATTRIBUTES = (Private, ); }; };
     2162                517DEEE51DE94ADC00B91644 /* ScrollingMomentumCalculatorMac.mm in Sources */ = {isa = PBXBuildFile; fileRef = 517DEEE31DE94ADC00B91644 /* ScrollingMomentumCalculatorMac.mm */; };
     2163                517DEEE81DE94B0800B91644 /* ScrollingMomentumCalculatorMac.h in Headers */ = {isa = PBXBuildFile; fileRef = 517DEEE71DE94B0800B91644 /* ScrollingMomentumCalculatorMac.h */; settings = {ATTRIBUTES = (Private, ); }; };
    21622164                517FBA1E151AB17C00B57959 /* DOMWindowExtension.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 517FBA17151AA71B00B57959 /* DOMWindowExtension.cpp */; };
    21632165                5185FC741BB4C4E80012898F /* DOMWindowIndexedDatabase.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 51D7196C181106DF0016DC51 /* DOMWindowIndexedDatabase.cpp */; };
     
    22582260                51C0AA390F2AA10A001648C2 /* CachedFrame.h in Headers */ = {isa = PBXBuildFile; fileRef = 51C0AA380F2AA10A001648C2 /* CachedFrame.h */; settings = {ATTRIBUTES = (Private, ); }; };
    22592261                51C0AA410F2AA15E001648C2 /* CachedFrame.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 51C0AA400F2AA15E001648C2 /* CachedFrame.cpp */; };
     2262                51C61B0A1DE536E7008A212D /* ScrollingMomentumCalculator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 51C61B081DE536E7008A212D /* ScrollingMomentumCalculator.cpp */; };
     2263                51C61B0B1DE536E7008A212D /* ScrollingMomentumCalculator.h in Headers */ = {isa = PBXBuildFile; fileRef = 51C61B091DE536E7008A212D /* ScrollingMomentumCalculator.h */; settings = {ATTRIBUTES = (Private, ); }; };
    22602264                51C81B890C4422F70019ECE3 /* FTPDirectoryParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 51C81B870C4422F70019ECE3 /* FTPDirectoryParser.cpp */; };
    22612265                51C81B8A0C4422F70019ECE3 /* FTPDirectoryParser.h in Headers */ = {isa = PBXBuildFile; fileRef = 51C81B880C4422F70019ECE3 /* FTPDirectoryParser.h */; };
     
    93979401                517B25A71CC820320061C011 /* IDBConnectionProxy.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = IDBConnectionProxy.cpp; sourceTree = "<group>"; };
    93989402                517B25A81CC820320061C011 /* IDBConnectionProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IDBConnectionProxy.h; sourceTree = "<group>"; };
     9403                517DEEE31DE94ADC00B91644 /* ScrollingMomentumCalculatorMac.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ScrollingMomentumCalculatorMac.mm; sourceTree = "<group>"; };
     9404                517DEEE71DE94B0800B91644 /* ScrollingMomentumCalculatorMac.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ScrollingMomentumCalculatorMac.h; sourceTree = "<group>"; };
    93999405                517FBA17151AA71B00B57959 /* DOMWindowExtension.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DOMWindowExtension.cpp; sourceTree = "<group>"; };
    94009406                517FBA18151AA71B00B57959 /* DOMWindowExtension.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DOMWindowExtension.h; sourceTree = "<group>"; };
     
    94579463                51C0AA380F2AA10A001648C2 /* CachedFrame.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CachedFrame.h; sourceTree = "<group>"; };
    94589464                51C0AA400F2AA15E001648C2 /* CachedFrame.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CachedFrame.cpp; sourceTree = "<group>"; };
     9465                51C61B081DE536E7008A212D /* ScrollingMomentumCalculator.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ScrollingMomentumCalculator.cpp; sourceTree = "<group>"; };
     9466                51C61B091DE536E7008A212D /* ScrollingMomentumCalculator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ScrollingMomentumCalculator.h; sourceTree = "<group>"; };
     9467                51C61B0C1DE5383D008A212D /* NSScrollingMomentumCalculatorSPI.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSScrollingMomentumCalculatorSPI.h; sourceTree = "<group>"; };
    94599468                51C81B870C4422F70019ECE3 /* FTPDirectoryParser.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = FTPDirectoryParser.cpp; sourceTree = "<group>"; };
    94609469                51C81B880C4422F70019ECE3 /* FTPDirectoryParser.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = FTPDirectoryParser.h; sourceTree = "<group>"; };
     
    1594615955                                F45C231B1995B73B00A6E2E3 /* AxisScrollSnapOffsets.cpp */,
    1594715956                                F45C231C1995B73B00A6E2E3 /* AxisScrollSnapOffsets.h */,
     15957                                51C61B081DE536E7008A212D /* ScrollingMomentumCalculator.cpp */,
     15958                                51C61B091DE536E7008A212D /* ScrollingMomentumCalculator.h */,
    1594815959                                0F605AEA15F94848004DF0C0 /* ScrollingConstraints.cpp */,
    1594915960                                0F605AEB15F94848004DF0C0 /* ScrollingConstraints.h */,
     
    1598715998                        isa = PBXGroup;
    1598815999                        children = (
     16000                                517DEEE71DE94B0800B91644 /* ScrollingMomentumCalculatorMac.h */,
     16001                                517DEEE31DE94ADC00B91644 /* ScrollingMomentumCalculatorMac.mm */,
    1598916002                                9391A990162746CB00297330 /* ScrollingCoordinatorMac.h */,
    1599016003                                1AF62EE314DA22A70041556C /* ScrollingCoordinatorMac.mm */,
     
    1850418517                                7C0406121C66EE9C00AF0711 /* NSScrollerImpSPI.h */,
    1850518518                                F40EA8AA1B867D6500CE5581 /* NSScrollingInputFilterSPI.h */,
     18519                                51C61B0C1DE5383D008A212D /* NSScrollingMomentumCalculatorSPI.h */,
    1850618520                                2DCB837719F99BBA00A7FBE4 /* NSSharingServicePickerSPI.h */,
    1850718521                                2DCB837819F99BBA00A7FBE4 /* NSSharingServiceSPI.h */,
     
    2577425788                                516953981329A3C800B92D04 /* IconDatabaseBase.h in Headers */,
    2577525789                                51E1ECBE0C91C90400DC255B /* IconDatabaseClient.h in Headers */,
     25790                                517DEEE81DE94B0800B91644 /* ScrollingMomentumCalculatorMac.h in Headers */,
    2577625791                                513F14540AB634C400094DDF /* IconLoader.h in Headers */,
    2577725792                                51E1ECC10C91C90400DC255B /* IconRecord.h in Headers */,
     
    2633526350                                46DFF49C1DC2620B00B80B48 /* JSShadowRootMode.h in Headers */,
    2633626351                                CD9DE17B17AAC75B00EA386D /* JSSourceBuffer.h in Headers */,
     26352                                51C61B0B1DE536E7008A212D /* ScrollingMomentumCalculator.h in Headers */,
    2633726353                                CD9DE17D17AAC75B00EA386D /* JSSourceBufferList.h in Headers */,
    2633826354                                AA7FEEAD16A4E74B004C0C33 /* JSSpeechSynthesis.h in Headers */,
     
    2935629372                                2D5036681BCDDDC400E20BB3 /* GestureEvents.cpp in Sources */,
    2935729373                                B2AFFC830D00A5C10030074D /* GlyphPageMac.cpp in Sources */,
     29374                                51C61B0A1DE536E7008A212D /* ScrollingMomentumCalculator.cpp in Sources */,
    2935829375                                BC53C6080DA56C570021EB5D /* Gradient.cpp in Sources */,
    2935929376                                BC53C60B0DA56CF10021EB5D /* GradientCG.cpp in Sources */,
     
    3092630943                                083DAEA60F01A7FB00342754 /* RenderTextControlMultiLine.cpp in Sources */,
    3092730944                                083DAEA80F01A7FB00342754 /* RenderTextControlSingleLine.cpp in Sources */,
     30945                                517DEEE51DE94ADC00B91644 /* ScrollingMomentumCalculatorMac.mm in Sources */,
    3092830946                                BCEA488D097D93020094C9E4 /* RenderTextFragment.cpp in Sources */,
    3092930947                                E4C91A18180999FB00A17F6D /* RenderTextLineBoxes.cpp in Sources */,
  • trunk/Source/WebCore/page/EventHandler.cpp

    r208903 r209070  
    304304}
    305305
    306 static inline bool handleWheelEventInAppropriateEnclosingBox(Node* startNode, WheelEvent& wheelEvent, Element** stopElement, const FloatSize& filteredPlatformDelta)
     306static inline bool handleWheelEventInAppropriateEnclosingBox(Node* startNode, WheelEvent& wheelEvent, Element** stopElement, const FloatSize& filteredPlatformDelta, const FloatPoint& filteredVelocity)
    307307{
    308308    bool shouldHandleEvent = wheelEvent.deltaX() || wheelEvent.deltaY();
     
    325325            const PlatformWheelEvent* platformEvent = wheelEvent.wheelEvent();
    326326            bool scrollingWasHandled;
    327             if (platformEvent != nullptr)
    328                 scrollingWasHandled = boxLayer->handleWheelEvent(platformEvent->copyWithDeltas(filteredPlatformDelta.width(), filteredPlatformDelta.height()));
    329             else
     327            if (platformEvent != nullptr) {
     328                auto copiedEvent = platformEvent->copyWithDeltasAndVelocity(filteredPlatformDelta.width(), filteredPlatformDelta.height(), filteredVelocity);
     329                scrollingWasHandled = boxLayer->handleWheelEvent(copiedEvent);
     330            } else
    330331                scrollingWasHandled = didScrollInScrollableArea(boxLayer, wheelEvent);
    331332
     
    27582759
    27592760    FloatSize filteredPlatformDelta(wheelEvent.deltaX(), wheelEvent.deltaY());
     2761    FloatPoint filteredVelocity;
    27602762    if (const PlatformWheelEvent* platformWheelEvent = wheelEvent.wheelEvent()) {
    27612763        filteredPlatformDelta.setWidth(platformWheelEvent->deltaX());
     
    27672769    Element* stopElement = latchedState ? latchedState->previousWheelScrolledElement() : nullptr;
    27682770
    2769     if (m_frame.mainFrame().wheelEventDeltaFilter()->isFilteringDeltas())
     2771    if (m_frame.mainFrame().wheelEventDeltaFilter()->isFilteringDeltas()) {
    27702772        filteredPlatformDelta = m_frame.mainFrame().wheelEventDeltaFilter()->filteredDelta();
     2773        filteredVelocity = m_frame.mainFrame().wheelEventDeltaFilter()->filteredVelocity();
     2774    }
    27712775#else
    27722776    Element* stopElement = nullptr;
     
    27742778   
    27752779   
    2776     if (handleWheelEventInAppropriateEnclosingBox(startNode, wheelEvent, &stopElement, filteredPlatformDelta))
     2780    if (handleWheelEventInAppropriateEnclosingBox(startNode, wheelEvent, &stopElement, filteredPlatformDelta, filteredVelocity))
    27772781        wheelEvent.setDefaultHandled();
    27782782   
  • trunk/Source/WebCore/page/Page.cpp

    r208985 r209070  
    19941994WheelEventTestTrigger& Page::ensureTestTrigger()
    19951995{
    1996     if (!m_testTrigger)
     1996    if (!m_testTrigger) {
    19971997        m_testTrigger = adoptRef(new WheelEventTestTrigger());
     1998        if (auto* frameView = mainFrame().view())
     1999            frameView->layout();
     2000    }
    19982001
    19992002    return *m_testTrigger;
  • trunk/Source/WebCore/page/WheelEventDeltaFilter.cpp

    r208662 r209070  
    2727#include "WheelEventDeltaFilter.h"
    2828
    29 #if PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101100
     29#if HAVE(NSSCROLLING_FILTERS)
    3030#include "WheelEventDeltaFilterMac.h"
    3131#endif
     
    4747std::unique_ptr<WheelEventDeltaFilter> WheelEventDeltaFilter::create()
    4848{
    49 #if PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101100
     49#if HAVE(NSSCROLLING_FILTERS)
    5050    return std::make_unique<WheelEventDeltaFilterMac>();
    5151#else
     
    6262{
    6363    return m_currentFilteredDelta;
     64}
     65
     66FloatPoint WheelEventDeltaFilter::filteredVelocity() const
     67{
     68    return m_currentFilteredVelocity;
    6469}
    6570
  • trunk/Source/WebCore/page/WheelEventDeltaFilter.h

    r208179 r209070  
    2626#pragma once
    2727
     28#include "FloatPoint.h"
    2829#include "FloatSize.h"
    2930#include <wtf/Deque.h>
     
    4041    WEBCORE_EXPORT virtual void beginFilteringDeltas() = 0;
    4142    WEBCORE_EXPORT virtual void endFilteringDeltas() = 0;
     43    WEBCORE_EXPORT FloatPoint filteredVelocity() const;
    4244    WEBCORE_EXPORT bool isFilteringDeltas() const;
    4345    WEBCORE_EXPORT FloatSize filteredDelta() const;
     
    4547protected:
    4648    FloatSize m_currentFilteredDelta;
     49    FloatPoint m_currentFilteredVelocity;
    4750    bool m_isFilteringDeltas { false };
    4851};
  • trunk/Source/WebCore/page/mac/WheelEventDeltaFilterMac.mm

    r191473 r209070  
    2626#include "config.h"
    2727
    28 #if PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101100
     28#if HAVE(NSSCROLLING_FILTERS)
    2929
    3030#include "WheelEventDeltaFilterMac.h"
     
    5656    NSPoint filteredVelocityResult;
    5757    [m_predominantAxisFilter filterInputDelta:NSPoint(FloatPoint(delta.width(), delta.height())) timestamp:monotonicallyIncreasingTime() - m_beginFilteringDeltasTime outputDelta:&filteredDeltaResult velocity:&filteredVelocityResult];
     58    m_currentFilteredVelocity = FloatPoint(filteredVelocityResult);
    5859    m_currentFilteredDelta = FloatSize(filteredDeltaResult.x, filteredDeltaResult.y);
    5960}
     
    6970}
    7071
    71 #endif /* PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101100 */
     72#endif /* HAVE(NSSCROLLING_FILTERS) */
  • trunk/Source/WebCore/page/scrolling/mac/ScrollingMomentumCalculatorMac.h

    r209069 r209070  
    11/*
    2  * Copyright (C) 2015 Apple Inc. All rights reserved.
     2 * Copyright (C) 2016 Apple Inc. All rights reserved.
    33 *
    44 * Redistribution and use in source and binary forms, with or without
     
    2626#pragma once
    2727
    28 #include "FloatSize.h"
    29 #include <wtf/Deque.h>
     28#include "ScrollingMomentumCalculator.h"
     29#include <wtf/RetainPtr.h>
     30
     31#if HAVE(NSSCROLLING_FILTERS)
     32
     33@class _NSScrollingMomentumCalculator;
    3034
    3135namespace WebCore {
    3236
    33 class WheelEventDeltaFilter {
     37class ScrollingMomentumCalculatorMac final : public ScrollingMomentumCalculator {
    3438public:
    35     WheelEventDeltaFilter();
    36     virtual ~WheelEventDeltaFilter();
    37 
    38     WEBCORE_EXPORT static std::unique_ptr<WheelEventDeltaFilter> create();
    39     WEBCORE_EXPORT virtual void updateFromDelta(const FloatSize&) = 0;
    40     WEBCORE_EXPORT virtual void beginFilteringDeltas() = 0;
    41     WEBCORE_EXPORT virtual void endFilteringDeltas() = 0;
    42     WEBCORE_EXPORT bool isFilteringDeltas() const;
    43     WEBCORE_EXPORT FloatSize filteredDelta() const;
    44 
    45 protected:
    46     FloatSize m_currentFilteredDelta;
    47     bool m_isFilteringDeltas { false };
    48 };
    49 
    50 enum class DominantScrollGestureDirection {
    51     None,
    52     Vertical,
    53     Horizontal
    54 };
    55 
    56 class BasicWheelEventDeltaFilter final : public WheelEventDeltaFilter {
    57 public:
    58     BasicWheelEventDeltaFilter();
    59     void updateFromDelta(const FloatSize&) override;
    60     void beginFilteringDeltas() override;
    61     void endFilteringDeltas() override;
     39    ScrollingMomentumCalculatorMac(const FloatSize& viewportSize, const FloatSize& contentSize, const FloatPoint& initialOffset, const FloatPoint& targetOffset, const FloatSize& initialDelta, const FloatPoint& initialVelocity);
    6240
    6341private:
    64     DominantScrollGestureDirection dominantScrollGestureDirection() const;
     42    FloatPoint scrollOffsetAfterElapsedTime(double time) final;
     43    double animationDuration() final;
     44    _NSScrollingMomentumCalculator *ensurePlatformMomentumCalculator();
    6545
    66     Deque<FloatSize> m_recentWheelEventDeltas;
     46    RetainPtr<_NSScrollingMomentumCalculator> m_platformMomentumCalculator;
    6747};
    6848
    6949} // namespace WebCore
     50
     51#endif // HAVE(NSSCROLLING_FILTERS)
  • trunk/Source/WebCore/page/scrolling/mac/ScrollingTreeFrameScrollingNodeMac.h

    r208503 r209070  
    8585
    8686#if ENABLE(CSS_SCROLL_SNAP) && PLATFORM(MAC)
    87     LayoutUnit scrollOffsetOnAxis(ScrollEventAxis) const override;
     87    FloatPoint scrollOffset() const override;
    8888    void immediateScrollOnAxis(ScrollEventAxis, float delta) override;
    8989    float pageScaleFactor() const override;
     
    9191    void stopScrollSnapTimer() override;
    9292    LayoutSize scrollExtent() const override;
     93    FloatSize viewportSize() const override;
    9394#endif
    9495
  • trunk/Source/WebCore/page/scrolling/mac/ScrollingTreeFrameScrollingNodeMac.mm

    r208985 r209070  
    582582
    583583#if ENABLE(CSS_SCROLL_SNAP)
    584 LayoutUnit ScrollingTreeFrameScrollingNodeMac::scrollOffsetOnAxis(ScrollEventAxis axis) const
    585 {
    586     const FloatPoint& currentPosition = scrollPosition();
    587     return axis == ScrollEventAxis::Horizontal ? currentPosition.x() : currentPosition.y();
     584FloatPoint ScrollingTreeFrameScrollingNodeMac::scrollOffset() const
     585{
     586    return scrollPosition();
    588587}
    589588
     
    619618    return LayoutSize(totalContentsSize());
    620619}
     620
     621FloatSize ScrollingTreeFrameScrollingNodeMac::viewportSize() const
     622{
     623    return scrollableAreaSize();
     624}
     625
    621626#endif
    622627
  • trunk/Source/WebCore/platform/PlatformWheelEvent.h

    r198805 r209070  
    2727#define PlatformWheelEvent_h
    2828
     29#include "FloatPoint.h"
    2930#include "IntPoint.h"
    3031#include "PlatformEvent.h"
     
    120121        }
    121122
    122         PlatformWheelEvent copyWithDeltas(float deltaX, float deltaY) const
     123        PlatformWheelEvent copyWithDeltasAndVelocity(float deltaX, float deltaY, FloatPoint velocity) const
    123124        {
    124125            PlatformWheelEvent copy = *this;
    125126            copy.m_deltaX = deltaX;
    126127            copy.m_deltaY = deltaY;
     128            copy.m_scrollingVelocity = velocity;
    127129            return copy;
    128130        }
     
    167169#endif
    168170
     171        FloatPoint scrollingVelocity() const { return m_scrollingVelocity; }
     172
    169173#if PLATFORM(WIN)
    170174        PlatformWheelEvent(HWND, WPARAM, LPARAM, bool isMouseHWheel);
     
    181185        PlatformWheelEventGranularity m_granularity;
    182186        bool m_directionInvertedFromDevice;
     187        FloatPoint m_scrollingVelocity;
    183188#if PLATFORM(COCOA)
    184189        bool m_hasPreciseScrollingDeltas;
  • trunk/Source/WebCore/platform/ScrollAnimator.cpp

    r195661 r209070  
    202202}
    203203
    204 LayoutUnit ScrollAnimator::scrollOffsetOnAxis(ScrollEventAxis axis) const
    205 {
    206     return axis == ScrollEventAxis::Horizontal ? m_currentPosition.x() : m_currentPosition.y();
     204FloatPoint ScrollAnimator::scrollOffset() const
     205{
     206    return m_currentPosition;
    207207}
    208208
     
    222222    return m_scrollableArea.contentsSize();
    223223}
     224
     225FloatSize ScrollAnimator::viewportSize() const
     226{
     227    return m_scrollableArea.visibleSize();
     228}
     229
    224230#endif
    225231
  • trunk/Source/WebCore/platform/ScrollAnimator.h

    r195810 r209070  
    135135#endif
    136136    void updateScrollSnapState();
    137     LayoutUnit scrollOffsetOnAxis(ScrollEventAxis) const override;
     137    FloatPoint scrollOffset() const override;
    138138    void immediateScrollOnAxis(ScrollEventAxis, float delta) override;
    139139    bool activeScrollSnapIndexDidChange() const;
    140140    unsigned activeScrollSnapIndexForAxis(ScrollEventAxis) const;
    141141    LayoutSize scrollExtent() const override;
     142    FloatSize viewportSize() const override;
    142143#endif
    143144
  • trunk/Source/WebCore/platform/cocoa/ScrollController.h

    r202611 r209070  
    8282
    8383#if ENABLE(CSS_SCROLL_SNAP)
    84     virtual LayoutUnit scrollOffsetOnAxis(ScrollEventAxis) const = 0;
     84    virtual FloatPoint scrollOffset() const = 0;
    8585    virtual void immediateScrollOnAxis(ScrollEventAxis, float delta) = 0;
    8686    virtual void startScrollSnapTimer()
     
    105105
    106106    virtual LayoutSize scrollExtent() const = 0;
    107 #endif
     107    virtual FloatSize viewportSize() const = 0;
     108#endif
     109};
     110
     111enum class WheelEventStatus {
     112    UserScrollBegin,
     113    UserScrolling,
     114    UserScrollEnd,
     115    InertialScrollBegin,
     116    InertialScrolling,
     117    InertialScrollEnd,
     118    StatelessScrollEvent,
     119    Unknown
    108120};
    109121
     
    145157
    146158#if ENABLE(CSS_SCROLL_SNAP)
    147     LayoutUnit scrollOffsetOnAxis(ScrollEventAxis) const;
    148159    void setNearestScrollSnapIndexForAxisAndOffset(ScrollEventAxis, int);
    149     ScrollSnapAnimatorState& scrollSnapPointState(ScrollEventAxis);
    150     const ScrollSnapAnimatorState& scrollSnapPointState(ScrollEventAxis) const;
    151160#if PLATFORM(MAC)
    152161    void scrollSnapTimerFired();
     
    154163    void stopScrollSnapTimer();
    155164
    156     void processWheelEventForScrollSnapOnAxis(ScrollEventAxis, const PlatformWheelEvent&);
    157     bool shouldOverrideWheelEvent(ScrollEventAxis, const PlatformWheelEvent&) const;
    158 
    159     void beginScrollSnapAnimation(ScrollEventAxis, ScrollSnapState);
    160    
    161     void endScrollSnapAnimation(ScrollSnapState);
    162     void initializeScrollSnapAnimationParameters();
    163     bool isSnappingOnAxis(ScrollEventAxis) const;
    164    
     165    bool shouldOverrideInertialScrolling() const;
     166    void statelessSnapTransitionTimerFired();
     167    void startDeferringTestsDueToScrollSnapping();
     168    void stopDeferringTestsDueToScrollSnapping();
     169    void scheduleStatelessScrollSnap();
    165170#endif
    166171#endif
    167172
    168173    ScrollControllerClient& m_client;
    169    
     174
     175#if PLATFORM(MAC)
    170176    CFTimeInterval m_lastMomentumScrollTimestamp { 0 };
     177#endif
    171178    FloatSize m_overflowScrollDelta;
    172179    FloatSize m_stretchScrollForce;
     
    182189
    183190#if ENABLE(CSS_SCROLL_SNAP)
    184     bool m_expectingHorizontalStatelessScrollSnap { false };
    185     bool m_expectingVerticalStatelessScrollSnap { false };
    186     std::unique_ptr<ScrollSnapAnimatorState> m_horizontalScrollSnapState;
    187     std::unique_ptr<ScrollSnapAnimatorState> m_verticalScrollSnapState;
    188     std::unique_ptr<ScrollSnapAnimationCurveState> m_scrollSnapCurveState;
    189 #if PLATFORM(MAC)
     191    std::unique_ptr<ScrollSnapAnimatorState> m_scrollSnapState;
     192#if PLATFORM(MAC)
     193    FloatPoint m_dragEndedScrollingVelocity;
     194    RunLoop::Timer<ScrollController> m_statelessSnapTransitionTimer;
    190195    RunLoop::Timer<ScrollController> m_scrollSnapTimer;
    191196#endif
    192197#endif
    193198
     199#if PLATFORM(MAC)
    194200    bool m_inScrollGesture { false };
    195201    bool m_momentumScrollInProgress { false };
    196202    bool m_ignoreMomentumScrolls { false };
    197203    bool m_snapRubberbandTimerIsActive { false };
     204#endif
     205
    198206    bool m_activeScrollSnapIndexDidChange { false };
    199207};
  • trunk/Source/WebCore/platform/cocoa/ScrollController.mm

    r202611 r209070  
    7575#endif
    7676
    77 #if ENABLE(CSS_SCROLL_SNAP) && PLATFORM(MAC)
    78 static const float inertialScrollPredictionFactor = 16.7;
    79 static const double statelessScrollSnapDelay = 0.5;
    80 #endif
    81 
    8277#if PLATFORM(MAC)
    83 enum class WheelEventStatus {
    84     UserScrollBegin,
    85     UserScrolling,
    86     UserScrollEnd,
    87     InertialScrollBegin,
    88     InertialScrolling,
    89     InertialScrollEnd,
    90     StatelessScrollEvent,
    91     Unknown
    92 };
    93 
    9478static float elasticDeltaForTimeDelta(float initialPosition, float initialVelocity, float elapsedTime)
    9579{
     
    117101    return multiplier;
    118102}
     103
     104static ScrollEventAxis otherScrollEventAxis(ScrollEventAxis axis)
     105{
     106    return axis == ScrollEventAxis::Horizontal ? ScrollEventAxis::Vertical : ScrollEventAxis::Horizontal;
     107}
    119108#endif
    120109
     
    125114#endif
    126115#if ENABLE(CSS_SCROLL_SNAP) && PLATFORM(MAC)
     116    , m_statelessSnapTransitionTimer(RunLoop::current(), this, &ScrollController::statelessSnapTransitionTimerFired)
    127117    , m_scrollSnapTimer(RunLoop::current(), this, &ScrollController::scrollSnapTimerFired)
    128118#endif
     
    467457
    468458#if ENABLE(CSS_SCROLL_SNAP)
    469 ScrollSnapAnimatorState& ScrollController::scrollSnapPointState(ScrollEventAxis axis)
    470 {
    471     ASSERT(axis != ScrollEventAxis::Horizontal || m_horizontalScrollSnapState);
    472     ASSERT(axis != ScrollEventAxis::Vertical || m_verticalScrollSnapState);
    473 
    474     return (axis == ScrollEventAxis::Horizontal) ? *m_horizontalScrollSnapState : *m_verticalScrollSnapState;
    475 }
    476 
    477 const ScrollSnapAnimatorState& ScrollController::scrollSnapPointState(ScrollEventAxis axis) const
    478 {
    479     ASSERT(axis != ScrollEventAxis::Horizontal || m_horizontalScrollSnapState);
    480     ASSERT(axis != ScrollEventAxis::Vertical || m_verticalScrollSnapState);
    481    
    482     return (axis == ScrollEventAxis::Horizontal) ? *m_horizontalScrollSnapState : *m_verticalScrollSnapState;
    483 }
    484459
    485460#if PLATFORM(MAC)
     
    524499}
    525500
    526 void ScrollController::processWheelEventForScrollSnapOnAxis(ScrollEventAxis axis, const PlatformWheelEvent& event)
    527 {
    528     ScrollSnapAnimatorState& snapState = scrollSnapPointState(axis);
    529 
    530     float wheelDelta = axis == ScrollEventAxis::Horizontal ? -event.deltaX() : -event.deltaY();
    531     WheelEventStatus wheelStatus = toWheelEventStatus(event.phase(), event.momentumPhase());
    532    
    533     switch (wheelStatus) {
     501bool ScrollController::shouldOverrideInertialScrolling() const
     502{
     503    if (!m_scrollSnapState)
     504        return false;
     505
     506    ScrollSnapState scrollSnapState = m_scrollSnapState->currentState();
     507    return scrollSnapState == ScrollSnapState::Gliding || scrollSnapState == ScrollSnapState::DestinationReached;
     508}
     509
     510void ScrollController::scheduleStatelessScrollSnap()
     511{
     512    stopScrollSnapTimer();
     513    m_statelessSnapTransitionTimer.stop();
     514    if (!m_scrollSnapState)
     515        return;
     516
     517    static const double statelessScrollSnapDelay = 0.75;
     518    m_statelessSnapTransitionTimer.startOneShot(statelessScrollSnapDelay);
     519    startDeferringTestsDueToScrollSnapping();
     520}
     521
     522void ScrollController::statelessSnapTransitionTimerFired()
     523{
     524    if (!m_scrollSnapState)
     525        return;
     526
     527    m_scrollSnapState->transitionToSnapAnimationState(m_client.scrollExtent(), m_client.viewportSize(), m_client.pageScaleFactor(), m_client.scrollOffset());
     528    startScrollSnapTimer();
     529}
     530
     531void ScrollController::startDeferringTestsDueToScrollSnapping()
     532{
     533    m_client.deferTestsForReason(reinterpret_cast<WheelEventTestTrigger::ScrollableAreaIdentifier>(this), WheelEventTestTrigger::ScrollSnapInProgress);
     534}
     535
     536void ScrollController::stopDeferringTestsDueToScrollSnapping()
     537{
     538    m_client.removeTestDeferralForReason(reinterpret_cast<WheelEventTestTrigger::ScrollableAreaIdentifier>(this), WheelEventTestTrigger::ScrollSnapInProgress);
     539}
     540
     541bool ScrollController::processWheelEventForScrollSnap(const PlatformWheelEvent& wheelEvent)
     542{
     543    if (!m_scrollSnapState)
     544        return true;
     545
     546    if (m_scrollSnapState->snapOffsetsForAxis(ScrollEventAxis::Horizontal).isEmpty() && m_scrollSnapState->snapOffsetsForAxis(ScrollEventAxis::Vertical).isEmpty())
     547        return true;
     548
     549    WheelEventStatus status = toWheelEventStatus(wheelEvent.phase(), wheelEvent.momentumPhase());
     550    bool isInertialScrolling = false;
     551    switch (status) {
    534552    case WheelEventStatus::UserScrollBegin:
    535553    case WheelEventStatus::UserScrolling:
    536         endScrollSnapAnimation(ScrollSnapState::UserInteraction);
     554        stopScrollSnapTimer();
     555        m_scrollSnapState->transitionToUserInteractionState();
    537556        break;
    538            
    539557    case WheelEventStatus::UserScrollEnd:
    540         beginScrollSnapAnimation(axis, ScrollSnapState::Snapping);
     558        m_dragEndedScrollingVelocity = -wheelEvent.scrollingVelocity();
     559        m_scrollSnapState->transitionToSnapAnimationState(m_client.scrollExtent(), m_client.viewportSize(), m_client.pageScaleFactor(), m_client.scrollOffset());
     560        startScrollSnapTimer();
    541561        break;
    542        
    543562    case WheelEventStatus::InertialScrollBegin:
    544         // Begin tracking wheel deltas for glide prediction.
    545         endScrollSnapAnimation(ScrollSnapState::UserInteraction);
    546         snapState.pushInitialWheelDelta(wheelDelta);
    547         snapState.m_beginTrackingWheelDeltaOffset = m_client.scrollOffsetOnAxis(axis);
     563        m_scrollSnapState->transitionToGlideAnimationState(m_client.scrollExtent(), m_client.viewportSize(), m_client.pageScaleFactor(), m_client.scrollOffset(), m_dragEndedScrollingVelocity, FloatSize(-wheelEvent.deltaX(), -wheelEvent.deltaY()));
     564        isInertialScrolling = true;
    548565        break;
    549            
    550566    case WheelEventStatus::InertialScrolling:
    551         // This check for DestinationReached ensures that we don't receive another set of momentum events after ending the last glide.
    552         if (snapState.m_currentState != ScrollSnapState::Gliding && snapState.m_currentState != ScrollSnapState::DestinationReached) {
    553             if (snapState.wheelDeltaTrackingIsInProgress() && wheelDelta)
    554                 snapState.pushInitialWheelDelta(wheelDelta);
    555            
    556             if (snapState.hasFinishedTrackingWheelDeltas() && snapState.averageInitialWheelDelta())
    557                 beginScrollSnapAnimation(axis, ScrollSnapState::Gliding);
    558         }
     567    case WheelEventStatus::InertialScrollEnd:
     568        isInertialScrolling = true;
    559569        break;
    560        
    561     case WheelEventStatus::InertialScrollEnd:
    562         if (snapState.wheelDeltaTrackingIsInProgress() && snapState.averageInitialWheelDelta())
    563             beginScrollSnapAnimation(axis, ScrollSnapState::Gliding);
    564 
    565         snapState.clearInitialWheelDeltaWindow();
    566         snapState.m_shouldOverrideWheelEvent = false;
     570    case WheelEventStatus::StatelessScrollEvent:
     571        m_scrollSnapState->transitionToUserInteractionState();
     572        scheduleStatelessScrollSnap();
    567573        break;
    568 
    569     case WheelEventStatus::StatelessScrollEvent:
    570         endScrollSnapAnimation(ScrollSnapState::UserInteraction);
    571         snapState.clearInitialWheelDeltaWindow();
    572         snapState.m_shouldOverrideWheelEvent = false;
    573         m_scrollSnapTimer.startOneShot(statelessScrollSnapDelay);
    574         if (axis == ScrollEventAxis::Horizontal)
    575             m_expectingHorizontalStatelessScrollSnap = true;
    576         else
    577             m_expectingVerticalStatelessScrollSnap = true;
    578         break;
    579 
    580574    case WheelEventStatus::Unknown:
    581575        ASSERT_NOT_REACHED();
    582576        break;
    583577    }
    584 }
    585 
    586 bool ScrollController::shouldOverrideWheelEvent(ScrollEventAxis axis, const PlatformWheelEvent& event) const
    587 {
    588     const ScrollSnapAnimatorState& snapState = scrollSnapPointState(axis);
    589 
    590     return snapState.m_shouldOverrideWheelEvent && toWheelEventStatus(event.phase(), event.momentumPhase()) == WheelEventStatus::InertialScrolling;
    591 }
    592 
    593 bool ScrollController::processWheelEventForScrollSnap(const PlatformWheelEvent& wheelEvent)
    594 {
    595     bool shouldAllowWheelEventToPropagate = true;
    596     if (m_verticalScrollSnapState) {
    597         processWheelEventForScrollSnapOnAxis(ScrollEventAxis::Vertical, wheelEvent);
    598         shouldAllowWheelEventToPropagate &= !shouldOverrideWheelEvent(ScrollEventAxis::Vertical, wheelEvent);
    599     }
    600     if (m_horizontalScrollSnapState) {
    601         processWheelEventForScrollSnapOnAxis(ScrollEventAxis::Horizontal, wheelEvent);
    602         shouldAllowWheelEventToPropagate &= !shouldOverrideWheelEvent(ScrollEventAxis::Horizontal, wheelEvent);
    603     }
    604     return shouldAllowWheelEventToPropagate;
    605 }
    606 #endif
     578
     579    return !(isInertialScrolling && shouldOverrideInertialScrolling());
     580}
    607581
    608582void ScrollController::updateScrollSnapState(const ScrollableArea& scrollableArea)
    609583{
    610     // FIXME: Currently, scroll snap animators are recreated even though the snap offsets alone can be updated.
    611     if (scrollableArea.horizontalSnapOffsets())
    612         m_horizontalScrollSnapState = std::make_unique<ScrollSnapAnimatorState>(ScrollEventAxis::Horizontal, *scrollableArea.horizontalSnapOffsets());
    613     else if (m_horizontalScrollSnapState)
    614         m_horizontalScrollSnapState = nullptr;
    615 
    616     if (scrollableArea.verticalSnapOffsets())
    617         m_verticalScrollSnapState = std::make_unique<ScrollSnapAnimatorState>(ScrollEventAxis::Vertical, *scrollableArea.verticalSnapOffsets());
    618     else if (m_verticalScrollSnapState)
    619         m_verticalScrollSnapState = nullptr;
     584    if (auto* snapOffsets = scrollableArea.horizontalSnapOffsets())
     585        updateScrollSnapPoints(ScrollEventAxis::Horizontal, *snapOffsets);
     586
     587    if (auto* snapOffsets = scrollableArea.verticalSnapOffsets())
     588        updateScrollSnapPoints(ScrollEventAxis::Vertical, *snapOffsets);
    620589}
    621590
    622591void ScrollController::updateScrollSnapPoints(ScrollEventAxis axis, const Vector<LayoutUnit>& snapPoints)
    623592{
    624     // FIXME: Currently, scroll snap animators are recreated even though the snap offsets alone can be updated.
    625     if (axis == ScrollEventAxis::Horizontal)
    626         m_horizontalScrollSnapState = !snapPoints.isEmpty() ? std::make_unique<ScrollSnapAnimatorState>(ScrollEventAxis::Horizontal, snapPoints) : nullptr;
    627 
    628     if (axis == ScrollEventAxis::Vertical)
    629         m_verticalScrollSnapState = !snapPoints.isEmpty() ? std::make_unique<ScrollSnapAnimatorState>(ScrollEventAxis::Vertical, snapPoints) : nullptr;
    630 }
    631 
    632 #if PLATFORM(MAC)
     593    if (!m_scrollSnapState) {
     594        if (snapPoints.isEmpty())
     595            return;
     596
     597        m_scrollSnapState = std::make_unique<ScrollSnapAnimatorState>();
     598    }
     599
     600    if (snapPoints.isEmpty() && m_scrollSnapState->snapOffsetsForAxis(otherScrollEventAxis(axis)).isEmpty())
     601        m_scrollSnapState = nullptr;
     602    else
     603        m_scrollSnapState->setSnapOffsetsForAxis(axis, snapPoints);
     604}
     605
    633606void ScrollController::startScrollSnapTimer()
    634607{
    635     if (!m_scrollSnapTimer.isActive()) {
    636         m_client.startScrollSnapTimer();
    637         m_scrollSnapTimer.startRepeating(1.0 / 60.0);
    638     }
    639 
     608    if (m_scrollSnapTimer.isActive())
     609        return;
     610
     611    startDeferringTestsDueToScrollSnapping();
     612    m_client.startScrollSnapTimer();
     613    m_scrollSnapTimer.startRepeating(1.0 / 60.0);
     614}
     615
     616void ScrollController::stopScrollSnapTimer()
     617{
    640618    if (!m_scrollSnapTimer.isActive())
    641619        return;
    642620
    643     m_client.deferTestsForReason(reinterpret_cast<WheelEventTestTrigger::ScrollableAreaIdentifier>(this), WheelEventTestTrigger::ScrollSnapInProgress);
    644 }
    645 
    646 void ScrollController::stopScrollSnapTimer()
    647 {
     621    stopDeferringTestsDueToScrollSnapping();
    648622    m_client.stopScrollSnapTimer();
    649623    m_scrollSnapTimer.stop();
    650    
    651     if (m_scrollSnapTimer.isActive())
    652         return;
    653 
    654     m_client.removeTestDeferralForReason(reinterpret_cast<WheelEventTestTrigger::ScrollableAreaIdentifier>(this), WheelEventTestTrigger::ScrollSnapInProgress);
    655624}
    656625
    657626void ScrollController::scrollSnapTimerFired()
    658627{
    659     if (m_expectingHorizontalStatelessScrollSnap || m_expectingVerticalStatelessScrollSnap) {
    660         if (m_expectingHorizontalStatelessScrollSnap)
    661             beginScrollSnapAnimation(ScrollEventAxis::Horizontal, ScrollSnapState::Snapping);
    662         if (m_expectingVerticalStatelessScrollSnap)
    663             beginScrollSnapAnimation(ScrollEventAxis::Vertical, ScrollSnapState::Snapping);
    664         return;
    665     }
    666        
    667     bool snapOnHorizontalAxis = isSnappingOnAxis(ScrollEventAxis::Horizontal);
    668     bool snapOnVerticalAxis = isSnappingOnAxis(ScrollEventAxis::Vertical);
    669     if (snapOnHorizontalAxis && !m_horizontalScrollSnapState->canReachTargetWithCurrentInitialScrollDelta()) {
    670         m_horizontalScrollSnapState->m_currentState = ScrollSnapState::DestinationReached;
    671         snapOnHorizontalAxis = false;
    672     }
    673     if (snapOnVerticalAxis && !m_verticalScrollSnapState->canReachTargetWithCurrentInitialScrollDelta()) {
    674         m_verticalScrollSnapState->m_currentState = ScrollSnapState::DestinationReached;
    675         snapOnVerticalAxis = false;
    676     }
    677     if (!snapOnHorizontalAxis && !snapOnVerticalAxis) {
    678         endScrollSnapAnimation(ScrollSnapState::DestinationReached);
    679         return;
    680     }
    681    
    682     double currentTime = monotonicallyIncreasingTime();
    683     if (m_scrollSnapCurveState->shouldCompleteSnapAnimationImmediatelyAtTime(currentTime)) {
    684         float finalHorizontalDelta = 0;
    685         float finalVerticalDelta = 0;
    686         if (snapOnHorizontalAxis)
    687             finalHorizontalDelta = m_horizontalScrollSnapState->m_targetOffset - m_client.scrollOffsetOnAxis(ScrollEventAxis::Horizontal);
    688         if (snapOnVerticalAxis)
    689             finalVerticalDelta = m_verticalScrollSnapState->m_targetOffset - m_client.scrollOffsetOnAxis(ScrollEventAxis::Vertical);
    690 
    691         if (finalHorizontalDelta || finalVerticalDelta)
    692             m_client.immediateScrollBy(FloatSize(finalHorizontalDelta, finalVerticalDelta));
    693 
    694         endScrollSnapAnimation(ScrollSnapState::DestinationReached);
    695         return;
    696     }
    697    
    698     float animationProgress = m_scrollSnapCurveState->animationProgressAtTime(currentTime);
    699     float horizontalDelta = 0;
    700     float verticalDelta = 0;
    701     if (m_scrollSnapCurveState->shouldAnimateDirectlyToSnapPoint) {
    702         if (snapOnHorizontalAxis)
    703             horizontalDelta = m_horizontalScrollSnapState->interpolatedOffsetAtProgress(animationProgress) - m_client.scrollOffsetOnAxis(ScrollEventAxis::Horizontal);
    704         if (snapOnVerticalAxis)
    705             verticalDelta = m_verticalScrollSnapState->interpolatedOffsetAtProgress(animationProgress) - m_client.scrollOffsetOnAxis(ScrollEventAxis::Vertical);
    706 
    707     } else {
    708         FloatPoint interpolatedPoint = m_scrollSnapCurveState->interpolatedPositionAtProgress(animationProgress);
    709         horizontalDelta = interpolatedPoint.x() - m_client.scrollOffsetOnAxis(ScrollEventAxis::Horizontal);
    710         verticalDelta = interpolatedPoint.y() - m_client.scrollOffsetOnAxis(ScrollEventAxis::Vertical);
    711     }
    712    
    713     if (horizontalDelta || verticalDelta)
    714         m_client.immediateScrollBy(FloatSize(horizontalDelta, verticalDelta));
    715 }
    716 
    717 static inline float projectedInertialScrollDistance(float initialWheelDelta)
    718 {
    719     // FIXME: Experiments with inertial scrolling show a fairly consistent linear relationship between initial wheel delta and total distance scrolled.
    720     // In the future, we'll want to find a more accurate way of inertial scroll prediction.
    721     return inertialScrollPredictionFactor * initialWheelDelta;
    722 }
    723 #endif
     628    if (!m_scrollSnapState) {
     629        ASSERT_NOT_REACHED();
     630        return;
     631    }
     632
     633    bool isAnimationComplete;
     634    auto animationOffset = m_scrollSnapState->currentAnimatedScrollOffset(isAnimationComplete);
     635    auto currentOffset = m_client.scrollOffset();
     636    m_client.immediateScrollByWithoutContentEdgeConstraints(FloatSize(animationOffset.x() - currentOffset.x(), animationOffset.y() - currentOffset.y()));
     637    if (isAnimationComplete) {
     638        m_scrollSnapState->transitionToDestinationReachedState();
     639        stopScrollSnapTimer();
     640    }
     641}
     642#else
     643void ScrollController::updateScrollSnapState(const ScrollableArea&)
     644{
     645}
     646#endif // PLATFORM(MAC)
    724647
    725648unsigned ScrollController::activeScrollSnapIndexForAxis(ScrollEventAxis axis) const
    726649{
    727     if ((axis == ScrollEventAxis::Horizontal) && !m_horizontalScrollSnapState)
     650    if (!m_scrollSnapState)
    728651        return 0;
    729     if ((axis == ScrollEventAxis::Vertical) && !m_verticalScrollSnapState)
    730         return 0;
    731    
    732     const ScrollSnapAnimatorState& snapState = scrollSnapPointState(axis);
    733     return snapState.m_activeSnapIndex;
     652
     653    return m_scrollSnapState->activeSnapIndexForAxis(axis);
    734654}
    735655
    736656void ScrollController::setActiveScrollSnapIndexForAxis(ScrollEventAxis axis, unsigned index)
    737657{
    738     auto* snapState = (axis == ScrollEventAxis::Horizontal) ? m_horizontalScrollSnapState.get() : m_verticalScrollSnapState.get();
    739     if (!snapState)
    740         return;
    741 
    742     snapState->m_activeSnapIndex = index;
     658    if (!m_scrollSnapState)
     659        return;
     660
     661    m_scrollSnapState->setActiveSnapIndexForAxis(axis, index);
    743662}
    744663
    745664void ScrollController::setNearestScrollSnapIndexForAxisAndOffset(ScrollEventAxis axis, int offset)
    746665{
     666    if (!m_scrollSnapState)
     667        return;
     668
    747669    float scaleFactor = m_client.pageScaleFactor();
    748     ScrollSnapAnimatorState& snapState = scrollSnapPointState(axis);
    749    
    750     LayoutUnit clampedOffset = std::min(std::max(LayoutUnit(offset / scaleFactor), snapState.m_snapOffsets.first()), snapState.m_snapOffsets.last());
     670    ScrollSnapAnimatorState& snapState = *m_scrollSnapState;
     671
     672    auto snapOffsets = snapState.snapOffsetsForAxis(axis);
     673    if (!snapOffsets.size())
     674        return;
     675
     676    LayoutUnit clampedOffset = std::min(std::max(LayoutUnit(offset / scaleFactor), snapOffsets.first()), snapOffsets.last());
    751677
    752678    unsigned activeIndex = 0;
    753     (void)closestSnapOffset<LayoutUnit, float>(snapState.m_snapOffsets, clampedOffset, 0, activeIndex);
    754 
    755     if (activeIndex == snapState.m_activeSnapIndex)
     679    closestSnapOffset<LayoutUnit, float>(snapState.snapOffsetsForAxis(axis), clampedOffset, 0, activeIndex);
     680
     681    if (activeIndex == activeScrollSnapIndexForAxis(axis))
    756682        return;
    757683
    758684    m_activeScrollSnapIndexDidChange = true;
    759     snapState.m_activeSnapIndex = activeIndex;
     685    setActiveScrollSnapIndexForAxis(axis, activeIndex);
    760686}
    761687
    762688void ScrollController::setActiveScrollSnapIndicesForOffset(int x, int y)
    763689{
    764     if (m_horizontalScrollSnapState)
    765         setNearestScrollSnapIndexForAxisAndOffset(ScrollEventAxis::Horizontal, x);
    766     if (m_verticalScrollSnapState)
    767         setNearestScrollSnapIndexForAxisAndOffset(ScrollEventAxis::Vertical, y);
    768 }
    769 
    770 #if PLATFORM(MAC)
    771 void ScrollController::beginScrollSnapAnimation(ScrollEventAxis axis, ScrollSnapState newState)
    772 {
    773     ASSERT(newState == ScrollSnapState::Gliding || newState == ScrollSnapState::Snapping);
    774     if (m_expectingHorizontalStatelessScrollSnap || m_expectingVerticalStatelessScrollSnap) {
    775         m_expectingHorizontalStatelessScrollSnap = false;
    776         m_expectingVerticalStatelessScrollSnap = false;
    777         stopScrollSnapTimer();
    778     }
    779     ScrollSnapAnimatorState& snapState = scrollSnapPointState(axis);
    780 
    781     LayoutUnit offset = m_client.scrollOffsetOnAxis(axis);
    782     float initialWheelDelta = newState == ScrollSnapState::Gliding ? snapState.averageInitialWheelDelta() : 0;
    783     LayoutUnit scaledProjectedScrollDestination = newState == ScrollSnapState::Gliding ? snapState.m_beginTrackingWheelDeltaOffset + LayoutUnit(projectedInertialScrollDistance(initialWheelDelta)) : offset;
    784     if (snapState.m_snapOffsets.isEmpty())
    785         return;
    786 
    787     float scaleFactor = m_client.pageScaleFactor();
    788     LayoutUnit originalProjectedScrollDestination = scaledProjectedScrollDestination / scaleFactor;
    789    
    790     LayoutUnit clampedScrollDestination = std::min(std::max(originalProjectedScrollDestination, snapState.m_snapOffsets.first()), snapState.m_snapOffsets.last());
    791     snapState.m_initialOffset = offset;
    792     m_activeScrollSnapIndexDidChange = false;
    793     snapState.m_targetOffset = scaleFactor * closestSnapOffset<LayoutUnit, float>(snapState.m_snapOffsets, clampedScrollDestination, initialWheelDelta, snapState.m_activeSnapIndex);
    794     if (snapState.m_initialOffset == snapState.m_targetOffset)
    795         return;
    796 
    797     LayoutUnit scrollExtent = (axis == ScrollEventAxis::Horizontal) ? m_client.scrollExtent().width() : m_client.scrollExtent().height();
    798     LayoutUnit projectedScrollDestination = clampedScrollDestination;
    799     if (originalProjectedScrollDestination < 0 || originalProjectedScrollDestination > scrollExtent)
    800         projectedScrollDestination = originalProjectedScrollDestination;
    801    
    802     m_activeScrollSnapIndexDidChange = true;
    803     snapState.m_currentState = newState;
    804     if (newState == ScrollSnapState::Gliding) {
    805         // Check if the other scroll axis needs to animate to the nearest snap point.
    806         snapState.m_initialScrollDelta = initialWheelDelta;
    807         snapState.m_shouldOverrideWheelEvent = true;
    808         snapState.clearInitialWheelDeltaWindow();
    809         ScrollEventAxis otherAxis = axis == ScrollEventAxis::Horizontal ? ScrollEventAxis::Vertical : ScrollEventAxis::Horizontal;
    810         if ((otherAxis == ScrollEventAxis::Horizontal && m_horizontalScrollSnapState && m_horizontalScrollSnapState->m_currentState == ScrollSnapState::UserInteraction)
    811             || (otherAxis == ScrollEventAxis::Vertical && m_verticalScrollSnapState && m_verticalScrollSnapState->m_currentState == ScrollSnapState::UserInteraction)) {
    812            
    813             ScrollSnapAnimatorState& otherState = scrollSnapPointState(otherAxis);
    814             if (!otherState.averageInitialWheelDelta()) {
    815                 float offsetOnOtherAxis = m_client.scrollOffsetOnAxis(otherAxis);
    816                 float snapOffsetForOtherAxis = scaleFactor * closestSnapOffset<LayoutUnit, float>(otherState.m_snapOffsets, offsetOnOtherAxis, 0, otherState.m_activeSnapIndex);
    817                 if (offsetOnOtherAxis != snapOffsetForOtherAxis) {
    818                     otherState.m_initialOffset = offsetOnOtherAxis;
    819                     otherState.m_targetOffset = snapOffsetForOtherAxis;
    820                     otherState.m_initialScrollDelta = 0;
    821                     otherState.m_currentState = ScrollSnapState::Gliding;
    822                 }
    823             }
    824         }
    825        
    826     } else {
    827         snapState.m_initialScrollDelta = initialWheelDelta;
    828     }
    829     initializeScrollSnapAnimationParameters();
    830     startScrollSnapTimer();
    831 }
    832 
    833 void ScrollController::endScrollSnapAnimation(ScrollSnapState newState)
    834 {
    835     ASSERT(newState == ScrollSnapState::DestinationReached || newState == ScrollSnapState::UserInteraction);
    836     if (m_horizontalScrollSnapState)
    837         m_horizontalScrollSnapState->m_currentState = newState;
    838 
    839     if (m_verticalScrollSnapState)
    840         m_verticalScrollSnapState->m_currentState = newState;
    841 
    842     stopScrollSnapTimer();
    843 }
    844 
    845 void ScrollController::initializeScrollSnapAnimationParameters()
    846 {
    847     if (!m_scrollSnapCurveState)
    848         m_scrollSnapCurveState = std::make_unique<ScrollSnapAnimationCurveState>();
    849    
    850     bool isSnappingOnHorizontalAxis = isSnappingOnAxis(ScrollEventAxis::Horizontal);
    851     bool isSnappingOnVerticalAxis = isSnappingOnAxis(ScrollEventAxis::Vertical);
    852     FloatSize initialVector(isSnappingOnHorizontalAxis ? m_horizontalScrollSnapState->m_initialOffset : m_client.scrollOffsetOnAxis(ScrollEventAxis::Horizontal),
    853         isSnappingOnVerticalAxis ? m_verticalScrollSnapState->m_initialOffset : m_client.scrollOffsetOnAxis(ScrollEventAxis::Vertical));
    854     FloatSize targetVector(isSnappingOnHorizontalAxis ? m_horizontalScrollSnapState->m_targetOffset : m_client.scrollOffsetOnAxis(ScrollEventAxis::Horizontal),
    855         isSnappingOnVerticalAxis ? m_verticalScrollSnapState->m_targetOffset : m_client.scrollOffsetOnAxis(ScrollEventAxis::Vertical));
    856     FloatSize initialDelta(isSnappingOnHorizontalAxis ? m_horizontalScrollSnapState->m_initialScrollDelta : 0,
    857         isSnappingOnVerticalAxis ? m_verticalScrollSnapState->m_initialScrollDelta : 0);
    858 
    859     // Animate directly by default. This flag will be changed as necessary if interpolation is possible.
    860     m_scrollSnapCurveState->shouldAnimateDirectlyToSnapPoint = true;
    861     m_scrollSnapCurveState->initializeSnapProgressCurve(initialVector, targetVector, initialDelta);
    862     if (isSnappingOnHorizontalAxis && isSnappingOnVerticalAxis)
    863         m_scrollSnapCurveState->initializeInterpolationCoefficientsIfNecessary(initialVector, targetVector, initialDelta);
    864 }
    865    
    866 bool ScrollController::isSnappingOnAxis(ScrollEventAxis axis) const
    867 {
    868     if (axis == ScrollEventAxis::Horizontal)
    869         return m_horizontalScrollSnapState && m_horizontalScrollSnapState->isSnapping();
    870 
    871     return m_verticalScrollSnapState && m_verticalScrollSnapState->isSnapping();
    872 }
    873    
    874 #endif
     690    if (!m_scrollSnapState)
     691        return;
     692
     693    setNearestScrollSnapIndexForAxisAndOffset(ScrollEventAxis::Horizontal, x);
     694    setNearestScrollSnapIndexForAxisAndOffset(ScrollEventAxis::Vertical, y);
     695}
    875696#endif
    876697
  • trunk/Source/WebCore/platform/cocoa/ScrollSnapAnimatorState.h

    r188641 r209070  
    3232#include "FloatPoint.h"
    3333#include "FloatSize.h"
    34 #include "LayoutUnit.h"
     34#include "LayoutPoint.h"
    3535#include "PlatformWheelEvent.h"
    3636#include "ScrollTypes.h"
     37#include "ScrollingMomentumCalculator.h"
    3738
    3839namespace WebCore {
     
    4546};
    4647
    47 struct ScrollSnapAnimatorState {
    48     ScrollSnapAnimatorState(ScrollEventAxis, const Vector<LayoutUnit>&);
     48class ScrollSnapAnimatorState {
     49public:
     50    Vector<LayoutUnit> snapOffsetsForAxis(ScrollEventAxis axis) const
     51    {
     52        return axis == ScrollEventAxis::Horizontal ? m_snapOffsetsX : m_snapOffsetsY;
     53    }
    4954
    50     void pushInitialWheelDelta(float);
    51     float averageInitialWheelDelta() const;
    52     void clearInitialWheelDeltaWindow();
    53     bool isSnapping() const;
    54     bool canReachTargetWithCurrentInitialScrollDelta() const;
    55     bool wheelDeltaTrackingIsInProgress() const;
    56     bool hasFinishedTrackingWheelDeltas() const;
    57     float interpolatedOffsetAtProgress(float) const;
    58    
    59     static const int wheelDeltaWindowSize = 3;
     55    void setSnapOffsetsForAxis(ScrollEventAxis axis, const Vector<LayoutUnit>& snapOffsets)
     56    {
     57        if (axis == ScrollEventAxis::Horizontal)
     58            m_snapOffsetsX = snapOffsets;
     59        else
     60            m_snapOffsetsY = snapOffsets;
     61    }
    6062
    61     Vector<LayoutUnit> m_snapOffsets;
    62     ScrollEventAxis m_axis;
    63     // Used to track both snapping and gliding behaviors.
    64     ScrollSnapState m_currentState;
    65     LayoutUnit m_initialOffset;
    66     LayoutUnit m_targetOffset;
    67     // Used to track gliding behavior.
    68     LayoutUnit m_beginTrackingWheelDeltaOffset;
    69     int m_numWheelDeltasTracked { 0 };
    70     unsigned m_activeSnapIndex { 0 };
    71     float m_wheelDeltaWindow[wheelDeltaWindowSize];
    72     float m_initialScrollDelta { 0 };
    73     bool m_shouldOverrideWheelEvent { false };
     63    ScrollSnapState currentState() const { return m_currentState; }
     64
     65    unsigned activeSnapIndexForAxis(ScrollEventAxis axis) const
     66    {
     67        return axis == ScrollEventAxis::Horizontal ? m_activeSnapIndexX : m_activeSnapIndexY;
     68    }
     69
     70    void setActiveSnapIndexForAxis(ScrollEventAxis axis, unsigned index)
     71    {
     72        if (axis == ScrollEventAxis::Horizontal)
     73            m_activeSnapIndexX = index;
     74        else
     75            m_activeSnapIndexY = index;
     76    }
     77
     78    FloatPoint currentAnimatedScrollOffset(bool& isAnimationComplete) const;
     79
     80    // State transition helpers.
     81    void transitionToSnapAnimationState(const FloatSize& contentSize, const FloatSize& viewportSize, float pageScale, const FloatPoint& initialOffset);
     82    void transitionToGlideAnimationState(const FloatSize& contentSize, const FloatSize& viewportSize, float pageScale, const FloatPoint& initialOffset, const FloatPoint& initialVelocity, const FloatSize& initialDelta);
     83    void transitionToUserInteractionState();
     84    void transitionToDestinationReachedState();
     85
     86private:
     87    float targetOffsetForStartOffset(ScrollEventAxis, float maxScrollOffset, float startOffset, float pageScale, float delta, unsigned& outActiveSnapIndex) const;
     88    void teardownAnimationForState(ScrollSnapState);
     89    void setupAnimationForState(ScrollSnapState, const FloatSize& contentSize, const FloatSize& viewportSize, float pageScale, const FloatPoint& initialOffset, const FloatPoint& initialVelocity, const FloatSize& initialDelta);
     90
     91    ScrollSnapState m_currentState { ScrollSnapState::UserInteraction };
     92
     93    Vector<LayoutUnit> m_snapOffsetsX;
     94    unsigned m_activeSnapIndexX { 0 };
     95    Vector<LayoutUnit> m_snapOffsetsY;
     96    unsigned m_activeSnapIndexY { 0 };
     97
     98    double m_startTime { 0 };
     99    std::unique_ptr<ScrollingMomentumCalculator> m_momentumCalculator;
    74100};
    75    
    76 /**
    77  * Stores state variables necessary to coordinate snapping animations between
    78  * horizontal and vertical axes.
    79  */
    80 struct ScrollSnapAnimationCurveState {
    81    
    82     void initializeSnapProgressCurve(const FloatSize&, const FloatSize&, const FloatSize&);
    83     void initializeInterpolationCoefficientsIfNecessary(const FloatSize&, const FloatSize&, const FloatSize&);
    84     FloatPoint interpolatedPositionAtProgress(float) const;
    85     bool shouldCompleteSnapAnimationImmediatelyAtTime(double) const;
    86     float animationProgressAtTime(double) const;
    87 
    88     bool shouldAnimateDirectlyToSnapPoint { false };
    89    
    90 private:
    91     double m_startTime { 0 };
    92     float m_snapAnimationCurveMagnitude { 0 };
    93     float m_snapAnimationDecayFactor { 0 };
    94     FloatSize m_snapAnimationCurveCoefficients[4] { };
    95 };
    96 
    97101
    98102} // namespace WebCore
  • trunk/Source/WebCore/platform/cocoa/ScrollSnapAnimatorState.mm

    r188641 r209070  
    2727#include "ScrollSnapAnimatorState.h"
    2828#include <wtf/CurrentTime.h>
     29#include <wtf/MathExtras.h>
    2930
    3031#if ENABLE(CSS_SCROLL_SNAP)
     
    3233namespace WebCore {
    3334
    34 ScrollSnapAnimatorState::ScrollSnapAnimatorState(ScrollEventAxis axis, const Vector<LayoutUnit>& snapOffsets)
    35     : m_snapOffsets(snapOffsets)
    36     , m_axis(axis)
    37     , m_currentState(ScrollSnapState::DestinationReached)
    38     , m_initialOffset(0)
    39     , m_targetOffset(0)
    40     , m_beginTrackingWheelDeltaOffset(0)
     35static const float inertialScrollPredictionFactor = 10;
     36static inline float projectedInertialScrollDistance(float initialWheelDelta)
    4137{
     38    return inertialScrollPredictionFactor * initialWheelDelta;
    4239}
    4340
    44 void ScrollSnapAnimatorState::pushInitialWheelDelta(float wheelDelta)
     41void ScrollSnapAnimatorState::transitionToSnapAnimationState(const FloatSize& contentSize, const FloatSize& viewportSize, float pageScale, const FloatPoint& initialOffset)
    4542{
    46     if (m_numWheelDeltasTracked < wheelDeltaWindowSize)
    47         m_wheelDeltaWindow[m_numWheelDeltasTracked++] = wheelDelta;
     43    setupAnimationForState(ScrollSnapState::Snapping, contentSize, viewportSize, pageScale, initialOffset, { }, { });
    4844}
    4945
    50 float ScrollSnapAnimatorState::averageInitialWheelDelta() const
     46void ScrollSnapAnimatorState::transitionToGlideAnimationState(const FloatSize& contentSize, const FloatSize& viewportSize, float pageScale, const FloatPoint& initialOffset, const FloatPoint& initialVelocity, const FloatSize& initialDelta)
    5147{
    52     if (!m_numWheelDeltasTracked)
    53         return 0;
     48    setupAnimationForState(ScrollSnapState::Gliding, contentSize, viewportSize, pageScale, initialOffset, initialVelocity, initialDelta);
     49}
    5450
    55     float sum = 0;
    56     int numZeroDeltas = 0;
    57     for (int i = 0; i < m_numWheelDeltasTracked; ++i) {
    58         sum += m_wheelDeltaWindow[i];
    59         if (!m_wheelDeltaWindow[i])
    60             numZeroDeltas++;
     51void ScrollSnapAnimatorState::setupAnimationForState(ScrollSnapState state, const FloatSize& contentSize, const FloatSize& viewportSize, float pageScale, const FloatPoint& initialOffset, const FloatPoint& initialVelocity, const FloatSize& initialDelta)
     52{
     53    ASSERT(state == ScrollSnapState::Snapping || state == ScrollSnapState::Gliding);
     54    if (m_currentState == state)
     55        return;
     56
     57    float targetOffsetX = targetOffsetForStartOffset(ScrollEventAxis::Horizontal, contentSize.width() - viewportSize.width(), initialOffset.x(), pageScale, initialDelta.width(), m_activeSnapIndexX);
     58    float targetOffsetY = targetOffsetForStartOffset(ScrollEventAxis::Vertical, contentSize.height() - viewportSize.height(), initialOffset.y(), pageScale, initialDelta.height(), m_activeSnapIndexY);
     59    m_momentumCalculator = ScrollingMomentumCalculator::create(viewportSize, contentSize, initialOffset, FloatPoint(targetOffsetX, targetOffsetY), initialDelta, initialVelocity);
     60    m_startTime = monotonicallyIncreasingTime();
     61    m_currentState = state;
     62}
     63
     64void ScrollSnapAnimatorState::transitionToUserInteractionState()
     65{
     66    teardownAnimationForState(ScrollSnapState::UserInteraction);
     67}
     68
     69void ScrollSnapAnimatorState::transitionToDestinationReachedState()
     70{
     71    teardownAnimationForState(ScrollSnapState::DestinationReached);
     72}
     73
     74void ScrollSnapAnimatorState::teardownAnimationForState(ScrollSnapState state)
     75{
     76    ASSERT(state == ScrollSnapState::UserInteraction || state == ScrollSnapState::DestinationReached);
     77    if (m_currentState == state)
     78        return;
     79
     80    m_momentumCalculator = nullptr;
     81    m_startTime = 0;
     82    m_currentState = state;
     83}
     84
     85FloatPoint ScrollSnapAnimatorState::currentAnimatedScrollOffset(bool& isAnimationComplete) const
     86{
     87    if (!m_momentumCalculator) {
     88        isAnimationComplete = true;
     89        return { };
    6190    }
    6291
    63     return m_numWheelDeltasTracked == numZeroDeltas ? 0 : sum / (m_numWheelDeltasTracked - numZeroDeltas);
     92    double elapsedTime = monotonicallyIncreasingTime() - m_startTime;
     93    isAnimationComplete = elapsedTime >= m_momentumCalculator->animationDuration();
     94    return m_momentumCalculator->scrollOffsetAfterElapsedTime(elapsedTime);
    6495}
    6596
    66 void ScrollSnapAnimatorState::clearInitialWheelDeltaWindow()
     97float ScrollSnapAnimatorState::targetOffsetForStartOffset(ScrollEventAxis axis, float maxScrollOffset, float startOffset, float pageScale, float initialDelta, unsigned& outActiveSnapIndex) const
    6798{
    68     for (int i = 0; i < m_numWheelDeltasTracked; ++i)
    69         m_wheelDeltaWindow[i] = 0;
     99    auto snapOffsets = snapOffsetsForAxis(axis);
     100    if (!snapOffsets.size()) {
     101        outActiveSnapIndex = 0;
     102        return clampTo<float>(startOffset, 0, maxScrollOffset);
     103    }
    70104
    71     m_numWheelDeltasTracked = 0;
    72 }
    73 
    74 bool ScrollSnapAnimatorState::isSnapping() const
    75 {
    76     return m_currentState == ScrollSnapState::Gliding || m_currentState == ScrollSnapState::Snapping;
    77 }
    78    
    79 bool ScrollSnapAnimatorState::canReachTargetWithCurrentInitialScrollDelta() const
    80 {
    81     if (m_initialOffset == m_targetOffset || !m_initialScrollDelta)
    82         return true;
    83    
    84     return m_initialOffset < m_targetOffset ? m_initialScrollDelta > 0 : m_initialScrollDelta < 0;
    85 }
    86    
    87 bool ScrollSnapAnimatorState::wheelDeltaTrackingIsInProgress() const
    88 {
    89     return m_numWheelDeltasTracked && m_numWheelDeltasTracked < wheelDeltaWindowSize;
    90 }
    91 
    92 bool ScrollSnapAnimatorState::hasFinishedTrackingWheelDeltas() const
    93 {
    94     return m_numWheelDeltasTracked == wheelDeltaWindowSize;
    95 }
    96 
    97 float ScrollSnapAnimatorState::interpolatedOffsetAtProgress(float progress) const
    98 {
    99     progress = std::max(0.0f, std::min(1.0f, progress));
    100     return m_initialOffset + progress * (m_targetOffset - m_initialOffset);
    101 }
    102    
    103 static const int maxNumScrollSnapParameterEstimationIterations = 10;
    104 static const float scrollSnapDecayFactorConvergenceThreshold = 0.001;
    105 static const float initialScrollSnapCurveMagnitude = 1.1;
    106 static const float minScrollSnapInitialProgress = 0.15;
    107 static const float maxScrollSnapInitialProgress = 0.5;
    108 static const double scrollSnapAnimationDuration = 0.5;
    109 
    110 /**
    111  * Computes and sets parameters required for tracking the progress of a snap animation curve, interpolated
    112  * or linear. The progress curve s(t) maps time t to progress s; both variables are in the interval [0, 1].
    113  * The time input t is 0 when the current time is the start of the animation, t = m_startTime, and 1 when the
    114  * current time is at or after the end of the animation, t = m_startTime + m_scrollSnapAnimationDuration.
    115  *
    116  * In this exponential progress model, s(t) = A - A * b^(-kt), where k = 60T is the number of frames in the
    117  * animation (assuming 60 FPS and an animation duration of T) and A, b are reals greater than or equal to 1.
    118  * Also note that we are given the initial progress, a value indicating the portion of the curve which our
    119  * initial scroll delta takes us. This is important when matching the initial speed of the animation to the
    120  * user's initial momentum scrolling speed. Let this initial progress amount equal v_0. I clamp this initial
    121  * progress amount to a minimum or maximum value.
    122  *
    123  * A is referred to as the curve magnitude, while b is referred to as the decay factor. We solve for A and b,
    124  * keeping the following constraints in mind:
    125  *     1. s(0) = 0
    126  *     2. s(1) = 1
    127  *     3. s(1/k) = v_0
    128  *
    129  * First, observe that s(0) = 0 holds for appropriate values of A, b. Solving for the remaining constraints
    130  * yields a nonlinear system of two equations. In lieu of a purely analytical solution, an alternating
    131  * optimization scheme is used to approximate A and b. This technique converges quickly (within 5 iterations
    132  * or so) for appropriate values of v_0. The optimization terminates early when the decay factor changes by
    133  * less than a threshold between one iteration and the next.
    134  */
    135 void ScrollSnapAnimationCurveState::initializeSnapProgressCurve(const FloatSize& initialVector, const FloatSize& targetVector, const FloatSize& initialDelta)
    136 {
    137     float initialProgress = std::max(minScrollSnapInitialProgress, std::min(initialDelta.diagonalLength() / (targetVector - initialVector).diagonalLength(), maxScrollSnapInitialProgress));
    138     float previousDecayFactor = 1.0f;
    139     m_snapAnimationCurveMagnitude = initialScrollSnapCurveMagnitude;
    140     for (int i = 0; i < maxNumScrollSnapParameterEstimationIterations; ++i) {
    141         m_snapAnimationDecayFactor = m_snapAnimationCurveMagnitude / (m_snapAnimationCurveMagnitude - initialProgress);
    142         m_snapAnimationCurveMagnitude = 1.0f / (1.0f - std::pow(m_snapAnimationDecayFactor, -60.0f * scrollSnapAnimationDuration));
    143         if (std::abs(m_snapAnimationDecayFactor - previousDecayFactor) < scrollSnapDecayFactorConvergenceThreshold)
    144             break;
    145        
    146         previousDecayFactor = m_snapAnimationDecayFactor;
    147     }
    148     m_startTime = monotonicallyIncreasingTime();
    149 }
    150 
    151 /**
    152  * Computes and sets coefficients required for interpolated snapping when scrolling in 2 dimensions, given
    153  * initial conditions (the initial and target vectors, along with the initial wheel delta as a vector). The
    154  * path is a cubic Bezier curve of the form p(s) = INITIAL + (C_1 * s) + (C_2 * s^2) + (C_3 * s^3) where each
    155  * C_i is a 2D vector and INITIAL is the vector representing the initial scroll offset. s is a real in the
    156  * interval [0, 1] indicating the "progress" of the curve (i.e. how much of the curve has been traveled).
    157  *
    158  * The curve has 4 control points, the first and last of which are the initial and target points, respectively.
    159  * The distances between adjacent control points are constrained to be the same, making the convex hull an
    160  * isosceles trapezoid with 3 sides of equal length. Additionally, the vector from the first control point to
    161  * the second points in the same direction as the initial scroll delta. These constraints ensure two properties:
    162  *     1. The direction of the snap animation at s=0 will be equal to the direction of the initial scroll delta.
    163  *     2. Points at regular intervals of s will be evenly spread out.
    164  *
    165  * If the initial scroll direction is orthogonal to or points in the opposite direction as the vector from the
    166  * initial point to the target point, initialization returns early and sets the curve to animate directly to the
    167  * snap point without interpolation.
    168  */
    169 void ScrollSnapAnimationCurveState::initializeInterpolationCoefficientsIfNecessary(const FloatSize& initialVector, const FloatSize& targetVector, const FloatSize& initialDelta)
    170 {
    171     FloatSize startToEndVector = targetVector - initialVector;
    172     float startToEndDistance = startToEndVector.diagonalLength();
    173     float initialDeltaMagnitude = initialDelta.diagonalLength();
    174     float cosTheta = initialDelta.isZero() ? 0 : (initialDelta.width() * startToEndVector.width() + initialDelta.height() * startToEndVector.height()) / (std::max(1.0f, initialDeltaMagnitude) * startToEndDistance);
    175     if (cosTheta <= 0)
    176         return;
    177    
    178     float sideLength = startToEndDistance / (2.0f * cosTheta + 1.0f);
    179     FloatSize controlVector1 = initialVector + sideLength * initialDelta / initialDeltaMagnitude;
    180     FloatSize controlVector2 = controlVector1 + (sideLength * startToEndVector / startToEndDistance);
    181     m_snapAnimationCurveCoefficients[0] = initialVector;
    182     m_snapAnimationCurveCoefficients[1] = 3 * (controlVector1 - initialVector);
    183     m_snapAnimationCurveCoefficients[2] = 3 * (initialVector - 2 * controlVector1 + controlVector2);
    184     m_snapAnimationCurveCoefficients[3] = 3 * (controlVector1 - controlVector2) - initialVector + targetVector;
    185     shouldAnimateDirectlyToSnapPoint = false;
    186 }
    187    
    188 FloatPoint ScrollSnapAnimationCurveState::interpolatedPositionAtProgress(float progress) const
    189 {
    190     ASSERT(!shouldAnimateDirectlyToSnapPoint);
    191     progress = std::max(0.0f, std::min(1.0f, progress));
    192     FloatPoint interpolatedPoint(0.0f, 0.0f);
    193     for (int i = 0; i < 4; ++i)
    194         interpolatedPoint += std::pow(progress, i) * m_snapAnimationCurveCoefficients[i];
    195    
    196     return interpolatedPoint;
    197 }
    198    
    199 bool ScrollSnapAnimationCurveState::shouldCompleteSnapAnimationImmediatelyAtTime(double time) const
    200 {
    201     return m_startTime + scrollSnapAnimationDuration < time;
    202 }
    203 
    204 float ScrollSnapAnimationCurveState::animationProgressAtTime(double time) const
    205 {
    206     float timeProgress = std::max(0.0, std::min(1.0, (time - m_startTime) / scrollSnapAnimationDuration));
    207     return std::min(1.0, m_snapAnimationCurveMagnitude * (1.0 - std::pow(m_snapAnimationDecayFactor, -60.0f * scrollSnapAnimationDuration * timeProgress)));
     105    float projectedDestination = (startOffset + projectedInertialScrollDistance(initialDelta)) / pageScale;
     106    float targetOffset = closestSnapOffset<LayoutUnit, float>(snapOffsets, projectedDestination, initialDelta, outActiveSnapIndex);
     107    targetOffset = clampTo<float>(targetOffset, snapOffsets.first(), snapOffsets.last());
     108    targetOffset = clampTo<float>(targetOffset, 0, maxScrollOffset);
     109    return pageScale * targetOffset;
    208110}
    209111   
  • trunk/Source/WebKit2/ChangeLog

    r209067 r209070  
     12016-11-29  Wenson Hsieh  <wenson_hsieh@apple.com>
     2
     3        Scroll snapping on Mac should use AppKit animations
     4        https://bugs.webkit.org/show_bug.cgi?id=147261
     5        <rdar://problem/29395293>
     6
     7        Reviewed by Brent Fulgham.
     8
     9        Add some logic to plumb filtered wheel velocity over to WebCore in the case of mainframe scrolling. See
     10        WebCore/ChangeLog for more details.
     11
     12        * WebProcess/WebPage/EventDispatcher.cpp:
     13        (WebKit::EventDispatcher::wheelEvent):
     14
    1152016-11-21  Brian Burg  <bburg@apple.com>
    216
  • trunk/Source/WebKit2/WebProcess/WebPage/EventDispatcher.cpp

    r208499 r209070  
    108108        m_recentWheelEventDeltaFilter->updateFromDelta(FloatSize(platformWheelEvent.deltaX(), platformWheelEvent.deltaY()));
    109109        FloatSize filteredDelta = m_recentWheelEventDeltaFilter->filteredDelta();
    110         platformWheelEvent = platformWheelEvent.copyWithDeltas(filteredDelta.width(), filteredDelta.height());
     110        platformWheelEvent = platformWheelEvent.copyWithDeltasAndVelocity(filteredDelta.width(), filteredDelta.height(), m_recentWheelEventDeltaFilter->filteredVelocity());
    111111    }
    112112#endif
Note: See TracChangeset for help on using the changeset viewer.