|  | /* | 
|  | * 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/scheduler/child/web_scheduler.h" | 
|  | #include "platform/scroll/ScrollAnimatorBase.h" | 
|  | #include "platform/scroll/ScrollableArea.h" | 
|  | #include "platform/scroll/ScrollbarTheme.h" | 
|  | #include "public/platform/Platform.h" | 
|  | #include "public/platform/WebThread.h" | 
|  | #include "public/platform/scheduler/test/renderer_scheduler_test_support.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 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_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) { | 
|  | 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::CreateWebTaskRunnerForTesting(); | 
|  | } | 
|  | return timer_task_runner_; | 
|  | } | 
|  |  | 
|  | ScrollbarTheme& GetPageScrollbarTheme() const override { | 
|  | return ScrollbarTheme::DeprecatedStaticGetTheme(); | 
|  | } | 
|  |  | 
|  | virtual void Trace(blink::Visitor* visitor) { | 
|  | 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(FloatSize(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 |