| /* |
| * 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 |