blob: 1d3ff2d77679abdbc0f2e1f67c2d8fc104651979 [file] [log] [blame]
/*
* 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/animation/CompositorAnimation.h"
#include "platform/graphics/GraphicsLayer.h"
#include "platform/scroll/MainThreadScrollingReason.h"
#include "platform/scroll/ScrollableArea.h"
#include "platform/tracing/TraceEvent.h"
#include "public/platform/Platform.h"
#include "public/platform/WebCompositorSupport.h"
#include "wtf/CurrentTime.h"
#include "wtf/PassRefPtr.h"
#include "wtf/PtrUtil.h"
#include <memory>
namespace blink {
namespace {
WebLayer* toWebLayer(GraphicsLayer* layer) {
return layer ? layer->platformLayer() : nullptr;
}
} // namespace
ScrollAnimatorBase* ScrollAnimatorBase::create(ScrollableArea* scrollableArea) {
if (scrollableArea && scrollableArea->scrollAnimatorEnabled())
return new ScrollAnimator(scrollableArea);
return new ScrollAnimatorBase(scrollableArea);
}
ScrollAnimator::ScrollAnimator(ScrollableArea* scrollableArea,
WTF::TimeFunction timeFunction)
: ScrollAnimatorBase(scrollableArea),
m_timeFunction(timeFunction),
m_lastGranularity(ScrollByPixel) {}
ScrollAnimator::~ScrollAnimator() {}
ScrollOffset ScrollAnimator::desiredTargetOffset() const {
if (m_runState == RunState::WaitingToCancelOnCompositor)
return currentOffset();
return (m_animationCurve || m_runState == RunState::WaitingToSendToCompositor)
? m_targetOffset
: currentOffset();
}
bool ScrollAnimator::hasRunningAnimation() const {
return m_runState != RunState::PostAnimationCleanup &&
(m_animationCurve ||
m_runState == RunState::WaitingToSendToCompositor);
}
ScrollOffset ScrollAnimator::computeDeltaToConsume(
const ScrollOffset& delta) const {
ScrollOffset pos = desiredTargetOffset();
ScrollOffset newPos = m_scrollableArea->clampScrollOffset(pos + delta);
return newPos - pos;
}
void ScrollAnimator::resetAnimationState() {
ScrollAnimatorCompositorCoordinator::resetAnimationState();
if (m_animationCurve)
m_animationCurve.reset();
m_startTime = 0.0;
}
ScrollResult ScrollAnimator::userScroll(ScrollGranularity granularity,
const ScrollOffset& 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);
}
bool needsPostAnimationCleanup = m_runState == RunState::PostAnimationCleanup;
if (m_runState == RunState::PostAnimationCleanup)
resetAnimationState();
ScrollOffset consumedDelta = computeDeltaToConsume(delta);
ScrollOffset targetOffset = desiredTargetOffset();
targetOffset += consumedDelta;
if (willAnimateToOffset(targetOffset)) {
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);
}
// If the run state when this method was called was PostAnimationCleanup and
// we're not starting an animation, stay in PostAnimationCleanup state so
// that the main thread scrolling reason can be removed.
if (needsPostAnimationCleanup)
m_runState = RunState::PostAnimationCleanup;
// 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 ScrollOffset& targetOffset) {
if (m_runState == RunState::PostAnimationCleanup)
resetAnimationState();
if (m_runState == RunState::WaitingToCancelOnCompositor ||
m_runState == RunState::WaitingToCancelOnCompositorButNewScroll) {
ASSERT(m_animationCurve);
m_targetOffset = targetOffset;
if (registerAndScheduleAnimation())
m_runState = RunState::WaitingToCancelOnCompositorButNewScroll;
return true;
}
if (m_animationCurve) {
if ((targetOffset - m_targetOffset).isZero())
return true;
m_targetOffset = targetOffset;
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,
compositorOffsetFromBlinkOffset(targetOffset));
return true;
}
if (registerAndScheduleAnimation())
m_runState = RunState::RunningOnCompositorButNeedsUpdate;
return true;
}
if ((targetOffset - currentOffset()).isZero())
return false;
m_targetOffset = targetOffset;
m_startTime = m_timeFunction();
if (registerAndScheduleAnimation())
m_runState = RunState::WaitingToSendToCompositor;
return true;
}
void ScrollAnimator::adjustAnimationAndSetScrollOffset(
const ScrollOffset& offset,
ScrollType scrollType) {
IntSize adjustment =
roundedIntSize(offset) - roundedIntSize(m_scrollableArea->scrollOffset());
scrollOffsetChanged(offset, scrollType);
if (m_runState == RunState::Idle) {
adjustImplOnlyScrollOffsetAnimation(adjustment);
} else if (hasRunningAnimation()) {
m_targetOffset += ScrollOffset(adjustment);
if (m_animationCurve) {
m_animationCurve->applyAdjustment(adjustment);
if (m_runState != RunState::RunningOnMainThread &&
registerAndScheduleAnimation())
m_runState = RunState::RunningOnCompositorButNeedsAdjustment;
}
}
}
void ScrollAnimator::scrollToOffsetWithoutAnimation(
const ScrollOffset& offset) {
m_currentOffset = offset;
resetAnimationState();
notifyOffsetChanged();
}
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());
ScrollOffset offset = blinkOffsetFromCompositorOffset(
isFinished ? m_animationCurve->targetValue()
: m_animationCurve->getValue(elapsedTime));
offset = m_scrollableArea->clampScrollOffset(offset);
m_currentOffset = offset;
if (isFinished)
m_runState = RunState::PostAnimationCleanup;
else
getScrollableArea()->scheduleAnimation();
TRACE_EVENT0("blink", "ScrollAnimator::notifyOffsetChanged");
notifyOffsetChanged();
}
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;
std::unique_ptr<CompositorAnimation> animation = CompositorAnimation::create(
*m_animationCurve, CompositorTargetProperty::SCROLL_OFFSET, 0, 0);
// 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(std::move(animation));
if (sentToCompositor) {
m_runState = RunState::RunningOnCompositor;
m_compositorAnimationId = animationId;
m_compositorAnimationGroupId = animationGroupId;
}
return sentToCompositor;
}
void ScrollAnimator::createAnimationCurve() {
DCHECK(!m_animationCurve);
m_animationCurve = CompositorScrollOffsetAnimationCurve::create(
compositorOffsetFromBlinkOffset(m_targetOffset),
m_lastGranularity == ScrollByPixel
? CompositorScrollOffsetAnimationCurve::ScrollDurationInverseDelta
: CompositorScrollOffsetAnimationCurve::ScrollDurationConstant);
m_animationCurve->setInitialValue(
compositorOffsetFromBlinkOffset(currentOffset()));
}
void ScrollAnimator::updateCompositorAnimations() {
ScrollAnimatorCompositorCoordinator::updateCompositorAnimations();
if (m_runState == RunState::PostAnimationCleanup) {
postAnimationCleanupAndReset();
return;
}
if (m_runState == RunState::WaitingToCancelOnCompositor) {
DCHECK(m_compositorAnimationId);
abortAnimation();
postAnimationCleanupAndReset();
return;
}
if (m_runState == RunState::RunningOnCompositorButNeedsTakeover) {
// The call to ::takeOverCompositorAnimation aborted the animation and
// put us in this state. The assumption is that takeOver is called
// because a main thread scrolling reason is added, and simply trying
// to ::sendAnimationToCompositor will fail and we will run on the main
// thread.
resetAnimationIds();
m_runState = RunState::WaitingToSendToCompositor;
}
if (m_runState == RunState::RunningOnCompositorButNeedsUpdate ||
m_runState == RunState::WaitingToCancelOnCompositorButNewScroll ||
m_runState == RunState::RunningOnCompositorButNeedsAdjustment) {
// Abort the running animation before a new one with an updated
// target is added.
abortAnimation();
resetAnimationIds();
if (m_runState != RunState::RunningOnCompositorButNeedsAdjustment) {
// When in RunningOnCompositorButNeedsAdjustment, the call to
// ::adjustScrollOffsetAnimation should have made the necessary
// adjustment to the curve.
m_animationCurve->updateTarget(
m_timeFunction() - m_startTime,
compositorOffsetFromBlinkOffset(m_targetOffset));
}
if (m_runState == RunState::WaitingToCancelOnCompositorButNewScroll) {
m_animationCurve->setInitialValue(
compositorOffsetFromBlinkOffset(currentOffset()));
}
m_runState = RunState::WaitingToSendToCompositor;
}
if (m_runState == RunState::WaitingToSendToCompositor) {
if (!m_compositorAnimationAttachedToElementId)
reattachCompositorPlayerIfNeeded(
getScrollableArea()->compositorAnimationTimeline());
if (!m_animationCurve)
createAnimationCurve();
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(getScrollableArea()->layerForScrolling())) {
scrollLayer->addMainThreadScrollingReasons(
MainThreadScrollingReason::kHandlingScrollFromMainThread);
}
}
void ScrollAnimator::removeMainThreadScrollingReason() {
if (WebLayer* scrollLayer =
toWebLayer(getScrollableArea()->layerForScrolling())) {
scrollLayer->clearMainThreadScrollingReasons(
MainThreadScrollingReason::kHandlingScrollFromMainThread);
}
}
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,
std::unique_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();
ScrollOffset targetValue(scrollOffsetAnimationCurve->target_value().x(),
scrollOffsetAnimationCurve->target_value().y());
if (willAnimateToOffset(targetValue)) {
m_animationCurve = CompositorScrollOffsetAnimationCurve::create(
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() {
getScrollableArea()->registerForAnimation();
if (!m_scrollableArea->scheduleAnimation()) {
scrollToOffsetWithoutAnimation(m_targetOffset);
resetAnimationState();
return false;
}
return true;
}
DEFINE_TRACE(ScrollAnimator) {
ScrollAnimatorBase::trace(visitor);
}
} // namespace blink