blob: 946efa8168c2d3da98539e340b96d34ce890ace2 [file] [log] [blame]
/*
* Copyright (C) 2010 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:
*
* 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 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 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.
*/
// Tests for the ScrollAnimator class.
#include "platform/scroll/ScrollAnimator.h"
#include "platform/geometry/FloatPoint.h"
#include "platform/geometry/IntRect.h"
#include "platform/scroll/ScrollAnimatorBase.h"
#include "platform/scroll/ScrollableArea.h"
#include "public/platform/Platform.h"
#include "public/platform/WebScheduler.h"
#include "public/platform/WebThread.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace blink {
using testing::AtLeast;
using testing::Return;
using testing::_;
static double gMockedTime = 0.0;
static double getMockedTime() {
return gMockedTime;
}
namespace {
class MockScrollableArea : public GarbageCollectedFinalized<MockScrollableArea>,
public ScrollableArea {
USING_GARBAGE_COLLECTED_MIXIN(MockScrollableArea);
public:
static MockScrollableArea* create(bool scrollAnimatorEnabled,
const ScrollOffset& minOffset,
const ScrollOffset& maxOffset) {
return new MockScrollableArea(scrollAnimatorEnabled, minOffset, maxOffset);
}
MOCK_CONST_METHOD0(visualRectForScrollbarParts, LayoutRect());
MOCK_CONST_METHOD0(isActive, bool());
MOCK_CONST_METHOD1(scrollSize, int(ScrollbarOrientation));
MOCK_CONST_METHOD0(isScrollCornerVisible, bool());
MOCK_CONST_METHOD0(scrollCornerRect, IntRect());
MOCK_METHOD2(updateScrollOffset, void(const ScrollOffset&, ScrollType));
MOCK_METHOD0(scrollControlWasSetNeedsPaintInvalidation, void());
MOCK_CONST_METHOD0(enclosingScrollableArea, ScrollableArea*());
MOCK_CONST_METHOD1(visibleContentRect, IntRect(IncludeScrollbarsInRect));
MOCK_CONST_METHOD0(contentsSize, IntSize());
MOCK_CONST_METHOD0(scrollbarsCanBeActive, bool());
MOCK_CONST_METHOD0(scrollableAreaBoundingBox, IntRect());
MOCK_METHOD0(registerForAnimation, void());
MOCK_METHOD0(scheduleAnimation, bool());
bool userInputScrollable(ScrollbarOrientation) const override { return true; }
bool shouldPlaceVerticalScrollbarOnLeft() const override { return false; }
IntSize scrollOffsetInt() const override { return IntSize(); }
int visibleHeight() const override { return 768; }
int visibleWidth() const override { return 1024; }
bool scrollAnimatorEnabled() const override {
return m_scrollAnimatorEnabled;
}
int pageStep(ScrollbarOrientation) const override { return 0; }
IntSize minimumScrollOffsetInt() const override {
return flooredIntSize(m_minOffset);
}
IntSize maximumScrollOffsetInt() const override {
return flooredIntSize(m_maxOffset);
}
void setScrollAnimator(ScrollAnimator* scrollAnimator) {
animator = scrollAnimator;
}
ScrollOffset getScrollOffset() const override {
if (animator)
return animator->currentOffset();
return ScrollableArea::getScrollOffset();
}
void setScrollOffset(const ScrollOffset& offset,
ScrollType type,
ScrollBehavior behavior = ScrollBehaviorInstant) {
if (animator)
animator->setCurrentOffset(offset);
ScrollableArea::setScrollOffset(offset, type, behavior);
}
RefPtr<WebTaskRunner> getTimerTaskRunner() const final {
return Platform::current()->currentThread()->scheduler()->timerTaskRunner();
}
DEFINE_INLINE_VIRTUAL_TRACE() {
visitor->trace(animator);
ScrollableArea::trace(visitor);
}
private:
explicit MockScrollableArea(bool scrollAnimatorEnabled,
const ScrollOffset& minOffset,
const ScrollOffset& maxOffset)
: m_scrollAnimatorEnabled(scrollAnimatorEnabled),
m_minOffset(minOffset),
m_maxOffset(maxOffset) {}
bool m_scrollAnimatorEnabled;
ScrollOffset m_minOffset;
ScrollOffset m_maxOffset;
Member<ScrollAnimator> animator;
};
class TestScrollAnimator : public ScrollAnimator {
public:
TestScrollAnimator(ScrollableArea* scrollableArea,
WTF::TimeFunction timingFunction)
: ScrollAnimator(scrollableArea, timingFunction){};
~TestScrollAnimator() override{};
void setShouldSendToCompositor(bool send) { m_shouldSendToCompositor = send; }
bool sendAnimationToCompositor() override {
if (m_shouldSendToCompositor) {
m_runState =
ScrollAnimatorCompositorCoordinator::RunState::RunningOnCompositor;
m_compositorAnimationId = 1;
return true;
}
return false;
}
protected:
void abortAnimation() override {}
private:
bool m_shouldSendToCompositor = false;
};
} // namespace
static void reset(ScrollAnimator& scrollAnimator) {
scrollAnimator.scrollToOffsetWithoutAnimation(ScrollOffset());
}
// TODO(skobes): Add unit tests for composited scrolling paths.
TEST(ScrollAnimatorTest, MainThreadStates) {
MockScrollableArea* scrollableArea = MockScrollableArea::create(
true, ScrollOffset(), ScrollOffset(1000, 1000));
ScrollAnimator* scrollAnimator =
new ScrollAnimator(scrollableArea, getMockedTime);
EXPECT_CALL(*scrollableArea, updateScrollOffset(_, _)).Times(2);
// Once from userScroll, once from updateCompositorAnimations.
EXPECT_CALL(*scrollableArea, registerForAnimation()).Times(2);
EXPECT_CALL(*scrollableArea, scheduleAnimation())
.Times(AtLeast(1))
.WillRepeatedly(Return(true));
// Idle
EXPECT_FALSE(scrollAnimator->hasAnimationThatRequiresService());
EXPECT_EQ(scrollAnimator->m_runState,
ScrollAnimatorCompositorCoordinator::RunState::Idle);
// WaitingToSendToCompositor
scrollAnimator->userScroll(ScrollByLine, FloatSize(10, 0));
EXPECT_EQ(
scrollAnimator->m_runState,
ScrollAnimatorCompositorCoordinator::RunState::WaitingToSendToCompositor);
// RunningOnMainThread
gMockedTime += 0.05;
scrollAnimator->updateCompositorAnimations();
EXPECT_EQ(scrollAnimator->m_runState,
ScrollAnimatorCompositorCoordinator::RunState::RunningOnMainThread);
scrollAnimator->tickAnimation(getMockedTime());
EXPECT_EQ(scrollAnimator->m_runState,
ScrollAnimatorCompositorCoordinator::RunState::RunningOnMainThread);
// PostAnimationCleanup
scrollAnimator->cancelAnimation();
EXPECT_EQ(
scrollAnimator->m_runState,
ScrollAnimatorCompositorCoordinator::RunState::PostAnimationCleanup);
// Idle
scrollAnimator->updateCompositorAnimations();
scrollAnimator->tickAnimation(getMockedTime());
EXPECT_EQ(scrollAnimator->m_runState,
ScrollAnimatorCompositorCoordinator::RunState::Idle);
reset(*scrollAnimator);
// Forced GC in order to finalize objects depending on the mock object.
ThreadState::current()->collectAllGarbage();
}
TEST(ScrollAnimatorTest, MainThreadEnabled) {
MockScrollableArea* scrollableArea = MockScrollableArea::create(
true, ScrollOffset(), ScrollOffset(1000, 1000));
ScrollAnimator* scrollAnimator =
new ScrollAnimator(scrollableArea, getMockedTime);
EXPECT_CALL(*scrollableArea, updateScrollOffset(_, _)).Times(9);
EXPECT_CALL(*scrollableArea, registerForAnimation()).Times(6);
EXPECT_CALL(*scrollableArea, scheduleAnimation())
.Times(AtLeast(1))
.WillRepeatedly(Return(true));
EXPECT_FALSE(scrollAnimator->hasAnimationThatRequiresService());
ScrollResult result =
scrollAnimator->userScroll(ScrollByLine, FloatSize(-100, 0));
EXPECT_FALSE(scrollAnimator->hasAnimationThatRequiresService());
EXPECT_FALSE(result.didScrollX);
EXPECT_FLOAT_EQ(-100.0f, result.unusedScrollDeltaX);
result = scrollAnimator->userScroll(ScrollByLine, FloatSize(100, 0));
EXPECT_TRUE(scrollAnimator->hasAnimationThatRequiresService());
EXPECT_TRUE(result.didScrollX);
EXPECT_FLOAT_EQ(0.0, result.unusedScrollDeltaX);
gMockedTime += 0.05;
scrollAnimator->updateCompositorAnimations();
scrollAnimator->tickAnimation(getMockedTime());
EXPECT_NE(100, scrollAnimator->currentOffset().width());
EXPECT_NE(0, scrollAnimator->currentOffset().width());
EXPECT_EQ(0, scrollAnimator->currentOffset().height());
reset(*scrollAnimator);
scrollAnimator->userScroll(ScrollByPage, FloatSize(100, 0));
EXPECT_TRUE(scrollAnimator->hasAnimationThatRequiresService());
gMockedTime += 0.05;
scrollAnimator->updateCompositorAnimations();
scrollAnimator->tickAnimation(getMockedTime());
EXPECT_NE(100, scrollAnimator->currentOffset().width());
EXPECT_NE(0, scrollAnimator->currentOffset().width());
EXPECT_EQ(0, scrollAnimator->currentOffset().height());
reset(*scrollAnimator);
scrollAnimator->userScroll(ScrollByPixel, FloatSize(100, 0));
EXPECT_TRUE(scrollAnimator->hasAnimationThatRequiresService());
gMockedTime += 0.05;
scrollAnimator->updateCompositorAnimations();
scrollAnimator->tickAnimation(getMockedTime());
EXPECT_NE(100, scrollAnimator->currentOffset().width());
EXPECT_NE(0, scrollAnimator->currentOffset().width());
EXPECT_EQ(0, scrollAnimator->currentOffset().height());
gMockedTime += 1.0;
scrollAnimator->updateCompositorAnimations();
scrollAnimator->tickAnimation(getMockedTime());
gMockedTime += 0.05;
scrollAnimator->updateCompositorAnimations();
EXPECT_FALSE(scrollAnimator->hasAnimationThatRequiresService());
EXPECT_EQ(100, scrollAnimator->currentOffset().width());
reset(*scrollAnimator);
scrollAnimator->userScroll(ScrollByPrecisePixel, FloatSize(100, 0));
EXPECT_FALSE(scrollAnimator->hasAnimationThatRequiresService());
EXPECT_EQ(100, scrollAnimator->currentOffset().width());
EXPECT_NE(0, scrollAnimator->currentOffset().width());
EXPECT_EQ(0, scrollAnimator->currentOffset().height());
reset(*scrollAnimator);
}
// Test that a smooth scroll offset animation is aborted when followed by a
// non-smooth scroll offset animation.
TEST(ScrollAnimatorTest, AnimatedScrollAborted) {
MockScrollableArea* scrollableArea = MockScrollableArea::create(
true, ScrollOffset(), ScrollOffset(1000, 1000));
ScrollAnimator* scrollAnimator =
new ScrollAnimator(scrollableArea, getMockedTime);
EXPECT_CALL(*scrollableArea, updateScrollOffset(_, _)).Times(3);
EXPECT_CALL(*scrollableArea, registerForAnimation()).Times(2);
EXPECT_CALL(*scrollableArea, scheduleAnimation())
.Times(AtLeast(1))
.WillRepeatedly(Return(true));
EXPECT_FALSE(scrollAnimator->hasAnimationThatRequiresService());
// Smooth scroll.
ScrollResult result =
scrollAnimator->userScroll(ScrollByLine, FloatSize(100, 0));
EXPECT_TRUE(scrollAnimator->hasAnimationThatRequiresService());
EXPECT_TRUE(result.didScrollX);
EXPECT_FLOAT_EQ(0.0, result.unusedScrollDeltaX);
EXPECT_TRUE(scrollAnimator->hasRunningAnimation());
gMockedTime += 0.05;
scrollAnimator->updateCompositorAnimations();
scrollAnimator->tickAnimation(getMockedTime());
EXPECT_NE(100, scrollAnimator->currentOffset().width());
EXPECT_NE(0, scrollAnimator->currentOffset().width());
EXPECT_EQ(0, scrollAnimator->currentOffset().height());
float x = scrollAnimator->currentOffset().width();
// Instant scroll.
result = scrollAnimator->userScroll(ScrollByPrecisePixel, FloatSize(100, 0));
EXPECT_TRUE(result.didScrollX);
gMockedTime += 0.05;
scrollAnimator->updateCompositorAnimations();
EXPECT_FALSE(scrollAnimator->hasRunningAnimation());
EXPECT_EQ(x + 100, scrollAnimator->currentOffset().width());
EXPECT_EQ(0, scrollAnimator->currentOffset().height());
reset(*scrollAnimator);
}
// Test that a smooth scroll offset animation running on the compositor is
// completed on the main thread.
TEST(ScrollAnimatorTest, AnimatedScrollTakeover) {
MockScrollableArea* scrollableArea = MockScrollableArea::create(
true, ScrollOffset(), ScrollOffset(1000, 1000));
TestScrollAnimator* scrollAnimator =
new TestScrollAnimator(scrollableArea, getMockedTime);
EXPECT_CALL(*scrollableArea, updateScrollOffset(_, _)).Times(2);
// Called from userScroll, updateCompositorAnimations, then
// takeOverCompositorAnimation (to re-register after RunningOnCompositor).
EXPECT_CALL(*scrollableArea, registerForAnimation()).Times(3);
EXPECT_CALL(*scrollableArea, scheduleAnimation())
.Times(AtLeast(1))
.WillRepeatedly(Return(true));
EXPECT_FALSE(scrollAnimator->hasAnimationThatRequiresService());
// Smooth scroll.
ScrollResult result =
scrollAnimator->userScroll(ScrollByLine, FloatSize(100, 0));
EXPECT_TRUE(scrollAnimator->hasAnimationThatRequiresService());
EXPECT_TRUE(result.didScrollX);
EXPECT_FLOAT_EQ(0.0, result.unusedScrollDeltaX);
EXPECT_TRUE(scrollAnimator->hasRunningAnimation());
// Update compositor animation.
gMockedTime += 0.05;
scrollAnimator->setShouldSendToCompositor(true);
scrollAnimator->updateCompositorAnimations();
EXPECT_EQ(scrollAnimator->m_runState,
ScrollAnimatorCompositorCoordinator::RunState::RunningOnCompositor);
// Takeover.
scrollAnimator->takeOverCompositorAnimation();
EXPECT_EQ(scrollAnimator->m_runState,
ScrollAnimatorCompositorCoordinator::RunState::
RunningOnCompositorButNeedsTakeover);
// Animation should now be running on the main thread.
scrollAnimator->setShouldSendToCompositor(false);
scrollAnimator->updateCompositorAnimations();
EXPECT_EQ(scrollAnimator->m_runState,
ScrollAnimatorCompositorCoordinator::RunState::RunningOnMainThread);
scrollAnimator->tickAnimation(getMockedTime());
EXPECT_NE(100, scrollAnimator->currentOffset().width());
EXPECT_NE(0, scrollAnimator->currentOffset().width());
EXPECT_EQ(0, scrollAnimator->currentOffset().height());
reset(*scrollAnimator);
}
TEST(ScrollAnimatorTest, Disabled) {
MockScrollableArea* scrollableArea = MockScrollableArea::create(
false, ScrollOffset(), ScrollOffset(1000, 1000));
ScrollAnimator* scrollAnimator =
new ScrollAnimator(scrollableArea, getMockedTime);
EXPECT_CALL(*scrollableArea, updateScrollOffset(_, _)).Times(8);
EXPECT_CALL(*scrollableArea, registerForAnimation()).Times(0);
scrollAnimator->userScroll(ScrollByLine, FloatSize(100, 0));
EXPECT_EQ(100, scrollAnimator->currentOffset().width());
EXPECT_EQ(0, scrollAnimator->currentOffset().height());
reset(*scrollAnimator);
scrollAnimator->userScroll(ScrollByPage, FloatSize(100, 0));
EXPECT_EQ(100, scrollAnimator->currentOffset().width());
EXPECT_EQ(0, scrollAnimator->currentOffset().height());
reset(*scrollAnimator);
scrollAnimator->userScroll(ScrollByDocument, FloatSize(100, 0));
EXPECT_EQ(100, scrollAnimator->currentOffset().width());
EXPECT_EQ(0, scrollAnimator->currentOffset().height());
reset(*scrollAnimator);
scrollAnimator->userScroll(ScrollByPixel, FloatSize(100, 0));
EXPECT_EQ(100, scrollAnimator->currentOffset().width());
EXPECT_EQ(0, scrollAnimator->currentOffset().height());
reset(*scrollAnimator);
}
// Test that cancelling an animation resets the animation state.
// See crbug.com/598548.
TEST(ScrollAnimatorTest, CancellingAnimationResetsState) {
MockScrollableArea* scrollableArea = MockScrollableArea::create(
true, ScrollOffset(), ScrollOffset(1000, 1000));
ScrollAnimator* scrollAnimator =
new ScrollAnimator(scrollableArea, getMockedTime);
// Called from first userScroll, setCurrentOffset, and second userScroll.
EXPECT_CALL(*scrollableArea, updateScrollOffset(_, _)).Times(3);
// Called from userScroll, updateCompositorAnimations.
EXPECT_CALL(*scrollableArea, registerForAnimation()).Times(4);
EXPECT_CALL(*scrollableArea, scheduleAnimation())
.Times(AtLeast(1))
.WillRepeatedly(Return(true));
EXPECT_EQ(0, scrollAnimator->currentOffset().width());
EXPECT_EQ(0, scrollAnimator->currentOffset().height());
// WaitingToSendToCompositor
scrollAnimator->userScroll(ScrollByLine, FloatSize(10, 0));
EXPECT_EQ(
scrollAnimator->m_runState,
ScrollAnimatorCompositorCoordinator::RunState::WaitingToSendToCompositor);
// RunningOnMainThread
gMockedTime += 0.05;
scrollAnimator->updateCompositorAnimations();
EXPECT_EQ(scrollAnimator->m_runState,
ScrollAnimatorCompositorCoordinator::RunState::RunningOnMainThread);
scrollAnimator->tickAnimation(getMockedTime());
EXPECT_EQ(scrollAnimator->m_runState,
ScrollAnimatorCompositorCoordinator::RunState::RunningOnMainThread);
// Amount scrolled so far.
float offsetX = scrollAnimator->currentOffset().width();
// Interrupt user scroll.
scrollAnimator->cancelAnimation();
EXPECT_EQ(
scrollAnimator->m_runState,
ScrollAnimatorCompositorCoordinator::RunState::PostAnimationCleanup);
// Another userScroll after modified scroll offset.
scrollAnimator->setCurrentOffset(ScrollOffset(offsetX + 15, 0));
scrollAnimator->userScroll(ScrollByLine, FloatSize(10, 0));
EXPECT_EQ(
scrollAnimator->m_runState,
ScrollAnimatorCompositorCoordinator::RunState::WaitingToSendToCompositor);
// Finish scroll animation.
gMockedTime += 1.0;
scrollAnimator->updateCompositorAnimations();
scrollAnimator->tickAnimation(getMockedTime());
EXPECT_EQ(
scrollAnimator->m_runState,
ScrollAnimatorCompositorCoordinator::RunState::PostAnimationCleanup);
EXPECT_EQ(offsetX + 15 + 10, scrollAnimator->currentOffset().width());
EXPECT_EQ(0, scrollAnimator->currentOffset().height());
reset(*scrollAnimator);
}
// Test the behavior when in WaitingToCancelOnCompositor and a new user scroll
// happens.
TEST(ScrollAnimatorTest, CancellingCompositorAnimation) {
MockScrollableArea* scrollableArea = MockScrollableArea::create(
true, ScrollOffset(), ScrollOffset(1000, 1000));
TestScrollAnimator* scrollAnimator =
new TestScrollAnimator(scrollableArea, getMockedTime);
// Called when reset, not setting anywhere else.
EXPECT_CALL(*scrollableArea, updateScrollOffset(_, _)).Times(1);
// Called from userScroll, and first update.
EXPECT_CALL(*scrollableArea, registerForAnimation()).Times(4);
EXPECT_CALL(*scrollableArea, scheduleAnimation())
.Times(AtLeast(1))
.WillRepeatedly(Return(true));
EXPECT_FALSE(scrollAnimator->hasAnimationThatRequiresService());
// First user scroll.
ScrollResult result =
scrollAnimator->userScroll(ScrollByLine, FloatSize(100, 0));
EXPECT_TRUE(scrollAnimator->hasAnimationThatRequiresService());
EXPECT_TRUE(result.didScrollX);
EXPECT_FLOAT_EQ(0.0, result.unusedScrollDeltaX);
EXPECT_TRUE(scrollAnimator->hasRunningAnimation());
EXPECT_EQ(100, scrollAnimator->desiredTargetOffset().width());
EXPECT_EQ(0, scrollAnimator->desiredTargetOffset().height());
// Update compositor animation.
gMockedTime += 0.05;
scrollAnimator->setShouldSendToCompositor(true);
scrollAnimator->updateCompositorAnimations();
EXPECT_EQ(scrollAnimator->m_runState,
ScrollAnimatorCompositorCoordinator::RunState::RunningOnCompositor);
// Cancel
scrollAnimator->cancelAnimation();
EXPECT_EQ(scrollAnimator->m_runState,
ScrollAnimatorCompositorCoordinator::RunState::
WaitingToCancelOnCompositor);
// Unrelated scroll offset update.
scrollAnimator->setCurrentOffset(ScrollOffset(50, 0));
// Desired target offset should be that of the second scroll.
result = scrollAnimator->userScroll(ScrollByLine, FloatSize(100, 0));
EXPECT_TRUE(scrollAnimator->hasAnimationThatRequiresService());
EXPECT_TRUE(result.didScrollX);
EXPECT_FLOAT_EQ(0.0, result.unusedScrollDeltaX);
EXPECT_EQ(scrollAnimator->m_runState,
ScrollAnimatorCompositorCoordinator::RunState::
WaitingToCancelOnCompositorButNewScroll);
EXPECT_EQ(150, scrollAnimator->desiredTargetOffset().width());
EXPECT_EQ(0, scrollAnimator->desiredTargetOffset().height());
// Update compositor animation.
gMockedTime += 0.05;
scrollAnimator->updateCompositorAnimations();
EXPECT_EQ(scrollAnimator->m_runState,
ScrollAnimatorCompositorCoordinator::RunState::RunningOnCompositor);
// Third user scroll after compositor update updates the target.
result = scrollAnimator->userScroll(ScrollByLine, FloatSize(100, 0));
EXPECT_TRUE(scrollAnimator->hasAnimationThatRequiresService());
EXPECT_TRUE(result.didScrollX);
EXPECT_FLOAT_EQ(0.0, result.unusedScrollDeltaX);
EXPECT_EQ(scrollAnimator->m_runState,
ScrollAnimatorCompositorCoordinator::RunState::
RunningOnCompositorButNeedsUpdate);
EXPECT_EQ(250, scrollAnimator->desiredTargetOffset().width());
EXPECT_EQ(0, scrollAnimator->desiredTargetOffset().height());
reset(*scrollAnimator);
// Forced GC in order to finalize objects depending on the mock object.
ThreadState::current()->collectAllGarbage();
}
// This test verifies that impl only animation updates get cleared once they
// are pushed to compositor animation host.
TEST(ScrollAnimatorTest, ImplOnlyAnimationUpdatesCleared) {
MockScrollableArea* scrollableArea = MockScrollableArea::create(
true, ScrollOffset(), ScrollOffset(1000, 1000));
TestScrollAnimator* animator =
new TestScrollAnimator(scrollableArea, getMockedTime);
// From calls to adjust/takeoverImplOnlyScrollOffsetAnimation.
EXPECT_CALL(*scrollableArea, registerForAnimation()).Times(3);
// Verify that the adjustment update is cleared.
EXPECT_EQ(animator->m_runState,
ScrollAnimatorCompositorCoordinator::RunState::Idle);
EXPECT_FALSE(animator->hasAnimationThatRequiresService());
EXPECT_TRUE(animator->implOnlyAnimationAdjustmentForTesting().isZero());
animator->adjustImplOnlyScrollOffsetAnimation(IntSize(100, 100));
animator->adjustImplOnlyScrollOffsetAnimation(IntSize(10, -10));
EXPECT_TRUE(animator->hasAnimationThatRequiresService());
EXPECT_EQ(FloatSize(110, 90),
animator->implOnlyAnimationAdjustmentForTesting());
animator->updateCompositorAnimations();
EXPECT_EQ(animator->m_runState,
ScrollAnimatorCompositorCoordinator::RunState::Idle);
EXPECT_FALSE(animator->hasAnimationThatRequiresService());
EXPECT_TRUE(animator->implOnlyAnimationAdjustmentForTesting().isZero());
// Verify that the takeover update is cleared.
animator->takeOverImplOnlyScrollOffsetAnimation();
EXPECT_FALSE(animator->hasAnimationThatRequiresService());
// Forced GC in order to finalize objects depending on the mock object.
ThreadState::current()->collectAllGarbage();
}
TEST(ScrollAnimatorTest, MainThreadAnimationTargetAdjustment) {
MockScrollableArea* scrollableArea = MockScrollableArea::create(
true, ScrollOffset(-100, -100), ScrollOffset(1000, 1000));
ScrollAnimator* animator = new ScrollAnimator(scrollableArea, getMockedTime);
scrollableArea->setScrollAnimator(animator);
// Twice from tickAnimation, once from reset, and twice from
// adjustAnimationAndSetScrollOffset.
EXPECT_CALL(*scrollableArea, updateScrollOffset(_, _)).Times(5);
// One from call to userScroll and one from updateCompositorAnimations.
EXPECT_CALL(*scrollableArea, registerForAnimation()).Times(2);
EXPECT_CALL(*scrollableArea, scheduleAnimation())
.Times(AtLeast(1))
.WillRepeatedly(Return(true));
// Idle
EXPECT_FALSE(animator->hasAnimationThatRequiresService());
EXPECT_EQ(ScrollOffset(), animator->currentOffset());
// WaitingToSendToCompositor
animator->userScroll(ScrollByLine, ScrollOffset(100, 100));
// RunningOnMainThread
gMockedTime += 0.05;
animator->updateCompositorAnimations();
animator->tickAnimation(getMockedTime());
ScrollOffset offset = animator->currentOffset();
EXPECT_EQ(ScrollOffset(100, 100), animator->desiredTargetOffset());
EXPECT_GT(offset.width(), 0);
EXPECT_GT(offset.height(), 0);
// Adjustment
ScrollOffset newOffset = offset + ScrollOffset(10, -10);
animator->adjustAnimationAndSetScrollOffset(newOffset, AnchoringScroll);
EXPECT_EQ(ScrollOffset(110, 90), animator->desiredTargetOffset());
// Adjusting after finished animation should do nothing.
gMockedTime += 1.0;
animator->updateCompositorAnimations();
animator->tickAnimation(getMockedTime());
EXPECT_EQ(
animator->runStateForTesting(),
ScrollAnimatorCompositorCoordinator::RunState::PostAnimationCleanup);
newOffset = animator->currentOffset() + ScrollOffset(10, -10);
animator->adjustAnimationAndSetScrollOffset(newOffset, AnchoringScroll);
EXPECT_EQ(
animator->runStateForTesting(),
ScrollAnimatorCompositorCoordinator::RunState::PostAnimationCleanup);
EXPECT_EQ(ScrollOffset(110, 90), animator->desiredTargetOffset());
reset(*animator);
// Forced GC in order to finalize objects depending on the mock object.
ThreadState::current()->collectAllGarbage();
}
} // namespace blink