blob: 14951e6f426f87426534ccfd1b3aa3984de9d238 [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 "third_party/blink/renderer/core/scroll/scroll_animator.h"
#include "base/single_thread_task_runner.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/public/platform/scheduler/test/renderer_scheduler_test_support.h"
#include "third_party/blink/public/platform/web_thread.h"
#include "third_party/blink/renderer/core/scroll/scroll_animator_base.h"
#include "third_party/blink/renderer/core/scroll/scrollable_area.h"
#include "third_party/blink/renderer/core/scroll/scrollbar_theme.h"
#include "third_party/blink/renderer/platform/geometry/float_point.h"
#include "third_party/blink/renderer/platform/geometry/int_rect.h"
#include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
namespace blink {
using testing::AtLeast;
using testing::Return;
using testing::_;
static double g_mocked_time = 0.0;
static double GetMockedTime() {
return g_mocked_time;
}
namespace {
class MockScrollableAreaForAnimatorTest
: public GarbageCollectedFinalized<MockScrollableAreaForAnimatorTest>,
public ScrollableArea {
USING_GARBAGE_COLLECTED_MIXIN(MockScrollableAreaForAnimatorTest);
public:
static MockScrollableAreaForAnimatorTest* Create(
bool scroll_animator_enabled,
const ScrollOffset& min_offset,
const ScrollOffset& max_offset) {
return new MockScrollableAreaForAnimatorTest(scroll_animator_enabled,
min_offset, max_offset);
}
MOCK_CONST_METHOD0(VisualRectForScrollbarParts, LayoutRect());
MOCK_CONST_METHOD0(IsActive, bool());
MOCK_CONST_METHOD0(IsThrottled, 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; }
CompositorElementId GetCompositorElementId() const override {
return CompositorElementId();
}
bool ScrollAnimatorEnabled() const override {
return scroll_animator_enabled_;
}
int PageStep(ScrollbarOrientation) const override { return 0; }
IntSize MinimumScrollOffsetInt() const override {
return FlooredIntSize(min_offset_);
}
IntSize MaximumScrollOffsetInt() const override {
return FlooredIntSize(max_offset_);
}
void SetScrollAnimator(ScrollAnimator* scroll_animator) {
animator = scroll_animator;
}
ScrollOffset GetScrollOffset() const override {
if (animator)
return animator->CurrentOffset();
return ScrollableArea::GetScrollOffset();
}
void SetScrollOffset(
const ScrollOffset& offset,
ScrollType type,
ScrollBehavior behavior = kScrollBehaviorInstant) override {
if (animator)
animator->SetCurrentOffset(offset);
ScrollableArea::SetScrollOffset(offset, type, behavior);
}
scoped_refptr<base::SingleThreadTaskRunner> GetTimerTaskRunner() const final {
if (!timer_task_runner_) {
timer_task_runner_ =
blink::scheduler::GetSingleThreadTaskRunnerForTesting();
}
return timer_task_runner_;
}
ScrollbarTheme& GetPageScrollbarTheme() const override {
return ScrollbarTheme::DeprecatedStaticGetTheme();
}
void Trace(blink::Visitor* visitor) override {
visitor->Trace(animator);
ScrollableArea::Trace(visitor);
}
private:
explicit MockScrollableAreaForAnimatorTest(bool scroll_animator_enabled,
const ScrollOffset& min_offset,
const ScrollOffset& max_offset)
: scroll_animator_enabled_(scroll_animator_enabled),
min_offset_(min_offset),
max_offset_(max_offset) {}
bool scroll_animator_enabled_;
ScrollOffset min_offset_;
ScrollOffset max_offset_;
Member<ScrollAnimator> animator;
mutable scoped_refptr<base::SingleThreadTaskRunner> timer_task_runner_;
};
class TestScrollAnimator : public ScrollAnimator {
public:
TestScrollAnimator(ScrollableArea* scrollable_area,
WTF::TimeFunction timing_function)
: ScrollAnimator(scrollable_area, timing_function) {}
~TestScrollAnimator() override = default;
void SetShouldSendToCompositor(bool send) {
should_send_to_compositor_ = send;
}
bool SendAnimationToCompositor() override {
if (should_send_to_compositor_) {
run_state_ =
ScrollAnimatorCompositorCoordinator::RunState::kRunningOnCompositor;
compositor_animation_id_ = 1;
return true;
}
return false;
}
protected:
void AbortAnimation() override {}
private:
bool should_send_to_compositor_ = false;
};
} // namespace
static void Reset(ScrollAnimator& scroll_animator) {
scroll_animator.ScrollToOffsetWithoutAnimation(ScrollOffset());
}
// TODO(skobes): Add unit tests for composited scrolling paths.
TEST(ScrollAnimatorTest, MainThreadStates) {
MockScrollableAreaForAnimatorTest* scrollable_area =
MockScrollableAreaForAnimatorTest::Create(true, ScrollOffset(),
ScrollOffset(1000, 1000));
ScrollAnimator* scroll_animator =
new ScrollAnimator(scrollable_area, GetMockedTime);
EXPECT_CALL(*scrollable_area, UpdateScrollOffset(_, _)).Times(2);
// Once from userScroll, once from updateCompositorAnimations.
EXPECT_CALL(*scrollable_area, RegisterForAnimation()).Times(2);
EXPECT_CALL(*scrollable_area, ScheduleAnimation())
.Times(AtLeast(1))
.WillRepeatedly(Return(true));
// Idle
EXPECT_FALSE(scroll_animator->HasAnimationThatRequiresService());
EXPECT_EQ(scroll_animator->run_state_,
ScrollAnimatorCompositorCoordinator::RunState::kIdle);
// WaitingToSendToCompositor
scroll_animator->UserScroll(kScrollByLine, FloatSize(10, 0));
EXPECT_EQ(scroll_animator->run_state_,
ScrollAnimatorCompositorCoordinator::RunState::
kWaitingToSendToCompositor);
// RunningOnMainThread
g_mocked_time += 0.05;
scroll_animator->UpdateCompositorAnimations();
EXPECT_EQ(
scroll_animator->run_state_,
ScrollAnimatorCompositorCoordinator::RunState::kRunningOnMainThread);
scroll_animator->TickAnimation(GetMockedTime());
EXPECT_EQ(
scroll_animator->run_state_,
ScrollAnimatorCompositorCoordinator::RunState::kRunningOnMainThread);
// PostAnimationCleanup
scroll_animator->CancelAnimation();
EXPECT_EQ(
scroll_animator->run_state_,
ScrollAnimatorCompositorCoordinator::RunState::kPostAnimationCleanup);
// Idle
scroll_animator->UpdateCompositorAnimations();
scroll_animator->TickAnimation(GetMockedTime());
EXPECT_EQ(scroll_animator->run_state_,
ScrollAnimatorCompositorCoordinator::RunState::kIdle);
Reset(*scroll_animator);
// Forced GC in order to finalize objects depending on the mock object.
ThreadState::Current()->CollectAllGarbage();
}
TEST(ScrollAnimatorTest, MainThreadEnabled) {
MockScrollableAreaForAnimatorTest* scrollable_area =
MockScrollableAreaForAnimatorTest::Create(true, ScrollOffset(),
ScrollOffset(1000, 1000));
ScrollAnimator* scroll_animator =
new ScrollAnimator(scrollable_area, GetMockedTime);
EXPECT_CALL(*scrollable_area, UpdateScrollOffset(_, _)).Times(9);
EXPECT_CALL(*scrollable_area, RegisterForAnimation()).Times(6);
EXPECT_CALL(*scrollable_area, ScheduleAnimation())
.Times(AtLeast(1))
.WillRepeatedly(Return(true));
EXPECT_FALSE(scroll_animator->HasAnimationThatRequiresService());
ScrollResult result =
scroll_animator->UserScroll(kScrollByLine, FloatSize(-100, 0));
EXPECT_FALSE(scroll_animator->HasAnimationThatRequiresService());
EXPECT_FALSE(result.did_scroll_x);
EXPECT_FLOAT_EQ(-100.0f, result.unused_scroll_delta_x);
result = scroll_animator->UserScroll(kScrollByLine, FloatSize(100, 0));
EXPECT_TRUE(scroll_animator->HasAnimationThatRequiresService());
EXPECT_TRUE(result.did_scroll_x);
EXPECT_FLOAT_EQ(0.0, result.unused_scroll_delta_x);
g_mocked_time += 0.05;
scroll_animator->UpdateCompositorAnimations();
scroll_animator->TickAnimation(GetMockedTime());
EXPECT_NE(100, scroll_animator->CurrentOffset().Width());
EXPECT_NE(0, scroll_animator->CurrentOffset().Width());
EXPECT_EQ(0, scroll_animator->CurrentOffset().Height());
Reset(*scroll_animator);
scroll_animator->UserScroll(kScrollByPage, FloatSize(100, 0));
EXPECT_TRUE(scroll_animator->HasAnimationThatRequiresService());
g_mocked_time += 0.05;
scroll_animator->UpdateCompositorAnimations();
scroll_animator->TickAnimation(GetMockedTime());
EXPECT_NE(100, scroll_animator->CurrentOffset().Width());
EXPECT_NE(0, scroll_animator->CurrentOffset().Width());
EXPECT_EQ(0, scroll_animator->CurrentOffset().Height());
Reset(*scroll_animator);
scroll_animator->UserScroll(kScrollByPixel, FloatSize(100, 0));
EXPECT_TRUE(scroll_animator->HasAnimationThatRequiresService());
g_mocked_time += 0.05;
scroll_animator->UpdateCompositorAnimations();
scroll_animator->TickAnimation(GetMockedTime());
EXPECT_NE(100, scroll_animator->CurrentOffset().Width());
EXPECT_NE(0, scroll_animator->CurrentOffset().Width());
EXPECT_EQ(0, scroll_animator->CurrentOffset().Height());
g_mocked_time += 1.0;
scroll_animator->UpdateCompositorAnimations();
scroll_animator->TickAnimation(GetMockedTime());
g_mocked_time += 0.05;
scroll_animator->UpdateCompositorAnimations();
EXPECT_FALSE(scroll_animator->HasAnimationThatRequiresService());
EXPECT_EQ(100, scroll_animator->CurrentOffset().Width());
Reset(*scroll_animator);
scroll_animator->UserScroll(kScrollByPrecisePixel, FloatSize(100, 0));
EXPECT_FALSE(scroll_animator->HasAnimationThatRequiresService());
EXPECT_EQ(100, scroll_animator->CurrentOffset().Width());
EXPECT_NE(0, scroll_animator->CurrentOffset().Width());
EXPECT_EQ(0, scroll_animator->CurrentOffset().Height());
Reset(*scroll_animator);
}
// Test that a smooth scroll offset animation is aborted when followed by a
// non-smooth scroll offset animation.
TEST(ScrollAnimatorTest, AnimatedScrollAborted) {
MockScrollableAreaForAnimatorTest* scrollable_area =
MockScrollableAreaForAnimatorTest::Create(true, ScrollOffset(),
ScrollOffset(1000, 1000));
ScrollAnimator* scroll_animator =
new ScrollAnimator(scrollable_area, GetMockedTime);
EXPECT_CALL(*scrollable_area, UpdateScrollOffset(_, _)).Times(3);
EXPECT_CALL(*scrollable_area, RegisterForAnimation()).Times(2);
EXPECT_CALL(*scrollable_area, ScheduleAnimation())
.Times(AtLeast(1))
.WillRepeatedly(Return(true));
EXPECT_FALSE(scroll_animator->HasAnimationThatRequiresService());
// Smooth scroll.
ScrollResult result =
scroll_animator->UserScroll(kScrollByLine, FloatSize(100, 0));
EXPECT_TRUE(scroll_animator->HasAnimationThatRequiresService());
EXPECT_TRUE(result.did_scroll_x);
EXPECT_FLOAT_EQ(0.0, result.unused_scroll_delta_x);
EXPECT_TRUE(scroll_animator->HasRunningAnimation());
g_mocked_time += 0.05;
scroll_animator->UpdateCompositorAnimations();
scroll_animator->TickAnimation(GetMockedTime());
EXPECT_NE(100, scroll_animator->CurrentOffset().Width());
EXPECT_NE(0, scroll_animator->CurrentOffset().Width());
EXPECT_EQ(0, scroll_animator->CurrentOffset().Height());
float x = scroll_animator->CurrentOffset().Width();
// Instant scroll.
result =
scroll_animator->UserScroll(kScrollByPrecisePixel, FloatSize(100, 0));
EXPECT_TRUE(result.did_scroll_x);
g_mocked_time += 0.05;
scroll_animator->UpdateCompositorAnimations();
EXPECT_FALSE(scroll_animator->HasRunningAnimation());
EXPECT_EQ(x + 100, scroll_animator->CurrentOffset().Width());
EXPECT_EQ(0, scroll_animator->CurrentOffset().Height());
Reset(*scroll_animator);
}
// Test that a smooth scroll offset animation running on the compositor is
// completed on the main thread.
TEST(ScrollAnimatorTest, AnimatedScrollTakeover) {
MockScrollableAreaForAnimatorTest* scrollable_area =
MockScrollableAreaForAnimatorTest::Create(true, ScrollOffset(),
ScrollOffset(1000, 1000));
TestScrollAnimator* scroll_animator =
new TestScrollAnimator(scrollable_area, GetMockedTime);
EXPECT_CALL(*scrollable_area, UpdateScrollOffset(_, _)).Times(2);
// Called from userScroll, updateCompositorAnimations, then
// takeOverCompositorAnimation (to re-register after RunningOnCompositor).
EXPECT_CALL(*scrollable_area, RegisterForAnimation()).Times(3);
EXPECT_CALL(*scrollable_area, ScheduleAnimation())
.Times(AtLeast(1))
.WillRepeatedly(Return(true));
EXPECT_FALSE(scroll_animator->HasAnimationThatRequiresService());
// Smooth scroll.
ScrollResult result =
scroll_animator->UserScroll(kScrollByLine, FloatSize(100, 0));
EXPECT_TRUE(scroll_animator->HasAnimationThatRequiresService());
EXPECT_TRUE(result.did_scroll_x);
EXPECT_FLOAT_EQ(0.0, result.unused_scroll_delta_x);
EXPECT_TRUE(scroll_animator->HasRunningAnimation());
// Update compositor animation.
g_mocked_time += 0.05;
scroll_animator->SetShouldSendToCompositor(true);
scroll_animator->UpdateCompositorAnimations();
EXPECT_EQ(
scroll_animator->run_state_,
ScrollAnimatorCompositorCoordinator::RunState::kRunningOnCompositor);
// Takeover.
scroll_animator->TakeOverCompositorAnimation();
EXPECT_EQ(scroll_animator->run_state_,
ScrollAnimatorCompositorCoordinator::RunState::
kRunningOnCompositorButNeedsTakeover);
// Animation should now be running on the main thread.
scroll_animator->SetShouldSendToCompositor(false);
scroll_animator->UpdateCompositorAnimations();
EXPECT_EQ(
scroll_animator->run_state_,
ScrollAnimatorCompositorCoordinator::RunState::kRunningOnMainThread);
scroll_animator->TickAnimation(GetMockedTime());
EXPECT_NE(100, scroll_animator->CurrentOffset().Width());
EXPECT_NE(0, scroll_animator->CurrentOffset().Width());
EXPECT_EQ(0, scroll_animator->CurrentOffset().Height());
Reset(*scroll_animator);
}
TEST(ScrollAnimatorTest, Disabled) {
MockScrollableAreaForAnimatorTest* scrollable_area =
MockScrollableAreaForAnimatorTest::Create(false, ScrollOffset(),
ScrollOffset(1000, 1000));
ScrollAnimator* scroll_animator =
new ScrollAnimator(scrollable_area, GetMockedTime);
EXPECT_CALL(*scrollable_area, UpdateScrollOffset(_, _)).Times(8);
EXPECT_CALL(*scrollable_area, RegisterForAnimation()).Times(0);
scroll_animator->UserScroll(kScrollByLine, FloatSize(100, 0));
EXPECT_EQ(100, scroll_animator->CurrentOffset().Width());
EXPECT_EQ(0, scroll_animator->CurrentOffset().Height());
Reset(*scroll_animator);
scroll_animator->UserScroll(kScrollByPage, FloatSize(100, 0));
EXPECT_EQ(100, scroll_animator->CurrentOffset().Width());
EXPECT_EQ(0, scroll_animator->CurrentOffset().Height());
Reset(*scroll_animator);
scroll_animator->UserScroll(kScrollByDocument, FloatSize(100, 0));
EXPECT_EQ(100, scroll_animator->CurrentOffset().Width());
EXPECT_EQ(0, scroll_animator->CurrentOffset().Height());
Reset(*scroll_animator);
scroll_animator->UserScroll(kScrollByPixel, FloatSize(100, 0));
EXPECT_EQ(100, scroll_animator->CurrentOffset().Width());
EXPECT_EQ(0, scroll_animator->CurrentOffset().Height());
Reset(*scroll_animator);
}
// Test that cancelling an animation resets the animation state.
// See crbug.com/598548.
TEST(ScrollAnimatorTest, CancellingAnimationResetsState) {
MockScrollableAreaForAnimatorTest* scrollable_area =
MockScrollableAreaForAnimatorTest::Create(true, ScrollOffset(),
ScrollOffset(1000, 1000));
ScrollAnimator* scroll_animator =
new ScrollAnimator(scrollable_area, GetMockedTime);
// Called from first userScroll, setCurrentOffset, and second userScroll.
EXPECT_CALL(*scrollable_area, UpdateScrollOffset(_, _)).Times(3);
// Called from userScroll, updateCompositorAnimations.
EXPECT_CALL(*scrollable_area, RegisterForAnimation()).Times(4);
EXPECT_CALL(*scrollable_area, ScheduleAnimation())
.Times(AtLeast(1))
.WillRepeatedly(Return(true));
EXPECT_EQ(0, scroll_animator->CurrentOffset().Width());
EXPECT_EQ(0, scroll_animator->CurrentOffset().Height());
// WaitingToSendToCompositor
scroll_animator->UserScroll(kScrollByLine, FloatSize(10, 0));
EXPECT_EQ(scroll_animator->run_state_,
ScrollAnimatorCompositorCoordinator::RunState::
kWaitingToSendToCompositor);
// RunningOnMainThread
g_mocked_time += 0.05;
scroll_animator->UpdateCompositorAnimations();
EXPECT_EQ(
scroll_animator->run_state_,
ScrollAnimatorCompositorCoordinator::RunState::kRunningOnMainThread);
scroll_animator->TickAnimation(GetMockedTime());
EXPECT_EQ(
scroll_animator->run_state_,
ScrollAnimatorCompositorCoordinator::RunState::kRunningOnMainThread);
// Amount scrolled so far.
float offset_x = scroll_animator->CurrentOffset().Width();
// Interrupt user scroll.
scroll_animator->CancelAnimation();
EXPECT_EQ(
scroll_animator->run_state_,
ScrollAnimatorCompositorCoordinator::RunState::kPostAnimationCleanup);
// Another userScroll after modified scroll offset.
scroll_animator->SetCurrentOffset(ScrollOffset(offset_x + 15, 0));
scroll_animator->UserScroll(kScrollByLine, FloatSize(10, 0));
EXPECT_EQ(scroll_animator->run_state_,
ScrollAnimatorCompositorCoordinator::RunState::
kWaitingToSendToCompositor);
// Finish scroll animation.
g_mocked_time += 1.0;
scroll_animator->UpdateCompositorAnimations();
scroll_animator->TickAnimation(GetMockedTime());
EXPECT_EQ(
scroll_animator->run_state_,
ScrollAnimatorCompositorCoordinator::RunState::kPostAnimationCleanup);
EXPECT_EQ(offset_x + 15 + 10, scroll_animator->CurrentOffset().Width());
EXPECT_EQ(0, scroll_animator->CurrentOffset().Height());
Reset(*scroll_animator);
}
// Test the behavior when in WaitingToCancelOnCompositor and a new user scroll
// happens.
TEST(ScrollAnimatorTest, CancellingCompositorAnimation) {
MockScrollableAreaForAnimatorTest* scrollable_area =
MockScrollableAreaForAnimatorTest::Create(true, ScrollOffset(),
ScrollOffset(1000, 1000));
TestScrollAnimator* scroll_animator =
new TestScrollAnimator(scrollable_area, GetMockedTime);
// Called when reset, not setting anywhere else.
EXPECT_CALL(*scrollable_area, UpdateScrollOffset(_, _)).Times(1);
// Called from userScroll, and first update.
EXPECT_CALL(*scrollable_area, RegisterForAnimation()).Times(4);
EXPECT_CALL(*scrollable_area, ScheduleAnimation())
.Times(AtLeast(1))
.WillRepeatedly(Return(true));
EXPECT_FALSE(scroll_animator->HasAnimationThatRequiresService());
// First user scroll.
ScrollResult result =
scroll_animator->UserScroll(kScrollByLine, FloatSize(100, 0));
EXPECT_TRUE(scroll_animator->HasAnimationThatRequiresService());
EXPECT_TRUE(result.did_scroll_x);
EXPECT_FLOAT_EQ(0.0, result.unused_scroll_delta_x);
EXPECT_TRUE(scroll_animator->HasRunningAnimation());
EXPECT_EQ(100, scroll_animator->DesiredTargetOffset().Width());
EXPECT_EQ(0, scroll_animator->DesiredTargetOffset().Height());
// Update compositor animation.
g_mocked_time += 0.05;
scroll_animator->SetShouldSendToCompositor(true);
scroll_animator->UpdateCompositorAnimations();
EXPECT_EQ(
scroll_animator->run_state_,
ScrollAnimatorCompositorCoordinator::RunState::kRunningOnCompositor);
// Cancel
scroll_animator->CancelAnimation();
EXPECT_EQ(scroll_animator->run_state_,
ScrollAnimatorCompositorCoordinator::RunState::
kWaitingToCancelOnCompositor);
// Unrelated scroll offset update.
scroll_animator->SetCurrentOffset(ScrollOffset(50, 0));
// Desired target offset should be that of the second scroll.
result = scroll_animator->UserScroll(kScrollByLine, FloatSize(100, 0));
EXPECT_TRUE(scroll_animator->HasAnimationThatRequiresService());
EXPECT_TRUE(result.did_scroll_x);
EXPECT_FLOAT_EQ(0.0, result.unused_scroll_delta_x);
EXPECT_EQ(scroll_animator->run_state_,
ScrollAnimatorCompositorCoordinator::RunState::
kWaitingToCancelOnCompositorButNewScroll);
EXPECT_EQ(150, scroll_animator->DesiredTargetOffset().Width());
EXPECT_EQ(0, scroll_animator->DesiredTargetOffset().Height());
// Update compositor animation.
g_mocked_time += 0.05;
scroll_animator->UpdateCompositorAnimations();
EXPECT_EQ(
scroll_animator->run_state_,
ScrollAnimatorCompositorCoordinator::RunState::kRunningOnCompositor);
// Third user scroll after compositor update updates the target.
result = scroll_animator->UserScroll(kScrollByLine, FloatSize(100, 0));
EXPECT_TRUE(scroll_animator->HasAnimationThatRequiresService());
EXPECT_TRUE(result.did_scroll_x);
EXPECT_FLOAT_EQ(0.0, result.unused_scroll_delta_x);
EXPECT_EQ(scroll_animator->run_state_,
ScrollAnimatorCompositorCoordinator::RunState::
kRunningOnCompositorButNeedsUpdate);
EXPECT_EQ(250, scroll_animator->DesiredTargetOffset().Width());
EXPECT_EQ(0, scroll_animator->DesiredTargetOffset().Height());
Reset(*scroll_animator);
// 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) {
MockScrollableAreaForAnimatorTest* scrollable_area =
MockScrollableAreaForAnimatorTest::Create(true, ScrollOffset(),
ScrollOffset(1000, 1000));
TestScrollAnimator* animator =
new TestScrollAnimator(scrollable_area, GetMockedTime);
// From calls to adjust/takeoverImplOnlyScrollOffsetAnimation.
EXPECT_CALL(*scrollable_area, RegisterForAnimation()).Times(3);
// Verify that the adjustment update is cleared.
EXPECT_EQ(animator->run_state_,
ScrollAnimatorCompositorCoordinator::RunState::kIdle);
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(IntSize(110, 90),
animator->ImplOnlyAnimationAdjustmentForTesting());
animator->UpdateCompositorAnimations();
EXPECT_EQ(animator->run_state_,
ScrollAnimatorCompositorCoordinator::RunState::kIdle);
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) {
MockScrollableAreaForAnimatorTest* scrollable_area =
MockScrollableAreaForAnimatorTest::Create(true, ScrollOffset(-100, -100),
ScrollOffset(1000, 1000));
ScrollAnimator* animator = new ScrollAnimator(scrollable_area, GetMockedTime);
scrollable_area->SetScrollAnimator(animator);
// Twice from tickAnimation, once from reset, and twice from
// adjustAnimationAndSetScrollOffset.
EXPECT_CALL(*scrollable_area, UpdateScrollOffset(_, _)).Times(5);
// One from call to userScroll and one from updateCompositorAnimations.
EXPECT_CALL(*scrollable_area, RegisterForAnimation()).Times(2);
EXPECT_CALL(*scrollable_area, ScheduleAnimation())
.Times(AtLeast(1))
.WillRepeatedly(Return(true));
// Idle
EXPECT_FALSE(animator->HasAnimationThatRequiresService());
EXPECT_EQ(ScrollOffset(), animator->CurrentOffset());
// WaitingToSendToCompositor
animator->UserScroll(kScrollByLine, ScrollOffset(100, 100));
// RunningOnMainThread
g_mocked_time += 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 new_offset = offset + ScrollOffset(10, -10);
animator->AdjustAnimationAndSetScrollOffset(new_offset, kAnchoringScroll);
EXPECT_EQ(ScrollOffset(110, 90), animator->DesiredTargetOffset());
// Adjusting after finished animation should do nothing.
g_mocked_time += 1.0;
animator->UpdateCompositorAnimations();
animator->TickAnimation(GetMockedTime());
EXPECT_EQ(
animator->RunStateForTesting(),
ScrollAnimatorCompositorCoordinator::RunState::kPostAnimationCleanup);
new_offset = animator->CurrentOffset() + ScrollOffset(10, -10);
animator->AdjustAnimationAndSetScrollOffset(new_offset, kAnchoringScroll);
EXPECT_EQ(
animator->RunStateForTesting(),
ScrollAnimatorCompositorCoordinator::RunState::kPostAnimationCleanup);
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