| /* |
| * Copyright (c) 2011, Google 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: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * 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. |
| * * Neither the name of Google Inc. nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT |
| * OWNER OR 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. |
| */ |
| |
| #include "platform/scroll/ScrollAnimator.h" |
| |
| #include "cc/animation/scroll_offset_animation_curve.h" |
| #include "platform/TraceEvent.h" |
| #include "platform/animation/CompositorAnimation.h" |
| #include "platform/graphics/CompositorFactory.h" |
| #include "platform/graphics/GraphicsLayer.h" |
| #include "platform/scroll/MainThreadScrollingReason.h" |
| #include "platform/scroll/ScrollableArea.h" |
| #include "public/platform/Platform.h" |
| #include "public/platform/WebCompositorSupport.h" |
| #include "wtf/CurrentTime.h" |
| #include "wtf/PassRefPtr.h" |
| |
| namespace blink { |
| |
| namespace { |
| |
| WebLayer* toWebLayer(GraphicsLayer* layer) |
| { |
| return layer ? layer->platformLayer() : nullptr; |
| } |
| |
| } // namespace |
| |
| PassOwnPtrWillBeRawPtr<ScrollAnimatorBase> ScrollAnimatorBase::create(ScrollableArea* scrollableArea) |
| { |
| if (scrollableArea && scrollableArea->scrollAnimatorEnabled()) |
| return adoptPtrWillBeNoop(new ScrollAnimator(scrollableArea)); |
| return adoptPtrWillBeNoop(new ScrollAnimatorBase(scrollableArea)); |
| } |
| |
| ScrollAnimator::ScrollAnimator(ScrollableArea* scrollableArea, WTF::TimeFunction timeFunction) |
| : ScrollAnimatorBase(scrollableArea) |
| , m_timeFunction(timeFunction) |
| , m_lastGranularity(ScrollByPixel) |
| { |
| } |
| |
| ScrollAnimator::~ScrollAnimator() |
| { |
| } |
| |
| FloatPoint ScrollAnimator::desiredTargetPosition() const |
| { |
| return (m_animationCurve || m_runState == RunState::WaitingToSendToCompositor) |
| ? m_targetOffset : currentPosition(); |
| } |
| |
| bool ScrollAnimator::hasRunningAnimation() const |
| { |
| return (m_animationCurve || m_runState == RunState::WaitingToSendToCompositor); |
| } |
| |
| FloatSize ScrollAnimator::computeDeltaToConsume(const FloatSize& delta) const |
| { |
| FloatPoint pos = desiredTargetPosition(); |
| FloatPoint newPos = toFloatPoint(m_scrollableArea->clampScrollPosition(pos + delta)); |
| return newPos - pos; |
| } |
| |
| void ScrollAnimator::resetAnimationState() |
| { |
| ScrollAnimatorCompositorCoordinator::resetAnimationState(); |
| if (m_animationCurve) |
| m_animationCurve.clear(); |
| m_startTime = 0.0; |
| } |
| |
| ScrollResult ScrollAnimator::userScroll( |
| ScrollGranularity granularity, const FloatSize& delta) |
| { |
| if (!m_scrollableArea->scrollAnimatorEnabled()) |
| return ScrollAnimatorBase::userScroll(granularity, delta); |
| |
| TRACE_EVENT0("blink", "ScrollAnimator::scroll"); |
| |
| if (granularity == ScrollByPrecisePixel) { |
| // Cancel scroll animation because asked to instant scroll. |
| if (hasRunningAnimation()) |
| cancelAnimation(); |
| return ScrollAnimatorBase::userScroll(granularity, delta); |
| } |
| |
| FloatSize consumedDelta = computeDeltaToConsume(delta); |
| |
| FloatPoint targetPos = desiredTargetPosition(); |
| targetPos.move(consumedDelta); |
| |
| if (willAnimateToOffset(targetPos)) { |
| m_lastGranularity = granularity; |
| // Report unused delta only if there is no animation running. See |
| // comment below regarding scroll latching. |
| // TODO(bokan): Need to standardize how ScrollAnimators report |
| // unusedDelta. This differs from ScrollAnimatorMac currently. |
| return ScrollResult(true, true, 0, 0); |
| } |
| // Report unused delta only if there is no animation and we are not |
| // starting one. This ensures we latch for the duration of the |
| // animation rather than animating multiple scrollers at the same time. |
| return ScrollResult(false, false, delta.width(), delta.height()); |
| } |
| |
| bool ScrollAnimator::willAnimateToOffset(const FloatPoint& targetPos) |
| { |
| if (m_runState == RunState::PostAnimationCleanup) |
| resetAnimationState(); |
| |
| if (m_animationCurve && m_runState != RunState::WaitingToCancelOnCompositor) { |
| if ((targetPos - m_targetOffset).isZero()) |
| return true; |
| |
| m_targetOffset = targetPos; |
| ASSERT(m_runState == RunState::RunningOnMainThread |
| || m_runState == RunState::RunningOnCompositor |
| || m_runState == RunState::RunningOnCompositorButNeedsUpdate |
| || m_runState == RunState::RunningOnCompositorButNeedsTakeover); |
| |
| // Running on the main thread, simply update the target offset instead |
| // of sending to the compositor. |
| if (m_runState == RunState::RunningOnMainThread) { |
| m_animationCurve->updateTarget(m_timeFunction() - m_startTime, targetPos); |
| return true; |
| } |
| |
| if (registerAndScheduleAnimation()) |
| m_runState = RunState::RunningOnCompositorButNeedsUpdate; |
| return true; |
| } |
| |
| if ((targetPos - currentPosition()).isZero()) |
| return false; |
| |
| m_targetOffset = targetPos; |
| m_startTime = m_timeFunction(); |
| |
| if (registerAndScheduleAnimation()) |
| m_runState = RunState::WaitingToSendToCompositor; |
| |
| return true; |
| } |
| |
| void ScrollAnimator::scrollToOffsetWithoutAnimation(const FloatPoint& offset) |
| { |
| m_currentPos = offset; |
| |
| resetAnimationState(); |
| notifyPositionChanged(); |
| } |
| |
| void ScrollAnimator::tickAnimation(double monotonicTime) |
| { |
| if (m_runState != RunState::RunningOnMainThread) |
| return; |
| |
| TRACE_EVENT0("blink", "ScrollAnimator::tickAnimation"); |
| double elapsedTime = monotonicTime - m_startTime; |
| |
| bool isFinished = (elapsedTime > m_animationCurve->duration()); |
| FloatPoint offset = isFinished ? m_animationCurve->targetValue() |
| : m_animationCurve->getValue(elapsedTime); |
| |
| offset = FloatPoint(m_scrollableArea->clampScrollPosition(offset)); |
| |
| m_currentPos = offset; |
| |
| if (isFinished) |
| m_runState = RunState::PostAnimationCleanup; |
| else |
| scrollableArea()->scheduleAnimation(); |
| |
| TRACE_EVENT0("blink", "ScrollAnimator::notifyPositionChanged"); |
| notifyPositionChanged(); |
| } |
| |
| void ScrollAnimator::postAnimationCleanupAndReset() |
| { |
| // Remove the temporary main thread scrolling reason that was added while |
| // main thread had scheduled an animation. |
| removeMainThreadScrollingReason(); |
| |
| resetAnimationState(); |
| } |
| |
| bool ScrollAnimator::sendAnimationToCompositor() |
| { |
| if (m_scrollableArea->shouldScrollOnMainThread()) |
| return false; |
| |
| OwnPtr<CompositorAnimation> animation = adoptPtr( |
| CompositorFactory::current().createAnimation( |
| *m_animationCurve, |
| CompositorTargetProperty::SCROLL_OFFSET)); |
| // Being here means that either there is an animation that needs |
| // to be sent to the compositor, or an animation that needs to |
| // be updated (a new scroll event before the previous animation |
| // is finished). In either case, the start time is when the |
| // first animation was initiated. This re-targets the animation |
| // using the current time on main thread. |
| animation->setStartTime(m_startTime); |
| |
| int animationId = animation->id(); |
| int animationGroupId = animation->group(); |
| |
| bool sentToCompositor = addAnimation(animation.release()); |
| if (sentToCompositor) { |
| m_runState = RunState::RunningOnCompositor; |
| m_compositorAnimationId = animationId; |
| m_compositorAnimationGroupId = animationGroupId; |
| } |
| |
| return sentToCompositor; |
| } |
| |
| void ScrollAnimator::updateCompositorAnimations() |
| { |
| if (m_runState == RunState::PostAnimationCleanup) { |
| postAnimationCleanupAndReset(); |
| return; |
| } |
| |
| if (m_compositorAnimationId && m_runState != RunState::RunningOnCompositor |
| && m_runState != RunState::RunningOnCompositorButNeedsUpdate) { |
| // If the current run state is WaitingToSendToCompositor but we have a |
| // non-zero compositor animation id, there's a currently running |
| // compositor animation that needs to be removed here before the new |
| // animation is added below. |
| ASSERT(m_runState == RunState::WaitingToCancelOnCompositor |
| || m_runState == RunState::WaitingToSendToCompositor |
| || m_runState == RunState::RunningOnCompositorButNeedsTakeover); |
| |
| if (m_runState == RunState::RunningOnCompositorButNeedsTakeover) { |
| // The animation is already aborted when the call to |
| // ::takeoverCompositorAnimation is made. |
| m_runState = RunState::WaitingToSendToCompositor; |
| } else { |
| abortAnimation(); |
| } |
| |
| m_compositorAnimationId = 0; |
| m_compositorAnimationGroupId = 0; |
| if (m_runState == RunState::WaitingToCancelOnCompositor) { |
| postAnimationCleanupAndReset(); |
| return; |
| } |
| } |
| |
| if (m_runState == RunState::WaitingToSendToCompositor |
| || m_runState == RunState::RunningOnCompositorButNeedsUpdate) { |
| if (m_runState == RunState::RunningOnCompositorButNeedsUpdate) { |
| // Abort the running animation before a new one with an updated |
| // target is added. |
| abortAnimation(); |
| |
| m_compositorAnimationId = 0; |
| m_compositorAnimationGroupId = 0; |
| |
| m_animationCurve->updateTarget(m_timeFunction() - m_startTime, |
| m_targetOffset); |
| m_runState = RunState::WaitingToSendToCompositor; |
| } |
| |
| if (!m_animationCurve) { |
| m_animationCurve = adoptPtr(CompositorFactory::current().createScrollOffsetAnimationCurve( |
| m_targetOffset, |
| CompositorAnimationCurve::TimingFunctionTypeEaseInOut, |
| m_lastGranularity == ScrollByPixel ? |
| CompositorScrollOffsetAnimationCurve::ScrollDurationInverseDelta : |
| CompositorScrollOffsetAnimationCurve::ScrollDurationConstant)); |
| m_animationCurve->setInitialValue(currentPosition()); |
| } |
| |
| bool runningOnMainThread = false; |
| bool sentToCompositor = sendAnimationToCompositor(); |
| if (!sentToCompositor) { |
| runningOnMainThread = registerAndScheduleAnimation(); |
| if (runningOnMainThread) |
| m_runState = RunState::RunningOnMainThread; |
| } |
| |
| // Main thread should deal with the scroll animations it started. |
| if (sentToCompositor || runningOnMainThread) |
| addMainThreadScrollingReason(); |
| else |
| removeMainThreadScrollingReason(); |
| } |
| } |
| |
| void ScrollAnimator::addMainThreadScrollingReason() |
| { |
| if (WebLayer* scrollLayer = toWebLayer(scrollableArea()->layerForScrolling())) { |
| scrollLayer->addMainThreadScrollingReasons( |
| MainThreadScrollingReason::kAnimatingScrollOnMainThread); |
| } |
| } |
| |
| void ScrollAnimator::removeMainThreadScrollingReason() |
| { |
| if (WebLayer* scrollLayer = toWebLayer(scrollableArea()->layerForScrolling())) { |
| scrollLayer->clearMainThreadScrollingReasons( |
| MainThreadScrollingReason::kAnimatingScrollOnMainThread); |
| } |
| } |
| |
| void ScrollAnimator::notifyCompositorAnimationAborted(int groupId) |
| { |
| // An animation aborted by the compositor is treated as a finished |
| // animation. |
| ScrollAnimatorCompositorCoordinator::compositorAnimationFinished(groupId); |
| } |
| |
| void ScrollAnimator::notifyCompositorAnimationFinished(int groupId) |
| { |
| ScrollAnimatorCompositorCoordinator::compositorAnimationFinished(groupId); |
| } |
| |
| void ScrollAnimator::notifyAnimationTakeover( |
| double monotonicTime, |
| double animationStartTime, |
| scoped_ptr<cc::AnimationCurve> curve) |
| { |
| // If there is already an animation running and the compositor asks to take |
| // over an animation, do nothing to avoid judder. |
| if (hasRunningAnimation()) |
| return; |
| |
| cc::ScrollOffsetAnimationCurve* scrollOffsetAnimationCurve = |
| curve->ToScrollOffsetAnimationCurve(); |
| FloatPoint targetValue(scrollOffsetAnimationCurve->target_value().x(), |
| scrollOffsetAnimationCurve->target_value().y()); |
| if (willAnimateToOffset(targetValue)) { |
| m_animationCurve = adoptPtr( |
| CompositorFactory::current().createScrollOffsetAnimationCurve( |
| std::move(scrollOffsetAnimationCurve))); |
| m_startTime = animationStartTime; |
| } |
| } |
| |
| void ScrollAnimator::cancelAnimation() |
| { |
| ScrollAnimatorCompositorCoordinator::cancelAnimation(); |
| } |
| |
| void ScrollAnimator::takeoverCompositorAnimation() |
| { |
| if (m_runState == RunState::RunningOnCompositor |
| || m_runState == RunState::RunningOnCompositorButNeedsUpdate) |
| removeMainThreadScrollingReason(); |
| |
| ScrollAnimatorCompositorCoordinator::takeoverCompositorAnimation(); |
| } |
| |
| void ScrollAnimator::layerForCompositedScrollingDidChange( |
| CompositorAnimationTimeline* timeline) |
| { |
| if (reattachCompositorPlayerIfNeeded(timeline) && m_animationCurve) |
| addMainThreadScrollingReason(); |
| } |
| |
| bool ScrollAnimator::registerAndScheduleAnimation() |
| { |
| scrollableArea()->registerForAnimation(); |
| if (!m_scrollableArea->scheduleAnimation()) { |
| scrollToOffsetWithoutAnimation(m_targetOffset); |
| resetAnimationState(); |
| return false; |
| } |
| return true; |
| } |
| |
| DEFINE_TRACE(ScrollAnimator) |
| { |
| ScrollAnimatorBase::trace(visitor); |
| } |
| |
| } // namespace blink |