| /* |
| * Copyright (C) 2011-2017 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
| * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
| * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
| * THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #import "config.h" |
| #import "ScrollingEffectsController.h" |
| |
| #import "Logging.h" |
| #import "PlatformWheelEvent.h" |
| #import "ScrollAnimationRubberBand.h" |
| #import "ScrollExtents.h" |
| #import "ScrollableArea.h" |
| #import <pal/spi/mac/NSScrollViewSPI.h> |
| #import <sys/sysctl.h> |
| #import <sys/time.h> |
| #import <wtf/SystemTracing.h> |
| #import <wtf/text/TextStream.h> |
| |
| #if PLATFORM(MAC) |
| |
| namespace WebCore { |
| |
| static const Seconds scrollVelocityZeroingTimeout = 100_ms; |
| static const float rubberbandDirectionLockStretchRatio = 1; |
| static const float rubberbandMinimumRequiredDeltaBeforeStretch = 10; |
| |
| static float elasticDeltaForReboundDelta(float delta) |
| { |
| return _NSElasticDeltaForReboundDelta(delta); |
| } |
| |
| static float reboundDeltaForElasticDelta(float delta) |
| { |
| return _NSReboundDeltaForElasticDelta(delta); |
| } |
| |
| static float scrollWheelMultiplier() |
| { |
| static float multiplier = -1; |
| if (multiplier < 0) { |
| multiplier = [[NSUserDefaults standardUserDefaults] floatForKey:@"NSScrollWheelMultiplier"]; |
| if (multiplier <= 0) |
| multiplier = 1; |
| } |
| return multiplier; |
| } |
| |
| ScrollingEffectsController::~ScrollingEffectsController() |
| { |
| ASSERT(m_timersWereStopped); |
| } |
| |
| void ScrollingEffectsController::stopAllTimers() |
| { |
| if (m_statelessSnapTransitionTimer) |
| m_statelessSnapTransitionTimer->stop(); |
| |
| #if ASSERT_ENABLED |
| m_timersWereStopped = true; |
| #endif |
| } |
| |
| static ScrollEventAxis dominantAxisFavoringVertical(FloatSize delta) |
| { |
| if (fabsf(delta.height()) >= fabsf(delta.width())) |
| return ScrollEventAxis::Vertical; |
| |
| return ScrollEventAxis::Horizontal; |
| } |
| |
| static FloatSize deltaAlignedToAxis(FloatSize delta, ScrollEventAxis axis) |
| { |
| switch (axis) { |
| case ScrollEventAxis::Horizontal: return FloatSize { delta.width(), 0 }; |
| case ScrollEventAxis::Vertical: return FloatSize { 0, delta.height() }; |
| } |
| |
| return { }; |
| } |
| |
| static FloatSize deltaAlignedToDominantAxis(FloatSize delta) |
| { |
| auto dominantAxis = dominantAxisFavoringVertical(delta); |
| return deltaAlignedToAxis(delta, dominantAxis); |
| } |
| |
| static std::optional<BoxSide> affectedSideOnDominantAxis(FloatSize delta) |
| { |
| auto dominantAxis = dominantAxisFavoringVertical(delta); |
| return ScrollableArea::targetSideForScrollDelta(delta, dominantAxis); |
| } |
| |
| bool ScrollingEffectsController::handleWheelEvent(const PlatformWheelEvent& wheelEvent) |
| { |
| if (processWheelEventForScrollSnap(wheelEvent)) |
| return true; |
| |
| if (wheelEvent.phase() == PlatformWheelEventPhase::MayBegin || wheelEvent.phase() == PlatformWheelEventPhase::Cancelled) |
| return false; |
| |
| if (wheelEvent.phase() == PlatformWheelEventPhase::Began) { |
| // FIXME: Trying to decide if a gesture is horizontal or vertical at the "began" phase is very error-prone. |
| auto horizontalSide = ScrollableArea::targetSideForScrollDelta(-wheelEvent.delta(), ScrollEventAxis::Horizontal); |
| if (horizontalSide && m_client.isPinnedOnSide(*horizontalSide) && !shouldRubberBandOnSide(*horizontalSide)) |
| return false; |
| |
| auto verticalSide = ScrollableArea::targetSideForScrollDelta(-wheelEvent.delta(), ScrollEventAxis::Vertical); |
| if (verticalSide && m_client.isPinnedOnSide(*verticalSide) && !shouldRubberBandOnSide(*verticalSide)) |
| return false; |
| |
| m_momentumScrollInProgress = false; |
| m_ignoreMomentumScrolls = false; |
| m_lastMomentumScrollTimestamp = { }; |
| m_momentumVelocity = { }; |
| |
| IntSize stretchAmount = m_client.stretchAmount(); |
| m_stretchScrollForce.setWidth(reboundDeltaForElasticDelta(stretchAmount.width())); |
| m_stretchScrollForce.setHeight(reboundDeltaForElasticDelta(stretchAmount.height())); |
| m_unappliedOverscrollDelta = { }; |
| |
| stopRubberBandAnimation(); |
| updateRubberBandingState(); |
| } |
| |
| if (wheelEvent.phase() == PlatformWheelEventPhase::Ended) { |
| // FIXME: This triggers the rubberband timer even when we don't start rubberbanding. |
| startRubberBandAnimationIfNecessary(); |
| updateRubberBandingState(); |
| return true; |
| } |
| |
| bool isMomentumScrollEvent = (wheelEvent.momentumPhase() != PlatformWheelEventPhase::None); |
| if (m_ignoreMomentumScrolls && (isMomentumScrollEvent || m_isAnimatingRubberBand)) { |
| if (wheelEvent.momentumPhase() == PlatformWheelEventPhase::Ended) { |
| m_ignoreMomentumScrolls = false; |
| return true; |
| } |
| |
| if (isMomentumScrollEvent && m_ignoreMomentumScrolls) |
| return true; |
| |
| return false; |
| } |
| |
| IntSize stretchAmount = m_client.stretchAmount(); |
| bool isVerticallyStretched = stretchAmount.height(); |
| bool isHorizontallyStretched = stretchAmount.width(); |
| |
| // Much of this code, including this use of unaccelerated deltas when stretched, is based on AppKit behavior. |
| auto eventDelta = (isVerticallyStretched || isHorizontallyStretched) ? -wheelEvent.unacceleratedScrollingDelta() : -wheelEvent.delta(); |
| |
| // Reset unapplied overscroll because we may decide to remove delta at various points and put it into this value. |
| auto delta = std::exchange(m_unappliedOverscrollDelta, { }); |
| delta += eventDelta; |
| |
| if (wheelEvent.isGestureEvent()) { |
| // FIXME: This replicates what WheelEventDeltaFilter does. We should apply that to events in all phases, and remove axis locking here (webkit.org/b/231207). |
| delta = deltaAlignedToDominantAxis(delta); |
| } |
| |
| auto momentumPhase = wheelEvent.momentumPhase(); |
| |
| if (momentumPhase == PlatformWheelEventPhase::Began) |
| WTFBeginAnimationSignpostIntervalAlways("Momentum scroll", ""); |
| |
| if (!m_momentumScrollInProgress && (momentumPhase == PlatformWheelEventPhase::Began || momentumPhase == PlatformWheelEventPhase::Changed)) |
| m_momentumScrollInProgress = true; |
| |
| bool shouldStretch = false; |
| auto timeDelta = wheelEvent.timestamp() - m_lastMomentumScrollTimestamp; |
| if (m_inScrollGesture || m_momentumScrollInProgress) { |
| if (m_lastMomentumScrollTimestamp && timeDelta > 0_s && timeDelta < scrollVelocityZeroingTimeout) { |
| m_momentumVelocity = eventDelta / timeDelta.seconds(); |
| m_lastMomentumScrollTimestamp = wheelEvent.timestamp(); |
| } else { |
| m_lastMomentumScrollTimestamp = wheelEvent.timestamp(); |
| m_momentumVelocity = { }; |
| } |
| |
| shouldStretch = modifyScrollDeltaForStretching(wheelEvent, delta, isHorizontallyStretched, isVerticallyStretched); |
| } |
| |
| bool handled = true; |
| |
| if (!delta.isZero()) { |
| if (shouldStretch || isVerticallyStretched || isHorizontallyStretched) { |
| if (delta.width() && !m_client.allowsHorizontalStretching(wheelEvent)) |
| handled = false; |
| |
| if (delta.height() && !m_client.allowsVerticalStretching(wheelEvent)) |
| handled = false; |
| |
| bool canStartAnimation = applyScrollDeltaWithStretching(wheelEvent, delta, isHorizontallyStretched, isVerticallyStretched); |
| if (m_momentumScrollInProgress) { |
| if (canStartAnimation && m_lastMomentumScrollTimestamp) { |
| m_ignoreMomentumScrolls = true; |
| m_momentumScrollInProgress = false; |
| startRubberBandAnimationIfNecessary(); |
| } |
| } |
| } else { |
| delta.scale(scrollWheelMultiplier()); |
| m_client.immediateScrollBy(delta); |
| } |
| } |
| |
| if (m_momentumScrollInProgress && momentumPhase == PlatformWheelEventPhase::Ended) { |
| WTFEndSignpostIntervalAlways("Momentum scroll", ""); |
| m_momentumScrollInProgress = false; |
| m_ignoreMomentumScrolls = false; |
| m_lastMomentumScrollTimestamp = { }; |
| } |
| |
| updateRubberBandingState(); |
| |
| return handled; |
| } |
| |
| bool ScrollingEffectsController::modifyScrollDeltaForStretching(const PlatformWheelEvent& wheelEvent, FloatSize& delta, bool isHorizontallyStretched, bool isVerticallyStretched) |
| { |
| auto affectedSide = affectedSideOnDominantAxis(delta); |
| if (isVerticallyStretched) { |
| if (!isHorizontallyStretched && affectedSide && m_client.isPinnedOnSide(*affectedSide)) { |
| // Stretching only in the vertical. |
| if (delta.height() && (fabsf(delta.width() / delta.height()) < rubberbandDirectionLockStretchRatio)) |
| delta.setWidth(0); |
| else if (fabsf(delta.width()) < rubberbandMinimumRequiredDeltaBeforeStretch) { |
| m_unappliedOverscrollDelta.expand(delta.width(), 0); |
| delta.setWidth(0); |
| } else |
| m_unappliedOverscrollDelta.expand(delta.width(), 0); |
| } |
| |
| return false; |
| } |
| |
| if (isHorizontallyStretched) { |
| // Stretching only in the horizontal. |
| if (affectedSide && m_client.isPinnedOnSide(*affectedSide)) { |
| if (delta.width() && (fabsf(delta.height() / delta.width()) < rubberbandDirectionLockStretchRatio)) |
| delta.setHeight(0); |
| else if (fabsf(delta.height()) < rubberbandMinimumRequiredDeltaBeforeStretch) { |
| m_unappliedOverscrollDelta.expand(0, delta.height()); |
| delta.setHeight(0); |
| } else |
| m_unappliedOverscrollDelta.expand(0, delta.height()); |
| } |
| |
| return false; |
| } |
| |
| // Not stretching at all yet. |
| if (affectedSide && m_client.isPinnedOnSide(*affectedSide)) { |
| if (fabsf(delta.height()) >= fabsf(delta.width())) { |
| if (fabsf(delta.width()) < rubberbandMinimumRequiredDeltaBeforeStretch) { |
| m_unappliedOverscrollDelta.expand(delta.width(), 0); |
| delta.setWidth(0); |
| } else |
| m_unappliedOverscrollDelta.expand(delta.width(), 0); |
| } |
| |
| if (!m_client.allowsHorizontalStretching(wheelEvent)) |
| delta.setWidth(0); |
| |
| if (!m_client.allowsVerticalStretching(wheelEvent)) |
| delta.setHeight(0); |
| |
| return !delta.isZero(); |
| } |
| |
| return false; |
| } |
| |
| bool ScrollingEffectsController::applyScrollDeltaWithStretching(const PlatformWheelEvent& wheelEvent, FloatSize delta, bool isHorizontallyStretched, bool isVerticallyStretched) |
| { |
| auto eventDelta = (isVerticallyStretched || isHorizontallyStretched) ? -wheelEvent.unacceleratedScrollingDelta() : -wheelEvent.delta(); |
| auto affectedSide = affectedSideOnDominantAxis(delta); |
| |
| FloatSize deltaToScroll; |
| |
| if (delta.width()) { |
| if (!m_client.allowsHorizontalStretching(wheelEvent)) { |
| delta.setWidth(0); |
| eventDelta.setWidth(0); |
| } else if (!isHorizontallyStretched && !m_client.isPinnedOnSide(*affectedSide)) { |
| delta.scale(scrollWheelMultiplier(), 1); |
| deltaToScroll += FloatSize { delta.width(), 0 }; |
| delta.setWidth(0); |
| } |
| } |
| |
| if (delta.height()) { |
| if (!m_client.allowsVerticalStretching(wheelEvent)) { |
| delta.setHeight(0); |
| eventDelta.setHeight(0); |
| } else if (!isVerticallyStretched && !m_client.isPinnedOnSide(*affectedSide)) { |
| delta.scale(1, scrollWheelMultiplier()); |
| deltaToScroll += FloatSize { 0, delta.height() }; |
| delta.setHeight(0); |
| } |
| } |
| |
| if (!deltaToScroll.isZero()) |
| m_client.immediateScrollBy(deltaToScroll, ScrollClamping::Unclamped); |
| |
| bool canStartAnimation = false; |
| if (m_momentumScrollInProgress) { |
| // Compute canStartAnimation, which looks at isPinnedOnSide(), before applying the stretch delta. |
| auto sideAffectedByEventDelta = affectedSideOnDominantAxis(eventDelta); |
| canStartAnimation = !sideAffectedByEventDelta || m_client.isPinnedOnSide(*sideAffectedByEventDelta); |
| } |
| |
| m_stretchScrollForce += delta; |
| auto dampedDelta = FloatSize { |
| ceilf(elasticDeltaForReboundDelta(m_stretchScrollForce.width())), |
| ceilf(elasticDeltaForReboundDelta(m_stretchScrollForce.height())) |
| }; |
| |
| LOG_WITH_STREAM(ScrollAnimations, stream << "ScrollingEffectsController::applyScrollDeltaWithStretching() - stretchScrollForce " << m_stretchScrollForce << " move delta " << delta << " dampedDelta " << dampedDelta); |
| |
| auto stretchAmount = m_client.stretchAmount(); |
| m_client.immediateScrollBy(dampedDelta - stretchAmount, ScrollClamping::Unclamped); |
| |
| return canStartAnimation; |
| } |
| |
| FloatSize ScrollingEffectsController::wheelDeltaBiasingTowardsVertical(const FloatSize& wheelDelta) |
| { |
| return deltaAlignedToDominantAxis(wheelDelta); |
| } |
| |
| void ScrollingEffectsController::updateRubberBandAnimatingState() |
| { |
| if (!m_isAnimatingRubberBand) |
| return; |
| |
| if (isScrollSnapInProgress()) |
| return; |
| |
| if (m_momentumScrollInProgress && !m_ignoreMomentumScrolls) { |
| LOG_WITH_STREAM(ScrollAnimations, stream << "ScrollingEffectsController::updateRubberBandAnimatingState() - not animating, momentumScrollInProgress " << m_momentumScrollInProgress << " ignoreMomentumScrolls " << m_ignoreMomentumScrolls); |
| if (!isRubberBandInProgressInternal()) |
| stopRubberBandAnimation(); |
| } |
| |
| updateRubberBandingState(); |
| } |
| |
| void ScrollingEffectsController::scrollPositionChanged() |
| { |
| updateRubberBandingState(); |
| } |
| |
| bool ScrollingEffectsController::isUserScrollInProgress() const |
| { |
| return m_inScrollGesture; |
| } |
| |
| bool ScrollingEffectsController::isRubberBandInProgress() const |
| { |
| return m_isRubberBanding; |
| } |
| |
| bool ScrollingEffectsController::isScrollSnapInProgress() const |
| { |
| if (!usesScrollSnap()) |
| return false; |
| |
| if (m_inScrollGesture || m_momentumScrollInProgress || m_isAnimatingScrollSnap) |
| return true; |
| |
| return false; |
| } |
| |
| void ScrollingEffectsController::stopRubberBanding() |
| { |
| stopRubberBandAnimation(); |
| updateRubberBandingState(); |
| } |
| |
| bool ScrollingEffectsController::startRubberBandAnimation(const FloatSize& initialVelocity, const FloatSize& initialOverscroll) |
| { |
| if (m_currentAnimation) |
| m_currentAnimation->stop(); |
| |
| m_currentAnimation = makeUnique<ScrollAnimationRubberBand>(*this); |
| bool started = downcast<ScrollAnimationRubberBand>(*m_currentAnimation).startRubberBandAnimation(initialVelocity, initialOverscroll); |
| LOG_WITH_STREAM(ScrollAnimations, stream << "ScrollingEffectsController::startRubberBandAnimation() - animation " << *m_currentAnimation << " started " << started); |
| return started; |
| } |
| |
| void ScrollingEffectsController::stopRubberBandAnimation() |
| { |
| if (m_isAnimatingRubberBand) |
| stopAnimatedScroll(); |
| |
| m_stretchScrollForce = { }; |
| } |
| |
| void ScrollingEffectsController::willStartRubberBandAnimation() |
| { |
| m_isAnimatingRubberBand = true; |
| m_client.willStartRubberBandAnimation(); |
| m_client.deferWheelEventTestCompletionForReason(reinterpret_cast<WheelEventTestMonitor::ScrollableAreaIdentifier>(this), WheelEventTestMonitor::RubberbandInProgress); |
| } |
| |
| void ScrollingEffectsController::didStopRubberBandAnimation() |
| { |
| m_isAnimatingRubberBand = false; |
| m_client.didStopRubberBandAnimation(); |
| m_client.removeWheelEventTestCompletionDeferralForReason(reinterpret_cast<WheelEventTestMonitor::ScrollableAreaIdentifier>(this), WheelEventTestMonitor::RubberbandInProgress); |
| } |
| |
| void ScrollingEffectsController::startRubberBandAnimationIfNecessary() |
| { |
| auto timeDelta = WallTime::now() - m_lastMomentumScrollTimestamp; |
| if (m_lastMomentumScrollTimestamp && timeDelta >= scrollVelocityZeroingTimeout) |
| m_momentumVelocity = { }; |
| |
| if (m_isAnimatingRubberBand) |
| return; |
| |
| auto extents = m_client.scrollExtents(); |
| auto targetOffset = m_client.scrollOffset(); |
| auto contrainedOffset = targetOffset.constrainedBetween(extents.minimumScrollOffset(), extents.maximumScrollOffset()); |
| bool willOverscroll = targetOffset != contrainedOffset; |
| |
| auto stretchAmount = m_client.stretchAmount(); |
| if (stretchAmount.isZero() && !willOverscroll && m_stretchScrollForce.isZero()) |
| return; |
| |
| auto initialVelocity = m_momentumVelocity; |
| |
| // Just like normal scrolling, prefer vertical rubberbanding |
| if (fabsf(initialVelocity.height()) >= fabsf(initialVelocity.width())) |
| initialVelocity.setWidth(0); |
| |
| // Don't rubber-band horizontally if it's not possible to scroll horizontally |
| if (!m_client.allowsHorizontalScrolling()) |
| initialVelocity.setWidth(0); |
| |
| // Don't rubber-band vertically if it's not possible to scroll vertically |
| if (!m_client.allowsVerticalScrolling()) |
| initialVelocity.setHeight(0); |
| |
| LOG_WITH_STREAM(ScrollAnimations, stream << "ScrollingEffectsController::startRubberBandAnimationIfNecessary() - rubberBandAnimationRunning " << m_isAnimatingRubberBand << " stretchAmount " << stretchAmount << " initialVelocity " << initialVelocity); |
| startRubberBandAnimation(initialVelocity, stretchAmount); |
| } |
| |
| bool ScrollingEffectsController::shouldRubberBandOnSide(BoxSide side) const |
| { |
| return m_client.shouldRubberBandOnSide(side); |
| } |
| |
| bool ScrollingEffectsController::isRubberBandInProgressInternal() const |
| { |
| if (!m_inScrollGesture && !m_momentumScrollInProgress && !m_isAnimatingRubberBand) |
| return false; |
| |
| return !m_client.stretchAmount().isZero(); |
| } |
| |
| void ScrollingEffectsController::updateRubberBandingState() |
| { |
| bool isRubberBanding = isRubberBandInProgressInternal(); |
| if (isRubberBanding == m_isRubberBanding) |
| return; |
| |
| LOG_WITH_STREAM(ScrollAnimations, stream << "ScrollingEffectsController " << this << " updateRubberBandingState - isRubberBanding " << isRubberBanding); |
| |
| m_isRubberBanding = isRubberBanding; |
| if (m_isRubberBanding) |
| updateRubberBandingEdges(m_client.stretchAmount()); |
| else |
| m_rubberBandingEdges = { }; |
| |
| m_client.rubberBandingStateChanged(m_isRubberBanding); |
| } |
| |
| void ScrollingEffectsController::updateRubberBandingEdges(IntSize clientStretch) |
| { |
| m_rubberBandingEdges.setLeft(clientStretch.width() < 0); |
| m_rubberBandingEdges.setRight(clientStretch.width() > 0); |
| |
| m_rubberBandingEdges.setTop(clientStretch.height() < 0); |
| m_rubberBandingEdges.setBottom(clientStretch.height() > 0); |
| } |
| |
| enum class WheelEventStatus { |
| UserScrollBegin, |
| UserScrolling, |
| UserScrollEnd, |
| MomentumScrollBegin, |
| MomentumScrolling, |
| MomentumScrollEnd, |
| StatelessScrollEvent, |
| Unknown |
| }; |
| |
| static inline WheelEventStatus toWheelEventStatus(PlatformWheelEventPhase phase, PlatformWheelEventPhase momentumPhase) |
| { |
| if (phase == PlatformWheelEventPhase::None) { |
| switch (momentumPhase) { |
| case PlatformWheelEventPhase::Began: |
| return WheelEventStatus::MomentumScrollBegin; |
| |
| case PlatformWheelEventPhase::Changed: |
| return WheelEventStatus::MomentumScrolling; |
| |
| case PlatformWheelEventPhase::Ended: |
| return WheelEventStatus::MomentumScrollEnd; |
| |
| case PlatformWheelEventPhase::None: |
| return WheelEventStatus::StatelessScrollEvent; |
| |
| default: |
| return WheelEventStatus::Unknown; |
| } |
| } |
| if (momentumPhase == PlatformWheelEventPhase::None) { |
| switch (phase) { |
| case PlatformWheelEventPhase::Began: |
| case PlatformWheelEventPhase::MayBegin: |
| return WheelEventStatus::UserScrollBegin; |
| |
| case PlatformWheelEventPhase::Changed: |
| return WheelEventStatus::UserScrolling; |
| |
| case PlatformWheelEventPhase::Ended: |
| case PlatformWheelEventPhase::Cancelled: |
| return WheelEventStatus::UserScrollEnd; |
| |
| default: |
| return WheelEventStatus::Unknown; |
| } |
| } |
| return WheelEventStatus::Unknown; |
| } |
| |
| #if !LOG_DISABLED |
| static TextStream& operator<<(TextStream& ts, WheelEventStatus status) |
| { |
| switch (status) { |
| case WheelEventStatus::UserScrollBegin: ts << "UserScrollBegin"; break; |
| case WheelEventStatus::UserScrolling: ts << "UserScrolling"; break; |
| case WheelEventStatus::UserScrollEnd: ts << "UserScrollEnd"; break; |
| case WheelEventStatus::MomentumScrollBegin: ts << "MomentumScrollBegin"; break; |
| case WheelEventStatus::MomentumScrolling: ts << "MomentumScrolling"; break; |
| case WheelEventStatus::MomentumScrollEnd: ts << "MomentumScrollEnd"; break; |
| case WheelEventStatus::StatelessScrollEvent: ts << "StatelessScrollEvent"; break; |
| case WheelEventStatus::Unknown: ts << "Unknown"; break; |
| } |
| return ts; |
| } |
| #endif |
| |
| bool ScrollingEffectsController::shouldOverrideMomentumScrolling() const |
| { |
| if (!usesScrollSnap()) |
| return false; |
| |
| ScrollSnapState scrollSnapState = m_scrollSnapState->currentState(); |
| return scrollSnapState == ScrollSnapState::Gliding || scrollSnapState == ScrollSnapState::DestinationReached; |
| } |
| |
| void ScrollingEffectsController::scheduleStatelessScrollSnap() |
| { |
| stopScrollSnapAnimation(); |
| if (m_statelessSnapTransitionTimer) { |
| m_statelessSnapTransitionTimer->stop(); |
| m_statelessSnapTransitionTimer = nullptr; |
| } |
| if (!usesScrollSnap()) |
| return; |
| |
| static const Seconds statelessScrollSnapDelay = 750_ms; |
| m_statelessSnapTransitionTimer = m_client.createTimer([this] { |
| statelessSnapTransitionTimerFired(); |
| }); |
| m_statelessSnapTransitionTimer->startOneShot(statelessScrollSnapDelay); |
| startDeferringWheelEventTestCompletion(WheelEventTestMonitor::ScrollSnapInProgress); |
| } |
| |
| void ScrollingEffectsController::statelessSnapTransitionTimerFired() |
| { |
| m_statelessSnapTransitionTimer = nullptr; |
| |
| if (!usesScrollSnap()) |
| return; |
| |
| if (m_scrollSnapState->transitionToSnapAnimationState(m_client.scrollExtents(), m_client.pageScaleFactor(), m_client.scrollOffset())) |
| startScrollSnapAnimation(); |
| else |
| stopDeferringWheelEventTestCompletion(WheelEventTestMonitor::ScrollSnapInProgress); |
| } |
| |
| bool ScrollingEffectsController::processWheelEventForScrollSnap(const PlatformWheelEvent& wheelEvent) |
| { |
| if (!usesScrollSnap()) |
| return false; |
| |
| if (m_scrollSnapState->snapOffsetsForAxis(ScrollEventAxis::Horizontal).isEmpty() && m_scrollSnapState->snapOffsetsForAxis(ScrollEventAxis::Vertical).isEmpty()) |
| return false; |
| |
| auto status = toWheelEventStatus(wheelEvent.phase(), wheelEvent.momentumPhase()); |
| |
| LOG_WITH_STREAM(ScrollSnap, stream << "ScrollingEffectsController " << this << " processWheelEventForScrollSnap: status " << status); |
| |
| bool isMomentumScrolling = false; |
| switch (status) { |
| case WheelEventStatus::UserScrollBegin: |
| case WheelEventStatus::UserScrolling: |
| stopScrollSnapAnimation(); |
| m_scrollSnapState->transitionToUserInteractionState(); |
| m_scrollingVelocityForScrollSnap = -wheelEvent.scrollingVelocity(); |
| break; |
| case WheelEventStatus::UserScrollEnd: |
| if (m_scrollSnapState->transitionToSnapAnimationState(m_client.scrollExtents(), m_client.pageScaleFactor(), m_client.scrollOffset())) |
| startScrollSnapAnimation(); |
| break; |
| case WheelEventStatus::MomentumScrollBegin: |
| if (m_scrollSnapState->transitionToGlideAnimationState(m_client.scrollExtents(), m_client.pageScaleFactor(), m_client.scrollOffset(), m_scrollingVelocityForScrollSnap, -wheelEvent.delta())) { |
| startScrollSnapAnimation(); |
| isMomentumScrolling = true; |
| } |
| m_scrollingVelocityForScrollSnap = { }; |
| break; |
| case WheelEventStatus::MomentumScrolling: |
| case WheelEventStatus::MomentumScrollEnd: |
| isMomentumScrolling = true; |
| break; |
| case WheelEventStatus::StatelessScrollEvent: |
| m_scrollSnapState->transitionToUserInteractionState(); |
| scheduleStatelessScrollSnap(); |
| break; |
| case WheelEventStatus::Unknown: |
| ASSERT_NOT_REACHED(); |
| break; |
| } |
| |
| return isMomentumScrolling && shouldOverrideMomentumScrolling(); |
| } |
| |
| void ScrollingEffectsController::updateGestureInProgressState(const PlatformWheelEvent& wheelEvent) |
| { |
| if (wheelEvent.isGestureStart() || wheelEvent.isTransitioningToMomentumScroll()) |
| m_inScrollGesture = true; |
| else if (wheelEvent.isEndOfNonMomentumScroll() || wheelEvent.isGestureCancel() || wheelEvent.isEndOfMomentumScroll()) |
| m_inScrollGesture = false; |
| |
| updateRubberBandingState(); |
| } |
| |
| } // namespace WebCore |
| |
| #endif // PLATFORM(MAC) |