// Copyright 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "cc/trees/layer_tree_host.h" #include #include #include "base/bind.h" #include "cc/animation/animation_curve.h" #include "cc/animation/animation_host.h" #include "cc/animation/animation_id_provider.h" #include "cc/animation/animation_timeline.h" #include "cc/animation/element_animations.h" #include "cc/animation/keyframe_effect.h" #include "cc/animation/scroll_offset_animation_curve.h" #include "cc/animation/scroll_offset_animations.h" #include "cc/animation/single_keyframe_effect_animation.h" #include "cc/animation/timing_function.h" #include "cc/animation/transform_operations.h" #include "cc/base/completion_event.h" #include "cc/layers/layer.h" #include "cc/layers/layer_impl.h" #include "cc/test/animation_test_common.h" #include "cc/test/fake_content_layer_client.h" #include "cc/test/fake_picture_layer.h" #include "cc/test/layer_tree_test.h" #include "cc/trees/effect_node.h" #include "cc/trees/layer_tree_impl.h" #include "cc/trees/target_property.h" #include "cc/trees/transform_node.h" #include "components/viz/common/quads/compositor_frame.h" namespace cc { namespace { class LayerTreeHostAnimationTest : public LayerTreeTest { public: LayerTreeHostAnimationTest() : timeline_id_(AnimationIdProvider::NextTimelineId()), animation_id_(AnimationIdProvider::NextAnimationId()), animation_child_id_(AnimationIdProvider::NextAnimationId()) { timeline_ = AnimationTimeline::Create(timeline_id_); animation_ = SingleKeyframeEffectAnimation::Create(animation_id_); animation_child_ = SingleKeyframeEffectAnimation::Create(animation_child_id_); animation_->set_animation_delegate(this); } void AttachAnimationsToTimeline() { animation_host()->AddAnimationTimeline(timeline_.get()); layer_tree_host()->SetElementIdsForTesting(); timeline_->AttachAnimation(animation_.get()); timeline_->AttachAnimation(animation_child_.get()); } void GetImplTimelineAndAnimationByID(const LayerTreeHostImpl& host_impl) { AnimationHost* animation_host_impl = GetImplAnimationHost(&host_impl); timeline_impl_ = animation_host_impl->GetTimelineById(timeline_id_); EXPECT_TRUE(timeline_impl_); animation_impl_ = static_cast( timeline_impl_->GetAnimationById(animation_id_)); EXPECT_TRUE(animation_impl_); animation_child_impl_ = static_cast( timeline_impl_->GetAnimationById(animation_child_id_)); EXPECT_TRUE(animation_child_impl_); } AnimationHost* GetImplAnimationHost( const LayerTreeHostImpl* host_impl) const { return static_cast(host_impl->mutator_host()); } protected: scoped_refptr timeline_; scoped_refptr animation_; scoped_refptr animation_child_; scoped_refptr timeline_impl_; scoped_refptr animation_impl_; scoped_refptr animation_child_impl_; const int timeline_id_; const int animation_id_; const int animation_child_id_; }; // Makes sure that SetNeedsAnimate does not cause the CommitRequested() state to // be set. class LayerTreeHostAnimationTestSetNeedsAnimateShouldNotSetCommitRequested : public LayerTreeHostAnimationTest { public: LayerTreeHostAnimationTestSetNeedsAnimateShouldNotSetCommitRequested() : num_commits_(0) {} void BeginTest() override { PostSetNeedsCommitToMainThread(); } void BeginMainFrame(const viz::BeginFrameArgs& args) override { // We skip the first commit because its the commit that populates the // impl thread with a tree. After the second commit, the test is done. if (num_commits_ != 1) return; layer_tree_host()->SetNeedsAnimate(); // Right now, CommitRequested is going to be true, because during // BeginFrame, we force CommitRequested to true to prevent requests from // hitting the impl thread. But, when the next DidCommit happens, we should // verify that CommitRequested has gone back to false. } void DidCommit() override { if (!num_commits_) { EXPECT_FALSE(layer_tree_host()->CommitRequested()); layer_tree_host()->SetNeedsAnimate(); EXPECT_FALSE(layer_tree_host()->CommitRequested()); } // Verifies that the SetNeedsAnimate we made in ::Animate did not // trigger CommitRequested. EXPECT_FALSE(layer_tree_host()->CommitRequested()); EndTest(); num_commits_++; } private: int num_commits_; }; MULTI_THREAD_TEST_F( LayerTreeHostAnimationTestSetNeedsAnimateShouldNotSetCommitRequested); // Trigger a frame with SetNeedsCommit. Then, inside the resulting animate // callback, request another frame using SetNeedsAnimate. End the test when // animate gets called yet-again, indicating that the proxy is correctly // handling the case where SetNeedsAnimate() is called inside the BeginFrame // flow. class LayerTreeHostAnimationTestSetNeedsAnimateInsideAnimationCallback : public LayerTreeHostAnimationTest { public: LayerTreeHostAnimationTestSetNeedsAnimateInsideAnimationCallback() : num_begin_frames_(0) {} void BeginTest() override { PostSetNeedsCommitToMainThread(); } void BeginMainFrame(const viz::BeginFrameArgs& args) override { if (!num_begin_frames_) { layer_tree_host()->SetNeedsAnimate(); num_begin_frames_++; return; } EndTest(); } private: int num_begin_frames_; }; SINGLE_AND_MULTI_THREAD_TEST_F( LayerTreeHostAnimationTestSetNeedsAnimateInsideAnimationCallback); // Add a layer animation and confirm that // LayerTreeHostImpl::UpdateAnimationState does get called. class LayerTreeHostAnimationTestAddKeyframeModel : public LayerTreeHostAnimationTest { public: LayerTreeHostAnimationTestAddKeyframeModel() : update_animation_state_was_called_(false) {} void BeginTest() override { AttachAnimationsToTimeline(); animation_->AttachElement(layer_tree_host()->root_layer()->element_id()); PostAddOpacityAnimationToMainThreadInstantly(animation_.get()); } void UpdateAnimationState(LayerTreeHostImpl* host_impl, bool has_unfinished_animation) override { EXPECT_FALSE(has_unfinished_animation); update_animation_state_was_called_ = true; } void NotifyAnimationStarted(base::TimeTicks monotonic_time, int target_property, int group) override { EXPECT_LT(base::TimeTicks(), monotonic_time); KeyframeModel* keyframe_model = animation_->GetKeyframeModel(TargetProperty::OPACITY); if (keyframe_model) animation_->RemoveKeyframeModel(keyframe_model->id()); EndTest(); } void AfterTest() override { EXPECT_TRUE(update_animation_state_was_called_); } private: bool update_animation_state_was_called_; }; SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostAnimationTestAddKeyframeModel); // Add an animation that does not cause damage, followed by an animation that // does. Confirm that the first animation finishes even though // enable_early_damage_check should abort draws. class LayerTreeHostAnimationTestNoDamageAnimation : public LayerTreeHostAnimationTest { void InitializeSettings(LayerTreeSettings* settings) override { settings->using_synchronous_renderer_compositor = true; settings->enable_early_damage_check = true; } public: LayerTreeHostAnimationTestNoDamageAnimation() : started_animating_(false), finished_animating_(false) {} void BeginTest() override { AttachAnimationsToTimeline(); animation_->AttachElement(layer_tree_host()->root_layer()->element_id()); PostAddNoDamageAnimationToMainThread(animation_.get()); PostAddOpacityAnimationToMainThread(animation_.get()); } void AnimateLayers(LayerTreeHostImpl* host_impl, base::TimeTicks monotonic_time) override { base::AutoLock auto_lock(lock_); started_animating_ = true; } void DrawLayersOnThread(LayerTreeHostImpl* host_impl) override { base::AutoLock auto_lock(lock_); if (started_animating_ && finished_animating_) EndTest(); } void NotifyAnimationFinished(base::TimeTicks monotonic_time, int target_property, int group) override { base::AutoLock auto_lock(lock_); finished_animating_ = true; } private: bool started_animating_; bool finished_animating_; base::Lock lock_; }; // This behavior is specific to Android WebView, which only uses // multi-threaded compositor. MULTI_THREAD_TEST_F(LayerTreeHostAnimationTestNoDamageAnimation); // Add a layer animation to a layer, but continually fail to draw. Confirm that // after a while, we do eventually force a draw. class LayerTreeHostAnimationTestCheckerboardDoesNotStarveDraws : public LayerTreeHostAnimationTest { public: LayerTreeHostAnimationTestCheckerboardDoesNotStarveDraws() : started_animating_(false) {} void BeginTest() override { AttachAnimationsToTimeline(); animation_->AttachElement(layer_tree_host()->root_layer()->element_id()); PostAddOpacityAnimationToMainThread(animation_.get()); } void AnimateLayers(LayerTreeHostImpl* host_impl, base::TimeTicks monotonic_time) override { started_animating_ = true; } void DrawLayersOnThread(LayerTreeHostImpl* host_impl) override { if (started_animating_) EndTest(); } DrawResult PrepareToDrawOnThread(LayerTreeHostImpl* host_impl, LayerTreeHostImpl::FrameData* frame, DrawResult draw_result) override { return DRAW_ABORTED_CHECKERBOARD_ANIMATIONS; } private: bool started_animating_; }; // Starvation can only be an issue with the MT compositor. MULTI_THREAD_TEST_F(LayerTreeHostAnimationTestCheckerboardDoesNotStarveDraws); // Ensures that animations eventually get deleted. class LayerTreeHostAnimationTestAnimationsGetDeleted : public LayerTreeHostAnimationTest { public: LayerTreeHostAnimationTestAnimationsGetDeleted() : started_animating_(false) {} void BeginTest() override { AttachAnimationsToTimeline(); animation_->AttachElement(layer_tree_host()->root_layer()->element_id()); PostAddOpacityAnimationToMainThread(animation_.get()); } void AnimateLayers(LayerTreeHostImpl* host_impl, base::TimeTicks monotonic_time) override { bool have_animations = !GetImplAnimationHost(host_impl) ->ticking_animations_for_testing() .empty(); if (!started_animating_ && have_animations) { started_animating_ = true; return; } if (started_animating_ && !have_animations) EndTest(); } void NotifyAnimationFinished(base::TimeTicks monotonic_time, int target_property, int group) override { // Animations on the impl-side ElementAnimations only get deleted during // a commit, so we need to schedule a commit. layer_tree_host()->SetNeedsCommit(); } private: bool started_animating_; }; SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostAnimationTestAnimationsGetDeleted); // Ensure that an animation's timing function is respected. class LayerTreeHostAnimationTestAddKeyframeModelWithTimingFunction : public LayerTreeHostAnimationTest { public: void SetupTree() override { LayerTreeHostAnimationTest::SetupTree(); picture_ = FakePictureLayer::Create(&client_); picture_->SetBounds(gfx::Size(4, 4)); client_.set_bounds(picture_->bounds()); layer_tree_host()->root_layer()->AddChild(picture_); AttachAnimationsToTimeline(); animation_child_->AttachElement(picture_->element_id()); } void BeginTest() override { PostAddOpacityAnimationToMainThread(animation_child_.get()); } void AnimateLayers(LayerTreeHostImpl* host_impl, base::TimeTicks monotonic_time) override { // TODO(ajuma): This test only checks the active tree. Add checks for // pending tree too. if (!host_impl->active_tree()->root_layer()) return; // Wait for the commit with the animation to happen. if (host_impl->active_tree()->source_frame_number() != 0) return; // The impl thread may start a second frame before the test finishes. This // can lead to a race as if the main thread has committed a new sync tree // the impl thread can now delete the KeyframeModel as the animation only // lasts a single frame and no longer affects either tree. Only test the // first frame the animation shows up for. if (!first_animation_frame_) return; first_animation_frame_ = false; scoped_refptr timeline_impl = GetImplAnimationHost(host_impl)->GetTimelineById(timeline_id_); scoped_refptr animation_child_impl = static_cast( timeline_impl->GetAnimationById(animation_child_id_)); KeyframeModel* keyframe_model = animation_child_impl->GetKeyframeModel(TargetProperty::OPACITY); const FloatAnimationCurve* curve = keyframe_model->curve()->ToFloatAnimationCurve(); float start_opacity = curve->GetValue(base::TimeDelta()); float end_opacity = curve->GetValue(curve->Duration()); float linearly_interpolated_opacity = 0.25f * end_opacity + 0.75f * start_opacity; base::TimeDelta time = curve->Duration() * 0.25f; // If the linear timing function associated with this animation was not // picked up, then the linearly interpolated opacity would be different // because of the default ease timing function. EXPECT_FLOAT_EQ(linearly_interpolated_opacity, curve->GetValue(time)); EndTest(); } FakeContentLayerClient client_; scoped_refptr picture_; bool first_animation_frame_ = true; }; SINGLE_AND_MULTI_THREAD_TEST_F( LayerTreeHostAnimationTestAddKeyframeModelWithTimingFunction); // Ensures that main thread animations have their start times synchronized with // impl thread animations. class LayerTreeHostAnimationTestSynchronizeAnimationStartTimes : public LayerTreeHostAnimationTest { public: void SetupTree() override { LayerTreeHostAnimationTest::SetupTree(); picture_ = FakePictureLayer::Create(&client_); picture_->SetBounds(gfx::Size(4, 4)); client_.set_bounds(picture_->bounds()); layer_tree_host()->root_layer()->AddChild(picture_); AttachAnimationsToTimeline(); animation_child_->set_animation_delegate(this); animation_child_->AttachElement(picture_->element_id()); } void BeginTest() override { PostAddOpacityAnimationToMainThread(animation_child_.get()); } void NotifyAnimationStarted(base::TimeTicks monotonic_time, int target_property, int group) override { KeyframeModel* keyframe_model = animation_child_->GetKeyframeModel(TargetProperty::OPACITY); main_start_time_ = keyframe_model->start_time(); animation_child_->RemoveKeyframeModel(keyframe_model->id()); EndTest(); } void UpdateAnimationState(LayerTreeHostImpl* impl_host, bool has_unfinished_animation) override { scoped_refptr timeline_impl = GetImplAnimationHost(impl_host)->GetTimelineById(timeline_id_); scoped_refptr animation_child_impl = static_cast( timeline_impl->GetAnimationById(animation_child_id_)); KeyframeModel* keyframe_model = animation_child_impl->GetKeyframeModel(TargetProperty::OPACITY); if (!keyframe_model) return; impl_start_time_ = keyframe_model->start_time(); } void AfterTest() override { EXPECT_EQ(impl_start_time_, main_start_time_); EXPECT_LT(base::TimeTicks(), impl_start_time_); } private: base::TimeTicks main_start_time_; base::TimeTicks impl_start_time_; FakeContentLayerClient client_; scoped_refptr picture_; }; SINGLE_AND_MULTI_THREAD_TEST_F( LayerTreeHostAnimationTestSynchronizeAnimationStartTimes); // Ensures that notify animation finished is called. class LayerTreeHostAnimationTestAnimationFinishedEvents : public LayerTreeHostAnimationTest { public: void BeginTest() override { AttachAnimationsToTimeline(); animation_->AttachElement(layer_tree_host()->root_layer()->element_id()); PostAddOpacityAnimationToMainThreadInstantly(animation_.get()); } void NotifyAnimationFinished(base::TimeTicks monotonic_time, int target_property, int group) override { KeyframeModel* keyframe_model = animation_->GetKeyframeModel(TargetProperty::OPACITY); if (keyframe_model) animation_->RemoveKeyframeModel(keyframe_model->id()); EndTest(); } }; SINGLE_AND_MULTI_THREAD_TEST_F( LayerTreeHostAnimationTestAnimationFinishedEvents); // Ensures that when opacity is being animated, this value does not cause the // subtree to be skipped. class LayerTreeHostAnimationTestDoNotSkipLayersWithAnimatedOpacity : public LayerTreeHostAnimationTest { public: LayerTreeHostAnimationTestDoNotSkipLayersWithAnimatedOpacity() : update_check_layer_() {} void SetupTree() override { update_check_layer_ = FakePictureLayer::Create(&client_); update_check_layer_->SetOpacity(0.f); layer_tree_host()->SetRootLayer(update_check_layer_); client_.set_bounds(update_check_layer_->bounds()); LayerTreeHostAnimationTest::SetupTree(); AttachAnimationsToTimeline(); animation_->AttachElement(update_check_layer_->element_id()); } void BeginTest() override { PostAddOpacityAnimationToMainThread(animation_.get()); } void DidActivateTreeOnThread(LayerTreeHostImpl* host_impl) override { scoped_refptr timeline_impl = GetImplAnimationHost(host_impl)->GetTimelineById(timeline_id_); scoped_refptr animation_impl = static_cast( timeline_impl->GetAnimationById(animation_id_)); KeyframeModel* keyframe_model_impl = animation_impl->GetKeyframeModel(TargetProperty::OPACITY); animation_impl->RemoveKeyframeModel(keyframe_model_impl->id()); EndTest(); } void AfterTest() override { // Update() should have been called once, proving that the layer was not // skipped. EXPECT_EQ(1, update_check_layer_->update_count()); // clear update_check_layer_ so LayerTreeHost dies. update_check_layer_ = nullptr; } private: FakeContentLayerClient client_; scoped_refptr update_check_layer_; }; SINGLE_AND_MULTI_THREAD_TEST_F( LayerTreeHostAnimationTestDoNotSkipLayersWithAnimatedOpacity); // Layers added to tree with existing active animations should have the // animation correctly recognized. class LayerTreeHostAnimationTestLayerAddedWithAnimation : public LayerTreeHostAnimationTest { public: void BeginTest() override { PostSetNeedsCommitToMainThread(); } void DidCommit() override { if (layer_tree_host()->SourceFrameNumber() == 1) { AttachAnimationsToTimeline(); scoped_refptr layer = Layer::Create(); layer->SetElementId(ElementId(42)); animation_->AttachElement(layer->element_id()); animation_->set_animation_delegate(this); // Any valid AnimationCurve will do here. std::unique_ptr curve(new FakeFloatAnimationCurve()); std::unique_ptr keyframe_model(KeyframeModel::Create( std::move(curve), 1, 1, TargetProperty::OPACITY)); animation_->AddKeyframeModel(std::move(keyframe_model)); // We add the animation *before* attaching the layer to the tree. layer_tree_host()->root_layer()->AddChild(layer); } } void AnimateLayers(LayerTreeHostImpl* impl_host, base::TimeTicks monotonic_time) override { EndTest(); } }; SINGLE_AND_MULTI_THREAD_TEST_F( LayerTreeHostAnimationTestLayerAddedWithAnimation); class LayerTreeHostAnimationTestCancelAnimateCommit : public LayerTreeHostAnimationTest { public: LayerTreeHostAnimationTestCancelAnimateCommit() : num_begin_frames_(0), num_commit_calls_(0), num_draw_calls_(0) {} void BeginTest() override { PostSetNeedsCommitToMainThread(); } void BeginMainFrame(const viz::BeginFrameArgs& args) override { num_begin_frames_++; // No-op animate will cancel the commit. if (layer_tree_host()->SourceFrameNumber() == 1) { EndTest(); return; } layer_tree_host()->SetNeedsAnimate(); } void CommitCompleteOnThread(LayerTreeHostImpl* impl) override { num_commit_calls_++; if (impl->active_tree()->source_frame_number() > 1) FAIL() << "Commit should have been canceled."; } void DrawLayersOnThread(LayerTreeHostImpl* impl) override { num_draw_calls_++; if (impl->active_tree()->source_frame_number() > 1) FAIL() << "Draw should have been canceled."; } void AfterTest() override { EXPECT_EQ(2, num_begin_frames_); EXPECT_EQ(1, num_commit_calls_); EXPECT_EQ(1, num_draw_calls_); } private: int num_begin_frames_; int num_commit_calls_; int num_draw_calls_; }; MULTI_THREAD_TEST_F(LayerTreeHostAnimationTestCancelAnimateCommit); class LayerTreeHostAnimationTestForceRedraw : public LayerTreeHostAnimationTest { public: LayerTreeHostAnimationTestForceRedraw() : num_animate_(0), num_draw_layers_(0) {} void BeginTest() override { PostSetNeedsCommitToMainThread(); } void BeginMainFrame(const viz::BeginFrameArgs& args) override { if (++num_animate_ < 2) layer_tree_host()->SetNeedsAnimate(); } void UpdateLayerTreeHost() override { layer_tree_host()->SetNeedsCommitWithForcedRedraw(); } void DrawLayersOnThread(LayerTreeHostImpl* impl) override { if (++num_draw_layers_ == 2) EndTest(); } void AfterTest() override { // The first commit will always draw; make sure the second draw triggered // by the animation was not cancelled. EXPECT_EQ(2, num_draw_layers_); EXPECT_EQ(2, num_animate_); } private: int num_animate_; int num_draw_layers_; }; MULTI_THREAD_TEST_F(LayerTreeHostAnimationTestForceRedraw); class LayerTreeHostAnimationTestAnimateAfterSetNeedsCommit : public LayerTreeHostAnimationTest { public: LayerTreeHostAnimationTestAnimateAfterSetNeedsCommit() : num_animate_(0), num_draw_layers_(0) {} void BeginTest() override { PostSetNeedsCommitToMainThread(); } void BeginMainFrame(const viz::BeginFrameArgs& args) override { if (++num_animate_ <= 2) { layer_tree_host()->SetNeedsCommit(); layer_tree_host()->SetNeedsAnimate(); } } void DrawLayersOnThread(LayerTreeHostImpl* impl) override { if (++num_draw_layers_ == 2) EndTest(); } void AfterTest() override { // The first commit will always draw; make sure the second draw triggered // by the SetNeedsCommit was not cancelled. EXPECT_EQ(2, num_draw_layers_); EXPECT_GE(num_animate_, 2); } private: int num_animate_; int num_draw_layers_; }; MULTI_THREAD_TEST_F(LayerTreeHostAnimationTestAnimateAfterSetNeedsCommit); // Animations should not be started when frames are being skipped due to // checkerboard. class LayerTreeHostAnimationTestCheckerboardDoesntStartAnimations : public LayerTreeHostAnimationTest { void SetupTree() override { LayerTreeHostAnimationTest::SetupTree(); picture_ = FakePictureLayer::Create(&client_); picture_->SetBounds(gfx::Size(4, 4)); client_.set_bounds(picture_->bounds()); layer_tree_host()->root_layer()->AddChild(picture_); AttachAnimationsToTimeline(); animation_child_->AttachElement(picture_->element_id()); animation_child_->set_animation_delegate(this); } void BeginTest() override { prevented_draw_ = 0; started_times_ = 0; PostSetNeedsCommitToMainThread(); } DrawResult PrepareToDrawOnThread(LayerTreeHostImpl* host_impl, LayerTreeHostImpl::FrameData* frame_data, DrawResult draw_result) override { // Don't checkerboard when the first animation wants to start. if (host_impl->active_tree()->source_frame_number() < 2) return draw_result; if (TestEnded()) return draw_result; // Act like there is checkerboard when the second animation wants to draw. ++prevented_draw_; if (prevented_draw_ > 2) EndTest(); return DRAW_ABORTED_CHECKERBOARD_ANIMATIONS; } void DidCommitAndDrawFrame() override { switch (layer_tree_host()->SourceFrameNumber()) { case 1: // The animation is longer than 1 BeginFrame interval. AddOpacityTransitionToAnimation(animation_child_.get(), 0.1, 0.2f, 0.8f, false); break; case 2: // This second animation will not be drawn so it should not start. AddAnimatedTransformToAnimation(animation_child_.get(), 0.1, 5, 5); break; } } void NotifyAnimationStarted(base::TimeTicks monotonic_time, int target_property, int group) override { if (TestEnded()) return; started_times_++; } void AfterTest() override { // Make sure we tried to draw the second animation but failed. EXPECT_LT(0, prevented_draw_); // The first animation should be started, but the second should not because // of checkerboard. EXPECT_EQ(1, started_times_); } int prevented_draw_; int started_times_; FakeContentLayerClient client_; scoped_refptr picture_; }; MULTI_THREAD_TEST_F( LayerTreeHostAnimationTestCheckerboardDoesntStartAnimations); // Verifies that scroll offset animations are only accepted when impl-scrolling // is supported, and that when scroll offset animations are accepted, // scroll offset updates are sent back to the main thread. class LayerTreeHostAnimationTestScrollOffsetChangesArePropagated : public LayerTreeHostAnimationTest { public: void SetupTree() override { LayerTreeHostAnimationTest::SetupTree(); scroll_layer_ = FakePictureLayer::Create(&client_); scroll_layer_->SetScrollable(gfx::Size(100, 100)); scroll_layer_->SetBounds(gfx::Size(1000, 1000)); client_.set_bounds(scroll_layer_->bounds()); scroll_layer_->SetScrollOffset(gfx::ScrollOffset(10, 20)); layer_tree_host()->root_layer()->AddChild(scroll_layer_); AttachAnimationsToTimeline(); animation_child_->AttachElement(scroll_layer_->element_id()); } void BeginTest() override { PostSetNeedsCommitToMainThread(); } void DidCommit() override { switch (layer_tree_host()->SourceFrameNumber()) { case 1: { std::unique_ptr curve( ScrollOffsetAnimationCurve::Create( gfx::ScrollOffset(500.f, 550.f), CubicBezierTimingFunction::CreatePreset( CubicBezierTimingFunction::EaseType::EASE_IN_OUT))); std::unique_ptr keyframe_model(KeyframeModel::Create( std::move(curve), 1, 0, TargetProperty::SCROLL_OFFSET)); keyframe_model->set_needs_synchronized_start_time(true); bool impl_scrolling_supported = proxy()->SupportsImplScrolling(); if (impl_scrolling_supported) animation_child_->AddKeyframeModel(std::move(keyframe_model)); else EndTest(); break; } default: EXPECT_GE(scroll_layer_->CurrentScrollOffset().x(), 10); EXPECT_GE(scroll_layer_->CurrentScrollOffset().y(), 20); if (scroll_layer_->CurrentScrollOffset().x() > 10 && scroll_layer_->CurrentScrollOffset().y() > 20) EndTest(); } } private: FakeContentLayerClient client_; scoped_refptr scroll_layer_; }; SINGLE_AND_MULTI_THREAD_TEST_F( LayerTreeHostAnimationTestScrollOffsetChangesArePropagated); // Verifies that when a main thread scrolling reason gets added, the // notification to takover the animation on the main thread gets sent. class LayerTreeHostAnimationTestScrollOffsetAnimationTakeover : public LayerTreeHostAnimationTest { public: LayerTreeHostAnimationTestScrollOffsetAnimationTakeover() = default; void SetupTree() override { LayerTreeHostAnimationTest::SetupTree(); scroll_layer_ = FakePictureLayer::Create(&client_); scroll_layer_->SetBounds(gfx::Size(10000, 10000)); client_.set_bounds(scroll_layer_->bounds()); scroll_layer_->SetScrollOffset(gfx::ScrollOffset(10, 20)); scroll_layer_->SetScrollable(gfx::Size(10, 10)); layer_tree_host()->root_layer()->AddChild(scroll_layer_); AttachAnimationsToTimeline(); animation_child_->AttachElement(scroll_layer_->element_id()); // Allows NotifyAnimationTakeover to get called. animation_child_->set_animation_delegate(this); } void BeginTest() override { PostSetNeedsCommitToMainThread(); } void DidCommit() override { if (layer_tree_host()->SourceFrameNumber() == 1) { // Add an update after the first commit to trigger the animation takeover // path. animation_host()->scroll_offset_animations().AddTakeoverUpdate( scroll_layer_->element_id()); EXPECT_TRUE( animation_host()->scroll_offset_animations().HasUpdatesForTesting()); } } void WillCommitCompleteOnThread(LayerTreeHostImpl* host_impl) override { if (host_impl->sync_tree()->source_frame_number() == 0) { GetImplAnimationHost(host_impl)->ImplOnlyScrollAnimationCreate( scroll_layer_->element_id(), gfx::ScrollOffset(650.f, 750.f), gfx::ScrollOffset(10, 20), base::TimeDelta(), base::TimeDelta()); } } void NotifyAnimationTakeover(base::TimeTicks monotonic_time, int target_property, base::TimeTicks animation_start_time, std::unique_ptr curve) override { EndTest(); } private: FakeContentLayerClient client_; scoped_refptr scroll_layer_; }; MULTI_THREAD_TEST_F(LayerTreeHostAnimationTestScrollOffsetAnimationTakeover); // Verifies that an impl-only scroll offset animation gets updated when the // scroll offset is adjusted on the main thread. class LayerTreeHostAnimationTestScrollOffsetAnimationAdjusted : public LayerTreeHostAnimationTest { public: LayerTreeHostAnimationTestScrollOffsetAnimationAdjusted() = default; void SetupTree() override { LayerTreeHostAnimationTest::SetupTree(); scroll_layer_ = FakePictureLayer::Create(&client_); scroll_layer_->SetBounds(gfx::Size(10000, 10000)); client_.set_bounds(scroll_layer_->bounds()); scroll_layer_->SetScrollOffset(gfx::ScrollOffset(10, 20)); scroll_layer_->SetScrollable(gfx::Size(10, 10)); layer_tree_host()->root_layer()->AddChild(scroll_layer_); AttachAnimationsToTimeline(); } KeyframeEffect& ScrollOffsetKeyframeEffect( const LayerTreeHostImpl& host_impl, scoped_refptr layer) const { scoped_refptr element_animations = GetImplAnimationHost(&host_impl) ->GetElementAnimationsForElementId(layer->element_id()); DCHECK(element_animations); KeyframeEffect* keyframe_effect = &*element_animations->FirstKeyframeEffectForTesting(); DCHECK(keyframe_effect); return *keyframe_effect; } void BeginTest() override { PostSetNeedsCommitToMainThread(); } void DidCommit() override { if (layer_tree_host()->SourceFrameNumber() == 1) { // Add an update after the first commit to trigger the animation update // path. animation_host()->scroll_offset_animations().AddAdjustmentUpdate( scroll_layer_->element_id(), gfx::Vector2dF(100.f, 100.f)); EXPECT_TRUE( animation_host()->scroll_offset_animations().HasUpdatesForTesting()); } else if (layer_tree_host()->SourceFrameNumber() == 2) { // Verify that the update queue is cleared after the update is applied. EXPECT_FALSE( animation_host()->scroll_offset_animations().HasUpdatesForTesting()); } } void BeginCommitOnThread(LayerTreeHostImpl* host_impl) override { // Note that the frame number gets incremented after BeginCommitOnThread but // before WillCommitCompleteOnThread and CommitCompleteOnThread. if (host_impl->sync_tree()->source_frame_number() == 0) { GetImplTimelineAndAnimationByID(*host_impl); // This happens after the impl-only animation is added in // WillCommitCompleteOnThread. KeyframeModel* keyframe_model = ScrollOffsetKeyframeEffect(*host_impl, scroll_layer_) .GetKeyframeModel(TargetProperty::SCROLL_OFFSET); DCHECK(keyframe_model); ScrollOffsetAnimationCurve* curve = keyframe_model->curve()->ToScrollOffsetAnimationCurve(); // Verifiy the initial and target position before the scroll offset // update from MT. EXPECT_EQ(gfx::ScrollOffset(10.f, 20.f), curve->GetValue(base::TimeDelta())); EXPECT_EQ(gfx::ScrollOffset(650.f, 750.f), curve->target_value()); } } void WillCommitCompleteOnThread(LayerTreeHostImpl* host_impl) override { if (host_impl->sync_tree()->source_frame_number() == 0) { GetImplAnimationHost(host_impl)->ImplOnlyScrollAnimationCreate( scroll_layer_->element_id(), gfx::ScrollOffset(650.f, 750.f), gfx::ScrollOffset(10, 20), base::TimeDelta(), base::TimeDelta()); } } void CommitCompleteOnThread(LayerTreeHostImpl* host_impl) override { if (host_impl->sync_tree()->source_frame_number() == 1) { KeyframeModel* keyframe_model = ScrollOffsetKeyframeEffect(*host_impl, scroll_layer_) .GetKeyframeModel(TargetProperty::SCROLL_OFFSET); DCHECK(keyframe_model); ScrollOffsetAnimationCurve* curve = keyframe_model->curve()->ToScrollOffsetAnimationCurve(); // Verifiy the initial and target position after the scroll offset // update from MT EXPECT_EQ(KeyframeModel::RunState::STARTING, keyframe_model->run_state()); EXPECT_EQ(gfx::ScrollOffset(110.f, 120.f), curve->GetValue(base::TimeDelta())); EXPECT_EQ(gfx::ScrollOffset(750.f, 850.f), curve->target_value()); EndTest(); } } private: FakeContentLayerClient client_; scoped_refptr scroll_layer_; }; MULTI_THREAD_TEST_F(LayerTreeHostAnimationTestScrollOffsetAnimationAdjusted); // Tests that presentation-time requested from the main-thread is attached to // the correct frame (i.e. activation needs to happen before the // presentation-request is attached to the frame). class LayerTreeHostPresentationDuringAnimation : public LayerTreeHostAnimationTest { public: void SetupTree() override { LayerTreeHostAnimationTest::SetupTree(); scroll_layer_ = FakePictureLayer::Create(&client_); scroll_layer_->SetScrollable(gfx::Size(100, 100)); scroll_layer_->SetBounds(gfx::Size(10000, 10000)); client_.set_bounds(scroll_layer_->bounds()); scroll_layer_->SetScrollOffset(gfx::ScrollOffset(100.0, 200.0)); layer_tree_host()->root_layer()->AddChild(scroll_layer_); std::unique_ptr curve( ScrollOffsetAnimationCurve::Create( gfx::ScrollOffset(6500.f, 7500.f), CubicBezierTimingFunction::CreatePreset( CubicBezierTimingFunction::EaseType::EASE_IN_OUT))); std::unique_ptr keyframe_model(KeyframeModel::Create( std::move(curve), 1, 0, TargetProperty::SCROLL_OFFSET)); keyframe_model->set_needs_synchronized_start_time(true); AttachAnimationsToTimeline(); animation_child_->AttachElement(scroll_layer_->element_id()); animation_child_->AddKeyframeModel(std::move(keyframe_model)); } void BeginTest() override { PostSetNeedsCommitToMainThread(); } void BeginMainFrame(const viz::BeginFrameArgs& args) override { PostSetNeedsCommitToMainThread(); } void BeginCommitOnThread(LayerTreeHostImpl* host_impl) override { if (host_impl->active_tree()->source_frame_number() == 1) { request_token_ = host_impl->next_frame_token(); layer_tree_host()->RequestPresentationTimeForNextFrame(base::BindOnce( &LayerTreeHostPresentationDuringAnimation::OnPresentation, base::Unretained(this))); host_impl->BlockNotifyReadyToActivateForTesting(true); } } void WillBeginImplFrameOnThread(LayerTreeHostImpl* host_impl, const viz::BeginFrameArgs& args) override { if (host_impl->next_frame_token() >= 5) host_impl->BlockNotifyReadyToActivateForTesting(false); } void DisplayReceivedCompositorFrameOnThread( const viz::CompositorFrame& frame) override { received_token_ = frame.metadata.frame_token; } void AfterTest() override { EXPECT_GT(request_token_, 0u); EXPECT_GT(received_token_, request_token_); EXPECT_GE(received_token_, 5u); } private: void OnPresentation(const gfx::PresentationFeedback& feedback) { EndTest(); } FakeContentLayerClient client_; scoped_refptr scroll_layer_; uint32_t request_token_ = 0; uint32_t received_token_ = 0; }; MULTI_THREAD_TEST_F(LayerTreeHostPresentationDuringAnimation); // Verifies that when the main thread removes a scroll animation and sets a new // scroll position, the active tree takes on exactly this new scroll position // after activation, and the main thread doesn't receive a spurious scroll // delta. class LayerTreeHostAnimationTestScrollOffsetAnimationRemoval : public LayerTreeHostAnimationTest { public: LayerTreeHostAnimationTestScrollOffsetAnimationRemoval() : final_postion_(50.0, 100.0) {} void SetupTree() override { LayerTreeHostAnimationTest::SetupTree(); scroll_layer_ = FakePictureLayer::Create(&client_); scroll_layer_->SetScrollable(gfx::Size(100, 100)); scroll_layer_->SetBounds(gfx::Size(10000, 10000)); client_.set_bounds(scroll_layer_->bounds()); scroll_layer_->SetScrollOffset(gfx::ScrollOffset(100.0, 200.0)); layer_tree_host()->root_layer()->AddChild(scroll_layer_); std::unique_ptr curve( ScrollOffsetAnimationCurve::Create( gfx::ScrollOffset(6500.f, 7500.f), CubicBezierTimingFunction::CreatePreset( CubicBezierTimingFunction::EaseType::EASE_IN_OUT))); std::unique_ptr keyframe_model(KeyframeModel::Create( std::move(curve), 1, 0, TargetProperty::SCROLL_OFFSET)); keyframe_model->set_needs_synchronized_start_time(true); AttachAnimationsToTimeline(); animation_child_->AttachElement(scroll_layer_->element_id()); animation_child_->AddKeyframeModel(std::move(keyframe_model)); } void BeginTest() override { PostSetNeedsCommitToMainThread(); } void BeginMainFrame(const viz::BeginFrameArgs& args) override { switch (layer_tree_host()->SourceFrameNumber()) { case 0: EXPECT_EQ(scroll_layer_->CurrentScrollOffset().x(), 100); EXPECT_EQ(scroll_layer_->CurrentScrollOffset().y(), 200); break; case 1: { EXPECT_GE(scroll_layer_->CurrentScrollOffset().x(), 100); EXPECT_GE(scroll_layer_->CurrentScrollOffset().y(), 200); KeyframeModel* keyframe_model = animation_child_->GetKeyframeModel(TargetProperty::SCROLL_OFFSET); animation_child_->RemoveKeyframeModel(keyframe_model->id()); scroll_layer_->SetScrollOffset(final_postion_); break; } default: EXPECT_EQ(final_postion_, scroll_layer_->CurrentScrollOffset()); } } void BeginCommitOnThread(LayerTreeHostImpl* host_impl) override { host_impl->BlockNotifyReadyToActivateForTesting( ShouldBlockActivation(host_impl)); } void WillBeginImplFrameOnThread(LayerTreeHostImpl* host_impl, const viz::BeginFrameArgs& args) override { host_impl->BlockNotifyReadyToActivateForTesting( ShouldBlockActivation(host_impl)); } void WillActivateTreeOnThread(LayerTreeHostImpl* host_impl) override { if (host_impl->pending_tree()->source_frame_number() != 1) return; LayerImpl* scroll_layer_impl = host_impl->pending_tree()->LayerById(scroll_layer_->id()); EXPECT_EQ(final_postion_, scroll_layer_impl->CurrentScrollOffset()); } void DidActivateTreeOnThread(LayerTreeHostImpl* host_impl) override { if (host_impl->active_tree()->source_frame_number() != 1) return; LayerImpl* scroll_layer_impl = host_impl->active_tree()->LayerById(scroll_layer_->id()); EXPECT_EQ(final_postion_, scroll_layer_impl->CurrentScrollOffset()); EndTest(); } void AfterTest() override { EXPECT_EQ(final_postion_, scroll_layer_->CurrentScrollOffset()); } private: bool ShouldBlockActivation(LayerTreeHostImpl* host_impl) { if (!host_impl->pending_tree()) return false; if (!host_impl->active_tree()->root_layer()) return false; scoped_refptr timeline_impl = GetImplAnimationHost(host_impl)->GetTimelineById(timeline_id_); scoped_refptr animation_impl = static_cast( timeline_impl->GetAnimationById(animation_child_id_)); LayerImpl* scroll_layer_impl = host_impl->active_tree()->LayerById(scroll_layer_->id()); KeyframeModel* keyframe_model = animation_impl->GetKeyframeModel(TargetProperty::SCROLL_OFFSET); if (!keyframe_model || keyframe_model->run_state() != KeyframeModel::RUNNING) return false; // Block activation until the running animation has a chance to produce a // scroll delta. gfx::Vector2dF scroll_delta = ScrollDelta(scroll_layer_impl); if (scroll_delta.x() > 0.f || scroll_delta.y() > 0.f) return false; return true; } FakeContentLayerClient client_; scoped_refptr scroll_layer_; const gfx::ScrollOffset final_postion_; }; MULTI_THREAD_TEST_F(LayerTreeHostAnimationTestScrollOffsetAnimationRemoval); // Verifies that the state of a scroll animation is tracked correctly on the // main and compositor thread and that the KeyframeModel is removed when // the scroll animation completes. This is a regression test for // https://crbug.com/962346 and demonstrates the necessity of // LayerTreeHost::AnimateLayers for https://crbug.com/762717. class LayerTreeHostAnimationTestScrollOffsetAnimationCompletion : public LayerTreeHostAnimationTest { public: LayerTreeHostAnimationTestScrollOffsetAnimationCompletion() : final_position_(80.0, 180.0) {} void SetupTree() override { LayerTreeHostAnimationTest::SetupTree(); scroll_layer_ = FakePictureLayer::Create(&client_); scroll_layer_->SetScrollable(gfx::Size(100, 100)); scroll_layer_->SetBounds(gfx::Size(10000, 10000)); client_.set_bounds(scroll_layer_->bounds()); scroll_layer_->SetScrollOffset(gfx::ScrollOffset(100.0, 200.0)); layer_tree_host()->root_layer()->AddChild(scroll_layer_); std::unique_ptr curve( ScrollOffsetAnimationCurve::Create( final_position_, CubicBezierTimingFunction::CreatePreset( CubicBezierTimingFunction::EaseType::EASE_IN_OUT))); std::unique_ptr keyframe_model(KeyframeModel::Create( std::move(curve), 1, 0, TargetProperty::SCROLL_OFFSET)); keyframe_model->set_needs_synchronized_start_time(true); AttachAnimationsToTimeline(); animation_child_->AttachElement(scroll_layer_->element_id()); animation_child_->AddKeyframeModel(std::move(keyframe_model)); } void BeginTest() override { PostSetNeedsCommitToMainThread(); } void BeginMainFrame(const viz::BeginFrameArgs& args) override { KeyframeModel* keyframe_model = animation_child_->GetKeyframeModel(TargetProperty::SCROLL_OFFSET); switch (layer_tree_host()->SourceFrameNumber()) { case 0: EXPECT_EQ(scroll_layer_->CurrentScrollOffset().x(), 100); EXPECT_EQ(scroll_layer_->CurrentScrollOffset().y(), 200); EXPECT_EQ(KeyframeModel::RunState::WAITING_FOR_TARGET_AVAILABILITY, keyframe_model->run_state()); break; case 1: EXPECT_EQ(KeyframeModel::RunState::RUNNING, keyframe_model->run_state()); break; default: break; } } void CommitCompleteOnThread(LayerTreeHostImpl* host_impl) override { if (host_impl->sync_tree()->source_frame_number() == 0) { GetImplTimelineAndAnimationByID(*host_impl); return; } KeyframeModel* keyframe_model = animation_child_impl_->GetKeyframeModel(TargetProperty::SCROLL_OFFSET); if (!keyframe_model || keyframe_model->run_state() == KeyframeModel::RunState::WAITING_FOR_DELETION) EndTest(); } void DidFinishImplFrameOnThread(LayerTreeHostImpl* host_impl) override { if (!animation_child_impl_) return; if (KeyframeModel* keyframe_model = animation_child_impl_->GetKeyframeModel( TargetProperty::SCROLL_OFFSET)) { if (keyframe_model->run_state() == KeyframeModel::RunState::RUNNING) { ran_animation_ = true; } } } void AfterTest() override { // The animation should have run for some frames. EXPECT_TRUE(ran_animation_); // The finished KeyframeModel should have been removed from both the // main and impl side animations. EXPECT_EQ(nullptr, animation_child_->GetKeyframeModel( TargetProperty::SCROLL_OFFSET)); EXPECT_EQ(nullptr, animation_child_impl_->GetKeyframeModel( TargetProperty::SCROLL_OFFSET)); // The scroll should have been completed. EXPECT_EQ(final_position_, scroll_layer_->CurrentScrollOffset()); } private: FakeContentLayerClient client_; scoped_refptr scroll_layer_; const gfx::ScrollOffset final_position_; bool ran_animation_ = false; }; MULTI_THREAD_TEST_F(LayerTreeHostAnimationTestScrollOffsetAnimationCompletion); // When animations are simultaneously added to an existing layer and to a new // layer, they should start at the same time, even when there's already a // running animation on the existing layer. class LayerTreeHostAnimationTestAnimationsAddedToNewAndExistingLayers : public LayerTreeHostAnimationTest { public: LayerTreeHostAnimationTestAnimationsAddedToNewAndExistingLayers() : frame_count_with_pending_tree_(0) {} void SetupTree() override { LayerTreeHostAnimationTest::SetupTree(); layer_ = Layer::Create(); layer_->SetBounds(gfx::Size(4, 4)); layer_tree_host()->root_layer()->AddChild(layer_); layer_tree_host()->SetElementIdsForTesting(); } void BeginTest() override { AttachAnimationsToTimeline(); PostSetNeedsCommitToMainThread(); } void DidCommit() override { if (layer_tree_host()->SourceFrameNumber() == 1) { animation_->AttachElement(layer_->element_id()); AddAnimatedTransformToAnimation(animation_.get(), 4, 1, 1); } else if (layer_tree_host()->SourceFrameNumber() == 2) { AddOpacityTransitionToAnimation(animation_.get(), 1, 0.f, 0.5f, true); scoped_refptr child_layer = Layer::Create(); layer_->AddChild(child_layer); layer_tree_host()->SetElementIdsForTesting(); child_layer->SetBounds(gfx::Size(4, 4)); animation_child_->AttachElement(child_layer->element_id()); animation_child_->set_animation_delegate(this); AddOpacityTransitionToAnimation(animation_child_.get(), 1, 0.f, 0.5f, true); } } void BeginCommitOnThread(LayerTreeHostImpl* host_impl) override { host_impl->BlockNotifyReadyToActivateForTesting(true); } void CommitCompleteOnThread(LayerTreeHostImpl* host_impl) override { // For the commit that added animations to new and existing layers, keep // blocking activation. We want to verify that even with activation blocked, // the animation on the layer that's already in the active tree won't get a // head start. if (host_impl->pending_tree()->source_frame_number() != 2) { host_impl->BlockNotifyReadyToActivateForTesting(false); } } void WillBeginImplFrameOnThread(LayerTreeHostImpl* host_impl, const viz::BeginFrameArgs& args) override { if (!host_impl->pending_tree() || host_impl->pending_tree()->source_frame_number() != 2) return; frame_count_with_pending_tree_++; if (frame_count_with_pending_tree_ == 2) { host_impl->BlockNotifyReadyToActivateForTesting(false); } } void UpdateAnimationState(LayerTreeHostImpl* host_impl, bool has_unfinished_animation) override { scoped_refptr timeline_impl = GetImplAnimationHost(host_impl)->GetTimelineById(timeline_id_); scoped_refptr animation_impl = static_cast( timeline_impl->GetAnimationById(animation_id_)); scoped_refptr animation_child_impl = static_cast( timeline_impl->GetAnimationById(animation_child_id_)); // wait for tree activation. if (!animation_impl->keyframe_effect()->element_animations()) return; KeyframeModel* root_keyframe_model = animation_impl->GetKeyframeModel(TargetProperty::OPACITY); if (!root_keyframe_model || root_keyframe_model->run_state() != KeyframeModel::RUNNING) return; KeyframeModel* child_keyframe_model = animation_child_impl->GetKeyframeModel(TargetProperty::OPACITY); EXPECT_EQ(KeyframeModel::RUNNING, child_keyframe_model->run_state()); EXPECT_EQ(root_keyframe_model->start_time(), child_keyframe_model->start_time()); animation_impl->AbortKeyframeModelsWithProperty(TargetProperty::OPACITY, false); animation_impl->AbortKeyframeModelsWithProperty(TargetProperty::TRANSFORM, false); animation_child_impl->AbortKeyframeModelsWithProperty( TargetProperty::OPACITY, false); EndTest(); } private: scoped_refptr layer_; int frame_count_with_pending_tree_; }; // This test blocks activation which is not supported for single thread mode. MULTI_THREAD_BLOCKNOTIFY_TEST_F( LayerTreeHostAnimationTestAnimationsAddedToNewAndExistingLayers); class LayerTreeHostAnimationTestPendingTreeAnimatesFirstCommit : public LayerTreeHostAnimationTest { public: void SetupTree() override { LayerTreeHostAnimationTest::SetupTree(); layer_ = FakePictureLayer::Create(&client_); layer_->SetBounds(gfx::Size(2, 2)); client_.set_bounds(layer_->bounds()); // Transform the layer to 4,4 to start. gfx::Transform start_transform; start_transform.Translate(4.0, 4.0); layer_->SetTransform(start_transform); layer_tree_host()->root_layer()->AddChild(layer_); layer_tree_host()->SetElementIdsForTesting(); animation_->AttachElement(layer_->element_id()); AttachAnimationsToTimeline(); } void BeginTest() override { // Add a translate from 6,7 to 8,9. TransformOperations start; start.AppendTranslate(6.f, 7.f, 0.f); TransformOperations end; end.AppendTranslate(8.f, 9.f, 0.f); AddAnimatedTransformToAnimation(animation_.get(), 4.0, start, end); PostSetNeedsCommitToMainThread(); } void WillPrepareTilesOnThread(LayerTreeHostImpl* host_impl) override { // After activating the sync tree PrepareTiles will be called // again (which races with the test exiting). LayerTreeImpl* sync_tree = host_impl->sync_tree(); if (!sync_tree || TestEnded()) return; if (sync_tree->source_frame_number() != 0) return; scoped_refptr timeline_impl = GetImplAnimationHost(host_impl)->GetTimelineById(timeline_id_); scoped_refptr animation_impl = static_cast( timeline_impl->GetAnimationById(animation_id_)); LayerImpl* child = sync_tree->LayerById(layer_->id()); KeyframeModel* keyframe_model = animation_impl->GetKeyframeModel(TargetProperty::TRANSFORM); // The animation should be starting for the first frame. EXPECT_EQ(KeyframeModel::STARTING, keyframe_model->run_state()); // And the transform should be propogated to the sync tree layer, at its // starting state which is 6,7. gfx::Transform expected_transform; expected_transform.Translate(6.0, 7.0); EXPECT_TRANSFORMATION_MATRIX_EQ(expected_transform, child->DrawTransform()); // And the sync tree layer should know it is animating. EXPECT_TRUE(child->screen_space_transform_is_animating()); animation_impl->AbortKeyframeModelsWithProperty(TargetProperty::TRANSFORM, false); EndTest(); } FakeContentLayerClient client_; scoped_refptr layer_; }; SINGLE_AND_MULTI_THREAD_TEST_F( LayerTreeHostAnimationTestPendingTreeAnimatesFirstCommit); // When a layer with an animation is removed from the tree and later re-added, // the animation should resume. class LayerTreeHostAnimationTestAnimatedLayerRemovedAndAdded : public LayerTreeHostAnimationTest { public: void SetupTree() override { LayerTreeHostAnimationTest::SetupTree(); layer_ = Layer::Create(); layer_->SetBounds(gfx::Size(4, 4)); layer_tree_host()->root_layer()->AddChild(layer_); layer_tree_host()->SetElementIdsForTesting(); animation_host()->AddAnimationTimeline(timeline_.get()); timeline_->AttachAnimation(animation_.get()); animation_->AttachElement(layer_->element_id()); DCHECK(animation_->keyframe_effect()->element_animations()); AddOpacityTransitionToAnimation(animation_.get(), 10000.0, 0.1f, 0.9f, true); } void BeginTest() override { PostSetNeedsCommitToMainThread(); } void DidCommit() override { switch (layer_tree_host()->SourceFrameNumber()) { case 0: EXPECT_TRUE(animation_->keyframe_effect() ->element_animations() ->has_element_in_active_list()); EXPECT_FALSE(animation_->keyframe_effect() ->element_animations() ->has_element_in_pending_list()); EXPECT_TRUE(animation_host()->NeedsTickAnimations()); break; case 1: layer_->RemoveFromParent(); EXPECT_FALSE(animation_->keyframe_effect() ->element_animations() ->has_element_in_active_list()); EXPECT_FALSE(animation_->keyframe_effect() ->element_animations() ->has_element_in_pending_list()); // Animations still need one more tick to deliver finished event. EXPECT_TRUE(animation_host()->NeedsTickAnimations()); break; case 2: EXPECT_FALSE(animation_host()->NeedsTickAnimations()); layer_tree_host()->root_layer()->AddChild(layer_); EXPECT_TRUE(animation_->keyframe_effect() ->element_animations() ->has_element_in_active_list()); EXPECT_FALSE(animation_->keyframe_effect() ->element_animations() ->has_element_in_pending_list()); EXPECT_TRUE(animation_host()->NeedsTickAnimations()); break; } } void DidActivateTreeOnThread(LayerTreeHostImpl* host_impl) override { scoped_refptr timeline_impl = GetImplAnimationHost(host_impl)->GetTimelineById(timeline_id_); scoped_refptr animation_impl = static_cast( timeline_impl->GetAnimationById(animation_id_)); switch (host_impl->active_tree()->source_frame_number()) { case 0: EXPECT_TRUE(animation_impl->keyframe_effect() ->element_animations() ->has_element_in_active_list()); EXPECT_TRUE(GetImplAnimationHost(host_impl)->NeedsTickAnimations()); break; case 1: EXPECT_FALSE(animation_impl->keyframe_effect() ->element_animations() ->has_element_in_active_list()); // Having updated state on the host_impl during the commit, we no longer // need to tick animations. EXPECT_FALSE(GetImplAnimationHost(host_impl)->NeedsTickAnimations()); break; case 2: EXPECT_TRUE(animation_impl->keyframe_effect() ->element_animations() ->has_element_in_active_list()); EXPECT_TRUE(GetImplAnimationHost(host_impl)->NeedsTickAnimations()); EndTest(); break; } } private: scoped_refptr layer_; }; SINGLE_AND_MULTI_THREAD_TEST_F( LayerTreeHostAnimationTestAnimatedLayerRemovedAndAdded); class LayerTreeHostAnimationTestAddKeyframeModelAfterAnimating : public LayerTreeHostAnimationTest { public: void SetupTree() override { LayerTreeHostAnimationTest::SetupTree(); layer_ = Layer::Create(); layer_->SetBounds(gfx::Size(4, 4)); layer_tree_host()->root_layer()->AddChild(layer_); child_layer_ = Layer::Create(); child_layer_->SetBounds(gfx::Size(4, 4)); layer_->AddChild(child_layer_); AttachAnimationsToTimeline(); animation_->AttachElement(layer_->element_id()); animation_child_->AttachElement(child_layer_->element_id()); } void BeginTest() override { PostSetNeedsCommitToMainThread(); } void DidCommit() override { switch (layer_tree_host()->SourceFrameNumber()) { case 1: // First frame: add an animation to the root layer. AddAnimatedTransformToAnimation(animation_.get(), 0.1, 5, 5); break; case 2: // Second frame: add an animation to the content layer. The root layer // animation has caused us to animate already during this frame. AddOpacityTransitionToAnimation(animation_child_.get(), 0.1, 5, 5, false); break; } } void DrawLayersOnThread(LayerTreeHostImpl* host_impl) override { // After both animations have started, verify that they have valid // start times. if (host_impl->active_tree()->source_frame_number() < 2) return; // Animation state is updated after drawing. Only do this once. if (!TestEnded()) { ImplThreadTaskRunner()->PostTask( FROM_HERE, base::BindOnce( &LayerTreeHostAnimationTestAddKeyframeModelAfterAnimating:: CheckAnimations, base::Unretained(this), host_impl)); } } void CheckAnimations(LayerTreeHostImpl* host_impl) { GetImplTimelineAndAnimationByID(*host_impl); EXPECT_EQ(2u, GetImplAnimationHost(host_impl) ->ticking_animations_for_testing() .size()); KeyframeModel* root_anim = animation_impl_->GetKeyframeModel(TargetProperty::TRANSFORM); EXPECT_LT(base::TimeTicks(), root_anim->start_time()); KeyframeModel* anim = animation_child_impl_->GetKeyframeModel(TargetProperty::OPACITY); EXPECT_LT(base::TimeTicks(), anim->start_time()); EndTest(); } private: scoped_refptr layer_; scoped_refptr child_layer_; }; SINGLE_AND_MULTI_THREAD_TEST_F( LayerTreeHostAnimationTestAddKeyframeModelAfterAnimating); class LayerTreeHostAnimationTestRemoveKeyframeModel : public LayerTreeHostAnimationTest { public: void SetupTree() override { LayerTreeHostAnimationTest::SetupTree(); layer_ = FakePictureLayer::Create(&client_); layer_->SetBounds(gfx::Size(4, 4)); client_.set_bounds(layer_->bounds()); layer_tree_host()->root_layer()->AddChild(layer_); AttachAnimationsToTimeline(); animation_->AttachElement(layer_tree_host()->root_layer()->element_id()); animation_child_->AttachElement(layer_->element_id()); } void BeginTest() override { animation_stopped_ = false; last_frame_number_ = INT_MAX; PostSetNeedsCommitToMainThread(); } void DidCommit() override { switch (layer_tree_host()->SourceFrameNumber()) { case 1: AddAnimatedTransformToAnimation(animation_child_.get(), 1.0, 5, 5); break; case 2: KeyframeModel* keyframe_model = animation_child_->GetKeyframeModel(TargetProperty::TRANSFORM); animation_child_->RemoveKeyframeModel(keyframe_model->id()); gfx::Transform transform; transform.Translate(10.f, 10.f); layer_->SetTransform(transform); // Do something that causes property trees to get rebuilt. This is // intended to simulate the conditions that caused the bug whose fix // this is testing (the test will pass without it but won't test what // we want it to). We were updating the wrong transform node at the end // of an animation (we were assuming the layer with the finished // animation still had its own transform node). But nodes can only get // added/deleted when something triggers a rebuild. Adding a layer // triggers a rebuild, and since the layer that had an animation before // no longer has one, it doesn't get a transform node in the rebuild. layer_->AddChild(Layer::Create()); break; } } void DrawLayersOnThread(LayerTreeHostImpl* host_impl) override { GetImplTimelineAndAnimationByID(*host_impl); LayerImpl* child = host_impl->active_tree()->LayerById(layer_->id()); switch (host_impl->active_tree()->source_frame_number()) { case 0: // No animation yet. break; case 1: // Animation is started. EXPECT_TRUE(child->screen_space_transform_is_animating()); break; case 2: { // The animation is stopped, the transform that was set afterward is // applied. gfx::Transform expected_transform; expected_transform.Translate(10.f, 10.f); EXPECT_TRANSFORMATION_MATRIX_EQ(expected_transform, child->DrawTransform()); EXPECT_FALSE(child->screen_space_transform_is_animating()); animation_stopped_ = true; PostSetNeedsCommitToMainThread(); break; } } } void CommitCompleteOnThread(LayerTreeHostImpl* host_impl) override { if (host_impl->sync_tree()->source_frame_number() >= last_frame_number_) { // Check that eventually the animation is removed. EXPECT_FALSE( animation_child_impl_->keyframe_effect()->has_any_keyframe_model()); EndTest(); } } void UpdateAnimationState(LayerTreeHostImpl* host_impl, bool has_unfinished_animation) override { // Non impl only animations are removed during commit. After the animation // is fully stopped on compositor thread, make sure another commit happens. if (animation_stopped_ && !has_unfinished_animation) { last_frame_number_ = std::min(last_frame_number_, host_impl->active_tree()->source_frame_number() + 1); PostSetNeedsCommitToMainThread(); } } private: scoped_refptr layer_; FakeContentLayerClient client_; int last_frame_number_; bool animation_stopped_; }; SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostAnimationTestRemoveKeyframeModel); class LayerTreeHostAnimationTestIsAnimating : public LayerTreeHostAnimationTest { public: void SetupTree() override { LayerTreeHostAnimationTest::SetupTree(); layer_ = FakePictureLayer::Create(&client_); layer_->SetBounds(gfx::Size(4, 4)); client_.set_bounds(layer_->bounds()); layer_tree_host()->root_layer()->AddChild(layer_); AttachAnimationsToTimeline(); animation_->AttachElement(layer_->element_id()); } void BeginTest() override { PostSetNeedsCommitToMainThread(); } void DidCommit() override { switch (layer_tree_host()->SourceFrameNumber()) { case 1: AddAnimatedTransformToAnimation(animation_.get(), 1.0, 5, 5); break; case 2: KeyframeModel* keyframe_model = animation_->GetKeyframeModel(TargetProperty::TRANSFORM); EXPECT_EQ(KeyframeModel::RunState::RUNNING, keyframe_model->run_state()); animation_->RemoveKeyframeModel(keyframe_model->id()); break; } } void CommitCompleteOnThread(LayerTreeHostImpl* host_impl) override { LayerImpl* child = host_impl->sync_tree()->LayerById(layer_->id()); switch (host_impl->sync_tree()->source_frame_number()) { case 0: // No animation yet. break; case 1: // Animation is started. EXPECT_TRUE(child->screen_space_transform_is_animating()); break; case 2: // The animation is removed/stopped. EXPECT_FALSE(child->screen_space_transform_is_animating()); break; case 3: break; default: NOTREACHED(); } } void DidActivateTreeOnThread(LayerTreeHostImpl* host_impl) override { GetImplTimelineAndAnimationByID(*host_impl); switch (host_impl->active_tree()->source_frame_number()) { case 0: // No animation yet. break; case 1: // Animation is starting. EXPECT_EQ(KeyframeModel::RunState::STARTING, animation_impl_->GetKeyframeModel(TargetProperty::TRANSFORM) ->run_state()); break; case 2: // After activation, the KeyframeModel should be waiting for deletion. EXPECT_EQ(KeyframeModel::RunState::WAITING_FOR_DELETION, animation_impl_->GetKeyframeModel(TargetProperty::TRANSFORM) ->run_state()); break; case 3: // The animation KeyframeModel is cleaned up. EXPECT_EQ(nullptr, animation_impl_->GetKeyframeModel(TargetProperty::TRANSFORM)); EndTest(); break; default: NOTREACHED(); } } void DrawLayersOnThread(LayerTreeHostImpl* host_impl) override { LayerImpl* child = host_impl->active_tree()->LayerById(layer_->id()); switch (host_impl->active_tree()->source_frame_number()) { case 0: // No animation yet. break; case 1: // Animation is started. EXPECT_TRUE(child->screen_space_transform_is_animating()); break; case 2: case 3: // The animation is removed/stopped. EXPECT_FALSE(child->screen_space_transform_is_animating()); break; default: NOTREACHED(); } } private: scoped_refptr layer_; FakeContentLayerClient client_; }; SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostAnimationTestIsAnimating); class LayerTreeHostAnimationTestAnimationFinishesDuringCommit : public LayerTreeHostAnimationTest { public: LayerTreeHostAnimationTestAnimationFinishesDuringCommit() : signalled_(false) {} void SetupTree() override { LayerTreeHostAnimationTest::SetupTree(); layer_ = FakePictureLayer::Create(&client_); layer_->SetBounds(gfx::Size(4, 4)); client_.set_bounds(layer_->bounds()); layer_tree_host()->root_layer()->AddChild(layer_); AttachAnimationsToTimeline(); animation_->AttachElement(layer_tree_host()->root_layer()->element_id()); animation_child_->AttachElement(layer_->element_id()); } void BeginTest() override { PostSetNeedsCommitToMainThread(); } void DidCommit() override { if (layer_tree_host()->SourceFrameNumber() == 1) AddAnimatedTransformToAnimation(animation_child_.get(), 0.04, 5, 5); } void WillCommit() override { if (layer_tree_host()->SourceFrameNumber() == 2) { // Block until the animation finishes on the compositor thread. Since // animations have already been ticked on the main thread, when the commit // happens the state on the main thread will be consistent with having a // running animation but the state on the compositor thread will be // consistent with having only a finished animation. completion_.Wait(); } } void CommitCompleteOnThread(LayerTreeHostImpl* host_impl) override { switch (host_impl->sync_tree()->source_frame_number()) { case 1: PostSetNeedsCommitToMainThread(); break; case 2: gfx::Transform expected_transform; expected_transform.Translate(5.f, 5.f); LayerImpl* layer_impl = host_impl->sync_tree()->LayerById(layer_->id()); EXPECT_TRANSFORMATION_MATRIX_EQ(expected_transform, layer_impl->DrawTransform()); EndTest(); break; } } void UpdateAnimationState(LayerTreeHostImpl* host_impl, bool has_unfinished_animation) override { if (host_impl->active_tree()->source_frame_number() == 1 && !has_unfinished_animation && !signalled_) { // The animation has finished, so allow the main thread to commit. completion_.Signal(); signalled_ = true; } } private: scoped_refptr layer_; FakeContentLayerClient client_; CompletionEvent completion_; bool signalled_; }; // An animation finishing during commit can only happen when we have a separate // compositor thread. MULTI_THREAD_TEST_F(LayerTreeHostAnimationTestAnimationFinishesDuringCommit); class LayerTreeHostAnimationTestImplSideInvalidation : public LayerTreeHostAnimationTest { public: void SetupTree() override { LayerTreeHostAnimationTest::SetupTree(); layer_ = FakePictureLayer::Create(&client_); layer_->SetBounds(gfx::Size(4, 4)); client_.set_bounds(layer_->bounds()); layer_tree_host()->root_layer()->AddChild(layer_); AttachAnimationsToTimeline(); animation_->AttachElement(layer_tree_host()->root_layer()->element_id()); animation_child_->AttachElement(layer_->element_id()); } void BeginTest() override { PostSetNeedsCommitToMainThread(); } void DidCommit() override { if (layer_tree_host()->SourceFrameNumber() == 1) AddAnimatedTransformToAnimation(animation_child_.get(), 0.04, 5, 5); } void WillCommit() override { if (layer_tree_host()->SourceFrameNumber() == 2) { // Block until the animation finishes on the compositor thread. Since // animations have already been ticked on the main thread, when the commit // happens the state on the main thread will be consistent with having a // running animation but the state on the compositor thread will be // consistent with having only a finished animation. completion_.Wait(); } } void DidInvalidateContentOnImplSide(LayerTreeHostImpl* host_impl) override { DCHECK(did_request_impl_side_invalidation_); completion_.Signal(); } void UpdateAnimationState(LayerTreeHostImpl* host_impl, bool has_unfinished_animation) override { if (host_impl->active_tree()->source_frame_number() == 1 && !has_unfinished_animation && !did_request_impl_side_invalidation_) { // The animation on the active tree has finished, now request an impl-side // invalidation and make sure it finishes before the main thread is // released. did_request_impl_side_invalidation_ = true; host_impl->RequestImplSideInvalidationForCheckerImagedTiles(); } } void CommitCompleteOnThread(LayerTreeHostImpl* host_impl) override { switch (host_impl->sync_tree()->source_frame_number()) { case 1: PostSetNeedsCommitToMainThread(); break; case 2: gfx::Transform expected_transform; expected_transform.Translate(5.f, 5.f); LayerImpl* layer_impl = host_impl->sync_tree()->LayerById(layer_->id()); EXPECT_TRANSFORMATION_MATRIX_EQ(expected_transform, layer_impl->DrawTransform()); EndTest(); break; } } private: scoped_refptr layer_; FakeContentLayerClient client_; CompletionEvent completion_; bool did_request_impl_side_invalidation_ = false; }; MULTI_THREAD_TEST_F(LayerTreeHostAnimationTestImplSideInvalidation); class LayerTreeHostAnimationTestImplSideInvalidationWithoutCommit : public LayerTreeHostAnimationTest { public: void SetupTree() override { LayerTreeHostAnimationTest::SetupTree(); layer_ = FakePictureLayer::Create(&client_); layer_->SetBounds(gfx::Size(4, 4)); client_.set_bounds(layer_->bounds()); layer_tree_host()->root_layer()->AddChild(layer_); AttachAnimationsToTimeline(); animation_child_->AttachElement(layer_->element_id()); num_draws_ = 0; } void UpdateAnimationState(LayerTreeHostImpl* host_impl, bool has_unfinished_animation) override { if (!has_unfinished_animation && !did_request_impl_side_invalidation_) { // The animation on the active tree has finished, now request an impl-side // invalidation and verify that the final value on an impl-side pending // tree is correct. did_request_impl_side_invalidation_ = true; host_impl->RequestImplSideInvalidationForCheckerImagedTiles(); } } protected: scoped_refptr layer_; FakeContentLayerClient client_; bool did_request_impl_side_invalidation_ = false; int num_draws_; }; class ImplSideInvalidationWithoutCommitTestOpacity : public LayerTreeHostAnimationTestImplSideInvalidationWithoutCommit { public: void BeginTest() override { AddOpacityTransitionToAnimation(animation_child_.get(), 0.04, 0.2f, 0.8f, false); PostSetNeedsCommitToMainThread(); } void DrawLayersOnThread(LayerTreeHostImpl* host_impl) override { if (num_draws_++ > 0) return; EXPECT_EQ(0, host_impl->active_tree()->source_frame_number()); LayerImpl* layer_impl = host_impl->active_tree()->LayerById(layer_->id()); EXPECT_FLOAT_EQ(0.2f, layer_impl->Opacity()); } void DidInvalidateContentOnImplSide(LayerTreeHostImpl* host_impl) override { ASSERT_TRUE(did_request_impl_side_invalidation_); EXPECT_EQ(0, host_impl->sync_tree()->source_frame_number()); const float expected_opacity = 0.8f; LayerImpl* layer_impl = host_impl->sync_tree()->LayerById(layer_->id()); EXPECT_FLOAT_EQ(expected_opacity, layer_impl->Opacity()); EndTest(); } }; MULTI_THREAD_TEST_F(ImplSideInvalidationWithoutCommitTestOpacity); class ImplSideInvalidationWithoutCommitTestTransform : public LayerTreeHostAnimationTestImplSideInvalidationWithoutCommit { public: void BeginTest() override { AddAnimatedTransformToAnimation(animation_child_.get(), 0.04, 5, 5); PostSetNeedsCommitToMainThread(); } void DrawLayersOnThread(LayerTreeHostImpl* host_impl) override { if (num_draws_++ > 0) return; EXPECT_EQ(0, host_impl->active_tree()->source_frame_number()); LayerImpl* layer_impl = host_impl->active_tree()->LayerById(layer_->id()); EXPECT_TRANSFORMATION_MATRIX_EQ(gfx::Transform(), layer_impl->DrawTransform()); } void DidInvalidateContentOnImplSide(LayerTreeHostImpl* host_impl) override { ASSERT_TRUE(did_request_impl_side_invalidation_); EXPECT_EQ(0, host_impl->sync_tree()->source_frame_number()); gfx::Transform expected_transform; expected_transform.Translate(5.f, 5.f); LayerImpl* layer_impl = host_impl->sync_tree()->LayerById(layer_->id()); EXPECT_TRANSFORMATION_MATRIX_EQ(expected_transform, layer_impl->DrawTransform()); EndTest(); } }; MULTI_THREAD_TEST_F(ImplSideInvalidationWithoutCommitTestTransform); class ImplSideInvalidationWithoutCommitTestFilter : public LayerTreeHostAnimationTestImplSideInvalidationWithoutCommit { public: void BeginTest() override { AddAnimatedFilterToAnimation(animation_child_.get(), 0.04, 0.f, 1.f); PostSetNeedsCommitToMainThread(); } void DrawLayersOnThread(LayerTreeHostImpl* host_impl) override { if (num_draws_++ > 0) return; EXPECT_EQ(0, host_impl->active_tree()->source_frame_number()); EXPECT_EQ( std::string("{\"FilterOperations\":[{\"type\":5,\"amount\":0.0}]}"), host_impl->active_tree() ->property_trees() ->effect_tree.FindNodeFromElementId(layer_->element_id()) ->filters.ToString()); } void DidInvalidateContentOnImplSide(LayerTreeHostImpl* host_impl) override { ASSERT_TRUE(did_request_impl_side_invalidation_); EXPECT_EQ(0, host_impl->sync_tree()->source_frame_number()); // AddAnimatedFilterToAnimation adds brightness filter which is type 5. EXPECT_EQ( std::string("{\"FilterOperations\":[{\"type\":5,\"amount\":1.0}]}"), host_impl->sync_tree() ->property_trees() ->effect_tree.FindNodeFromElementId(layer_->element_id()) ->filters.ToString()); EndTest(); } }; MULTI_THREAD_TEST_F(ImplSideInvalidationWithoutCommitTestFilter); class ImplSideInvalidationWithoutCommitTestScroll : public LayerTreeHostAnimationTestImplSideInvalidationWithoutCommit { public: void SetupTree() override { LayerTreeHostAnimationTestImplSideInvalidationWithoutCommit::SetupTree(); layer_->SetScrollable(gfx::Size(100, 100)); layer_->SetBounds(gfx::Size(1000, 1000)); client_.set_bounds(layer_->bounds()); layer_->SetScrollOffset(gfx::ScrollOffset(10.f, 20.f)); } void BeginTest() override { std::unique_ptr curve( ScrollOffsetAnimationCurve::Create( gfx::ScrollOffset(500.f, 550.f), CubicBezierTimingFunction::CreatePreset( CubicBezierTimingFunction::EaseType::EASE_IN_OUT))); std::unique_ptr keyframe_model(KeyframeModel::Create( std::move(curve), 1, 0, TargetProperty::SCROLL_OFFSET)); keyframe_model->set_needs_synchronized_start_time(true); ASSERT_TRUE(proxy()->SupportsImplScrolling()); animation_child_->AddKeyframeModel(std::move(keyframe_model)); PostSetNeedsCommitToMainThread(); } void WillCommit() override { if (layer_tree_host()->SourceFrameNumber() == 1) { // Block until the invalidation is done after animation finishes on the // compositor thread. We need to make sure the pending tree has valid // information based on invalidation only. completion_.Wait(); } } void DrawLayersOnThread(LayerTreeHostImpl* host_impl) override { if (num_draws_++ > 0) return; EXPECT_EQ(0, host_impl->active_tree()->source_frame_number()); LayerImpl* layer_impl = host_impl->active_tree()->LayerById(layer_->id()); EXPECT_VECTOR2DF_EQ(gfx::ScrollOffset(10.f, 20.f), layer_impl->CurrentScrollOffset()); } void DidInvalidateContentOnImplSide(LayerTreeHostImpl* host_impl) override { ASSERT_TRUE(did_request_impl_side_invalidation_); EXPECT_EQ(0, host_impl->sync_tree()->source_frame_number()); LayerImpl* layer_impl = host_impl->pending_tree()->LayerById(layer_->id()); EXPECT_VECTOR2DF_EQ(gfx::ScrollOffset(500.f, 550.f), layer_impl->CurrentScrollOffset()); completion_.Signal(); EndTest(); } private: CompletionEvent completion_; }; MULTI_THREAD_TEST_F(ImplSideInvalidationWithoutCommitTestScroll); class LayerTreeHostAnimationTestNotifyAnimationFinished : public LayerTreeHostAnimationTest { public: LayerTreeHostAnimationTestNotifyAnimationFinished() : called_animation_started_(false), called_animation_finished_(false) {} void SetupTree() override { LayerTreeHostAnimationTest::SetupTree(); picture_ = FakePictureLayer::Create(&client_); picture_->SetBounds(gfx::Size(4, 4)); client_.set_bounds(picture_->bounds()); layer_tree_host()->root_layer()->AddChild(picture_); AttachAnimationsToTimeline(); animation_->AttachElement(picture_->element_id()); animation_->set_animation_delegate(this); } void BeginTest() override { PostAddOpacityAnimationToMainThreadDelayed(animation_.get()); } void NotifyAnimationStarted(base::TimeTicks monotonic_time, int target_property, int group) override { called_animation_started_ = true; layer_tree_host()->AnimateLayers(base::TimeTicks::Max()); PostSetNeedsCommitToMainThread(); } void NotifyAnimationFinished(base::TimeTicks monotonic_time, int target_property, int group) override { called_animation_finished_ = true; EndTest(); } void AfterTest() override { EXPECT_TRUE(called_animation_started_); EXPECT_TRUE(called_animation_finished_); } private: bool called_animation_started_; bool called_animation_finished_; FakeContentLayerClient client_; scoped_refptr picture_; }; SINGLE_AND_MULTI_THREAD_TEST_F( LayerTreeHostAnimationTestNotifyAnimationFinished); // Check that transform sync happens correctly at commit when we remove and add // a different animation animation to an element. class LayerTreeHostAnimationTestChangeSingleKeyframeEffectAnimation : public LayerTreeHostAnimationTest { public: void SetupTree() override { LayerTreeHostAnimationTest::SetupTree(); layer_ = Layer::Create(); layer_->SetBounds(gfx::Size(4, 4)); layer_tree_host()->root_layer()->AddChild(layer_); AttachAnimationsToTimeline(); timeline_->DetachAnimation(animation_child_.get()); animation_->AttachElement(layer_->element_id()); TransformOperations start; start.AppendTranslate(5.f, 5.f, 0.f); TransformOperations end; end.AppendTranslate(5.f, 5.f, 0.f); AddAnimatedTransformToAnimation(animation_.get(), 1.0, start, end); } void BeginTest() override { PostSetNeedsCommitToMainThread(); } void CommitCompleteOnThread(LayerTreeHostImpl* host_impl) override { PropertyTrees* property_trees = host_impl->sync_tree()->property_trees(); TransformNode* node = property_trees->transform_tree.Node(host_impl->sync_tree() ->LayerById(layer_->id()) ->transform_tree_index()); gfx::Transform translate; translate.Translate(5, 5); switch (host_impl->sync_tree()->source_frame_number()) { case 2: EXPECT_TRANSFORMATION_MATRIX_EQ(node->local, translate); EndTest(); break; default: break; } } void DidCommit() override { PostSetNeedsCommitToMainThread(); } void WillBeginMainFrame() override { if (layer_tree_host()->SourceFrameNumber() == 2) { // Destroy animation. timeline_->DetachAnimation(animation_.get()); animation_ = nullptr; timeline_->AttachAnimation(animation_child_.get()); animation_child_->AttachElement(layer_->element_id()); AddAnimatedTransformToAnimation(animation_child_.get(), 1.0, 10, 10); KeyframeModel* keyframe_model = animation_child_->GetKeyframeModel(TargetProperty::TRANSFORM); keyframe_model->set_start_time(base::TimeTicks::Now() + base::TimeDelta::FromSecondsD(1000)); keyframe_model->set_fill_mode(KeyframeModel::FillMode::NONE); } } private: scoped_refptr layer_; }; SINGLE_AND_MULTI_THREAD_TEST_F( LayerTreeHostAnimationTestChangeSingleKeyframeEffectAnimation); // Check that SetTransformIsPotentiallyAnimatingChanged is called // if we destroy ElementAnimations. class LayerTreeHostAnimationTestSetPotentiallyAnimatingOnLacDestruction : public LayerTreeHostAnimationTest { public: void SetupTree() override { prev_screen_space_transform_is_animating_ = true; screen_space_transform_animation_stopped_ = false; LayerTreeHostAnimationTest::SetupTree(); AttachAnimationsToTimeline(); animation_->AttachElement(layer_tree_host()->root_layer()->element_id()); AddAnimatedTransformToAnimation(animation_.get(), 1.0, 5, 5); } void BeginTest() override { PostSetNeedsCommitToMainThread(); } void CommitCompleteOnThread(LayerTreeHostImpl* host_impl) override { if (host_impl->pending_tree()->source_frame_number() <= 1) { EXPECT_TRUE(host_impl->pending_tree() ->root_layer() ->screen_space_transform_is_animating()); } else { EXPECT_FALSE(host_impl->pending_tree() ->root_layer() ->screen_space_transform_is_animating()); } } void DidCommit() override { PostSetNeedsCommitToMainThread(); } void UpdateLayerTreeHost() override { if (layer_tree_host()->SourceFrameNumber() == 2) { // Destroy animation. timeline_->DetachAnimation(animation_.get()); animation_ = nullptr; } } DrawResult PrepareToDrawOnThread(LayerTreeHostImpl* host_impl, LayerTreeHostImpl::FrameData* frame_data, DrawResult draw_result) override { const bool screen_space_transform_is_animating = host_impl->active_tree() ->root_layer() ->screen_space_transform_is_animating(); // Check that screen_space_transform_is_animating changes only once. if (screen_space_transform_is_animating && prev_screen_space_transform_is_animating_) EXPECT_FALSE(screen_space_transform_animation_stopped_); if (!screen_space_transform_is_animating && prev_screen_space_transform_is_animating_) { EXPECT_FALSE(screen_space_transform_animation_stopped_); screen_space_transform_animation_stopped_ = true; } if (!screen_space_transform_is_animating && !prev_screen_space_transform_is_animating_) EXPECT_TRUE(screen_space_transform_animation_stopped_); prev_screen_space_transform_is_animating_ = screen_space_transform_is_animating; return draw_result; } void DrawLayersOnThread(LayerTreeHostImpl* host_impl) override { if (host_impl->active_tree()->source_frame_number() >= 2) EndTest(); } void AfterTest() override { EXPECT_TRUE(screen_space_transform_animation_stopped_); } bool prev_screen_space_transform_is_animating_; bool screen_space_transform_animation_stopped_; }; MULTI_THREAD_TEST_F( LayerTreeHostAnimationTestSetPotentiallyAnimatingOnLacDestruction); // Check that we invalidate property trees on // SingleKeyframeEffectAnimation::SetNeedsCommit. class LayerTreeHostAnimationTestRebuildPropertyTreesOnAnimationSetNeedsCommit : public LayerTreeHostAnimationTest { public: void SetupTree() override { LayerTreeHostAnimationTest::SetupTree(); layer_ = FakePictureLayer::Create(&client_); layer_->SetBounds(gfx::Size(4, 4)); client_.set_bounds(layer_->bounds()); layer_tree_host()->root_layer()->AddChild(layer_); AttachAnimationsToTimeline(); animation_->AttachElement(layer_tree_host()->root_layer()->element_id()); animation_child_->AttachElement(layer_->element_id()); } void BeginTest() override { PostSetNeedsCommitToMainThread(); } void DidCommit() override { if (layer_tree_host()->SourceFrameNumber() == 1 || layer_tree_host()->SourceFrameNumber() == 2) PostSetNeedsCommitToMainThread(); } void UpdateLayerTreeHost() override { if (layer_tree_host()->SourceFrameNumber() == 1) AddAnimatedTransformToAnimation(animation_child_.get(), 1.0, 5, 5); EXPECT_TRUE(layer_tree_host()->proxy()->CommitRequested()); } void DrawLayersOnThread(LayerTreeHostImpl* host_impl) override { if (host_impl->active_tree()->source_frame_number() >= 2) EndTest(); } private: scoped_refptr layer_; FakeContentLayerClient client_; }; MULTI_THREAD_TEST_F( LayerTreeHostAnimationTestRebuildPropertyTreesOnAnimationSetNeedsCommit); } // namespace } // namespace cc