// Copyright 2015 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 "cc/animation/animation_curve.h" #include "cc/animation/animation_host.h" #include "cc/animation/animation_id_provider.h" #include "cc/animation/animation_player.h" #include "cc/animation/animation_timeline.h" #include "cc/animation/element_animations.h" #include "cc/animation/layer_animation_controller.h" #include "cc/animation/scroll_offset_animation_curve.h" #include "cc/animation/timing_function.h" #include "cc/base/completion_event.h" #include "cc/base/time_util.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/layer_tree_impl.h" namespace cc { namespace { class LayerTreeHostTimelinesTest : public LayerTreeTest { public: LayerTreeHostTimelinesTest() : timeline_id_(AnimationIdProvider::NextTimelineId()), player_id_(AnimationIdProvider::NextPlayerId()), player_child_id_(AnimationIdProvider::NextPlayerId()) { timeline_ = AnimationTimeline::Create(timeline_id_); player_ = AnimationPlayer::Create(player_id_); player_child_ = AnimationPlayer::Create(player_child_id_); player_->set_layer_animation_delegate(this); } void InitializeSettings(LayerTreeSettings* settings) override { settings->use_compositor_animation_timelines = true; } void InitializeLayerSettings(LayerSettings* layer_settings) override { layer_settings->use_compositor_animation_timelines = true; } void SetupTree() override { LayerTreeTest::SetupTree(); } void AttachPlayersToTimeline() { layer_tree_host()->animation_host()->AddAnimationTimeline(timeline_.get()); timeline_->AttachPlayer(player_.get()); timeline_->AttachPlayer(player_child_.get()); } protected: scoped_refptr timeline_; scoped_refptr player_; scoped_refptr player_child_; const int timeline_id_; const int player_id_; const int player_child_id_; }; // Add a layer animation and confirm that // LayerTreeHostImpl::UpdateAnimationState does get called. // Evolved frome LayerTreeHostAnimationTestAddAnimation class LayerTreeHostTimelinesTestAddAnimation : public LayerTreeHostTimelinesTest { public: LayerTreeHostTimelinesTestAddAnimation() : update_animation_state_was_called_(false) {} void BeginTest() override { AttachPlayersToTimeline(); player_->AttachLayer(layer_tree_host()->root_layer()->id()); PostAddInstantAnimationToMainThreadPlayer(player_.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, Animation::TargetProperty target_property, int group) override { EXPECT_LT(base::TimeTicks(), monotonic_time); LayerAnimationController* controller = player_->element_animations()->layer_animation_controller(); Animation* animation = controller->GetAnimation(Animation::OPACITY); if (animation) player_->RemoveAnimation(animation->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(LayerTreeHostTimelinesTestAddAnimation); // Add a layer animation to a layer, but continually fail to draw. Confirm that // after a while, we do eventually force a draw. // Evolved from LayerTreeHostAnimationTestCheckerboardDoesNotStarveDraws. class LayerTreeHostTimelinesTestCheckerboardDoesNotStarveDraws : public LayerTreeHostTimelinesTest { public: LayerTreeHostTimelinesTestCheckerboardDoesNotStarveDraws() : started_animating_(false) {} void BeginTest() override { AttachPlayersToTimeline(); player_->AttachLayer(layer_tree_host()->root_layer()->id()); PostAddAnimationToMainThreadPlayer(player_.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; } void AfterTest() override {} private: bool started_animating_; }; // Starvation can only be an issue with the MT compositor. MULTI_THREAD_TEST_F(LayerTreeHostTimelinesTestCheckerboardDoesNotStarveDraws); // Ensures that animations eventually get deleted. // Evolved from LayerTreeHostAnimationTestAnimationsGetDeleted. class LayerTreeHostTimelinesTestAnimationsGetDeleted : public LayerTreeHostTimelinesTest { public: LayerTreeHostTimelinesTestAnimationsGetDeleted() : started_animating_(false) {} void BeginTest() override { AttachPlayersToTimeline(); player_->AttachLayer(layer_tree_host()->root_layer()->id()); PostAddAnimationToMainThreadPlayer(player_.get()); } void AnimateLayers(LayerTreeHostImpl* host_impl, base::TimeTicks monotonic_time) override { bool have_animations = !host_impl->animation_host() ->animation_registrar() ->active_animation_controllers_for_testing() .empty(); if (!started_animating_ && have_animations) { started_animating_ = true; return; } if (started_animating_ && !have_animations) EndTest(); } void NotifyAnimationFinished(base::TimeTicks monotonic_time, Animation::TargetProperty target_property, int group) override { // Animations on the impl-side controller only get deleted during a commit, // so we need to schedule a commit. layer_tree_host()->SetNeedsCommit(); } void AfterTest() override {} private: bool started_animating_; }; SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostTimelinesTestAnimationsGetDeleted); // Ensure that an animation's timing function is respected. // Evolved from LayerTreeHostAnimationTestAddAnimationWithTimingFunction. class LayerTreeHostTimelinesTestAddAnimationWithTimingFunction : public LayerTreeHostTimelinesTest { public: LayerTreeHostTimelinesTestAddAnimationWithTimingFunction() {} void SetupTree() override { LayerTreeHostTimelinesTest::SetupTree(); picture_ = FakePictureLayer::Create(layer_settings(), &client_); picture_->SetBounds(gfx::Size(4, 4)); layer_tree_host()->root_layer()->AddChild(picture_); AttachPlayersToTimeline(); player_child_->AttachLayer(picture_->id()); } void BeginTest() override { PostAddAnimationToMainThreadPlayer(player_child_.get()); } void AnimateLayers(LayerTreeHostImpl* host_impl, base::TimeTicks monotonic_time) override { scoped_refptr timeline_impl = host_impl->animation_host()->GetTimelineById(timeline_id_); scoped_refptr player_child_impl = timeline_impl->GetPlayerById(player_child_id_); LayerAnimationController* controller_impl = player_child_impl->element_animations()->layer_animation_controller(); if (!controller_impl) return; Animation* animation = controller_impl->GetAnimation(Animation::OPACITY); if (!animation) return; const FloatAnimationCurve* curve = animation->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 = TimeUtil::Scale(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(); } void AfterTest() override {} FakeContentLayerClient client_; scoped_refptr picture_; }; SINGLE_AND_MULTI_THREAD_TEST_F( LayerTreeHostTimelinesTestAddAnimationWithTimingFunction); // Ensures that main thread animations have their start times synchronized with // impl thread animations. // Evolved from LayerTreeHostAnimationTestSynchronizeAnimationStartTimes. class LayerTreeHostTimelinesTestSynchronizeAnimationStartTimes : public LayerTreeHostTimelinesTest { public: LayerTreeHostTimelinesTestSynchronizeAnimationStartTimes() {} void SetupTree() override { LayerTreeHostTimelinesTest::SetupTree(); picture_ = FakePictureLayer::Create(layer_settings(), &client_); picture_->SetBounds(gfx::Size(4, 4)); layer_tree_host()->root_layer()->AddChild(picture_); AttachPlayersToTimeline(); player_child_->set_layer_animation_delegate(this); player_child_->AttachLayer(picture_->id()); } void BeginTest() override { PostAddAnimationToMainThreadPlayer(player_child_.get()); } void NotifyAnimationStarted(base::TimeTicks monotonic_time, Animation::TargetProperty target_property, int group) override { LayerAnimationController* controller = player_child_->element_animations()->layer_animation_controller(); Animation* animation = controller->GetAnimation(Animation::OPACITY); main_start_time_ = animation->start_time(); controller->RemoveAnimation(animation->id()); EndTest(); } void UpdateAnimationState(LayerTreeHostImpl* impl_host, bool has_unfinished_animation) override { scoped_refptr timeline_impl = impl_host->animation_host()->GetTimelineById(timeline_id_); scoped_refptr player_child_impl = timeline_impl->GetPlayerById(player_child_id_); LayerAnimationController* controller = player_child_impl->element_animations()->layer_animation_controller(); Animation* animation = controller->GetAnimation(Animation::OPACITY); if (!animation) return; impl_start_time_ = animation->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( LayerTreeHostTimelinesTestSynchronizeAnimationStartTimes); // Ensures that notify animation finished is called. // Evolved from LayerTreeHostAnimationTestAnimationFinishedEvents. class LayerTreeHostTimelinesTestAnimationFinishedEvents : public LayerTreeHostTimelinesTest { public: LayerTreeHostTimelinesTestAnimationFinishedEvents() {} void BeginTest() override { AttachPlayersToTimeline(); player_->AttachLayer(layer_tree_host()->root_layer()->id()); PostAddInstantAnimationToMainThreadPlayer(player_.get()); } void NotifyAnimationFinished(base::TimeTicks monotonic_time, Animation::TargetProperty target_property, int group) override { LayerAnimationController* controller = player_->element_animations()->layer_animation_controller(); Animation* animation = controller->GetAnimation(Animation::OPACITY); if (animation) controller->RemoveAnimation(animation->id()); EndTest(); } void AfterTest() override {} }; SINGLE_AND_MULTI_THREAD_TEST_F( LayerTreeHostTimelinesTestAnimationFinishedEvents); // Ensures that when opacity is being animated, this value does not cause the // subtree to be skipped. // Evolved from LayerTreeHostAnimationTestDoNotSkipLayersWithAnimatedOpacity. class LayerTreeHostTimelinesTestDoNotSkipLayersWithAnimatedOpacity : public LayerTreeHostTimelinesTest { public: LayerTreeHostTimelinesTestDoNotSkipLayersWithAnimatedOpacity() : update_check_layer_( FakePictureLayer::Create(layer_settings(), &client_)) {} void SetupTree() override { update_check_layer_->SetOpacity(0.f); layer_tree_host()->SetRootLayer(update_check_layer_); LayerTreeHostTimelinesTest::SetupTree(); AttachPlayersToTimeline(); player_->AttachLayer(update_check_layer_->id()); } void BeginTest() override { PostAddAnimationToMainThreadPlayer(player_.get()); } void DidActivateTreeOnThread(LayerTreeHostImpl* host_impl) override { scoped_refptr timeline_impl = host_impl->animation_host()->GetTimelineById(timeline_id_); scoped_refptr player_impl = timeline_impl->GetPlayerById(player_id_); LayerAnimationController* controller_impl = player_impl->element_animations()->layer_animation_controller(); Animation* animation_impl = controller_impl->GetAnimation(Animation::OPACITY); controller_impl->RemoveAnimation(animation_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_ = NULL; } private: FakeContentLayerClient client_; scoped_refptr update_check_layer_; }; SINGLE_AND_MULTI_THREAD_TEST_F( LayerTreeHostTimelinesTestDoNotSkipLayersWithAnimatedOpacity); // Layers added to tree with existing active animations should have the // animation correctly recognized. // Evolved from LayerTreeHostAnimationTestLayerAddedWithAnimation. class LayerTreeHostTimelinesTestLayerAddedWithAnimation : public LayerTreeHostTimelinesTest { public: LayerTreeHostTimelinesTestLayerAddedWithAnimation() {} void BeginTest() override { PostSetNeedsCommitToMainThread(); } void DidCommit() override { if (layer_tree_host()->source_frame_number() == 1) { AttachPlayersToTimeline(); scoped_refptr layer = Layer::Create(layer_settings()); player_->AttachLayer(layer->id()); player_->set_layer_animation_delegate(this); // Any valid AnimationCurve will do here. scoped_ptr curve(new FakeFloatAnimationCurve()); scoped_ptr animation( Animation::Create(curve.Pass(), 1, 1, Animation::OPACITY)); player_->AddAnimation(animation.Pass()); // 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(); } void AfterTest() override {} }; SINGLE_AND_MULTI_THREAD_TEST_F( LayerTreeHostTimelinesTestLayerAddedWithAnimation); // Make sure the main thread can still execute animations when CanDraw() is not // true. // Evolved from LayerTreeHostAnimationTestRunAnimationWhenNotCanDraw class LayerTreeHostTimelinesTestRunAnimationWhenNotCanDraw : public LayerTreeHostTimelinesTest { public: LayerTreeHostTimelinesTestRunAnimationWhenNotCanDraw() : started_times_(0) {} void SetupTree() override { LayerTreeHostTimelinesTest::SetupTree(); picture_ = FakePictureLayer::Create(layer_settings(), &client_); picture_->SetBounds(gfx::Size(4, 4)); layer_tree_host()->root_layer()->AddChild(picture_); AttachPlayersToTimeline(); player_child_->AttachLayer(picture_->id()); player_child_->set_layer_animation_delegate(this); } void BeginTest() override { layer_tree_host()->SetViewportSize(gfx::Size()); PostAddAnimationToMainThreadPlayer(player_child_.get()); } void NotifyAnimationStarted(base::TimeTicks monotonic_time, Animation::TargetProperty target_property, int group) override { started_times_++; } void NotifyAnimationFinished(base::TimeTicks monotonic_time, Animation::TargetProperty target_property, int group) override { EndTest(); } void AfterTest() override { EXPECT_EQ(1, started_times_); } private: int started_times_; FakeContentLayerClient client_; scoped_refptr picture_; }; SINGLE_AND_MULTI_THREAD_TEST_F( LayerTreeHostTimelinesTestRunAnimationWhenNotCanDraw); // Animations should not be started when frames are being skipped due to // checkerboard. // Evolved from LayerTreeHostAnimationTestCheckerboardDoesntStartAnimations. class LayerTreeHostTimelinesTestCheckerboardDoesntStartAnimations : public LayerTreeHostTimelinesTest { void SetupTree() override { LayerTreeHostTimelinesTest::SetupTree(); picture_ = FakePictureLayer::Create(layer_settings(), &client_); picture_->SetBounds(gfx::Size(4, 4)); layer_tree_host()->root_layer()->AddChild(picture_); AttachPlayersToTimeline(); player_child_->AttachLayer(picture_->id()); player_child_->set_layer_animation_delegate(this); } void InitializeSettings(LayerTreeSettings* settings) override { // Make sure that drawing many times doesn't cause a checkerboarded // animation to start so we avoid flake in this test. settings->timeout_and_draw_when_animation_checkerboards = false; LayerTreeHostTimelinesTest::InitializeSettings(settings); } void BeginTest() override { prevented_draw_ = 0; added_animations_ = 0; started_times_ = 0; PostSetNeedsCommitToMainThread(); } DrawResult PrepareToDrawOnThread(LayerTreeHostImpl* host_impl, LayerTreeHostImpl::FrameData* frame_data, DrawResult draw_result) override { if (added_animations_ < 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()->source_frame_number()) { case 1: // The animation is longer than 1 BeginFrame interval. AddOpacityTransitionToPlayer(player_child_.get(), 0.1, 0.2f, 0.8f, false); added_animations_++; break; case 2: // This second animation will not be drawn so it should not start. AddAnimatedTransformToPlayer(player_child_.get(), 0.1, 5, 5); added_animations_++; break; } } void NotifyAnimationStarted(base::TimeTicks monotonic_time, Animation::TargetProperty 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 added_animations_; int started_times_; FakeContentLayerClient client_; scoped_refptr picture_; }; MULTI_THREAD_TEST_F( LayerTreeHostTimelinesTestCheckerboardDoesntStartAnimations); // 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. // Evolved from LayerTreeHostAnimationTestScrollOffsetChangesArePropagated class LayerTreeHostTimelinesTestScrollOffsetChangesArePropagated : public LayerTreeHostTimelinesTest { public: LayerTreeHostTimelinesTestScrollOffsetChangesArePropagated() {} void SetupTree() override { LayerTreeHostTimelinesTest::SetupTree(); scroll_layer_ = FakePictureLayer::Create(layer_settings(), &client_); scroll_layer_->SetScrollClipLayerId(layer_tree_host()->root_layer()->id()); scroll_layer_->SetBounds(gfx::Size(1000, 1000)); scroll_layer_->SetScrollOffset(gfx::ScrollOffset(10, 20)); layer_tree_host()->root_layer()->AddChild(scroll_layer_); AttachPlayersToTimeline(); player_child_->AttachLayer(scroll_layer_->id()); } void BeginTest() override { PostSetNeedsCommitToMainThread(); } void DidCommit() override { switch (layer_tree_host()->source_frame_number()) { case 1: { scoped_ptr curve( ScrollOffsetAnimationCurve::Create( gfx::ScrollOffset(500.f, 550.f), EaseInOutTimingFunction::Create())); scoped_ptr animation( Animation::Create(curve.Pass(), 1, 0, Animation::SCROLL_OFFSET)); animation->set_needs_synchronized_start_time(true); bool impl_scrolling_supported = layer_tree_host()->proxy()->SupportsImplScrolling(); if (impl_scrolling_supported) player_child_->AddAnimation(animation.Pass()); else EndTest(); break; } default: if (scroll_layer_->scroll_offset().x() > 10 && scroll_layer_->scroll_offset().y() > 20) EndTest(); } } void AfterTest() override {} private: FakeContentLayerClient client_; scoped_refptr scroll_layer_; }; SINGLE_AND_MULTI_THREAD_TEST_F( LayerTreeHostTimelinesTestScrollOffsetChangesArePropagated); // 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. // Evolved from LayerTreeHostAnimationTestScrollOffsetAnimationRemoval class LayerTreeHostTimelinesTestScrollOffsetAnimationRemoval : public LayerTreeHostTimelinesTest { public: LayerTreeHostTimelinesTestScrollOffsetAnimationRemoval() : final_postion_(50.0, 100.0) {} void SetupTree() override { LayerTreeHostTimelinesTest::SetupTree(); scroll_layer_ = FakePictureLayer::Create(layer_settings(), &client_); scroll_layer_->SetScrollClipLayerId(layer_tree_host()->root_layer()->id()); scroll_layer_->SetBounds(gfx::Size(10000, 10000)); scroll_layer_->SetScrollOffset(gfx::ScrollOffset(100.0, 200.0)); layer_tree_host()->root_layer()->AddChild(scroll_layer_); scoped_ptr curve( ScrollOffsetAnimationCurve::Create(gfx::ScrollOffset(6500.f, 7500.f), EaseInOutTimingFunction::Create())); scoped_ptr animation( Animation::Create(curve.Pass(), 1, 0, Animation::SCROLL_OFFSET)); animation->set_needs_synchronized_start_time(true); AttachPlayersToTimeline(); player_child_->AttachLayer(scroll_layer_->id()); player_child_->AddAnimation(animation.Pass()); } void BeginTest() override { PostSetNeedsCommitToMainThread(); } void BeginMainFrame(const BeginFrameArgs& args) override { switch (layer_tree_host()->source_frame_number()) { case 0: break; case 1: { Animation* animation = player_child_->element_animations() ->layer_animation_controller() ->GetAnimation(Animation::SCROLL_OFFSET); player_child_->RemoveAnimation(animation->id()); scroll_layer_->SetScrollOffset(final_postion_); break; } default: EXPECT_EQ(final_postion_, scroll_layer_->scroll_offset()); } } void BeginCommitOnThread(LayerTreeHostImpl* host_impl) override { host_impl->BlockNotifyReadyToActivateForTesting(true); } void WillBeginImplFrameOnThread(LayerTreeHostImpl* host_impl, const BeginFrameArgs& args) override { if (!host_impl->pending_tree()) return; if (!host_impl->active_tree()->root_layer()) { host_impl->BlockNotifyReadyToActivateForTesting(false); return; } scoped_refptr timeline_impl = host_impl->animation_host()->GetTimelineById(timeline_id_); scoped_refptr player_impl = timeline_impl->GetPlayerById(player_child_id_); LayerImpl* scroll_layer_impl = host_impl->active_tree()->root_layer()->children()[0]; Animation* animation = player_impl->element_animations() ->layer_animation_controller() ->GetAnimation(Animation::SCROLL_OFFSET); if (!animation || animation->run_state() != Animation::RUNNING) { host_impl->BlockNotifyReadyToActivateForTesting(false); return; } // Block activation until the running animation has a chance to produce a // scroll delta. gfx::Vector2dF scroll_delta = scroll_layer_impl->ScrollDelta(); if (scroll_delta.x() < 1.f || scroll_delta.y() < 1.f) return; host_impl->BlockNotifyReadyToActivateForTesting(false); } void WillActivateTreeOnThread(LayerTreeHostImpl* host_impl) override { if (host_impl->pending_tree()->source_frame_number() != 1) return; LayerImpl* scroll_layer_impl = host_impl->pending_tree()->root_layer()->children()[0]; 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()->root_layer()->children()[0]; EXPECT_EQ(final_postion_, scroll_layer_impl->CurrentScrollOffset()); EndTest(); } void AfterTest() override { EXPECT_EQ(final_postion_, scroll_layer_->scroll_offset()); } private: FakeContentLayerClient client_; scoped_refptr scroll_layer_; const gfx::ScrollOffset final_postion_; }; MULTI_THREAD_TEST_F(LayerTreeHostTimelinesTestScrollOffsetAnimationRemoval); // 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. // Evolved from LayerTreeHostAnimationTestAnimationsAddedToNewAndExistingLayers. class LayerTreeHostTimelinesTestAnimationsAddedToNewAndExistingLayers : public LayerTreeHostTimelinesTest { public: LayerTreeHostTimelinesTestAnimationsAddedToNewAndExistingLayers() : frame_count_with_pending_tree_(0) {} void BeginTest() override { AttachPlayersToTimeline(); PostSetNeedsCommitToMainThread(); } void DidCommit() override { if (layer_tree_host()->source_frame_number() == 1) { player_->AttachLayer(layer_tree_host()->root_layer()->id()); AddAnimatedTransformToPlayer(player_.get(), 4, 1, 1); } else if (layer_tree_host()->source_frame_number() == 2) { AddOpacityTransitionToPlayer(player_.get(), 1, 0.f, 0.5f, true); scoped_refptr layer = Layer::Create(layer_settings()); layer_tree_host()->root_layer()->AddChild(layer); layer->SetBounds(gfx::Size(4, 4)); player_child_->AttachLayer(layer->id()); player_child_->set_layer_animation_delegate(this); AddOpacityTransitionToPlayer(player_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 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 = host_impl->animation_host()->GetTimelineById(timeline_id_); scoped_refptr player_impl = timeline_impl->GetPlayerById(player_id_); scoped_refptr player_child_impl = timeline_impl->GetPlayerById(player_child_id_); // wait for tree activation. if (!player_impl->element_animations()) return; LayerAnimationController* root_controller_impl = player_impl->element_animations()->layer_animation_controller(); Animation* root_animation = root_controller_impl->GetAnimation(Animation::OPACITY); if (!root_animation || root_animation->run_state() != Animation::RUNNING) return; LayerAnimationController* child_controller_impl = player_child_impl->element_animations()->layer_animation_controller(); Animation* child_animation = child_controller_impl->GetAnimation(Animation::OPACITY); EXPECT_EQ(Animation::RUNNING, child_animation->run_state()); EXPECT_EQ(root_animation->start_time(), child_animation->start_time()); root_controller_impl->AbortAnimations(Animation::OPACITY); root_controller_impl->AbortAnimations(Animation::TRANSFORM); child_controller_impl->AbortAnimations(Animation::OPACITY); EndTest(); } void AfterTest() override {} private: int frame_count_with_pending_tree_; }; // This test blocks activation which is not supported for single thread mode. MULTI_THREAD_BLOCKNOTIFY_TEST_F( LayerTreeHostTimelinesTestAnimationsAddedToNewAndExistingLayers); // Evolved from LayerTreeHostAnimationTestAddAnimationAfterAnimating. class LayerTreeHostTimelinesTestAddAnimationAfterAnimating : public LayerTreeHostTimelinesTest { public: void SetupTree() override { LayerTreeHostTimelinesTest::SetupTree(); content_ = Layer::Create(layer_settings()); content_->SetBounds(gfx::Size(4, 4)); layer_tree_host()->root_layer()->AddChild(content_); AttachPlayersToTimeline(); player_->AttachLayer(layer_tree_host()->root_layer()->id()); player_child_->AttachLayer(content_->id()); } void BeginTest() override { PostSetNeedsCommitToMainThread(); } void DidCommit() override { switch (layer_tree_host()->source_frame_number()) { case 1: // First frame: add an animation to the root layer. AddAnimatedTransformToPlayer(player_.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. AddOpacityTransitionToPlayer(player_child_.get(), 0.1, 5, 5, false); break; } } void SwapBuffersOnThread(LayerTreeHostImpl* host_impl, bool result) override { // After both animations have started, verify that they have valid // start times. if (host_impl->active_tree()->source_frame_number() < 2) return; AnimationRegistrar::AnimationControllerMap controllers_copy = host_impl->animation_host() ->animation_registrar() ->active_animation_controllers_for_testing(); EXPECT_EQ(2u, controllers_copy.size()); for (auto& it : controllers_copy) { int id = it.first; if (id == host_impl->RootLayer()->id()) { Animation* anim = it.second->GetAnimation(Animation::TRANSFORM); EXPECT_GT((anim->start_time() - base::TimeTicks()).InSecondsF(), 0); } else if (id == host_impl->RootLayer()->children()[0]->id()) { Animation* anim = it.second->GetAnimation(Animation::OPACITY); EXPECT_GT((anim->start_time() - base::TimeTicks()).InSecondsF(), 0); } EndTest(); } } void AfterTest() override {} private: scoped_refptr content_; }; SINGLE_AND_MULTI_THREAD_TEST_F( LayerTreeHostTimelinesTestAddAnimationAfterAnimating); class LayerTreeHostTimelinesTestRemoveAnimation : public LayerTreeHostTimelinesTest { public: void SetupTree() override { LayerTreeHostTimelinesTest::SetupTree(); layer_ = FakePictureLayer::Create(layer_settings(), &client_); layer_->SetBounds(gfx::Size(4, 4)); layer_tree_host()->root_layer()->AddChild(layer_); AttachPlayersToTimeline(); player_->AttachLayer(layer_tree_host()->root_layer()->id()); player_child_->AttachLayer(layer_->id()); } void BeginTest() override { PostSetNeedsCommitToMainThread(); } void DidCommit() override { switch (layer_tree_host()->source_frame_number()) { case 1: AddAnimatedTransformToPlayer(player_child_.get(), 1.0, 5, 5); break; case 2: LayerAnimationController* controller = player_child_->element_animations()->layer_animation_controller(); Animation* animation = controller->GetAnimation(Animation::TRANSFORM); player_child_->RemoveAnimation(animation->id()); gfx::Transform transform; transform.Translate(10.f, 10.f); layer_->SetTransform(transform); // Do something that causes property trees to get rebuilt. layer_->AddChild(Layer::Create(layer_settings())); break; } } void DrawLayersOnThread(LayerTreeHostImpl* host_impl) override { if (host_impl->active_tree()->source_frame_number() < 2) return; gfx::Transform expected_transform; expected_transform.Translate(10.f, 10.f); EXPECT_EQ(expected_transform, host_impl->active_tree() ->root_layer() ->children()[0] ->draw_transform()); EndTest(); } void AfterTest() override {} private: scoped_refptr layer_; FakeContentLayerClient client_; }; SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostTimelinesTestRemoveAnimation); class LayerTreeHostTimelinesTestAnimationFinishesDuringCommit : public LayerTreeHostTimelinesTest { public: void SetupTree() override { LayerTreeHostTimelinesTest::SetupTree(); layer_ = FakePictureLayer::Create(layer_settings(), &client_); layer_->SetBounds(gfx::Size(4, 4)); layer_tree_host()->root_layer()->AddChild(layer_); AttachPlayersToTimeline(); player_->AttachLayer(layer_tree_host()->root_layer()->id()); player_child_->AttachLayer(layer_->id()); } void BeginTest() override { PostSetNeedsCommitToMainThread(); } void DidCommit() override { if (layer_tree_host()->source_frame_number() == 1) AddAnimatedTransformToPlayer(player_child_.get(), 0.04, 5, 5); } void WillCommit() override { if (layer_tree_host()->source_frame_number() == 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()->root_layer()->children()[0]; EXPECT_EQ(expected_transform, layer_impl->draw_transform()); EndTest(); break; } } void UpdateAnimationState(LayerTreeHostImpl* host_impl, bool has_unfinished_animation) override { if (host_impl->active_tree()->source_frame_number() == 1 && !has_unfinished_animation) { // The animation has finished, so allow the main thread to commit. completion_.Signal(); } } void AfterTest() override {} private: scoped_refptr layer_; FakeContentLayerClient client_; CompletionEvent completion_; }; // An animation finishing during commit can only happen when we have a separate // compositor thread. MULTI_THREAD_TEST_F(LayerTreeHostTimelinesTestAnimationFinishesDuringCommit); } // namespace } // namespace cc