| // 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/animation/animation.h" |
| |
| #include <memory> |
| |
| #include "base/strings/stringprintf.h" |
| #include "cc/animation/animation_delegate.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/test/animation_test_common.h" |
| #include "cc/test/animation_timelines_test_common.h" |
| |
| namespace cc { |
| namespace { |
| |
| class AnimationTest : public AnimationTimelinesTest { |
| public: |
| AnimationTest() = default; |
| ~AnimationTest() override = default; |
| }; |
| // See element_animations_unittest.cc for active/pending observers tests. |
| |
| TEST_F(AnimationTest, AttachDetachLayerIfTimelineAttached) { |
| EXPECT_TRUE(CheckKeyframeEffectTimelineNeedsPushProperties(false)); |
| |
| host_->AddAnimationTimeline(timeline_); |
| EXPECT_TRUE(timeline_->needs_push_properties()); |
| EXPECT_FALSE(animation_->keyframe_effect()->needs_push_properties()); |
| |
| timeline_->AttachAnimation(animation_); |
| EXPECT_FALSE(animation_->element_animations()); |
| EXPECT_TRUE(timeline_->needs_push_properties()); |
| EXPECT_FALSE(animation_->keyframe_effect()->needs_push_properties()); |
| |
| host_->PushPropertiesTo(host_impl_); |
| |
| EXPECT_FALSE(GetImplKeyframeEffectForLayerId(element_id_)); |
| |
| timeline_impl_ = host_impl_->GetTimelineById(timeline_id_); |
| EXPECT_TRUE(timeline_impl_); |
| animation_impl_ = timeline_impl_->GetAnimationById(animation_id_); |
| EXPECT_TRUE(animation_impl_); |
| |
| EXPECT_FALSE(animation_impl_->element_animations()); |
| EXPECT_FALSE(animation_impl_->keyframe_effect()->element_id()); |
| EXPECT_FALSE(animation_->keyframe_effect()->needs_push_properties()); |
| EXPECT_FALSE(timeline_->needs_push_properties()); |
| |
| animation_->AttachElement(element_id_); |
| EXPECT_EQ(animation_->keyframe_effect(), |
| GetKeyframeEffectForElementId(element_id_)); |
| EXPECT_TRUE(animation_->element_animations()); |
| EXPECT_EQ(animation_->keyframe_effect()->element_id(), element_id_); |
| CheckKeyframeEffectTimelineNeedsPushProperties(true); |
| |
| host_->PushPropertiesTo(host_impl_); |
| |
| EXPECT_EQ(animation_impl_->keyframe_effect(), |
| GetImplKeyframeEffectForLayerId(element_id_)); |
| EXPECT_TRUE(animation_impl_->element_animations()); |
| EXPECT_EQ(animation_impl_->keyframe_effect()->element_id(), element_id_); |
| CheckKeyframeEffectTimelineNeedsPushProperties(false); |
| |
| animation_->DetachElement(); |
| EXPECT_FALSE(GetKeyframeEffectForElementId(element_id_)); |
| EXPECT_FALSE(animation_->element_animations()); |
| EXPECT_FALSE(animation_->keyframe_effect()->element_id()); |
| CheckKeyframeEffectTimelineNeedsPushProperties(true); |
| |
| host_->PushPropertiesTo(host_impl_); |
| |
| EXPECT_FALSE(GetImplKeyframeEffectForLayerId(element_id_)); |
| EXPECT_FALSE(animation_impl_->element_animations()); |
| EXPECT_FALSE(animation_impl_->keyframe_effect()->element_id()); |
| CheckKeyframeEffectTimelineNeedsPushProperties(false); |
| |
| timeline_->DetachAnimation(animation_); |
| EXPECT_FALSE(animation_->animation_timeline()); |
| EXPECT_FALSE(animation_->element_animations()); |
| EXPECT_FALSE(animation_->keyframe_effect()->element_id()); |
| EXPECT_TRUE(timeline_->needs_push_properties()); |
| EXPECT_FALSE(animation_->keyframe_effect()->needs_push_properties()); |
| host_->PushPropertiesTo(host_impl_); |
| CheckKeyframeEffectTimelineNeedsPushProperties(false); |
| } |
| |
| TEST_F(AnimationTest, AttachDetachTimelineIfLayerAttached) { |
| host_->AddAnimationTimeline(timeline_); |
| |
| EXPECT_FALSE(animation_->keyframe_effect()->element_animations()); |
| EXPECT_FALSE(animation_->keyframe_effect()->element_id()); |
| EXPECT_FALSE(animation_->keyframe_effect()->needs_push_properties()); |
| |
| animation_->AttachElement(element_id_); |
| EXPECT_FALSE(animation_->animation_timeline()); |
| EXPECT_FALSE(GetKeyframeEffectForElementId(element_id_)); |
| EXPECT_FALSE(animation_->keyframe_effect()->element_animations()); |
| EXPECT_EQ(animation_->keyframe_effect()->element_id(), element_id_); |
| EXPECT_FALSE(animation_->keyframe_effect()->needs_push_properties()); |
| |
| timeline_->AttachAnimation(animation_); |
| EXPECT_EQ(timeline_, animation_->animation_timeline()); |
| EXPECT_EQ(animation_->keyframe_effect(), |
| GetKeyframeEffectForElementId(element_id_)); |
| EXPECT_TRUE(animation_->keyframe_effect()->element_animations()); |
| EXPECT_EQ(animation_->keyframe_effect()->element_id(), element_id_); |
| EXPECT_TRUE(animation_->keyframe_effect()->needs_push_properties()); |
| |
| // Removing animation from timeline detaches layer. |
| timeline_->DetachAnimation(animation_); |
| EXPECT_FALSE(animation_->animation_timeline()); |
| EXPECT_FALSE(GetKeyframeEffectForElementId(element_id_)); |
| EXPECT_FALSE(animation_->keyframe_effect()->element_animations()); |
| EXPECT_FALSE(animation_->keyframe_effect()->element_id()); |
| EXPECT_TRUE(animation_->keyframe_effect()->needs_push_properties()); |
| } |
| |
| TEST_F(AnimationTest, PropertiesMutate) { |
| client_.RegisterElementId(element_id_, ElementListType::ACTIVE); |
| client_impl_.RegisterElementId(element_id_, ElementListType::PENDING); |
| client_impl_.RegisterElementId(element_id_, ElementListType::ACTIVE); |
| |
| host_->AddAnimationTimeline(timeline_); |
| |
| timeline_->AttachAnimation(animation_); |
| animation_->AttachElement(element_id_); |
| CheckKeyframeEffectTimelineNeedsPushProperties(true); |
| |
| host_->PushPropertiesTo(host_impl_); |
| CheckKeyframeEffectTimelineNeedsPushProperties(false); |
| |
| const float start_opacity = .7f; |
| const float end_opacity = .3f; |
| |
| const float start_brightness = .6f; |
| const float end_brightness = .4f; |
| |
| const int transform_x = 10; |
| const int transform_y = 20; |
| |
| const float start_invert = .8f; |
| const float end_invert = .6f; |
| |
| const double duration = 1.; |
| |
| AddOpacityTransitionToAnimation(animation_.get(), duration, start_opacity, |
| end_opacity, false); |
| |
| AddAnimatedTransformToAnimation(animation_.get(), duration, transform_x, |
| transform_y); |
| AddAnimatedFilterToAnimation(animation_.get(), duration, start_brightness, |
| end_brightness); |
| AddAnimatedBackdropFilterToAnimation(animation_.get(), duration, start_invert, |
| end_invert); |
| CheckKeyframeEffectTimelineNeedsPushProperties(true); |
| |
| host_->PushPropertiesTo(host_impl_); |
| CheckKeyframeEffectTimelineNeedsPushProperties(false); |
| |
| EXPECT_FALSE(client_.IsPropertyMutated(element_id_, ElementListType::ACTIVE, |
| TargetProperty::OPACITY)); |
| EXPECT_FALSE(client_.IsPropertyMutated(element_id_, ElementListType::ACTIVE, |
| TargetProperty::TRANSFORM)); |
| EXPECT_FALSE(client_.IsPropertyMutated(element_id_, ElementListType::ACTIVE, |
| TargetProperty::FILTER)); |
| EXPECT_FALSE(client_.IsPropertyMutated(element_id_, ElementListType::ACTIVE, |
| TargetProperty::BACKDROP_FILTER)); |
| |
| EXPECT_FALSE(client_impl_.IsPropertyMutated( |
| element_id_, ElementListType::ACTIVE, TargetProperty::OPACITY)); |
| EXPECT_FALSE(client_impl_.IsPropertyMutated( |
| element_id_, ElementListType::ACTIVE, TargetProperty::TRANSFORM)); |
| EXPECT_FALSE(client_impl_.IsPropertyMutated( |
| element_id_, ElementListType::ACTIVE, TargetProperty::FILTER)); |
| EXPECT_FALSE(client_impl_.IsPropertyMutated( |
| element_id_, ElementListType::ACTIVE, TargetProperty::BACKDROP_FILTER)); |
| |
| host_impl_->ActivateAnimations(nullptr); |
| |
| base::TimeTicks time; |
| time += base::TimeDelta::FromSecondsD(0.1); |
| TickAnimationsTransferEvents(time, 4u); |
| CheckKeyframeEffectTimelineNeedsPushProperties(false); |
| |
| time += base::TimeDelta::FromSecondsD(duration); |
| TickAnimationsTransferEvents(time, 4u); |
| CheckKeyframeEffectTimelineNeedsPushProperties(true); |
| |
| client_.ExpectOpacityPropertyMutated(element_id_, ElementListType::ACTIVE, |
| end_opacity); |
| client_.ExpectTransformPropertyMutated(element_id_, ElementListType::ACTIVE, |
| transform_x, transform_y); |
| client_.ExpectFilterPropertyMutated(element_id_, ElementListType::ACTIVE, |
| end_brightness); |
| client_.ExpectBackdropFilterPropertyMutated( |
| element_id_, ElementListType::ACTIVE, end_invert); |
| |
| client_impl_.ExpectOpacityPropertyMutated( |
| element_id_, ElementListType::ACTIVE, end_opacity); |
| client_impl_.ExpectTransformPropertyMutated( |
| element_id_, ElementListType::ACTIVE, transform_x, transform_y); |
| client_impl_.ExpectFilterPropertyMutated(element_id_, ElementListType::ACTIVE, |
| end_brightness); |
| client_impl_.ExpectBackdropFilterPropertyMutated( |
| element_id_, ElementListType::ACTIVE, end_invert); |
| |
| client_impl_.ExpectOpacityPropertyMutated( |
| element_id_, ElementListType::PENDING, end_opacity); |
| client_impl_.ExpectTransformPropertyMutated( |
| element_id_, ElementListType::PENDING, transform_x, transform_y); |
| client_impl_.ExpectFilterPropertyMutated( |
| element_id_, ElementListType::PENDING, end_brightness); |
| client_impl_.ExpectBackdropFilterPropertyMutated( |
| element_id_, ElementListType::PENDING, end_invert); |
| } |
| |
| TEST_F(AnimationTest, AttachTwoAnimationsToOneLayer) { |
| TestAnimationDelegate delegate1; |
| TestAnimationDelegate delegate2; |
| |
| client_.RegisterElementId(element_id_, ElementListType::ACTIVE); |
| client_impl_.RegisterElementId(element_id_, ElementListType::PENDING); |
| client_impl_.RegisterElementId(element_id_, ElementListType::ACTIVE); |
| |
| scoped_refptr<Animation> animation1 = |
| Animation::Create(AnimationIdProvider::NextAnimationId()); |
| scoped_refptr<Animation> animation2 = |
| Animation::Create(AnimationIdProvider::NextAnimationId()); |
| |
| host_->AddAnimationTimeline(timeline_); |
| |
| timeline_->AttachAnimation(animation1); |
| EXPECT_TRUE(timeline_->needs_push_properties()); |
| |
| timeline_->AttachAnimation(animation2); |
| EXPECT_TRUE(timeline_->needs_push_properties()); |
| |
| animation1->set_animation_delegate(&delegate1); |
| animation2->set_animation_delegate(&delegate2); |
| |
| // Attach animations to the same layer. |
| animation1->AttachElement(element_id_); |
| animation2->AttachElement(element_id_); |
| |
| const float start_opacity = .7f; |
| const float end_opacity = .3f; |
| |
| const int transform_x = 10; |
| const int transform_y = 20; |
| |
| const double duration = 1.; |
| |
| AddOpacityTransitionToAnimation(animation1.get(), duration, start_opacity, |
| end_opacity, false); |
| AddAnimatedTransformToAnimation(animation2.get(), duration, transform_x, |
| transform_y); |
| |
| host_->PushPropertiesTo(host_impl_); |
| host_impl_->ActivateAnimations(nullptr); |
| |
| EXPECT_FALSE(delegate1.started()); |
| EXPECT_FALSE(delegate1.finished()); |
| |
| EXPECT_FALSE(delegate2.started()); |
| EXPECT_FALSE(delegate2.finished()); |
| |
| base::TimeTicks time; |
| time += base::TimeDelta::FromSecondsD(0.1); |
| TickAnimationsTransferEvents(time, 2u); |
| |
| EXPECT_TRUE(delegate1.started()); |
| EXPECT_FALSE(delegate1.finished()); |
| |
| EXPECT_TRUE(delegate2.started()); |
| EXPECT_FALSE(delegate2.finished()); |
| |
| EXPECT_FALSE(animation1->keyframe_effect()->needs_push_properties()); |
| EXPECT_FALSE(animation2->keyframe_effect()->needs_push_properties()); |
| |
| time += base::TimeDelta::FromSecondsD(duration); |
| TickAnimationsTransferEvents(time, 2u); |
| |
| EXPECT_TRUE(delegate1.finished()); |
| EXPECT_TRUE(delegate2.finished()); |
| |
| EXPECT_TRUE(animation1->keyframe_effect()->needs_push_properties()); |
| EXPECT_TRUE(animation2->keyframe_effect()->needs_push_properties()); |
| |
| client_.ExpectOpacityPropertyMutated(element_id_, ElementListType::ACTIVE, |
| end_opacity); |
| client_.ExpectTransformPropertyMutated(element_id_, ElementListType::ACTIVE, |
| transform_x, transform_y); |
| |
| client_impl_.ExpectOpacityPropertyMutated( |
| element_id_, ElementListType::ACTIVE, end_opacity); |
| client_impl_.ExpectTransformPropertyMutated( |
| element_id_, ElementListType::ACTIVE, transform_x, transform_y); |
| |
| client_impl_.ExpectOpacityPropertyMutated( |
| element_id_, ElementListType::PENDING, end_opacity); |
| client_impl_.ExpectTransformPropertyMutated( |
| element_id_, ElementListType::PENDING, transform_x, transform_y); |
| } |
| |
| TEST_F(AnimationTest, AddRemoveAnimationToNonAttachedAnimation) { |
| client_.RegisterElementId(element_id_, ElementListType::ACTIVE); |
| client_impl_.RegisterElementId(element_id_, ElementListType::PENDING); |
| client_impl_.RegisterElementId(element_id_, ElementListType::ACTIVE); |
| |
| const double duration = 1.; |
| const float start_opacity = .7f; |
| const float end_opacity = .3f; |
| |
| const int filter_id = |
| AddAnimatedFilterToAnimation(animation_.get(), duration, 0.1f, 0.9f); |
| AddOpacityTransitionToAnimation(animation_.get(), duration, start_opacity, |
| end_opacity, false); |
| |
| EXPECT_FALSE(animation_->keyframe_effect()->needs_push_properties()); |
| |
| host_->AddAnimationTimeline(timeline_); |
| timeline_->AttachAnimation(animation_); |
| |
| EXPECT_FALSE(animation_->keyframe_effect()->needs_push_properties()); |
| EXPECT_FALSE(animation_->keyframe_effect()->element_animations()); |
| animation_->RemoveKeyframeModel(filter_id); |
| EXPECT_FALSE(animation_->keyframe_effect()->needs_push_properties()); |
| |
| animation_->AttachElement(element_id_); |
| |
| EXPECT_TRUE(animation_->keyframe_effect()->element_animations()); |
| EXPECT_FALSE(animation_->keyframe_effect() |
| ->element_animations() |
| ->HasAnyAnimationTargetingProperty(TargetProperty::FILTER)); |
| EXPECT_TRUE(animation_->keyframe_effect() |
| ->element_animations() |
| ->HasAnyAnimationTargetingProperty(TargetProperty::OPACITY)); |
| EXPECT_TRUE(animation_->keyframe_effect()->needs_push_properties()); |
| |
| host_->PushPropertiesTo(host_impl_); |
| |
| EXPECT_FALSE(client_.IsPropertyMutated(element_id_, ElementListType::ACTIVE, |
| TargetProperty::OPACITY)); |
| EXPECT_FALSE(client_impl_.IsPropertyMutated( |
| element_id_, ElementListType::ACTIVE, TargetProperty::OPACITY)); |
| |
| EXPECT_FALSE(client_.IsPropertyMutated(element_id_, ElementListType::ACTIVE, |
| TargetProperty::FILTER)); |
| EXPECT_FALSE(client_impl_.IsPropertyMutated( |
| element_id_, ElementListType::ACTIVE, TargetProperty::FILTER)); |
| |
| host_impl_->ActivateAnimations(nullptr); |
| |
| base::TimeTicks time; |
| time += base::TimeDelta::FromSecondsD(0.1); |
| TickAnimationsTransferEvents(time, 1u); |
| |
| time += base::TimeDelta::FromSecondsD(duration); |
| TickAnimationsTransferEvents(time, 1u); |
| |
| client_.ExpectOpacityPropertyMutated(element_id_, ElementListType::ACTIVE, |
| end_opacity); |
| client_impl_.ExpectOpacityPropertyMutated( |
| element_id_, ElementListType::ACTIVE, end_opacity); |
| client_impl_.ExpectOpacityPropertyMutated( |
| element_id_, ElementListType::PENDING, end_opacity); |
| |
| EXPECT_FALSE(client_.IsPropertyMutated(element_id_, ElementListType::ACTIVE, |
| TargetProperty::FILTER)); |
| EXPECT_FALSE(client_impl_.IsPropertyMutated( |
| element_id_, ElementListType::ACTIVE, TargetProperty::FILTER)); |
| } |
| |
| TEST_F(AnimationTest, AddRemoveAnimationCausesSetNeedsCommit) { |
| client_.RegisterElementId(element_id_, ElementListType::ACTIVE); |
| host_->AddAnimationTimeline(timeline_); |
| timeline_->AttachAnimation(animation_); |
| animation_->AttachElement(element_id_); |
| |
| EXPECT_TRUE(client_.mutators_need_commit()); |
| client_.set_mutators_need_commit(false); |
| |
| const int keyframe_model_id = |
| AddOpacityTransitionToAnimation(animation_.get(), 1., .7f, .3f, false); |
| |
| EXPECT_TRUE(client_.mutators_need_commit()); |
| client_.set_mutators_need_commit(false); |
| |
| animation_->PauseKeyframeModel(keyframe_model_id, |
| base::TimeDelta::FromSeconds(1)); |
| EXPECT_TRUE(client_.mutators_need_commit()); |
| client_.set_mutators_need_commit(false); |
| |
| animation_->RemoveKeyframeModel(keyframe_model_id); |
| EXPECT_TRUE(client_.mutators_need_commit()); |
| client_.set_mutators_need_commit(false); |
| } |
| |
| // If main-thread animation switches to another layer within one frame then |
| // impl-thread animation must be switched as well. |
| TEST_F(AnimationTest, SwitchToLayer) { |
| host_->AddAnimationTimeline(timeline_); |
| timeline_->AttachAnimation(animation_); |
| animation_->AttachElement(element_id_); |
| |
| host_->PushPropertiesTo(host_impl_); |
| |
| timeline_impl_ = host_impl_->GetTimelineById(timeline_id_); |
| EXPECT_TRUE(timeline_impl_); |
| animation_impl_ = timeline_impl_->GetAnimationById(animation_id_); |
| EXPECT_TRUE(animation_impl_); |
| |
| EXPECT_EQ(animation_->keyframe_effect(), |
| GetKeyframeEffectForElementId(element_id_)); |
| EXPECT_TRUE(animation_->keyframe_effect()->element_animations()); |
| EXPECT_EQ(animation_->keyframe_effect()->element_id(), element_id_); |
| |
| timeline_impl_ = host_impl_->GetTimelineById(timeline_id_); |
| EXPECT_TRUE(timeline_impl_); |
| animation_impl_ = timeline_impl_->GetAnimationById(animation_id_); |
| EXPECT_TRUE(animation_impl_); |
| EXPECT_EQ(animation_impl_->keyframe_effect(), |
| GetImplKeyframeEffectForLayerId(element_id_)); |
| EXPECT_TRUE(animation_impl_->keyframe_effect()->element_animations()); |
| EXPECT_EQ(animation_impl_->keyframe_effect()->element_id(), element_id_); |
| CheckKeyframeEffectTimelineNeedsPushProperties(false); |
| |
| const ElementId new_element_id(NextTestLayerId()); |
| animation_->DetachElement(); |
| animation_->AttachElement(new_element_id); |
| |
| EXPECT_EQ(animation_->keyframe_effect(), |
| GetKeyframeEffectForElementId(new_element_id)); |
| EXPECT_TRUE(animation_->keyframe_effect()->element_animations()); |
| EXPECT_EQ(animation_->keyframe_effect()->element_id(), new_element_id); |
| CheckKeyframeEffectTimelineNeedsPushProperties(true); |
| |
| host_->PushPropertiesTo(host_impl_); |
| |
| EXPECT_EQ(animation_impl_->keyframe_effect(), |
| GetImplKeyframeEffectForLayerId(new_element_id)); |
| EXPECT_TRUE(animation_impl_->keyframe_effect()->element_animations()); |
| EXPECT_EQ(animation_impl_->keyframe_effect()->element_id(), new_element_id); |
| } |
| |
| TEST_F(AnimationTest, ToString) { |
| animation_->AttachElement(element_id_); |
| EXPECT_EQ( |
| base::StringPrintf("Animation{id=%d, element_id=%s, keyframe_models=[]}", |
| animation_->id(), element_id_.ToString().c_str()), |
| animation_->ToString()); |
| |
| animation_->AddKeyframeModel(KeyframeModel::Create( |
| std::make_unique<FakeFloatAnimationCurve>(15), 42, 73, |
| KeyframeModel::TargetPropertyId(TargetProperty::OPACITY))); |
| EXPECT_EQ( |
| base::StringPrintf("Animation{id=%d, element_id=%s, " |
| "keyframe_models=[KeyframeModel{id=42, " |
| "group=73, target_property_type=1, " |
| "custom_property_name=, native_property_type=1, " |
| "run_state=WAITING_FOR_TARGET_AVAILABILITY, " |
| "element_id=(0)}]}", |
| animation_->id(), element_id_.ToString().c_str()), |
| animation_->ToString()); |
| |
| animation_->AddKeyframeModel(KeyframeModel::Create( |
| std::make_unique<FakeFloatAnimationCurve>(18), 45, 76, |
| KeyframeModel::TargetPropertyId(TargetProperty::BOUNDS))); |
| EXPECT_EQ(base::StringPrintf( |
| "Animation{id=%d, element_id=%s, " |
| "keyframe_models=[KeyframeModel{id=42, " |
| "group=73, target_property_type=1, custom_property_name=, " |
| "native_property_type=1, " |
| "run_state=WAITING_FOR_TARGET_AVAILABILITY, element_id=(0)}, " |
| "KeyframeModel{id=45, group=76, target_property_type=5, " |
| "custom_property_name=, native_property_type=1, " |
| "run_state=WAITING_FOR_TARGET_AVAILABILITY, element_id=(0)}]}", |
| animation_->id(), element_id_.ToString().c_str()), |
| animation_->ToString()); |
| } |
| |
| } // namespace |
| } // namespace cc |