// 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/element_animations.h" #include "base/memory/ptr_util.h" #include "cc/animation/animation_delegate.h" #include "cc/animation/animation_events.h" #include "cc/animation/animation_host.h" #include "cc/animation/animation_id_provider.h" #include "cc/animation/animation_timeline.h" #include "cc/animation/keyframe_effect.h" #include "cc/animation/keyframed_animation_curve.h" #include "cc/animation/scroll_offset_animation_curve.h" #include "cc/animation/single_keyframe_effect_animation.h" #include "cc/animation/transform_operations.h" #include "cc/test/animation_test_common.h" #include "cc/test/animation_timelines_test_common.h" #include "ui/gfx/geometry/box_f.h" namespace cc { namespace { using base::TimeDelta; using base::TimeTicks; static base::TimeTicks TicksFromSecondsF(double seconds) { return base::TimeTicks() + base::TimeDelta::FromSecondsD(seconds); } // An ElementAnimations cannot be ticked at 0.0, since an animation // with start time 0.0 is treated as an animation whose start time has // not yet been set. const TimeTicks kInitialTickTime = TicksFromSecondsF(1.0); class ElementAnimationsTest : public AnimationTimelinesTest { public: ElementAnimationsTest() = default; ~ElementAnimationsTest() override = default; void SetUp() override { AnimationTimelinesTest::SetUp(); animation_ = SingleKeyframeEffectAnimation::Create(animation_id_); } void CreateImplTimelineAndAnimation() override { AnimationTimelinesTest::CreateImplTimelineAndAnimation(); } std::unique_ptr CreateEventsForTesting() { auto mutator_events = host_impl_->CreateEvents(); return base::WrapUnique( static_cast(mutator_events.release())); } }; // See animation_unittest.cc for integration with Animation. TEST_F(ElementAnimationsTest, AttachToLayerInActiveTree) { // Set up the layer which is in active tree for main thread and not // yet passed onto the impl thread. client_.RegisterElement(element_id_, ElementListType::ACTIVE); client_impl_.RegisterElement(element_id_, ElementListType::PENDING); EXPECT_TRUE(client_.IsElementInList(element_id_, ElementListType::ACTIVE)); EXPECT_FALSE(client_.IsElementInList(element_id_, ElementListType::PENDING)); AttachTimelineAnimationLayer(); EXPECT_TRUE(element_animations_->has_element_in_active_list()); EXPECT_FALSE(element_animations_->has_element_in_pending_list()); PushProperties(); GetImplTimelineAndAnimationByID(); EXPECT_FALSE(element_animations_impl_->has_element_in_active_list()); EXPECT_TRUE(element_animations_impl_->has_element_in_pending_list()); // Create the layer in the impl active tree. client_impl_.RegisterElement(element_id_, ElementListType::ACTIVE); EXPECT_TRUE(element_animations_impl_->has_element_in_active_list()); EXPECT_TRUE(element_animations_impl_->has_element_in_pending_list()); EXPECT_TRUE( client_impl_.IsElementInList(element_id_, ElementListType::ACTIVE)); EXPECT_TRUE( client_impl_.IsElementInList(element_id_, ElementListType::PENDING)); // kill layer on main thread. client_.UnregisterElement(element_id_, ElementListType::ACTIVE); EXPECT_EQ(element_animations_, animation_->keyframe_effect()->element_animations()); EXPECT_FALSE(element_animations_->has_element_in_active_list()); EXPECT_FALSE(element_animations_->has_element_in_pending_list()); // Sync doesn't detach LayerImpl. PushProperties(); EXPECT_EQ(element_animations_impl_, animation_impl_->keyframe_effect()->element_animations()); EXPECT_TRUE(element_animations_impl_->has_element_in_active_list()); EXPECT_TRUE(element_animations_impl_->has_element_in_pending_list()); // Kill layer on impl thread in pending tree. client_impl_.UnregisterElement(element_id_, ElementListType::PENDING); EXPECT_EQ(element_animations_impl_, animation_impl_->keyframe_effect()->element_animations()); EXPECT_TRUE(element_animations_impl_->has_element_in_active_list()); EXPECT_FALSE(element_animations_impl_->has_element_in_pending_list()); // Kill layer on impl thread in active tree. client_impl_.UnregisterElement(element_id_, ElementListType::ACTIVE); EXPECT_EQ(element_animations_impl_, animation_impl_->keyframe_effect()->element_animations()); EXPECT_FALSE(element_animations_impl_->has_element_in_active_list()); EXPECT_FALSE(element_animations_impl_->has_element_in_pending_list()); // Sync doesn't change anything. PushProperties(); EXPECT_EQ(element_animations_impl_, animation_impl_->keyframe_effect()->element_animations()); EXPECT_FALSE(element_animations_impl_->has_element_in_active_list()); EXPECT_FALSE(element_animations_impl_->has_element_in_pending_list()); animation_->DetachElement(); EXPECT_FALSE(animation_->keyframe_effect()->element_animations()); // Release ptrs now to test the order of destruction. ReleaseRefPtrs(); } TEST_F(ElementAnimationsTest, AttachToNotYetCreatedLayer) { host_->AddAnimationTimeline(timeline_); timeline_->AttachAnimation(animation_); PushProperties(); GetImplTimelineAndAnimationByID(); // Perform attachment separately. animation_->AttachElement(element_id_); element_animations_ = animation_->keyframe_effect()->element_animations(); EXPECT_FALSE(element_animations_->has_element_in_active_list()); EXPECT_FALSE(element_animations_->has_element_in_pending_list()); PushProperties(); element_animations_impl_ = animation_impl_->keyframe_effect()->element_animations(); EXPECT_FALSE(element_animations_impl_->has_element_in_active_list()); EXPECT_FALSE(element_animations_impl_->has_element_in_pending_list()); // Create layer. client_.RegisterElement(element_id_, ElementListType::ACTIVE); EXPECT_TRUE(element_animations_->has_element_in_active_list()); EXPECT_FALSE(element_animations_->has_element_in_pending_list()); client_impl_.RegisterElement(element_id_, ElementListType::PENDING); EXPECT_FALSE(element_animations_impl_->has_element_in_active_list()); EXPECT_TRUE(element_animations_impl_->has_element_in_pending_list()); client_impl_.RegisterElement(element_id_, ElementListType::ACTIVE); EXPECT_TRUE(element_animations_impl_->has_element_in_active_list()); EXPECT_TRUE(element_animations_impl_->has_element_in_pending_list()); } TEST_F(ElementAnimationsTest, AddRemoveAnimations) { host_->AddAnimationTimeline(timeline_); timeline_->AttachAnimation(animation_); animation_->AttachElement(element_id_); scoped_refptr element_animations = animation_->keyframe_effect()->element_animations(); EXPECT_TRUE(element_animations); scoped_refptr animation1 = SingleKeyframeEffectAnimation::Create( AnimationIdProvider::NextAnimationId()); scoped_refptr animation2 = SingleKeyframeEffectAnimation::Create( AnimationIdProvider::NextAnimationId()); timeline_->AttachAnimation(animation1); timeline_->AttachAnimation(animation2); // Attach animations to the same layer. animation1->AttachElement(element_id_); animation2->AttachElement(element_id_); EXPECT_EQ(element_animations, animation1->keyframe_effect()->element_animations()); EXPECT_EQ(element_animations, animation2->keyframe_effect()->element_animations()); PushProperties(); GetImplTimelineAndAnimationByID(); scoped_refptr element_animations_impl = animation_impl_->keyframe_effect()->element_animations(); EXPECT_TRUE(element_animations_impl); const ElementAnimations::KeyframeEffectsList& keyframe_effects = element_animations_impl_->keyframe_effects_list(); int list_size_before = 0; for (auto it = keyframe_effects.begin(); it != keyframe_effects.end(); ++it) ++list_size_before; EXPECT_EQ(3, list_size_before); animation2->DetachElement(); EXPECT_FALSE(animation2->keyframe_effect()->element_animations()); EXPECT_EQ(element_animations, animation_->keyframe_effect()->element_animations()); EXPECT_EQ(element_animations, animation1->keyframe_effect()->element_animations()); PushProperties(); EXPECT_EQ(element_animations_impl, animation_impl_->keyframe_effect()->element_animations()); int list_size_after = 0; for (auto it = keyframe_effects.begin(); it != keyframe_effects.end(); ++it) ++list_size_after; EXPECT_EQ(2, list_size_after); } TEST_F(ElementAnimationsTest, SyncNewAnimation) { CreateTestLayer(true, false); AttachTimelineAnimationLayer(); CreateImplTimelineAndAnimation(); EXPECT_FALSE(animation_->GetKeyframeModel(TargetProperty::OPACITY)); EXPECT_FALSE(animation_impl_->GetKeyframeModel(TargetProperty::OPACITY)); int keyframe_model_id = AddOpacityTransitionToAnimation(animation_.get(), 1, 0, 1, false); EXPECT_TRUE( animation_->keyframe_effect()->GetKeyframeModelById(keyframe_model_id)); EXPECT_EQ(KeyframeModel::WAITING_FOR_TARGET_AVAILABILITY, animation_->keyframe_effect() ->GetKeyframeModelById(keyframe_model_id) ->run_state()); EXPECT_FALSE(animation_impl_->keyframe_effect()->GetKeyframeModelById( keyframe_model_id)); PushProperties(); animation_impl_->ActivateKeyframeEffects(); EXPECT_TRUE(animation_impl_->keyframe_effect()->GetKeyframeModelById( keyframe_model_id)); EXPECT_EQ(KeyframeModel::WAITING_FOR_TARGET_AVAILABILITY, animation_impl_->keyframe_effect() ->GetKeyframeModelById(keyframe_model_id) ->run_state()); } TEST_F(ElementAnimationsTest, SyncScrollOffsetAnimationRespectsHasSetInitialValue) { CreateTestLayer(true, false); AttachTimelineAnimationLayer(); CreateImplTimelineAndAnimation(); EXPECT_FALSE(animation_->GetKeyframeModel(TargetProperty::SCROLL_OFFSET)); EXPECT_FALSE( animation_impl_->GetKeyframeModel(TargetProperty::SCROLL_OFFSET)); gfx::ScrollOffset initial_value(100.f, 300.f); gfx::ScrollOffset provider_initial_value(150.f, 300.f); gfx::ScrollOffset target_value(300.f, 200.f); client_impl_.SetScrollOffsetForAnimation(provider_initial_value); // Animation with initial value set. std::unique_ptr curve_fixed( ScrollOffsetAnimationCurve::Create( target_value, CubicBezierTimingFunction::CreatePreset( CubicBezierTimingFunction::EaseType::EASE_IN_OUT))); curve_fixed->SetInitialValue(initial_value); const int animation1_id = 1; std::unique_ptr animation_fixed(KeyframeModel::Create( std::move(curve_fixed), animation1_id, 0, TargetProperty::SCROLL_OFFSET)); animation_->AddKeyframeModel(std::move(animation_fixed)); PushProperties(); EXPECT_VECTOR2DF_EQ(initial_value, animation_impl_->keyframe_effect() ->GetKeyframeModelById(animation1_id) ->curve() ->ToScrollOffsetAnimationCurve() ->GetValue(base::TimeDelta())); // Animation without initial value set. std::unique_ptr curve( ScrollOffsetAnimationCurve::Create( target_value, CubicBezierTimingFunction::CreatePreset( CubicBezierTimingFunction::EaseType::EASE_IN_OUT))); const int animation2_id = 2; std::unique_ptr keyframe_model(KeyframeModel::Create( std::move(curve), animation2_id, 0, TargetProperty::SCROLL_OFFSET)); animation_->AddKeyframeModel(std::move(keyframe_model)); PushProperties(); EXPECT_VECTOR2DF_EQ(provider_initial_value, animation_impl_->keyframe_effect() ->GetKeyframeModelById(animation2_id) ->curve() ->ToScrollOffsetAnimationCurve() ->GetValue(base::TimeDelta())); } class TestAnimationDelegateThatDestroysAnimation : public TestAnimationDelegate { public: TestAnimationDelegateThatDestroysAnimation() = default; void NotifyAnimationStarted(base::TimeTicks monotonic_time, int target_property, int group) override { TestAnimationDelegate::NotifyAnimationStarted(monotonic_time, target_property, group); // Detaching animation from the timeline ensures that the timeline doesn't // hold a reference to the animation and the animation is destroyed. timeline_->DetachAnimation(animation_); }; void setTimelineAndAnimation( scoped_refptr timeline, scoped_refptr animation) { timeline_ = timeline; animation_ = animation; } private: scoped_refptr timeline_; scoped_refptr animation_; }; // Test that we don't crash if a animation is deleted while ElementAnimations is // iterating through the list of animations (see crbug.com/631052). This test // passes if it doesn't crash. TEST_F(ElementAnimationsTest, AddedAnimationIsDestroyed) { CreateTestLayer(true, false); AttachTimelineAnimationLayer(); CreateImplTimelineAndAnimation(); TestAnimationDelegateThatDestroysAnimation delegate; const int animation2_id = AnimationIdProvider::NextAnimationId(); scoped_refptr animation2 = SingleKeyframeEffectAnimation::Create(animation2_id); delegate.setTimelineAndAnimation(timeline_, animation2); timeline_->AttachAnimation(animation2); animation2->AttachElement(element_id_); animation2->set_animation_delegate(&delegate); int keyframe_model_id = AddOpacityTransitionToAnimation(animation2.get(), 1.0, 0.f, 1.f, false); PushProperties(); scoped_refptr animation2_impl = (SingleKeyframeEffectAnimation*)timeline_impl_->GetAnimationById( animation2_id); DCHECK(animation2_impl); animation2_impl->ActivateKeyframeEffects(); EXPECT_TRUE(animation2_impl->keyframe_effect()->GetKeyframeModelById( keyframe_model_id)); animation2_impl->Tick(kInitialTickTime); auto events = CreateEventsForTesting(); animation2_impl->UpdateState(true, events.get()); EXPECT_EQ(1u, events->events_.size()); EXPECT_EQ(AnimationEvent::STARTED, events->events_[0].type); // The actual detachment happens here, inside the callback animation2->keyframe_effect()->NotifyKeyframeModelStarted(events->events_[0]); EXPECT_TRUE(delegate.started()); } // If an animation is started on the impl thread before it is ticked on the main // thread, we must be sure to respect the synchronized start time. TEST_F(ElementAnimationsTest, DoNotClobberStartTimes) { CreateTestLayer(true, false); AttachTimelineAnimationLayer(); CreateImplTimelineAndAnimation(); EXPECT_FALSE(animation_impl_->GetKeyframeModel(TargetProperty::OPACITY)); int keyframe_model_id = AddOpacityTransitionToAnimation(animation_.get(), 1, 0, 1, false); PushProperties(); animation_impl_->ActivateKeyframeEffects(); EXPECT_TRUE(animation_impl_->keyframe_effect()->GetKeyframeModelById( keyframe_model_id)); EXPECT_EQ(KeyframeModel::WAITING_FOR_TARGET_AVAILABILITY, animation_impl_->keyframe_effect() ->GetKeyframeModelById(keyframe_model_id) ->run_state()); auto events = CreateEventsForTesting(); animation_impl_->Tick(kInitialTickTime); animation_impl_->UpdateState(true, events.get()); // Synchronize the start times. EXPECT_EQ(1u, events->events_.size()); animation_->keyframe_effect()->NotifyKeyframeModelStarted(events->events_[0]); EXPECT_EQ(animation_->keyframe_effect() ->GetKeyframeModelById(keyframe_model_id) ->start_time(), animation_impl_->keyframe_effect() ->GetKeyframeModelById(keyframe_model_id) ->start_time()); // Start the animation on the main thread. Should not affect the start time. animation_impl_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(500)); animation_impl_->UpdateState(true, nullptr); EXPECT_EQ(animation_->keyframe_effect() ->GetKeyframeModelById(keyframe_model_id) ->start_time(), animation_impl_->keyframe_effect() ->GetKeyframeModelById(keyframe_model_id) ->start_time()); } TEST_F(ElementAnimationsTest, UseSpecifiedStartTimes) { CreateTestLayer(true, false); AttachTimelineAnimationLayer(); CreateImplTimelineAndAnimation(); int keyframe_model_id = AddOpacityTransitionToAnimation(animation_.get(), 1, 0, 1, false); const TimeTicks start_time = TicksFromSecondsF(123); animation_->GetKeyframeModel(TargetProperty::OPACITY) ->set_start_time(start_time); PushProperties(); animation_impl_->ActivateKeyframeEffects(); EXPECT_TRUE(animation_impl_->keyframe_effect()->GetKeyframeModelById( keyframe_model_id)); EXPECT_EQ(KeyframeModel::WAITING_FOR_TARGET_AVAILABILITY, animation_impl_->keyframe_effect() ->GetKeyframeModelById(keyframe_model_id) ->run_state()); auto events = CreateEventsForTesting(); animation_impl_->Tick(kInitialTickTime); animation_impl_->UpdateState(true, events.get()); // Synchronize the start times. EXPECT_EQ(1u, events->events_.size()); animation_->keyframe_effect()->NotifyKeyframeModelStarted(events->events_[0]); EXPECT_EQ(start_time, animation_->keyframe_effect() ->GetKeyframeModelById(keyframe_model_id) ->start_time()); EXPECT_EQ(animation_->keyframe_effect() ->GetKeyframeModelById(keyframe_model_id) ->start_time(), animation_impl_->keyframe_effect() ->GetKeyframeModelById(keyframe_model_id) ->start_time()); // Start the animation on the main thread. Should not affect the start time. animation_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(500)); animation_->UpdateState(true, nullptr); EXPECT_EQ(start_time, animation_->keyframe_effect() ->GetKeyframeModelById(keyframe_model_id) ->start_time()); EXPECT_EQ(animation_->keyframe_effect() ->GetKeyframeModelById(keyframe_model_id) ->start_time(), animation_impl_->keyframe_effect() ->GetKeyframeModelById(keyframe_model_id) ->start_time()); } // Tests that animationss activate and deactivate as expected. TEST_F(ElementAnimationsTest, Activation) { CreateTestLayer(true, false); AttachTimelineAnimationLayer(); CreateImplTimelineAndAnimation(); AnimationHost* host = client_.host(); AnimationHost* host_impl = client_impl_.host(); auto events = CreateEventsForTesting(); EXPECT_EQ(1u, host->element_animations_for_testing().size()); EXPECT_EQ(1u, host_impl->element_animations_for_testing().size()); // Initially, both animationss should be inactive. EXPECT_EQ(0u, host->ticking_animations_for_testing().size()); EXPECT_EQ(0u, host_impl->ticking_animations_for_testing().size()); AddOpacityTransitionToAnimation(animation_.get(), 1, 0, 1, false); // The main thread animations should now be active. EXPECT_EQ(1u, host->ticking_animations_for_testing().size()); PushProperties(); animation_impl_->ActivateKeyframeEffects(); // Both animationss should now be active. EXPECT_EQ(1u, host->ticking_animations_for_testing().size()); EXPECT_EQ(1u, host_impl->ticking_animations_for_testing().size()); animation_impl_->Tick(kInitialTickTime); animation_impl_->UpdateState(true, events.get()); EXPECT_EQ(1u, events->events_.size()); animation_->keyframe_effect()->NotifyKeyframeModelStarted(events->events_[0]); EXPECT_EQ(1u, host->ticking_animations_for_testing().size()); EXPECT_EQ(1u, host_impl->ticking_animations_for_testing().size()); animation_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(500)); animation_->UpdateState(true, nullptr); EXPECT_EQ(1u, host->ticking_animations_for_testing().size()); animation_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(1000)); animation_->UpdateState(true, nullptr); EXPECT_EQ(KeyframeModel::FINISHED, animation_->GetKeyframeModel(TargetProperty::OPACITY)->run_state()); EXPECT_EQ(1u, host->ticking_animations_for_testing().size()); events = CreateEventsForTesting(); animation_impl_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(1500)); animation_impl_->UpdateState(true, events.get()); EXPECT_EQ( KeyframeModel::WAITING_FOR_DELETION, animation_impl_->GetKeyframeModel(TargetProperty::OPACITY)->run_state()); // The impl thread animations should have de-activated. EXPECT_EQ(0u, host_impl->ticking_animations_for_testing().size()); EXPECT_EQ(1u, events->events_.size()); animation_->keyframe_effect()->NotifyKeyframeModelFinished( events->events_[0]); animation_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(1500)); animation_->UpdateState(true, nullptr); EXPECT_EQ(KeyframeModel::WAITING_FOR_DELETION, animation_->GetKeyframeModel(TargetProperty::OPACITY)->run_state()); // The main thread animations should have de-activated. EXPECT_EQ(0u, host->ticking_animations_for_testing().size()); PushProperties(); animation_impl_->ActivateKeyframeEffects(); EXPECT_FALSE(animation_->keyframe_effect()->has_any_keyframe_model()); EXPECT_FALSE(animation_impl_->keyframe_effect()->has_any_keyframe_model()); EXPECT_EQ(0u, host->ticking_animations_for_testing().size()); EXPECT_EQ(0u, host_impl->ticking_animations_for_testing().size()); } TEST_F(ElementAnimationsTest, SyncPause) { CreateTestLayer(true, false); AttachTimelineAnimationLayer(); CreateImplTimelineAndAnimation(); EXPECT_FALSE(animation_impl_->GetKeyframeModel(TargetProperty::OPACITY)); // Two steps, three ranges: [0-1) -> 0.2, [1-2) -> 0.3, [2-3] -> 0.4. const double duration = 3.0; const int keyframe_model_id = AddOpacityStepsToAnimation(animation_.get(), duration, 0.2f, 0.4f, 2); // Set start offset to be at the beginning of the second range. animation_->keyframe_effect() ->GetKeyframeModelById(keyframe_model_id) ->set_time_offset(TimeDelta::FromSecondsD(1.01)); PushProperties(); animation_impl_->ActivateKeyframeEffects(); EXPECT_TRUE(animation_impl_->keyframe_effect()->GetKeyframeModelById( keyframe_model_id)); EXPECT_EQ(KeyframeModel::WAITING_FOR_TARGET_AVAILABILITY, animation_impl_->keyframe_effect() ->GetKeyframeModelById(keyframe_model_id) ->run_state()); TimeTicks time = kInitialTickTime; // Start the animations on each animations. auto events = CreateEventsForTesting(); animation_impl_->Tick(time); animation_impl_->UpdateState(true, events.get()); EXPECT_EQ(1u, events->events_.size()); animation_->Tick(time); animation_->UpdateState(true, nullptr); animation_->keyframe_effect()->NotifyKeyframeModelStarted(events->events_[0]); EXPECT_EQ(KeyframeModel::RUNNING, animation_impl_->keyframe_effect() ->GetKeyframeModelById(keyframe_model_id) ->run_state()); EXPECT_EQ(KeyframeModel::RUNNING, animation_->keyframe_effect() ->GetKeyframeModelById(keyframe_model_id) ->run_state()); EXPECT_EQ(0.3f, client_.GetOpacity(element_id_, ElementListType::ACTIVE)); EXPECT_EQ(0.3f, client_impl_.GetOpacity(element_id_, ElementListType::ACTIVE)); EXPECT_EQ(kInitialTickTime, animation_->keyframe_effect() ->GetKeyframeModelById(keyframe_model_id) ->start_time()); EXPECT_EQ(kInitialTickTime, animation_impl_->keyframe_effect() ->GetKeyframeModelById(keyframe_model_id) ->start_time()); // Pause the animation at the middle of the second range so the offset // delays animation until the middle of the third range. animation_->PauseKeyframeModel(keyframe_model_id, 1.5); EXPECT_EQ(KeyframeModel::PAUSED, animation_->keyframe_effect() ->GetKeyframeModelById(keyframe_model_id) ->run_state()); // The pause run state change should make it to the impl thread animations. PushProperties(); animation_impl_->ActivateKeyframeEffects(); // Advance time so it stays within the first range. time += TimeDelta::FromMilliseconds(10); animation_->Tick(time); animation_impl_->Tick(time); EXPECT_EQ(KeyframeModel::PAUSED, animation_impl_->keyframe_effect() ->GetKeyframeModelById(keyframe_model_id) ->run_state()); // Opacity value doesn't depend on time if paused at specified time offset. EXPECT_EQ(0.4f, client_.GetOpacity(element_id_, ElementListType::ACTIVE)); EXPECT_EQ(0.4f, client_impl_.GetOpacity(element_id_, ElementListType::ACTIVE)); } TEST_F(ElementAnimationsTest, DoNotSyncFinishedAnimation) { CreateTestLayer(true, false); AttachTimelineAnimationLayer(); CreateImplTimelineAndAnimation(); auto events = CreateEventsForTesting(); EXPECT_FALSE(animation_impl_->GetKeyframeModel(TargetProperty::OPACITY)); int keyframe_model_id = AddOpacityTransitionToAnimation(animation_.get(), 1, 0, 1, false); PushProperties(); animation_impl_->ActivateKeyframeEffects(); EXPECT_TRUE(animation_impl_->keyframe_effect()->GetKeyframeModelById( keyframe_model_id)); EXPECT_EQ(KeyframeModel::WAITING_FOR_TARGET_AVAILABILITY, animation_impl_->keyframe_effect() ->GetKeyframeModelById(keyframe_model_id) ->run_state()); events = CreateEventsForTesting(); animation_impl_->Tick(kInitialTickTime); animation_impl_->UpdateState(true, events.get()); EXPECT_EQ(1u, events->events_.size()); EXPECT_EQ(AnimationEvent::STARTED, events->events_[0].type); // Notify main thread animations that the animation has started. animation_->keyframe_effect()->NotifyKeyframeModelStarted(events->events_[0]); // Complete animation on impl thread. events = CreateEventsForTesting(); animation_impl_->Tick(kInitialTickTime + TimeDelta::FromSeconds(1)); animation_impl_->UpdateState(true, events.get()); EXPECT_EQ(1u, events->events_.size()); EXPECT_EQ(AnimationEvent::FINISHED, events->events_[0].type); animation_->keyframe_effect()->NotifyKeyframeModelFinished( events->events_[0]); animation_->Tick(kInitialTickTime + TimeDelta::FromSeconds(2)); animation_->UpdateState(true, nullptr); PushProperties(); animation_impl_->ActivateKeyframeEffects(); EXPECT_FALSE( animation_->keyframe_effect()->GetKeyframeModelById(keyframe_model_id)); EXPECT_FALSE(animation_impl_->keyframe_effect()->GetKeyframeModelById( keyframe_model_id)); } // Ensure that a finished animation is eventually deleted by both the // main-thread and the impl-thread animationss. TEST_F(ElementAnimationsTest, AnimationsAreDeleted) { CreateTestLayer(true, false); AttachTimelineAnimationLayer(); CreateImplTimelineAndAnimation(); auto events = CreateEventsForTesting(); AddOpacityTransitionToAnimation(animation_.get(), 1.0, 0.0f, 1.0f, false); animation_->Tick(kInitialTickTime); animation_->UpdateState(true, nullptr); EXPECT_TRUE(animation_->keyframe_effect()->needs_push_properties()); PushProperties(); EXPECT_FALSE(animation_->keyframe_effect()->needs_push_properties()); EXPECT_FALSE(host_->needs_push_properties()); EXPECT_FALSE(host_impl_->needs_push_properties()); animation_impl_->ActivateKeyframeEffects(); animation_impl_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(500)); animation_impl_->UpdateState(true, events.get()); // There should be a STARTED event for the animation. EXPECT_EQ(1u, events->events_.size()); EXPECT_EQ(AnimationEvent::STARTED, events->events_[0].type); animation_->keyframe_effect()->NotifyKeyframeModelStarted(events->events_[0]); animation_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(1000)); animation_->UpdateState(true, nullptr); EXPECT_FALSE(host_->needs_push_properties()); EXPECT_FALSE(host_impl_->needs_push_properties()); events = CreateEventsForTesting(); animation_impl_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(2000)); animation_impl_->UpdateState(true, events.get()); EXPECT_TRUE(host_impl_->needs_push_properties()); // There should be a FINISHED event for the animation. EXPECT_EQ(1u, events->events_.size()); EXPECT_EQ(AnimationEvent::FINISHED, events->events_[0].type); // Neither animations should have deleted the animation yet. EXPECT_TRUE(animation_->GetKeyframeModel(TargetProperty::OPACITY)); EXPECT_TRUE(animation_impl_->GetKeyframeModel(TargetProperty::OPACITY)); animation_->keyframe_effect()->NotifyKeyframeModelFinished( events->events_[0]); animation_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(3000)); animation_->UpdateState(true, nullptr); EXPECT_TRUE(host_->needs_push_properties()); PushProperties(); // Both animationss should now have deleted the animation. The impl animations // should have deleted the animation even though activation has not occurred, // since the animation was already waiting for deletion when // PushPropertiesTo was called. EXPECT_FALSE(animation_->keyframe_effect()->has_any_keyframe_model()); EXPECT_FALSE(animation_impl_->keyframe_effect()->has_any_keyframe_model()); } // Tests that transitioning opacity from 0 to 1 works as expected. static std::unique_ptr CreateKeyframeModel( std::unique_ptr curve, int group_id, TargetProperty::Type property) { return KeyframeModel::Create(std::move(curve), 0, group_id, property); } TEST_F(ElementAnimationsTest, TrivialTransition) { CreateTestLayer(true, false); AttachTimelineAnimationLayer(); auto events = CreateEventsForTesting(); std::unique_ptr to_add(CreateKeyframeModel( std::unique_ptr(new FakeFloatTransition(1.0, 0.f, 1.f)), 1, TargetProperty::OPACITY)); int keyframe_model_id = to_add->id(); EXPECT_FALSE( animation_->keyframe_effect()->GetKeyframeModelById(keyframe_model_id)); animation_->AddKeyframeModel(std::move(to_add)); EXPECT_TRUE( animation_->keyframe_effect()->GetKeyframeModelById(keyframe_model_id)); EXPECT_EQ(KeyframeModel::WAITING_FOR_TARGET_AVAILABILITY, animation_->keyframe_effect() ->GetKeyframeModelById(keyframe_model_id) ->run_state()); animation_->Tick(kInitialTickTime); EXPECT_EQ(KeyframeModel::STARTING, animation_->keyframe_effect() ->GetKeyframeModelById(keyframe_model_id) ->run_state()); animation_->UpdateState(true, events.get()); EXPECT_TRUE(animation_->keyframe_effect()->HasTickingKeyframeModel()); EXPECT_EQ(0.f, client_.GetOpacity(element_id_, ElementListType::ACTIVE)); animation_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(1000)); animation_->UpdateState(true, events.get()); EXPECT_EQ(1.f, client_.GetOpacity(element_id_, ElementListType::ACTIVE)); EXPECT_FALSE(animation_->keyframe_effect()->HasTickingKeyframeModel()); } TEST_F(ElementAnimationsTest, FilterTransition) { CreateTestLayer(true, false); AttachTimelineAnimationLayer(); auto events = CreateEventsForTesting(); std::unique_ptr curve( KeyframedFilterAnimationCurve::Create()); FilterOperations start_filters; start_filters.Append(FilterOperation::CreateBrightnessFilter(1.f)); curve->AddKeyframe( FilterKeyframe::Create(base::TimeDelta(), start_filters, nullptr)); FilterOperations end_filters; end_filters.Append(FilterOperation::CreateBrightnessFilter(2.f)); curve->AddKeyframe(FilterKeyframe::Create(base::TimeDelta::FromSecondsD(1.0), end_filters, nullptr)); std::unique_ptr keyframe_model( KeyframeModel::Create(std::move(curve), 1, 0, TargetProperty::FILTER)); animation_->AddKeyframeModel(std::move(keyframe_model)); animation_->Tick(kInitialTickTime); animation_->UpdateState(true, events.get()); EXPECT_TRUE(animation_->keyframe_effect()->HasTickingKeyframeModel()); EXPECT_EQ(start_filters, client_.GetFilters(element_id_, ElementListType::ACTIVE)); animation_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(500)); animation_->UpdateState(true, events.get()); EXPECT_EQ(1u, client_.GetFilters(element_id_, ElementListType::ACTIVE).size()); EXPECT_EQ(FilterOperation::CreateBrightnessFilter(1.5f), client_.GetFilters(element_id_, ElementListType::ACTIVE).at(0)); animation_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(1000)); animation_->UpdateState(true, events.get()); EXPECT_EQ(end_filters, client_.GetFilters(element_id_, ElementListType::ACTIVE)); EXPECT_FALSE(animation_->keyframe_effect()->HasTickingKeyframeModel()); } TEST_F(ElementAnimationsTest, ScrollOffsetTransition) { CreateTestLayer(true, false); AttachTimelineAnimationLayer(); CreateImplTimelineAndAnimation(); auto events = CreateEventsForTesting(); gfx::ScrollOffset initial_value(100.f, 300.f); gfx::ScrollOffset target_value(300.f, 200.f); std::unique_ptr curve( ScrollOffsetAnimationCurve::Create( target_value, 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); animation_->AddKeyframeModel(std::move(keyframe_model)); client_impl_.SetScrollOffsetForAnimation(initial_value); PushProperties(); animation_impl_->ActivateKeyframeEffects(); EXPECT_TRUE(animation_impl_->GetKeyframeModel(TargetProperty::SCROLL_OFFSET)); TimeDelta duration = animation_impl_->GetKeyframeModel(TargetProperty::SCROLL_OFFSET) ->curve() ->Duration(); EXPECT_EQ(duration, animation_->GetKeyframeModel(TargetProperty::SCROLL_OFFSET) ->curve() ->Duration()); animation_->Tick(kInitialTickTime); animation_->UpdateState(true, nullptr); EXPECT_TRUE(animation_->keyframe_effect()->HasTickingKeyframeModel()); EXPECT_EQ(initial_value, client_.GetScrollOffset(element_id_, ElementListType::ACTIVE)); animation_impl_->Tick(kInitialTickTime); animation_impl_->UpdateState(true, events.get()); EXPECT_TRUE(animation_impl_->keyframe_effect()->HasTickingKeyframeModel()); EXPECT_EQ(initial_value, client_impl_.GetScrollOffset(element_id_, ElementListType::ACTIVE)); animation_->keyframe_effect()->NotifyKeyframeModelStarted(events->events_[0]); animation_->Tick(kInitialTickTime + duration / 2); animation_->UpdateState(true, nullptr); EXPECT_TRUE(animation_->keyframe_effect()->HasTickingKeyframeModel()); EXPECT_VECTOR2DF_EQ( gfx::Vector2dF(200.f, 250.f), client_.GetScrollOffset(element_id_, ElementListType::ACTIVE)); animation_impl_->Tick(kInitialTickTime + duration / 2); animation_impl_->UpdateState(true, events.get()); EXPECT_VECTOR2DF_EQ( gfx::Vector2dF(200.f, 250.f), client_impl_.GetScrollOffset(element_id_, ElementListType::ACTIVE)); animation_impl_->Tick(kInitialTickTime + duration); animation_impl_->UpdateState(true, events.get()); EXPECT_VECTOR2DF_EQ(target_value, client_impl_.GetScrollOffset( element_id_, ElementListType::ACTIVE)); EXPECT_FALSE(animation_impl_->keyframe_effect()->HasTickingKeyframeModel()); animation_->Tick(kInitialTickTime + duration); animation_->UpdateState(true, nullptr); EXPECT_VECTOR2DF_EQ(target_value, client_.GetScrollOffset( element_id_, ElementListType::ACTIVE)); EXPECT_FALSE(animation_->keyframe_effect()->HasTickingKeyframeModel()); } TEST_F(ElementAnimationsTest, ScrollOffsetTransitionOnImplOnly) { CreateTestLayer(true, false); AttachTimelineAnimationLayer(); CreateImplTimelineAndAnimation(); auto events = CreateEventsForTesting(); gfx::ScrollOffset initial_value(100.f, 300.f); gfx::ScrollOffset target_value(300.f, 200.f); std::unique_ptr curve( ScrollOffsetAnimationCurve::Create( target_value, CubicBezierTimingFunction::CreatePreset( CubicBezierTimingFunction::EaseType::EASE_IN_OUT))); curve->SetInitialValue(initial_value); double duration_in_seconds = curve->Duration().InSecondsF(); std::unique_ptr keyframe_model(KeyframeModel::Create( std::move(curve), 1, 0, TargetProperty::SCROLL_OFFSET)); keyframe_model->SetIsImplOnly(); animation_impl_->AddKeyframeModel(std::move(keyframe_model)); animation_impl_->Tick(kInitialTickTime); animation_impl_->UpdateState(true, events.get()); EXPECT_TRUE(animation_impl_->keyframe_effect()->HasTickingKeyframeModel()); EXPECT_EQ(initial_value, client_impl_.GetScrollOffset(element_id_, ElementListType::ACTIVE)); TimeDelta duration = TimeDelta::FromMicroseconds( duration_in_seconds * base::Time::kMicrosecondsPerSecond); animation_impl_->Tick(kInitialTickTime + duration / 2); animation_impl_->UpdateState(true, events.get()); EXPECT_VECTOR2DF_EQ( gfx::Vector2dF(200.f, 250.f), client_impl_.GetScrollOffset(element_id_, ElementListType::ACTIVE)); animation_impl_->Tick(kInitialTickTime + duration); animation_impl_->UpdateState(true, events.get()); EXPECT_VECTOR2DF_EQ(target_value, client_impl_.GetScrollOffset( element_id_, ElementListType::ACTIVE)); EXPECT_FALSE(animation_impl_->keyframe_effect()->HasTickingKeyframeModel()); } // This test verifies that if an animation is added after a layer is animated, // it doesn't get promoted to be in the RUNNING state. This prevents cases where // a start time gets set on an animation using the stale value of // last_tick_time_. TEST_F(ElementAnimationsTest, UpdateStateWithoutAnimate) { CreateTestLayer(true, false); AttachTimelineAnimationLayer(); CreateImplTimelineAndAnimation(); auto events = CreateEventsForTesting(); // Add first scroll offset animation. AddScrollOffsetAnimationToAnimation(animation_impl_.get(), gfx::ScrollOffset(100.f, 300.f), gfx::ScrollOffset(100.f, 200.f)); // Calling UpdateState after Animate should promote the animation to running // state. animation_impl_->Tick(kInitialTickTime); animation_impl_->UpdateState(true, events.get()); EXPECT_EQ(KeyframeModel::RUNNING, animation_impl_->GetKeyframeModel(TargetProperty::SCROLL_OFFSET) ->run_state()); animation_impl_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(1500)); animation_impl_->UpdateState(true, events.get()); EXPECT_EQ(nullptr, animation_impl_->GetKeyframeModel(TargetProperty::SCROLL_OFFSET)); // Add second scroll offset animation. AddScrollOffsetAnimationToAnimation(animation_impl_.get(), gfx::ScrollOffset(100.f, 200.f), gfx::ScrollOffset(100.f, 100.f)); // Calling UpdateState without Animate should NOT promote the animation to // running state. animation_impl_->UpdateState(true, events.get()); EXPECT_EQ(KeyframeModel::WAITING_FOR_TARGET_AVAILABILITY, animation_impl_->GetKeyframeModel(TargetProperty::SCROLL_OFFSET) ->run_state()); animation_impl_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(2000)); animation_impl_->UpdateState(true, events.get()); EXPECT_EQ(KeyframeModel::RUNNING, animation_impl_->GetKeyframeModel(TargetProperty::SCROLL_OFFSET) ->run_state()); EXPECT_VECTOR2DF_EQ( gfx::ScrollOffset(100.f, 200.f), client_impl_.GetScrollOffset(element_id_, ElementListType::ACTIVE)); } // Ensure that when the impl animations doesn't have a value provider, // the main-thread animations's value provider is used to obtain the intial // scroll offset. TEST_F(ElementAnimationsTest, ScrollOffsetTransitionNoImplProvider) { CreateTestLayer(false, false); CreateTestImplLayer(ElementListType::PENDING); AttachTimelineAnimationLayer(); CreateImplTimelineAndAnimation(); EXPECT_TRUE(element_animations_impl_->has_element_in_pending_list()); EXPECT_FALSE(element_animations_impl_->has_element_in_active_list()); auto events = CreateEventsForTesting(); gfx::ScrollOffset initial_value(500.f, 100.f); gfx::ScrollOffset target_value(300.f, 200.f); std::unique_ptr curve( ScrollOffsetAnimationCurve::Create( target_value, 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); animation_->AddKeyframeModel(std::move(keyframe_model)); client_.SetScrollOffsetForAnimation(initial_value); PushProperties(); animation_impl_->ActivateKeyframeEffects(); EXPECT_TRUE(animation_impl_->GetKeyframeModel(TargetProperty::SCROLL_OFFSET)); TimeDelta duration = animation_impl_->GetKeyframeModel(TargetProperty::SCROLL_OFFSET) ->curve() ->Duration(); EXPECT_EQ(duration, animation_->GetKeyframeModel(TargetProperty::SCROLL_OFFSET) ->curve() ->Duration()); animation_->Tick(kInitialTickTime); animation_->UpdateState(true, nullptr); EXPECT_TRUE(animation_->keyframe_effect()->HasTickingKeyframeModel()); EXPECT_EQ(initial_value, client_.GetScrollOffset(element_id_, ElementListType::ACTIVE)); EXPECT_EQ(gfx::ScrollOffset(), client_impl_.GetScrollOffset( element_id_, ElementListType::PENDING)); animation_impl_->Tick(kInitialTickTime); EXPECT_TRUE(animation_impl_->keyframe_effect()->HasTickingKeyframeModel()); EXPECT_EQ(initial_value, client_impl_.GetScrollOffset( element_id_, ElementListType::PENDING)); CreateTestImplLayer(ElementListType::ACTIVE); animation_impl_->UpdateState(true, events.get()); DCHECK_EQ(1UL, events->events_.size()); animation_->keyframe_effect()->NotifyKeyframeModelStarted(events->events_[0]); animation_->Tick(kInitialTickTime + duration / 2); animation_->UpdateState(true, nullptr); EXPECT_TRUE(animation_->keyframe_effect()->HasTickingKeyframeModel()); EXPECT_VECTOR2DF_EQ( gfx::Vector2dF(400.f, 150.f), client_.GetScrollOffset(element_id_, ElementListType::ACTIVE)); animation_impl_->Tick(kInitialTickTime + duration / 2); animation_impl_->UpdateState(true, events.get()); EXPECT_VECTOR2DF_EQ( gfx::Vector2dF(400.f, 150.f), client_impl_.GetScrollOffset(element_id_, ElementListType::PENDING)); animation_impl_->Tick(kInitialTickTime + duration); animation_impl_->UpdateState(true, events.get()); EXPECT_VECTOR2DF_EQ(target_value, client_impl_.GetScrollOffset( element_id_, ElementListType::PENDING)); EXPECT_FALSE(animation_impl_->keyframe_effect()->HasTickingKeyframeModel()); animation_->Tick(kInitialTickTime + duration); animation_->UpdateState(true, nullptr); EXPECT_VECTOR2DF_EQ(target_value, client_.GetScrollOffset( element_id_, ElementListType::ACTIVE)); EXPECT_FALSE(animation_->keyframe_effect()->HasTickingKeyframeModel()); } TEST_F(ElementAnimationsTest, ScrollOffsetRemovalClearsScrollDelta) { CreateTestLayer(true, false); AttachTimelineAnimationLayer(); CreateImplTimelineAndAnimation(); auto events = CreateEventsForTesting(); // First test the 1-argument version of RemoveKeyframeModel. gfx::ScrollOffset target_value(300.f, 200.f); std::unique_ptr curve( ScrollOffsetAnimationCurve::Create( target_value, CubicBezierTimingFunction::CreatePreset( CubicBezierTimingFunction::EaseType::EASE_IN_OUT))); int keyframe_model_id = 1; std::unique_ptr keyframe_model(KeyframeModel::Create( std::move(curve), keyframe_model_id, 0, TargetProperty::SCROLL_OFFSET)); keyframe_model->set_needs_synchronized_start_time(true); animation_->AddKeyframeModel(std::move(keyframe_model)); PushProperties(); animation_impl_->ActivateKeyframeEffects(); EXPECT_FALSE( animation_->keyframe_effect()->scroll_offset_animation_was_interrupted()); EXPECT_FALSE(animation_impl_->keyframe_effect() ->scroll_offset_animation_was_interrupted()); animation_->RemoveKeyframeModel(keyframe_model_id); EXPECT_TRUE( animation_->keyframe_effect()->scroll_offset_animation_was_interrupted()); PushProperties(); EXPECT_TRUE(animation_impl_->keyframe_effect() ->scroll_offset_animation_was_interrupted()); EXPECT_FALSE( animation_->keyframe_effect()->scroll_offset_animation_was_interrupted()); animation_impl_->ActivateKeyframeEffects(); EXPECT_FALSE(animation_impl_->keyframe_effect() ->scroll_offset_animation_was_interrupted()); // Now, test the 2-argument version of RemoveKeyframeModel. curve = ScrollOffsetAnimationCurve::Create( target_value, CubicBezierTimingFunction::CreatePreset( CubicBezierTimingFunction::EaseType::EASE_IN_OUT)); keyframe_model = KeyframeModel::Create(std::move(curve), keyframe_model_id, 0, TargetProperty::SCROLL_OFFSET); keyframe_model->set_needs_synchronized_start_time(true); animation_->AddKeyframeModel(std::move(keyframe_model)); PushProperties(); animation_impl_->ActivateKeyframeEffects(); EXPECT_FALSE( animation_->keyframe_effect()->scroll_offset_animation_was_interrupted()); EXPECT_FALSE(animation_impl_->keyframe_effect() ->scroll_offset_animation_was_interrupted()); animation_->RemoveKeyframeModel(keyframe_model_id); EXPECT_TRUE( animation_->keyframe_effect()->scroll_offset_animation_was_interrupted()); PushProperties(); EXPECT_TRUE(animation_impl_->keyframe_effect() ->scroll_offset_animation_was_interrupted()); EXPECT_FALSE( animation_->keyframe_effect()->scroll_offset_animation_was_interrupted()); animation_impl_->ActivateKeyframeEffects(); EXPECT_FALSE(animation_impl_->keyframe_effect() ->scroll_offset_animation_was_interrupted()); // Check that removing non-scroll-offset animations does not cause // scroll_offset_animation_was_interrupted() to get set. keyframe_model_id = AddAnimatedTransformToAnimation(animation_.get(), 1.0, 1, 2); PushProperties(); animation_impl_->ActivateKeyframeEffects(); EXPECT_FALSE( animation_->keyframe_effect()->scroll_offset_animation_was_interrupted()); EXPECT_FALSE(animation_impl_->keyframe_effect() ->scroll_offset_animation_was_interrupted()); animation_->RemoveKeyframeModel(keyframe_model_id); EXPECT_FALSE( animation_->keyframe_effect()->scroll_offset_animation_was_interrupted()); PushProperties(); EXPECT_FALSE(animation_impl_->keyframe_effect() ->scroll_offset_animation_was_interrupted()); EXPECT_FALSE( animation_->keyframe_effect()->scroll_offset_animation_was_interrupted()); animation_impl_->ActivateKeyframeEffects(); EXPECT_FALSE(animation_impl_->keyframe_effect() ->scroll_offset_animation_was_interrupted()); keyframe_model_id = AddAnimatedFilterToAnimation(animation_.get(), 1.0, 0.1f, 0.2f); PushProperties(); animation_impl_->ActivateKeyframeEffects(); EXPECT_FALSE( animation_->keyframe_effect()->scroll_offset_animation_was_interrupted()); EXPECT_FALSE(animation_impl_->keyframe_effect() ->scroll_offset_animation_was_interrupted()); animation_->RemoveKeyframeModel(keyframe_model_id); EXPECT_FALSE( animation_->keyframe_effect()->scroll_offset_animation_was_interrupted()); PushProperties(); EXPECT_FALSE(animation_impl_->keyframe_effect() ->scroll_offset_animation_was_interrupted()); EXPECT_FALSE( animation_->keyframe_effect()->scroll_offset_animation_was_interrupted()); animation_impl_->ActivateKeyframeEffects(); EXPECT_FALSE(animation_impl_->keyframe_effect() ->scroll_offset_animation_was_interrupted()); } // Tests that impl-only animations lead to start and finished notifications // on the impl thread animations's animation delegate. TEST_F(ElementAnimationsTest, NotificationsForImplOnlyAnimationsAreSentToImplThreadDelegate) { CreateTestLayer(true, false); AttachTimelineAnimationLayer(); CreateImplTimelineAndAnimation(); auto events = CreateEventsForTesting(); TestAnimationDelegate delegate; animation_impl_->set_animation_delegate(&delegate); gfx::ScrollOffset initial_value(100.f, 300.f); gfx::ScrollOffset target_value(300.f, 200.f); std::unique_ptr curve( ScrollOffsetAnimationCurve::Create( target_value, CubicBezierTimingFunction::CreatePreset( CubicBezierTimingFunction::EaseType::EASE_IN_OUT))); curve->SetInitialValue(initial_value); TimeDelta duration = curve->Duration(); std::unique_ptr to_add(KeyframeModel::Create( std::move(curve), 1, 0, TargetProperty::SCROLL_OFFSET)); to_add->SetIsImplOnly(); animation_impl_->AddKeyframeModel(std::move(to_add)); EXPECT_FALSE(delegate.started()); EXPECT_FALSE(delegate.finished()); animation_impl_->Tick(kInitialTickTime); animation_impl_->UpdateState(true, events.get()); EXPECT_TRUE(delegate.started()); EXPECT_FALSE(delegate.finished()); events = CreateEventsForTesting(); animation_impl_->Tick(kInitialTickTime + duration); EXPECT_EQ(duration, animation_impl_->GetKeyframeModel(TargetProperty::SCROLL_OFFSET) ->curve() ->Duration()); animation_impl_->UpdateState(true, events.get()); EXPECT_TRUE(delegate.started()); EXPECT_TRUE(delegate.finished()); } // Tests that specified start times are sent to the main thread delegate TEST_F(ElementAnimationsTest, SpecifiedStartTimesAreSentToMainThreadDelegate) { CreateTestLayer(true, false); AttachTimelineAnimationLayer(); CreateImplTimelineAndAnimation(); TestAnimationDelegate delegate; animation_->set_animation_delegate(&delegate); int keyframe_model_id = AddOpacityTransitionToAnimation(animation_.get(), 1, 0, 1, false); const TimeTicks start_time = TicksFromSecondsF(123); animation_->GetKeyframeModel(TargetProperty::OPACITY) ->set_start_time(start_time); PushProperties(); animation_impl_->ActivateKeyframeEffects(); EXPECT_TRUE(animation_impl_->keyframe_effect()->GetKeyframeModelById( keyframe_model_id)); EXPECT_EQ(KeyframeModel::WAITING_FOR_TARGET_AVAILABILITY, animation_impl_->keyframe_effect() ->GetKeyframeModelById(keyframe_model_id) ->run_state()); auto events = CreateEventsForTesting(); animation_impl_->Tick(kInitialTickTime); animation_impl_->UpdateState(true, events.get()); // Synchronize the start times. EXPECT_EQ(1u, events->events_.size()); animation_->keyframe_effect()->NotifyKeyframeModelStarted(events->events_[0]); // Validate start time on the main thread delegate. EXPECT_EQ(start_time, delegate.start_time()); } // Tests animations that are waiting for a synchronized start time do not // finish. TEST_F(ElementAnimationsTest, AnimationsWaitingForStartTimeDoNotFinishIfTheyOutwaitTheirFinish) { CreateTestLayer(false, false); AttachTimelineAnimationLayer(); auto events = CreateEventsForTesting(); std::unique_ptr to_add(CreateKeyframeModel( std::unique_ptr(new FakeFloatTransition(1.0, 0.f, 1.f)), 1, TargetProperty::OPACITY)); to_add->set_needs_synchronized_start_time(true); // We should pause at the first keyframe indefinitely waiting for that // animation to start. animation_->AddKeyframeModel(std::move(to_add)); animation_->Tick(kInitialTickTime); animation_->UpdateState(true, events.get()); EXPECT_TRUE(animation_->keyframe_effect()->HasTickingKeyframeModel()); EXPECT_EQ(0.f, client_.GetOpacity(element_id_, ElementListType::ACTIVE)); animation_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(1000)); animation_->UpdateState(true, events.get()); EXPECT_TRUE(animation_->keyframe_effect()->HasTickingKeyframeModel()); EXPECT_EQ(0.f, client_.GetOpacity(element_id_, ElementListType::ACTIVE)); animation_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(2000)); animation_->UpdateState(true, events.get()); EXPECT_TRUE(animation_->keyframe_effect()->HasTickingKeyframeModel()); EXPECT_EQ(0.f, client_.GetOpacity(element_id_, ElementListType::ACTIVE)); // Send the synchronized start time. animation_->keyframe_effect()->NotifyKeyframeModelStarted(AnimationEvent( AnimationEvent::STARTED, ElementId(), 1, TargetProperty::OPACITY, kInitialTickTime + TimeDelta::FromMilliseconds(2000))); animation_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(5000)); animation_->UpdateState(true, events.get()); EXPECT_EQ(1.f, client_.GetOpacity(element_id_, ElementListType::ACTIVE)); EXPECT_FALSE(animation_->keyframe_effect()->HasTickingKeyframeModel()); } // Tests that two queued animations affecting the same property run in sequence. TEST_F(ElementAnimationsTest, TrivialQueuing) { CreateTestLayer(false, false); AttachTimelineAnimationLayer(); auto events = CreateEventsForTesting(); int animation1_id = 1; int animation2_id = 2; animation_->AddKeyframeModel(KeyframeModel::Create( std::unique_ptr(new FakeFloatTransition(1.0, 0.f, 1.f)), animation1_id, 1, TargetProperty::OPACITY)); animation_->AddKeyframeModel(KeyframeModel::Create( std::unique_ptr(new FakeFloatTransition(1.0, 1.f, 0.5f)), animation2_id, 2, TargetProperty::OPACITY)); animation_->Tick(kInitialTickTime); // The first animation should have been started. EXPECT_TRUE( animation_->keyframe_effect()->GetKeyframeModelById(animation1_id)); EXPECT_EQ(KeyframeModel::STARTING, animation_->keyframe_effect() ->GetKeyframeModelById(animation1_id) ->run_state()); // The second animation still needs to be started. EXPECT_TRUE( animation_->keyframe_effect()->GetKeyframeModelById(animation2_id)); EXPECT_EQ(KeyframeModel::WAITING_FOR_TARGET_AVAILABILITY, animation_->keyframe_effect() ->GetKeyframeModelById(animation2_id) ->run_state()); animation_->UpdateState(true, events.get()); EXPECT_TRUE(animation_->keyframe_effect()->HasTickingKeyframeModel()); EXPECT_EQ(0.f, client_.GetOpacity(element_id_, ElementListType::ACTIVE)); animation_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(1000)); animation_->UpdateState(true, events.get()); // Now the first should be finished, and the second started. EXPECT_TRUE( animation_->keyframe_effect()->GetKeyframeModelById(animation1_id)); EXPECT_EQ(KeyframeModel::FINISHED, animation_->keyframe_effect() ->GetKeyframeModelById(animation1_id) ->run_state()); EXPECT_TRUE( animation_->keyframe_effect()->GetKeyframeModelById(animation2_id)); EXPECT_EQ(KeyframeModel::RUNNING, animation_->keyframe_effect() ->GetKeyframeModelById(animation2_id) ->run_state()); EXPECT_TRUE(animation_->keyframe_effect()->HasTickingKeyframeModel()); EXPECT_EQ(1.f, client_.GetOpacity(element_id_, ElementListType::ACTIVE)); animation_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(2000)); animation_->UpdateState(true, events.get()); EXPECT_EQ(0.5f, client_.GetOpacity(element_id_, ElementListType::ACTIVE)); EXPECT_FALSE(animation_->keyframe_effect()->HasTickingKeyframeModel()); } // Tests interrupting a transition with another transition. TEST_F(ElementAnimationsTest, Interrupt) { CreateTestLayer(false, false); AttachTimelineAnimationLayer(); auto events = CreateEventsForTesting(); animation_->AddKeyframeModel(CreateKeyframeModel( std::unique_ptr(new FakeFloatTransition(1.0, 0.f, 1.f)), 1, TargetProperty::OPACITY)); animation_->Tick(kInitialTickTime); animation_->UpdateState(true, events.get()); EXPECT_TRUE(animation_->keyframe_effect()->HasTickingKeyframeModel()); EXPECT_EQ(0.f, client_.GetOpacity(element_id_, ElementListType::ACTIVE)); std::unique_ptr to_add(CreateKeyframeModel( std::unique_ptr(new FakeFloatTransition(1.0, 1.f, 0.5f)), 2, TargetProperty::OPACITY)); animation_->AbortKeyframeModels(TargetProperty::OPACITY, false); animation_->AddKeyframeModel(std::move(to_add)); // Since the previous animation was aborted, the new animation should start // right in this call to animate. animation_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(500)); animation_->UpdateState(true, events.get()); EXPECT_TRUE(animation_->keyframe_effect()->HasTickingKeyframeModel()); EXPECT_EQ(1.f, client_.GetOpacity(element_id_, ElementListType::ACTIVE)); animation_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(1500)); animation_->UpdateState(true, events.get()); EXPECT_EQ(0.5f, client_.GetOpacity(element_id_, ElementListType::ACTIVE)); EXPECT_FALSE(animation_->keyframe_effect()->HasTickingKeyframeModel()); } // Tests scheduling two animations to run together when only one property is // free. TEST_F(ElementAnimationsTest, ScheduleTogetherWhenAPropertyIsBlocked) { CreateTestLayer(false, false); AttachTimelineAnimationLayer(); auto events = CreateEventsForTesting(); animation_->AddKeyframeModel(CreateKeyframeModel( std::unique_ptr(new FakeTransformTransition(1)), 1, TargetProperty::TRANSFORM)); animation_->AddKeyframeModel(CreateKeyframeModel( std::unique_ptr(new FakeTransformTransition(1)), 2, TargetProperty::TRANSFORM)); animation_->AddKeyframeModel(CreateKeyframeModel( std::unique_ptr(new FakeFloatTransition(1.0, 0.f, 1.f)), 2, TargetProperty::OPACITY)); animation_->Tick(kInitialTickTime); animation_->UpdateState(true, events.get()); EXPECT_EQ(0.f, client_.GetOpacity(element_id_, ElementListType::ACTIVE)); EXPECT_TRUE(animation_->keyframe_effect()->HasTickingKeyframeModel()); animation_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(1000)); animation_->UpdateState(true, events.get()); // Should not have started the float transition yet. EXPECT_TRUE(animation_->keyframe_effect()->HasTickingKeyframeModel()); EXPECT_EQ(0.f, client_.GetOpacity(element_id_, ElementListType::ACTIVE)); // The float animation should have started at time 1 and should be done. animation_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(2000)); animation_->UpdateState(true, events.get()); EXPECT_EQ(1.f, client_.GetOpacity(element_id_, ElementListType::ACTIVE)); EXPECT_FALSE(animation_->keyframe_effect()->HasTickingKeyframeModel()); } // Tests scheduling two animations to run together with different lengths and // another animation queued to start when the shorter animation finishes (should // wait for both to finish). TEST_F(ElementAnimationsTest, ScheduleTogetherWithAnAnimWaiting) { CreateTestLayer(false, false); AttachTimelineAnimationLayer(); auto events = CreateEventsForTesting(); animation_->AddKeyframeModel(CreateKeyframeModel( std::unique_ptr(new FakeTransformTransition(2)), 1, TargetProperty::TRANSFORM)); animation_->AddKeyframeModel(CreateKeyframeModel( std::unique_ptr(new FakeFloatTransition(1.0, 0.f, 1.f)), 1, TargetProperty::OPACITY)); animation_->AddKeyframeModel(CreateKeyframeModel( std::unique_ptr(new FakeFloatTransition(1.0, 1.f, 0.5f)), 2, TargetProperty::OPACITY)); // Animations with id 1 should both start now. animation_->Tick(kInitialTickTime); animation_->UpdateState(true, events.get()); EXPECT_TRUE(animation_->keyframe_effect()->HasTickingKeyframeModel()); EXPECT_EQ(0.f, client_.GetOpacity(element_id_, ElementListType::ACTIVE)); // The opacity animation should have finished at time 1, but the group // of animations with id 1 don't finish until time 2 because of the length // of the transform animation. animation_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(2000)); animation_->UpdateState(true, events.get()); // Should not have started the float transition yet. EXPECT_TRUE(animation_->keyframe_effect()->HasTickingKeyframeModel()); EXPECT_EQ(1.f, client_.GetOpacity(element_id_, ElementListType::ACTIVE)); // The second opacity animation should start at time 2 and should be done by // time 3. animation_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(3000)); animation_->UpdateState(true, events.get()); EXPECT_EQ(0.5f, client_.GetOpacity(element_id_, ElementListType::ACTIVE)); EXPECT_FALSE(animation_->keyframe_effect()->HasTickingKeyframeModel()); } // Test that a looping animation loops and for the correct number of iterations. TEST_F(ElementAnimationsTest, TrivialLooping) { CreateTestLayer(false, false); AttachTimelineAnimationLayer(); auto events = CreateEventsForTesting(); std::unique_ptr to_add(CreateKeyframeModel( std::unique_ptr(new FakeFloatTransition(1.0, 0.f, 1.f)), 1, TargetProperty::OPACITY)); to_add->set_iterations(3); animation_->AddKeyframeModel(std::move(to_add)); animation_->Tick(kInitialTickTime); animation_->UpdateState(true, events.get()); EXPECT_TRUE(animation_->keyframe_effect()->HasTickingKeyframeModel()); EXPECT_EQ(0.f, client_.GetOpacity(element_id_, ElementListType::ACTIVE)); animation_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(1250)); animation_->UpdateState(true, events.get()); EXPECT_TRUE(animation_->keyframe_effect()->HasTickingKeyframeModel()); EXPECT_EQ(0.25f, client_.GetOpacity(element_id_, ElementListType::ACTIVE)); animation_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(1750)); animation_->UpdateState(true, events.get()); EXPECT_TRUE(animation_->keyframe_effect()->HasTickingKeyframeModel()); EXPECT_EQ(0.75f, client_.GetOpacity(element_id_, ElementListType::ACTIVE)); animation_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(2250)); animation_->UpdateState(true, events.get()); EXPECT_TRUE(animation_->keyframe_effect()->HasTickingKeyframeModel()); EXPECT_EQ(0.25f, client_.GetOpacity(element_id_, ElementListType::ACTIVE)); animation_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(2750)); animation_->UpdateState(true, events.get()); EXPECT_TRUE(animation_->keyframe_effect()->HasTickingKeyframeModel()); EXPECT_EQ(0.75f, client_.GetOpacity(element_id_, ElementListType::ACTIVE)); animation_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(3000)); animation_->UpdateState(true, events.get()); EXPECT_FALSE(animation_->keyframe_effect()->HasTickingKeyframeModel()); EXPECT_EQ(1.f, client_.GetOpacity(element_id_, ElementListType::ACTIVE)); // Just be extra sure. animation_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(4000)); animation_->UpdateState(true, events.get()); EXPECT_EQ(1.f, client_.GetOpacity(element_id_, ElementListType::ACTIVE)); } // Test that an infinitely looping animation does indeed go until aborted. TEST_F(ElementAnimationsTest, InfiniteLooping) { CreateTestLayer(false, false); AttachTimelineAnimationLayer(); auto events = CreateEventsForTesting(); std::unique_ptr to_add(CreateKeyframeModel( std::unique_ptr(new FakeFloatTransition(1.0, 0.f, 1.f)), 1, TargetProperty::OPACITY)); to_add->set_iterations(-1); animation_->AddKeyframeModel(std::move(to_add)); animation_->Tick(kInitialTickTime); animation_->UpdateState(true, events.get()); EXPECT_TRUE(animation_->keyframe_effect()->HasTickingKeyframeModel()); EXPECT_EQ(0.f, client_.GetOpacity(element_id_, ElementListType::ACTIVE)); animation_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(1250)); animation_->UpdateState(true, events.get()); EXPECT_TRUE(animation_->keyframe_effect()->HasTickingKeyframeModel()); EXPECT_EQ(0.25f, client_.GetOpacity(element_id_, ElementListType::ACTIVE)); animation_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(1750)); animation_->UpdateState(true, events.get()); EXPECT_TRUE(animation_->keyframe_effect()->HasTickingKeyframeModel()); EXPECT_EQ(0.75f, client_.GetOpacity(element_id_, ElementListType::ACTIVE)); animation_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(1073741824250)); animation_->UpdateState(true, events.get()); EXPECT_TRUE(animation_->keyframe_effect()->HasTickingKeyframeModel()); EXPECT_EQ(0.25f, client_.GetOpacity(element_id_, ElementListType::ACTIVE)); animation_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(1073741824750)); animation_->UpdateState(true, events.get()); EXPECT_TRUE(animation_->keyframe_effect()->HasTickingKeyframeModel()); EXPECT_EQ(0.75f, client_.GetOpacity(element_id_, ElementListType::ACTIVE)); EXPECT_TRUE(animation_->GetKeyframeModel(TargetProperty::OPACITY)); animation_->GetKeyframeModel(TargetProperty::OPACITY) ->SetRunState(KeyframeModel::ABORTED, kInitialTickTime + TimeDelta::FromMilliseconds(750)); EXPECT_FALSE(animation_->keyframe_effect()->HasTickingKeyframeModel()); EXPECT_EQ(0.75f, client_.GetOpacity(element_id_, ElementListType::ACTIVE)); } // Test that pausing and resuming work as expected. TEST_F(ElementAnimationsTest, PauseResume) { CreateTestLayer(false, false); AttachTimelineAnimationLayer(); auto events = CreateEventsForTesting(); animation_->AddKeyframeModel(CreateKeyframeModel( std::unique_ptr(new FakeFloatTransition(1.0, 0.f, 1.f)), 1, TargetProperty::OPACITY)); animation_->Tick(kInitialTickTime); animation_->UpdateState(true, events.get()); EXPECT_TRUE(animation_->keyframe_effect()->HasTickingKeyframeModel()); EXPECT_EQ(0.f, client_.GetOpacity(element_id_, ElementListType::ACTIVE)); animation_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(500)); animation_->UpdateState(true, events.get()); EXPECT_TRUE(animation_->keyframe_effect()->HasTickingKeyframeModel()); EXPECT_EQ(0.5f, client_.GetOpacity(element_id_, ElementListType::ACTIVE)); EXPECT_TRUE(animation_->GetKeyframeModel(TargetProperty::OPACITY)); animation_->GetKeyframeModel(TargetProperty::OPACITY) ->SetRunState(KeyframeModel::PAUSED, kInitialTickTime + TimeDelta::FromMilliseconds(500)); animation_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(1024000)); animation_->UpdateState(true, events.get()); EXPECT_TRUE(animation_->keyframe_effect()->HasTickingKeyframeModel()); EXPECT_EQ(0.5f, client_.GetOpacity(element_id_, ElementListType::ACTIVE)); EXPECT_TRUE(animation_->GetKeyframeModel(TargetProperty::OPACITY)); animation_->GetKeyframeModel(TargetProperty::OPACITY) ->SetRunState(KeyframeModel::RUNNING, kInitialTickTime + TimeDelta::FromMilliseconds(1024000)); animation_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(1024250)); animation_->UpdateState(true, events.get()); EXPECT_TRUE(animation_->keyframe_effect()->HasTickingKeyframeModel()); EXPECT_EQ(0.75f, client_.GetOpacity(element_id_, ElementListType::ACTIVE)); animation_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(1024500)); animation_->UpdateState(true, events.get()); EXPECT_FALSE(animation_->keyframe_effect()->HasTickingKeyframeModel()); EXPECT_EQ(1.f, client_.GetOpacity(element_id_, ElementListType::ACTIVE)); } TEST_F(ElementAnimationsTest, AbortAGroupedAnimation) { CreateTestLayer(false, false); AttachTimelineAnimationLayer(); auto events = CreateEventsForTesting(); const int keyframe_model_id = 2; animation_->AddKeyframeModel(KeyframeModel::Create( std::unique_ptr(new FakeTransformTransition(1)), 1, 1, TargetProperty::TRANSFORM)); animation_->AddKeyframeModel(KeyframeModel::Create( std::unique_ptr(new FakeFloatTransition(2.0, 0.f, 1.f)), keyframe_model_id, 1, TargetProperty::OPACITY)); animation_->AddKeyframeModel(KeyframeModel::Create( std::unique_ptr(new FakeFloatTransition(1.0, 1.f, 0.75f)), 3, 2, TargetProperty::OPACITY)); animation_->Tick(kInitialTickTime); animation_->UpdateState(true, events.get()); EXPECT_TRUE(animation_->keyframe_effect()->HasTickingKeyframeModel()); EXPECT_EQ(0.f, client_.GetOpacity(element_id_, ElementListType::ACTIVE)); animation_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(1000)); animation_->UpdateState(true, events.get()); EXPECT_TRUE(animation_->keyframe_effect()->HasTickingKeyframeModel()); EXPECT_EQ(0.5f, client_.GetOpacity(element_id_, ElementListType::ACTIVE)); EXPECT_TRUE( animation_->keyframe_effect()->GetKeyframeModelById(keyframe_model_id)); animation_->keyframe_effect() ->GetKeyframeModelById(keyframe_model_id) ->SetRunState(KeyframeModel::ABORTED, kInitialTickTime + TimeDelta::FromMilliseconds(1000)); animation_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(1000)); animation_->UpdateState(true, events.get()); EXPECT_TRUE(animation_->keyframe_effect()->HasTickingKeyframeModel()); EXPECT_EQ(1.f, client_.GetOpacity(element_id_, ElementListType::ACTIVE)); animation_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(2000)); animation_->UpdateState(true, events.get()); EXPECT_TRUE(!animation_->keyframe_effect()->HasTickingKeyframeModel()); EXPECT_EQ(0.75f, client_.GetOpacity(element_id_, ElementListType::ACTIVE)); } TEST_F(ElementAnimationsTest, PushUpdatesWhenSynchronizedStartTimeNeeded) { CreateTestLayer(true, false); AttachTimelineAnimationLayer(); CreateImplTimelineAndAnimation(); auto events = CreateEventsForTesting(); std::unique_ptr to_add(CreateKeyframeModel( std::unique_ptr(new FakeFloatTransition(2.0, 0.f, 1.f)), 0, TargetProperty::OPACITY)); to_add->set_needs_synchronized_start_time(true); animation_->AddKeyframeModel(std::move(to_add)); animation_->Tick(kInitialTickTime); animation_->UpdateState(true, events.get()); EXPECT_TRUE(animation_->keyframe_effect()->HasTickingKeyframeModel()); KeyframeModel* active_keyframe_model = animation_->GetKeyframeModel(TargetProperty::OPACITY); EXPECT_TRUE(active_keyframe_model); EXPECT_TRUE(active_keyframe_model->needs_synchronized_start_time()); EXPECT_TRUE(animation_->keyframe_effect()->needs_push_properties()); PushProperties(); animation_impl_->ActivateKeyframeEffects(); active_keyframe_model = animation_impl_->GetKeyframeModel(TargetProperty::OPACITY); EXPECT_TRUE(active_keyframe_model); EXPECT_EQ(KeyframeModel::WAITING_FOR_TARGET_AVAILABILITY, active_keyframe_model->run_state()); } // Tests that skipping a call to UpdateState works as expected. TEST_F(ElementAnimationsTest, SkipUpdateState) { CreateTestLayer(true, false); AttachTimelineAnimationLayer(); auto events = CreateEventsForTesting(); std::unique_ptr first_keyframe_model(CreateKeyframeModel( std::unique_ptr(new FakeTransformTransition(1)), 1, TargetProperty::TRANSFORM)); first_keyframe_model->set_is_controlling_instance_for_test(true); animation_->AddKeyframeModel(std::move(first_keyframe_model)); animation_->Tick(kInitialTickTime); animation_->UpdateState(true, events.get()); std::unique_ptr second_keyframe_model(CreateKeyframeModel( std::unique_ptr(new FakeFloatTransition(1.0, 0.f, 1.f)), 2, TargetProperty::OPACITY)); second_keyframe_model->set_is_controlling_instance_for_test(true); animation_->AddKeyframeModel(std::move(second_keyframe_model)); // Animate but don't UpdateState. animation_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(1000)); animation_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(2000)); events = CreateEventsForTesting(); animation_->UpdateState(true, events.get()); // Should have one STARTED event and one FINISHED event. EXPECT_EQ(2u, events->events_.size()); EXPECT_NE(events->events_[0].type, events->events_[1].type); // The float transition should still be at its starting point. EXPECT_TRUE(animation_->keyframe_effect()->HasTickingKeyframeModel()); EXPECT_EQ(0.f, client_.GetOpacity(element_id_, ElementListType::ACTIVE)); animation_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(3000)); animation_->UpdateState(true, events.get()); // The float tranisition should now be done. EXPECT_EQ(1.f, client_.GetOpacity(element_id_, ElementListType::ACTIVE)); EXPECT_FALSE(animation_->keyframe_effect()->HasTickingKeyframeModel()); } // Tests that an animation animations with only a pending observer gets ticked // but doesn't progress animations past the STARTING state. TEST_F(ElementAnimationsTest, InactiveObserverGetsTicked) { AttachTimelineAnimationLayer(); CreateImplTimelineAndAnimation(); auto events = CreateEventsForTesting(); const int id = 1; animation_impl_->AddKeyframeModel(CreateKeyframeModel( std::unique_ptr(new FakeFloatTransition(1.0, 0.5f, 1.f)), id, TargetProperty::OPACITY)); // Without an observer, the animation shouldn't progress to the STARTING // state. animation_impl_->Tick(kInitialTickTime); animation_impl_->UpdateState(true, events.get()); EXPECT_EQ(0u, events->events_.size()); EXPECT_EQ( KeyframeModel::WAITING_FOR_TARGET_AVAILABILITY, animation_impl_->GetKeyframeModel(TargetProperty::OPACITY)->run_state()); CreateTestImplLayer(ElementListType::PENDING); // With only a pending observer, the animation should progress to the // STARTING state and get ticked at its starting point, but should not // progress to RUNNING. animation_impl_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(1000)); animation_impl_->UpdateState(true, events.get()); EXPECT_EQ(0u, events->events_.size()); EXPECT_EQ( KeyframeModel::STARTING, animation_impl_->GetKeyframeModel(TargetProperty::OPACITY)->run_state()); EXPECT_EQ(0.5f, client_impl_.GetOpacity(element_id_, ElementListType::PENDING)); // Even when already in the STARTING state, the animation should stay // there, and shouldn't be ticked past its starting point. animation_impl_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(2000)); animation_impl_->UpdateState(true, events.get()); EXPECT_EQ(0u, events->events_.size()); EXPECT_EQ( KeyframeModel::STARTING, animation_impl_->GetKeyframeModel(TargetProperty::OPACITY)->run_state()); EXPECT_EQ(0.5f, client_impl_.GetOpacity(element_id_, ElementListType::PENDING)); CreateTestImplLayer(ElementListType::ACTIVE); // Now that an active observer has been added, the animation should still // initially tick at its starting point, but should now progress to RUNNING. animation_impl_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(3000)); animation_impl_->UpdateState(true, events.get()); EXPECT_EQ(1u, events->events_.size()); EXPECT_EQ( KeyframeModel::RUNNING, animation_impl_->GetKeyframeModel(TargetProperty::OPACITY)->run_state()); EXPECT_EQ(0.5f, client_impl_.GetOpacity(element_id_, ElementListType::PENDING)); EXPECT_EQ(0.5f, client_impl_.GetOpacity(element_id_, ElementListType::ACTIVE)); // The animation should now tick past its starting point. animation_impl_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(3500)); EXPECT_NE(0.5f, client_impl_.GetOpacity(element_id_, ElementListType::PENDING)); EXPECT_NE(0.5f, client_impl_.GetOpacity(element_id_, ElementListType::ACTIVE)); } // Tests that AbortKeyframeModels aborts all animations targeting the // specified property. TEST_F(ElementAnimationsTest, AbortKeyframeModels) { CreateTestLayer(false, false); AttachTimelineAnimationLayer(); // Start with several animations, and allow some of them to reach the finished // state. animation_->AddKeyframeModel(KeyframeModel::Create( std::unique_ptr(new FakeTransformTransition(1.0)), 1, 1, TargetProperty::TRANSFORM)); animation_->AddKeyframeModel(KeyframeModel::Create( std::unique_ptr(new FakeFloatTransition(1.0, 0.f, 1.f)), 2, 2, TargetProperty::OPACITY)); animation_->AddKeyframeModel(KeyframeModel::Create( std::unique_ptr(new FakeTransformTransition(1.0)), 3, 3, TargetProperty::TRANSFORM)); animation_->AddKeyframeModel(KeyframeModel::Create( std::unique_ptr(new FakeTransformTransition(2.0)), 4, 4, TargetProperty::TRANSFORM)); animation_->AddKeyframeModel(KeyframeModel::Create( std::unique_ptr(new FakeFloatTransition(1.0, 0.f, 1.f)), 5, 5, TargetProperty::OPACITY)); animation_->Tick(kInitialTickTime); animation_->UpdateState(true, nullptr); animation_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(1000)); animation_->UpdateState(true, nullptr); EXPECT_EQ( KeyframeModel::FINISHED, animation_->keyframe_effect()->GetKeyframeModelById(1)->run_state()); EXPECT_EQ( KeyframeModel::FINISHED, animation_->keyframe_effect()->GetKeyframeModelById(2)->run_state()); EXPECT_EQ( KeyframeModel::RUNNING, animation_->keyframe_effect()->GetKeyframeModelById(3)->run_state()); EXPECT_EQ( KeyframeModel::WAITING_FOR_TARGET_AVAILABILITY, animation_->keyframe_effect()->GetKeyframeModelById(4)->run_state()); EXPECT_EQ( KeyframeModel::RUNNING, animation_->keyframe_effect()->GetKeyframeModelById(5)->run_state()); animation_->AbortKeyframeModels(TargetProperty::TRANSFORM, false); // Only un-finished TRANSFORM animations should have been aborted. EXPECT_EQ( KeyframeModel::FINISHED, animation_->keyframe_effect()->GetKeyframeModelById(1)->run_state()); EXPECT_EQ( KeyframeModel::FINISHED, animation_->keyframe_effect()->GetKeyframeModelById(2)->run_state()); EXPECT_EQ( KeyframeModel::ABORTED, animation_->keyframe_effect()->GetKeyframeModelById(3)->run_state()); EXPECT_EQ( KeyframeModel::ABORTED, animation_->keyframe_effect()->GetKeyframeModelById(4)->run_state()); EXPECT_EQ( KeyframeModel::RUNNING, animation_->keyframe_effect()->GetKeyframeModelById(5)->run_state()); } // An animation aborted on the main thread should get deleted on both threads. TEST_F(ElementAnimationsTest, MainThreadAbortedAnimationGetsDeleted) { CreateTestLayer(true, false); AttachTimelineAnimationLayer(); CreateImplTimelineAndAnimation(); int keyframe_model_id = AddOpacityTransitionToAnimation(animation_.get(), 1.0, 0.f, 1.f, false); EXPECT_TRUE(host_->needs_push_properties()); PushProperties(); animation_impl_->ActivateKeyframeEffects(); EXPECT_TRUE(animation_impl_->keyframe_effect()->GetKeyframeModelById( keyframe_model_id)); EXPECT_FALSE(host_->needs_push_properties()); animation_->AbortKeyframeModels(TargetProperty::OPACITY, false); EXPECT_EQ(KeyframeModel::ABORTED, animation_->GetKeyframeModel(TargetProperty::OPACITY)->run_state()); EXPECT_TRUE(host_->needs_push_properties()); animation_->Tick(kInitialTickTime); animation_->UpdateState(true, nullptr); EXPECT_EQ(KeyframeModel::ABORTED, animation_->GetKeyframeModel(TargetProperty::OPACITY)->run_state()); EXPECT_TRUE(animation_->keyframe_effect()->needs_push_properties()); EXPECT_TRUE(host_->needs_push_properties()); PushProperties(); EXPECT_FALSE(animation_->keyframe_effect()->needs_push_properties()); EXPECT_FALSE(host_->needs_push_properties()); EXPECT_FALSE( animation_->keyframe_effect()->GetKeyframeModelById(keyframe_model_id)); EXPECT_FALSE(animation_impl_->keyframe_effect()->GetKeyframeModelById( keyframe_model_id)); } // An animation aborted on the impl thread should get deleted on both threads. TEST_F(ElementAnimationsTest, ImplThreadAbortedAnimationGetsDeleted) { CreateTestLayer(true, false); AttachTimelineAnimationLayer(); CreateImplTimelineAndAnimation(); TestAnimationDelegate delegate; animation_->set_animation_delegate(&delegate); int keyframe_model_id = AddOpacityTransitionToAnimation(animation_.get(), 1.0, 0.f, 1.f, false); PushProperties(); EXPECT_FALSE(host_->needs_push_properties()); animation_impl_->ActivateKeyframeEffects(); EXPECT_TRUE(animation_impl_->keyframe_effect()->GetKeyframeModelById( keyframe_model_id)); animation_impl_->AbortKeyframeModels(TargetProperty::OPACITY, false); EXPECT_EQ( KeyframeModel::ABORTED, animation_impl_->GetKeyframeModel(TargetProperty::OPACITY)->run_state()); EXPECT_TRUE(host_impl_->needs_push_properties()); EXPECT_TRUE(animation_impl_->keyframe_effect()->needs_push_properties()); auto events = CreateEventsForTesting(); animation_impl_->Tick(kInitialTickTime); animation_impl_->UpdateState(true, events.get()); EXPECT_TRUE(host_impl_->needs_push_properties()); EXPECT_EQ(1u, events->events_.size()); EXPECT_EQ(AnimationEvent::ABORTED, events->events_[0].type); EXPECT_EQ( KeyframeModel::WAITING_FOR_DELETION, animation_impl_->GetKeyframeModel(TargetProperty::OPACITY)->run_state()); animation_->keyframe_effect()->NotifyKeyframeModelAborted(events->events_[0]); EXPECT_EQ(KeyframeModel::ABORTED, animation_->GetKeyframeModel(TargetProperty::OPACITY)->run_state()); EXPECT_TRUE(delegate.aborted()); animation_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(500)); animation_->UpdateState(true, nullptr); EXPECT_TRUE(host_->needs_push_properties()); EXPECT_EQ(KeyframeModel::WAITING_FOR_DELETION, animation_->GetKeyframeModel(TargetProperty::OPACITY)->run_state()); PushProperties(); animation_impl_->ActivateKeyframeEffects(); EXPECT_FALSE( animation_->keyframe_effect()->GetKeyframeModelById(keyframe_model_id)); EXPECT_FALSE(animation_impl_->keyframe_effect()->GetKeyframeModelById( keyframe_model_id)); } // Test that an impl-only scroll offset animation that needs to be completed on // the main thread gets deleted. TEST_F(ElementAnimationsTest, ImplThreadTakeoverAnimationGetsDeleted) { CreateTestLayer(true, false); AttachTimelineAnimationLayer(); CreateImplTimelineAndAnimation(); TestAnimationDelegate delegate_impl; animation_impl_->set_animation_delegate(&delegate_impl); TestAnimationDelegate delegate; animation_->set_animation_delegate(&delegate); // Add impl-only scroll offset animation. const int keyframe_model_id = 1; gfx::ScrollOffset initial_value(100.f, 300.f); gfx::ScrollOffset target_value(300.f, 200.f); std::unique_ptr curve( ScrollOffsetAnimationCurve::Create( target_value, CubicBezierTimingFunction::CreatePreset( CubicBezierTimingFunction::EaseType::EASE_IN_OUT))); curve->SetInitialValue(initial_value); std::unique_ptr keyframe_model(KeyframeModel::Create( std::move(curve), keyframe_model_id, 0, TargetProperty::SCROLL_OFFSET)); keyframe_model->set_start_time(TicksFromSecondsF(123)); keyframe_model->SetIsImplOnly(); animation_impl_->AddKeyframeModel(std::move(keyframe_model)); PushProperties(); EXPECT_FALSE(host_->needs_push_properties()); animation_impl_->ActivateKeyframeEffects(); EXPECT_TRUE(animation_impl_->keyframe_effect()->GetKeyframeModelById( keyframe_model_id)); animation_impl_->AbortKeyframeModels(TargetProperty::SCROLL_OFFSET, true); EXPECT_TRUE(host_impl_->needs_push_properties()); EXPECT_EQ(KeyframeModel::ABORTED_BUT_NEEDS_COMPLETION, animation_impl_->GetKeyframeModel(TargetProperty::SCROLL_OFFSET) ->run_state()); auto events = CreateEventsForTesting(); animation_impl_->Tick(kInitialTickTime); animation_impl_->UpdateState(true, events.get()); EXPECT_TRUE(delegate_impl.finished()); EXPECT_TRUE(host_impl_->needs_push_properties()); EXPECT_EQ(1u, events->events_.size()); EXPECT_EQ(AnimationEvent::TAKEOVER, events->events_[0].type); EXPECT_EQ(TicksFromSecondsF(123), events->events_[0].animation_start_time); EXPECT_EQ( target_value, events->events_[0].curve->ToScrollOffsetAnimationCurve()->target_value()); EXPECT_EQ(nullptr, animation_impl_->GetKeyframeModel(TargetProperty::SCROLL_OFFSET)); // MT receives the event to take over. animation_->keyframe_effect()->NotifyKeyframeModelTakeover( events->events_[0]); EXPECT_TRUE(delegate.takeover()); // SingleKeyframeEffectAnimation::NotifyAnimationTakeover requests // SetNeedsPushProperties to purge CT animations marked for deletion. EXPECT_TRUE(animation_->keyframe_effect()->needs_push_properties()); // ElementAnimations::PurgeAnimationsMarkedForDeletion call happens only in // ElementAnimations::PushPropertiesTo. PushProperties(); animation_impl_->ActivateKeyframeEffects(); EXPECT_FALSE( animation_->keyframe_effect()->GetKeyframeModelById(keyframe_model_id)); EXPECT_FALSE(animation_impl_->keyframe_effect()->GetKeyframeModelById( keyframe_model_id)); } // Ensure that we only generate FINISHED events for animations in a group // once all animations in that group are finished. TEST_F(ElementAnimationsTest, FinishedEventsForGroup) { CreateTestLayer(true, false); AttachTimelineAnimationLayer(); CreateImplTimelineAndAnimation(); auto events = CreateEventsForTesting(); const int group_id = 1; // Add two animations with the same group id but different durations. std::unique_ptr first_keyframe_model(KeyframeModel::Create( std::unique_ptr(new FakeTransformTransition(2.0)), 1, group_id, TargetProperty::TRANSFORM)); first_keyframe_model->set_is_controlling_instance_for_test(true); animation_impl_->AddKeyframeModel(std::move(first_keyframe_model)); std::unique_ptr second_keyframe_model(KeyframeModel::Create( std::unique_ptr(new FakeFloatTransition(1.0, 0.f, 1.f)), 2, group_id, TargetProperty::OPACITY)); second_keyframe_model->set_is_controlling_instance_for_test(true); animation_impl_->AddKeyframeModel(std::move(second_keyframe_model)); animation_impl_->Tick(kInitialTickTime); animation_impl_->UpdateState(true, events.get()); // Both animations should have started. EXPECT_EQ(2u, events->events_.size()); EXPECT_EQ(AnimationEvent::STARTED, events->events_[0].type); EXPECT_EQ(AnimationEvent::STARTED, events->events_[1].type); events = CreateEventsForTesting(); animation_impl_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(1000)); animation_impl_->UpdateState(true, events.get()); // The opacity animation should be finished, but should not have generated // a FINISHED event yet. EXPECT_EQ(0u, events->events_.size()); EXPECT_EQ( KeyframeModel::FINISHED, animation_impl_->keyframe_effect()->GetKeyframeModelById(2)->run_state()); EXPECT_EQ( KeyframeModel::RUNNING, animation_impl_->keyframe_effect()->GetKeyframeModelById(1)->run_state()); animation_impl_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(2000)); animation_impl_->UpdateState(true, events.get()); // Both animations should have generated FINISHED events. EXPECT_EQ(2u, events->events_.size()); EXPECT_EQ(AnimationEvent::FINISHED, events->events_[0].type); EXPECT_EQ(AnimationEvent::FINISHED, events->events_[1].type); } // Ensure that when a group has a mix of aborted and finished animations, // we generate a FINISHED event for the finished animation and an ABORTED // event for the aborted animation. TEST_F(ElementAnimationsTest, FinishedAndAbortedEventsForGroup) { CreateTestLayer(true, false); AttachTimelineAnimationLayer(); CreateImplTimelineAndAnimation(); auto events = CreateEventsForTesting(); // Add two animations with the same group id. std::unique_ptr first_keyframe_model(CreateKeyframeModel( std::unique_ptr(new FakeTransformTransition(1.0)), 1, TargetProperty::TRANSFORM)); first_keyframe_model->set_is_controlling_instance_for_test(true); animation_impl_->AddKeyframeModel(std::move(first_keyframe_model)); std::unique_ptr second_keyframe_model(CreateKeyframeModel( std::unique_ptr(new FakeFloatTransition(1.0, 0.f, 1.f)), 1, TargetProperty::OPACITY)); second_keyframe_model->set_is_controlling_instance_for_test(true); animation_impl_->AddKeyframeModel(std::move(second_keyframe_model)); animation_impl_->Tick(kInitialTickTime); animation_impl_->UpdateState(true, events.get()); // Both animations should have started. EXPECT_EQ(2u, events->events_.size()); EXPECT_EQ(AnimationEvent::STARTED, events->events_[0].type); EXPECT_EQ(AnimationEvent::STARTED, events->events_[1].type); animation_impl_->AbortKeyframeModels(TargetProperty::OPACITY, false); events = CreateEventsForTesting(); animation_impl_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(1000)); animation_impl_->UpdateState(true, events.get()); // We should have exactly 2 events: a FINISHED event for the tranform // animation, and an ABORTED event for the opacity animation. EXPECT_EQ(2u, events->events_.size()); EXPECT_EQ(AnimationEvent::FINISHED, events->events_[0].type); EXPECT_EQ(TargetProperty::TRANSFORM, events->events_[0].target_property); EXPECT_EQ(AnimationEvent::ABORTED, events->events_[1].type); EXPECT_EQ(TargetProperty::OPACITY, events->events_[1].target_property); } TEST_F(ElementAnimationsTest, HasOnlyTranslationTransforms) { CreateTestLayer(true, false); AttachTimelineAnimationLayer(); CreateImplTimelineAndAnimation(); EXPECT_TRUE(animation_impl_->keyframe_effect()->HasOnlyTranslationTransforms( ElementListType::ACTIVE)); EXPECT_TRUE(animation_impl_->keyframe_effect()->HasOnlyTranslationTransforms( ElementListType::PENDING)); animation_impl_->AddKeyframeModel(CreateKeyframeModel( std::unique_ptr(new FakeFloatTransition(1.0, 0.f, 1.f)), 1, TargetProperty::OPACITY)); // Opacity animations aren't non-translation transforms. EXPECT_TRUE(animation_impl_->keyframe_effect()->HasOnlyTranslationTransforms( ElementListType::ACTIVE)); EXPECT_TRUE(animation_impl_->keyframe_effect()->HasOnlyTranslationTransforms( ElementListType::PENDING)); std::unique_ptr curve1( KeyframedTransformAnimationCurve::Create()); TransformOperations operations1; curve1->AddKeyframe( TransformKeyframe::Create(base::TimeDelta(), operations1, nullptr)); operations1.AppendTranslate(10.0, 15.0, 0.0); curve1->AddKeyframe(TransformKeyframe::Create( base::TimeDelta::FromSecondsD(1.0), operations1, nullptr)); std::unique_ptr keyframe_model(KeyframeModel::Create( std::move(curve1), 2, 2, TargetProperty::TRANSFORM)); animation_impl_->AddKeyframeModel(std::move(keyframe_model)); // The only transform animation we've added is a translation. EXPECT_TRUE(animation_impl_->keyframe_effect()->HasOnlyTranslationTransforms( ElementListType::ACTIVE)); EXPECT_TRUE(animation_impl_->keyframe_effect()->HasOnlyTranslationTransforms( ElementListType::PENDING)); std::unique_ptr curve2( KeyframedTransformAnimationCurve::Create()); TransformOperations operations2; curve2->AddKeyframe( TransformKeyframe::Create(base::TimeDelta(), operations2, nullptr)); operations2.AppendScale(2.0, 3.0, 4.0); curve2->AddKeyframe(TransformKeyframe::Create( base::TimeDelta::FromSecondsD(1.0), operations2, nullptr)); keyframe_model = KeyframeModel::Create(std::move(curve2), 3, 3, TargetProperty::TRANSFORM); keyframe_model->set_affects_active_elements(false); animation_impl_->AddKeyframeModel(std::move(keyframe_model)); // A scale animation is not a translation. EXPECT_FALSE(animation_impl_->keyframe_effect()->HasOnlyTranslationTransforms( ElementListType::PENDING)); EXPECT_TRUE(animation_impl_->keyframe_effect()->HasOnlyTranslationTransforms( ElementListType::ACTIVE)); animation_impl_->ActivateKeyframeEffects(); EXPECT_FALSE(animation_impl_->keyframe_effect()->HasOnlyTranslationTransforms( ElementListType::PENDING)); EXPECT_FALSE(animation_impl_->keyframe_effect()->HasOnlyTranslationTransforms( ElementListType::ACTIVE)); animation_impl_->keyframe_effect() ->GetKeyframeModelById(3) ->set_affects_pending_elements(false); EXPECT_TRUE(animation_impl_->keyframe_effect()->HasOnlyTranslationTransforms( ElementListType::PENDING)); EXPECT_FALSE(animation_impl_->keyframe_effect()->HasOnlyTranslationTransforms( ElementListType::ACTIVE)); animation_impl_->keyframe_effect()->GetKeyframeModelById(3)->SetRunState( KeyframeModel::FINISHED, TicksFromSecondsF(0.0)); // Only unfinished animations should be considered by // HasOnlyTranslationTransforms. EXPECT_TRUE(animation_impl_->keyframe_effect()->HasOnlyTranslationTransforms( ElementListType::PENDING)); EXPECT_TRUE(animation_impl_->keyframe_effect()->HasOnlyTranslationTransforms( ElementListType::ACTIVE)); } TEST_F(ElementAnimationsTest, AnimationStartScale) { CreateTestLayer(true, false); AttachTimelineAnimationLayer(); CreateImplTimelineAndAnimation(); std::unique_ptr curve1( KeyframedTransformAnimationCurve::Create()); TransformOperations operations1; operations1.AppendScale(2.0, 3.0, 4.0); curve1->AddKeyframe( TransformKeyframe::Create(base::TimeDelta(), operations1, nullptr)); TransformOperations operations2; curve1->AddKeyframe(TransformKeyframe::Create( base::TimeDelta::FromSecondsD(1.0), operations2, nullptr)); std::unique_ptr keyframe_model(KeyframeModel::Create( std::move(curve1), 1, 1, TargetProperty::TRANSFORM)); keyframe_model->set_affects_active_elements(false); animation_impl_->AddKeyframeModel(std::move(keyframe_model)); float start_scale = 0.f; EXPECT_TRUE(animation_impl_->keyframe_effect()->AnimationStartScale( ElementListType::PENDING, &start_scale)); EXPECT_EQ(4.f, start_scale); EXPECT_TRUE(animation_impl_->keyframe_effect()->AnimationStartScale( ElementListType::ACTIVE, &start_scale)); EXPECT_EQ(0.f, start_scale); animation_impl_->ActivateKeyframeEffects(); EXPECT_TRUE(animation_impl_->keyframe_effect()->AnimationStartScale( ElementListType::PENDING, &start_scale)); EXPECT_EQ(4.f, start_scale); EXPECT_TRUE(animation_impl_->keyframe_effect()->AnimationStartScale( ElementListType::ACTIVE, &start_scale)); EXPECT_EQ(4.f, start_scale); std::unique_ptr curve2( KeyframedTransformAnimationCurve::Create()); TransformOperations operations3; curve2->AddKeyframe( TransformKeyframe::Create(base::TimeDelta(), operations3, nullptr)); operations3.AppendScale(6.0, 5.0, 4.0); curve2->AddKeyframe(TransformKeyframe::Create( base::TimeDelta::FromSecondsD(1.0), operations3, nullptr)); animation_impl_->RemoveKeyframeModel(1); keyframe_model = KeyframeModel::Create(std::move(curve2), 2, 2, TargetProperty::TRANSFORM); // Reverse Direction keyframe_model->set_direction(KeyframeModel::Direction::REVERSE); keyframe_model->set_affects_active_elements(false); animation_impl_->AddKeyframeModel(std::move(keyframe_model)); std::unique_ptr curve3( KeyframedTransformAnimationCurve::Create()); TransformOperations operations4; operations4.AppendScale(5.0, 3.0, 1.0); curve3->AddKeyframe( TransformKeyframe::Create(base::TimeDelta(), operations4, nullptr)); TransformOperations operations5; curve3->AddKeyframe(TransformKeyframe::Create( base::TimeDelta::FromSecondsD(1.0), operations5, nullptr)); keyframe_model = KeyframeModel::Create(std::move(curve3), 3, 3, TargetProperty::TRANSFORM); keyframe_model->set_affects_active_elements(false); animation_impl_->AddKeyframeModel(std::move(keyframe_model)); EXPECT_TRUE(animation_impl_->keyframe_effect()->AnimationStartScale( ElementListType::PENDING, &start_scale)); EXPECT_EQ(6.f, start_scale); EXPECT_TRUE(animation_impl_->keyframe_effect()->AnimationStartScale( ElementListType::ACTIVE, &start_scale)); EXPECT_EQ(0.f, start_scale); animation_impl_->ActivateKeyframeEffects(); EXPECT_TRUE(animation_impl_->keyframe_effect()->AnimationStartScale( ElementListType::PENDING, &start_scale)); EXPECT_EQ(6.f, start_scale); EXPECT_TRUE(animation_impl_->keyframe_effect()->AnimationStartScale( ElementListType::ACTIVE, &start_scale)); EXPECT_EQ(6.f, start_scale); animation_impl_->keyframe_effect()->GetKeyframeModelById(2)->SetRunState( KeyframeModel::FINISHED, TicksFromSecondsF(0.0)); // Only unfinished animations should be considered by // AnimationStartScale. EXPECT_TRUE(animation_impl_->keyframe_effect()->AnimationStartScale( ElementListType::PENDING, &start_scale)); EXPECT_EQ(5.f, start_scale); EXPECT_TRUE(animation_impl_->keyframe_effect()->AnimationStartScale( ElementListType::ACTIVE, &start_scale)); EXPECT_EQ(5.f, start_scale); } TEST_F(ElementAnimationsTest, MaximumTargetScale) { CreateTestLayer(true, false); AttachTimelineAnimationLayer(); CreateImplTimelineAndAnimation(); float max_scale = 0.f; EXPECT_TRUE(animation_impl_->keyframe_effect()->MaximumTargetScale( ElementListType::PENDING, &max_scale)); EXPECT_EQ(0.f, max_scale); EXPECT_TRUE(animation_impl_->keyframe_effect()->MaximumTargetScale( ElementListType::ACTIVE, &max_scale)); EXPECT_EQ(0.f, max_scale); std::unique_ptr curve1( KeyframedTransformAnimationCurve::Create()); TransformOperations operations1; curve1->AddKeyframe( TransformKeyframe::Create(base::TimeDelta(), operations1, nullptr)); operations1.AppendScale(2.0, 3.0, 4.0); curve1->AddKeyframe(TransformKeyframe::Create( base::TimeDelta::FromSecondsD(1.0), operations1, nullptr)); std::unique_ptr keyframe_model(KeyframeModel::Create( std::move(curve1), 1, 1, TargetProperty::TRANSFORM)); keyframe_model->set_affects_active_elements(false); animation_impl_->AddKeyframeModel(std::move(keyframe_model)); EXPECT_TRUE(animation_impl_->keyframe_effect()->MaximumTargetScale( ElementListType::PENDING, &max_scale)); EXPECT_EQ(4.f, max_scale); EXPECT_TRUE(animation_impl_->keyframe_effect()->MaximumTargetScale( ElementListType::ACTIVE, &max_scale)); EXPECT_EQ(0.f, max_scale); animation_impl_->ActivateKeyframeEffects(); EXPECT_TRUE(animation_impl_->keyframe_effect()->MaximumTargetScale( ElementListType::PENDING, &max_scale)); EXPECT_EQ(4.f, max_scale); EXPECT_TRUE(animation_impl_->keyframe_effect()->MaximumTargetScale( ElementListType::ACTIVE, &max_scale)); EXPECT_EQ(4.f, max_scale); std::unique_ptr curve2( KeyframedTransformAnimationCurve::Create()); TransformOperations operations2; curve2->AddKeyframe( TransformKeyframe::Create(base::TimeDelta(), operations2, nullptr)); operations2.AppendScale(6.0, 5.0, 4.0); curve2->AddKeyframe(TransformKeyframe::Create( base::TimeDelta::FromSecondsD(1.0), operations2, nullptr)); keyframe_model = KeyframeModel::Create(std::move(curve2), 2, 2, TargetProperty::TRANSFORM); keyframe_model->set_affects_active_elements(false); animation_impl_->AddKeyframeModel(std::move(keyframe_model)); EXPECT_TRUE(animation_impl_->keyframe_effect()->MaximumTargetScale( ElementListType::PENDING, &max_scale)); EXPECT_EQ(6.f, max_scale); EXPECT_TRUE(animation_impl_->keyframe_effect()->MaximumTargetScale( ElementListType::ACTIVE, &max_scale)); EXPECT_EQ(4.f, max_scale); animation_impl_->ActivateKeyframeEffects(); EXPECT_TRUE(animation_impl_->keyframe_effect()->MaximumTargetScale( ElementListType::PENDING, &max_scale)); EXPECT_EQ(6.f, max_scale); EXPECT_TRUE(animation_impl_->keyframe_effect()->MaximumTargetScale( ElementListType::ACTIVE, &max_scale)); EXPECT_EQ(6.f, max_scale); std::unique_ptr curve3( KeyframedTransformAnimationCurve::Create()); TransformOperations operations3; curve3->AddKeyframe( TransformKeyframe::Create(base::TimeDelta(), operations3, nullptr)); operations3.AppendPerspective(6.0); curve3->AddKeyframe(TransformKeyframe::Create( base::TimeDelta::FromSecondsD(1.0), operations3, nullptr)); keyframe_model = KeyframeModel::Create(std::move(curve3), 3, 3, TargetProperty::TRANSFORM); keyframe_model->set_affects_active_elements(false); animation_impl_->AddKeyframeModel(std::move(keyframe_model)); EXPECT_FALSE(animation_impl_->keyframe_effect()->MaximumTargetScale( ElementListType::PENDING, &max_scale)); EXPECT_TRUE(animation_impl_->keyframe_effect()->MaximumTargetScale( ElementListType::ACTIVE, &max_scale)); EXPECT_EQ(6.f, max_scale); animation_impl_->ActivateKeyframeEffects(); EXPECT_FALSE(animation_impl_->keyframe_effect()->MaximumTargetScale( ElementListType::PENDING, &max_scale)); EXPECT_FALSE(animation_impl_->keyframe_effect()->MaximumTargetScale( ElementListType::ACTIVE, &max_scale)); animation_impl_->keyframe_effect()->GetKeyframeModelById(3)->SetRunState( KeyframeModel::FINISHED, TicksFromSecondsF(0.0)); animation_impl_->keyframe_effect()->GetKeyframeModelById(2)->SetRunState( KeyframeModel::FINISHED, TicksFromSecondsF(0.0)); // Only unfinished animations should be considered by // MaximumTargetScale. EXPECT_TRUE(animation_impl_->keyframe_effect()->MaximumTargetScale( ElementListType::PENDING, &max_scale)); EXPECT_EQ(4.f, max_scale); EXPECT_TRUE(animation_impl_->keyframe_effect()->MaximumTargetScale( ElementListType::ACTIVE, &max_scale)); EXPECT_EQ(4.f, max_scale); } TEST_F(ElementAnimationsTest, MaximumTargetScaleWithDirection) { CreateTestLayer(true, false); AttachTimelineAnimationLayer(); CreateImplTimelineAndAnimation(); std::unique_ptr curve1( KeyframedTransformAnimationCurve::Create()); TransformOperations operations1; operations1.AppendScale(1.0, 2.0, 3.0); curve1->AddKeyframe( TransformKeyframe::Create(base::TimeDelta(), operations1, nullptr)); TransformOperations operations2; operations2.AppendScale(4.0, 5.0, 6.0); curve1->AddKeyframe(TransformKeyframe::Create( base::TimeDelta::FromSecondsD(1.0), operations2, nullptr)); std::unique_ptr keyframe_model_owned(KeyframeModel::Create( std::move(curve1), 1, 1, TargetProperty::TRANSFORM)); KeyframeModel* keyframe_model = keyframe_model_owned.get(); animation_impl_->AddKeyframeModel(std::move(keyframe_model_owned)); float max_scale = 0.f; EXPECT_GT(keyframe_model->playback_rate(), 0.0); // NORMAL direction with positive playback rate. keyframe_model->set_direction(KeyframeModel::Direction::NORMAL); EXPECT_TRUE(animation_impl_->keyframe_effect()->MaximumTargetScale( ElementListType::PENDING, &max_scale)); EXPECT_EQ(6.f, max_scale); EXPECT_TRUE(animation_impl_->keyframe_effect()->MaximumTargetScale( ElementListType::ACTIVE, &max_scale)); EXPECT_EQ(6.f, max_scale); // ALTERNATE direction with positive playback rate. keyframe_model->set_direction(KeyframeModel::Direction::ALTERNATE_NORMAL); EXPECT_TRUE(animation_impl_->keyframe_effect()->MaximumTargetScale( ElementListType::PENDING, &max_scale)); EXPECT_EQ(6.f, max_scale); EXPECT_TRUE(animation_impl_->keyframe_effect()->MaximumTargetScale( ElementListType::ACTIVE, &max_scale)); EXPECT_EQ(6.f, max_scale); // REVERSE direction with positive playback rate. keyframe_model->set_direction(KeyframeModel::Direction::REVERSE); EXPECT_TRUE(animation_impl_->keyframe_effect()->MaximumTargetScale( ElementListType::PENDING, &max_scale)); EXPECT_EQ(3.f, max_scale); EXPECT_TRUE(animation_impl_->keyframe_effect()->MaximumTargetScale( ElementListType::ACTIVE, &max_scale)); EXPECT_EQ(3.f, max_scale); // ALTERNATE reverse direction. keyframe_model->set_direction(KeyframeModel::Direction::REVERSE); EXPECT_TRUE(animation_impl_->keyframe_effect()->MaximumTargetScale( ElementListType::PENDING, &max_scale)); EXPECT_EQ(3.f, max_scale); EXPECT_TRUE(animation_impl_->keyframe_effect()->MaximumTargetScale( ElementListType::ACTIVE, &max_scale)); EXPECT_EQ(3.f, max_scale); keyframe_model->set_playback_rate(-1.0); // NORMAL direction with negative playback rate. keyframe_model->set_direction(KeyframeModel::Direction::NORMAL); EXPECT_TRUE(animation_impl_->keyframe_effect()->MaximumTargetScale( ElementListType::PENDING, &max_scale)); EXPECT_EQ(3.f, max_scale); EXPECT_TRUE(animation_impl_->keyframe_effect()->MaximumTargetScale( ElementListType::ACTIVE, &max_scale)); EXPECT_EQ(3.f, max_scale); // ALTERNATE direction with negative playback rate. keyframe_model->set_direction(KeyframeModel::Direction::ALTERNATE_NORMAL); EXPECT_TRUE(animation_impl_->keyframe_effect()->MaximumTargetScale( ElementListType::PENDING, &max_scale)); EXPECT_EQ(3.f, max_scale); EXPECT_TRUE(animation_impl_->keyframe_effect()->MaximumTargetScale( ElementListType::ACTIVE, &max_scale)); EXPECT_EQ(3.f, max_scale); // REVERSE direction with negative playback rate. keyframe_model->set_direction(KeyframeModel::Direction::REVERSE); EXPECT_TRUE(animation_impl_->keyframe_effect()->MaximumTargetScale( ElementListType::PENDING, &max_scale)); EXPECT_EQ(6.f, max_scale); EXPECT_TRUE(animation_impl_->keyframe_effect()->MaximumTargetScale( ElementListType::ACTIVE, &max_scale)); EXPECT_EQ(6.f, max_scale); // ALTERNATE reverse direction with negative playback rate. keyframe_model->set_direction(KeyframeModel::Direction::REVERSE); EXPECT_TRUE(animation_impl_->keyframe_effect()->MaximumTargetScale( ElementListType::PENDING, &max_scale)); EXPECT_EQ(6.f, max_scale); EXPECT_TRUE(animation_impl_->keyframe_effect()->MaximumTargetScale( ElementListType::ACTIVE, &max_scale)); EXPECT_EQ(6.f, max_scale); } TEST_F(ElementAnimationsTest, NewlyPushedAnimationWaitsForActivation) { CreateTestLayer(true, true); AttachTimelineAnimationLayer(); CreateImplTimelineAndAnimation(); auto events = CreateEventsForTesting(); int keyframe_model_id = AddOpacityTransitionToAnimation(animation_.get(), 1, 0.5f, 1.f, false); EXPECT_TRUE( animation_->keyframe_effect()->GetKeyframeModelById(keyframe_model_id)); EXPECT_FALSE(animation_impl_->keyframe_effect()->GetKeyframeModelById( keyframe_model_id)); PushProperties(); EXPECT_TRUE(animation_impl_->keyframe_effect()->GetKeyframeModelById( keyframe_model_id)); EXPECT_EQ(KeyframeModel::WAITING_FOR_TARGET_AVAILABILITY, animation_impl_->keyframe_effect() ->GetKeyframeModelById(keyframe_model_id) ->run_state()); EXPECT_TRUE(animation_impl_->keyframe_effect() ->GetKeyframeModelById(keyframe_model_id) ->affects_pending_elements()); EXPECT_FALSE(animation_impl_->keyframe_effect() ->GetKeyframeModelById(keyframe_model_id) ->affects_active_elements()); animation_impl_->Tick(kInitialTickTime); EXPECT_EQ(KeyframeModel::STARTING, animation_impl_->keyframe_effect() ->GetKeyframeModelById(keyframe_model_id) ->run_state()); animation_impl_->UpdateState(true, events.get()); // Since the animation hasn't been activated, it should still be STARTING // rather than RUNNING. EXPECT_EQ(KeyframeModel::STARTING, animation_impl_->keyframe_effect() ->GetKeyframeModelById(keyframe_model_id) ->run_state()); // Since the animation hasn't been activated, only the pending observer // should have been ticked. EXPECT_EQ(0.5f, client_impl_.GetOpacity(element_id_, ElementListType::PENDING)); EXPECT_EQ(0.f, client_impl_.GetOpacity(element_id_, ElementListType::ACTIVE)); animation_impl_->ActivateKeyframeEffects(); EXPECT_TRUE(animation_impl_->keyframe_effect() ->GetKeyframeModelById(keyframe_model_id) ->affects_pending_elements()); EXPECT_TRUE(animation_impl_->keyframe_effect() ->GetKeyframeModelById(keyframe_model_id) ->affects_active_elements()); animation_impl_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(1000)); animation_impl_->UpdateState(true, events.get()); // Since the animation has been activated, it should have reached the // RUNNING state and the active observer should start to get ticked. EXPECT_EQ(KeyframeModel::RUNNING, animation_impl_->keyframe_effect() ->GetKeyframeModelById(keyframe_model_id) ->run_state()); EXPECT_EQ(0.5f, client_impl_.GetOpacity(element_id_, ElementListType::PENDING)); EXPECT_EQ(0.5f, client_impl_.GetOpacity(element_id_, ElementListType::ACTIVE)); } TEST_F(ElementAnimationsTest, ActivationBetweenAnimateAndUpdateState) { CreateTestLayer(true, true); AttachTimelineAnimationLayer(); CreateImplTimelineAndAnimation(); auto events = CreateEventsForTesting(); const int keyframe_model_id = AddOpacityTransitionToAnimation(animation_.get(), 1, 0.5f, 1.f, true); PushProperties(); EXPECT_TRUE(animation_impl_->keyframe_effect()->GetKeyframeModelById( keyframe_model_id)); EXPECT_EQ(KeyframeModel::WAITING_FOR_TARGET_AVAILABILITY, animation_impl_->keyframe_effect() ->GetKeyframeModelById(keyframe_model_id) ->run_state()); EXPECT_TRUE(animation_impl_->keyframe_effect() ->GetKeyframeModelById(keyframe_model_id) ->affects_pending_elements()); EXPECT_FALSE(animation_impl_->keyframe_effect() ->GetKeyframeModelById(keyframe_model_id) ->affects_active_elements()); animation_impl_->Tick(kInitialTickTime); // Since the animation hasn't been activated, only the pending observer // should have been ticked. EXPECT_EQ(0.5f, client_impl_.GetOpacity(element_id_, ElementListType::PENDING)); EXPECT_EQ(0.f, client_impl_.GetOpacity(element_id_, ElementListType::ACTIVE)); animation_impl_->ActivateKeyframeEffects(); EXPECT_TRUE(animation_impl_->keyframe_effect() ->GetKeyframeModelById(keyframe_model_id) ->affects_pending_elements()); EXPECT_TRUE(animation_impl_->keyframe_effect() ->GetKeyframeModelById(keyframe_model_id) ->affects_active_elements()); animation_impl_->UpdateState(true, events.get()); // Since the animation has been activated, it should have reached the // RUNNING state. EXPECT_EQ(KeyframeModel::RUNNING, animation_impl_->keyframe_effect() ->GetKeyframeModelById(keyframe_model_id) ->run_state()); animation_impl_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(500)); // Both elements should have been ticked. EXPECT_EQ(0.75f, client_impl_.GetOpacity(element_id_, ElementListType::PENDING)); EXPECT_EQ(0.75f, client_impl_.GetOpacity(element_id_, ElementListType::ACTIVE)); } TEST_F(ElementAnimationsTest, ObserverNotifiedWhenTransformAnimationChanges) { CreateTestLayer(true, true); AttachTimelineAnimationLayer(); CreateImplTimelineAndAnimation(); auto events = CreateEventsForTesting(); EXPECT_FALSE(client_.GetHasPotentialTransformAnimation( element_id_, ElementListType::ACTIVE)); EXPECT_FALSE(client_.GetTransformIsCurrentlyAnimating( element_id_, ElementListType::ACTIVE)); EXPECT_FALSE(client_impl_.GetHasPotentialTransformAnimation( element_id_, ElementListType::PENDING)); EXPECT_FALSE(client_impl_.GetTransformIsCurrentlyAnimating( element_id_, ElementListType::PENDING)); EXPECT_FALSE(client_impl_.GetHasPotentialTransformAnimation( element_id_, ElementListType::ACTIVE)); EXPECT_FALSE(client_impl_.GetTransformIsCurrentlyAnimating( element_id_, ElementListType::ACTIVE)); // Case 1: An animation that's allowed to run until its finish point. AddAnimatedTransformToAnimation(animation_.get(), 1.0, 1, 1); EXPECT_TRUE(client_.GetHasPotentialTransformAnimation( element_id_, ElementListType::ACTIVE)); EXPECT_TRUE(client_.GetTransformIsCurrentlyAnimating( element_id_, ElementListType::ACTIVE)); PushProperties(); EXPECT_TRUE(client_impl_.GetHasPotentialTransformAnimation( element_id_, ElementListType::PENDING)); EXPECT_TRUE(client_impl_.GetTransformIsCurrentlyAnimating( element_id_, ElementListType::PENDING)); EXPECT_FALSE(client_impl_.GetHasPotentialTransformAnimation( element_id_, ElementListType::ACTIVE)); EXPECT_FALSE(client_impl_.GetTransformIsCurrentlyAnimating( element_id_, ElementListType::ACTIVE)); animation_impl_->ActivateKeyframeEffects(); EXPECT_TRUE(client_impl_.GetHasPotentialTransformAnimation( element_id_, ElementListType::PENDING)); EXPECT_TRUE(client_impl_.GetTransformIsCurrentlyAnimating( element_id_, ElementListType::PENDING)); EXPECT_TRUE(client_impl_.GetHasPotentialTransformAnimation( element_id_, ElementListType::ACTIVE)); EXPECT_TRUE(client_impl_.GetTransformIsCurrentlyAnimating( element_id_, ElementListType::ACTIVE)); animation_impl_->Tick(kInitialTickTime); animation_impl_->UpdateState(true, events.get()); animation_->keyframe_effect()->NotifyKeyframeModelStarted(events->events_[0]); events->events_.clear(); // Finish the animation. animation_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(1000)); animation_->UpdateState(true, nullptr); EXPECT_FALSE(client_.GetHasPotentialTransformAnimation( element_id_, ElementListType::ACTIVE)); EXPECT_FALSE(client_.GetTransformIsCurrentlyAnimating( element_id_, ElementListType::ACTIVE)); PushProperties(); // Finished animations are pushed, but animations_impl hasn't yet ticked // at/past the end of the animation. EXPECT_FALSE(client_impl_.GetHasPotentialTransformAnimation( element_id_, ElementListType::PENDING)); EXPECT_FALSE(client_impl_.GetTransformIsCurrentlyAnimating( element_id_, ElementListType::PENDING)); EXPECT_TRUE(client_impl_.GetHasPotentialTransformAnimation( element_id_, ElementListType::ACTIVE)); EXPECT_TRUE(client_impl_.GetTransformIsCurrentlyAnimating( element_id_, ElementListType::ACTIVE)); animation_impl_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(1000)); animation_impl_->UpdateState(true, events.get()); EXPECT_FALSE(client_impl_.GetHasPotentialTransformAnimation( element_id_, ElementListType::PENDING)); EXPECT_FALSE(client_impl_.GetTransformIsCurrentlyAnimating( element_id_, ElementListType::PENDING)); EXPECT_FALSE(client_impl_.GetHasPotentialTransformAnimation( element_id_, ElementListType::ACTIVE)); EXPECT_FALSE(client_impl_.GetTransformIsCurrentlyAnimating( element_id_, ElementListType::ACTIVE)); // Case 2: An animation that's removed before it finishes. int keyframe_model_id = AddAnimatedTransformToAnimation(animation_.get(), 10.0, 2, 2); int animation2_id = AddAnimatedTransformToAnimation(animation_.get(), 10.0, 2, 1); animation_->keyframe_effect() ->GetKeyframeModelById(animation2_id) ->set_time_offset(base::TimeDelta::FromMilliseconds(-10000)); animation_->keyframe_effect() ->GetKeyframeModelById(animation2_id) ->set_fill_mode(KeyframeModel::FillMode::NONE); EXPECT_TRUE(client_.GetHasPotentialTransformAnimation( element_id_, ElementListType::ACTIVE)); EXPECT_TRUE(client_.GetTransformIsCurrentlyAnimating( element_id_, ElementListType::ACTIVE)); PushProperties(); EXPECT_TRUE(client_impl_.GetHasPotentialTransformAnimation( element_id_, ElementListType::PENDING)); EXPECT_TRUE(client_impl_.GetTransformIsCurrentlyAnimating( element_id_, ElementListType::PENDING)); EXPECT_FALSE(client_impl_.GetHasPotentialTransformAnimation( element_id_, ElementListType::ACTIVE)); EXPECT_FALSE(client_impl_.GetTransformIsCurrentlyAnimating( element_id_, ElementListType::ACTIVE)); animation_impl_->ActivateKeyframeEffects(); EXPECT_TRUE(client_impl_.GetHasPotentialTransformAnimation( element_id_, ElementListType::ACTIVE)); // animation1 is in effect currently and animation2 isn't. As the element has // atleast one animation that's in effect currently, client should be notified // that the transform is currently animating. EXPECT_TRUE(client_impl_.GetTransformIsCurrentlyAnimating( element_id_, ElementListType::ACTIVE)); animation_impl_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(2000)); animation_impl_->UpdateState(true, events.get()); animation_->keyframe_effect()->NotifyKeyframeModelStarted(events->events_[0]); events->events_.clear(); animation_->RemoveKeyframeModel(keyframe_model_id); animation_->RemoveKeyframeModel(animation2_id); EXPECT_FALSE(client_.GetHasPotentialTransformAnimation( element_id_, ElementListType::ACTIVE)); EXPECT_FALSE(client_.GetTransformIsCurrentlyAnimating( element_id_, ElementListType::ACTIVE)); PushProperties(); EXPECT_FALSE(client_impl_.GetHasPotentialTransformAnimation( element_id_, ElementListType::PENDING)); EXPECT_FALSE(client_impl_.GetTransformIsCurrentlyAnimating( element_id_, ElementListType::PENDING)); EXPECT_TRUE(client_impl_.GetHasPotentialTransformAnimation( element_id_, ElementListType::ACTIVE)); EXPECT_TRUE(client_impl_.GetTransformIsCurrentlyAnimating( element_id_, ElementListType::ACTIVE)); animation_impl_->ActivateKeyframeEffects(); EXPECT_FALSE(client_impl_.GetHasPotentialTransformAnimation( element_id_, ElementListType::ACTIVE)); EXPECT_FALSE(client_impl_.GetTransformIsCurrentlyAnimating( element_id_, ElementListType::ACTIVE)); // Case 3: An animation that's aborted before it finishes. keyframe_model_id = AddAnimatedTransformToAnimation(animation_.get(), 10.0, 3, 3); EXPECT_TRUE(client_.GetHasPotentialTransformAnimation( element_id_, ElementListType::ACTIVE)); EXPECT_TRUE(client_.GetTransformIsCurrentlyAnimating( element_id_, ElementListType::ACTIVE)); PushProperties(); EXPECT_TRUE(client_impl_.GetHasPotentialTransformAnimation( element_id_, ElementListType::PENDING)); EXPECT_TRUE(client_impl_.GetTransformIsCurrentlyAnimating( element_id_, ElementListType::PENDING)); EXPECT_FALSE(client_impl_.GetHasPotentialTransformAnimation( element_id_, ElementListType::ACTIVE)); EXPECT_FALSE(client_impl_.GetTransformIsCurrentlyAnimating( element_id_, ElementListType::ACTIVE)); animation_impl_->ActivateKeyframeEffects(); EXPECT_TRUE(client_impl_.GetHasPotentialTransformAnimation( element_id_, ElementListType::ACTIVE)); EXPECT_TRUE(client_impl_.GetTransformIsCurrentlyAnimating( element_id_, ElementListType::ACTIVE)); animation_impl_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(2000)); animation_impl_->UpdateState(true, events.get()); animation_->keyframe_effect()->NotifyKeyframeModelStarted(events->events_[0]); events->events_.clear(); animation_impl_->AbortKeyframeModels(TargetProperty::TRANSFORM, false); EXPECT_FALSE(client_impl_.GetHasPotentialTransformAnimation( element_id_, ElementListType::PENDING)); EXPECT_FALSE(client_impl_.GetTransformIsCurrentlyAnimating( element_id_, ElementListType::PENDING)); EXPECT_FALSE(client_impl_.GetHasPotentialTransformAnimation( element_id_, ElementListType::ACTIVE)); EXPECT_FALSE(client_impl_.GetTransformIsCurrentlyAnimating( element_id_, ElementListType::ACTIVE)); animation_impl_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(4000)); animation_impl_->UpdateState(true, events.get()); element_animations_->NotifyAnimationAborted(events->events_[0]); EXPECT_FALSE(client_.GetHasPotentialTransformAnimation( element_id_, ElementListType::ACTIVE)); EXPECT_FALSE(client_.GetTransformIsCurrentlyAnimating( element_id_, ElementListType::ACTIVE)); // Case 4 : An animation that's not in effect. keyframe_model_id = AddAnimatedTransformToAnimation(animation_.get(), 1.0, 1, 6); animation_->keyframe_effect() ->GetKeyframeModelById(keyframe_model_id) ->set_time_offset(base::TimeDelta::FromMilliseconds(-10000)); animation_->keyframe_effect() ->GetKeyframeModelById(keyframe_model_id) ->set_fill_mode(KeyframeModel::FillMode::NONE); PushProperties(); EXPECT_TRUE(client_impl_.GetHasPotentialTransformAnimation( element_id_, ElementListType::PENDING)); EXPECT_FALSE(client_impl_.GetTransformIsCurrentlyAnimating( element_id_, ElementListType::PENDING)); EXPECT_FALSE(client_impl_.GetHasPotentialTransformAnimation( element_id_, ElementListType::ACTIVE)); EXPECT_FALSE(client_impl_.GetTransformIsCurrentlyAnimating( element_id_, ElementListType::ACTIVE)); animation_impl_->ActivateKeyframeEffects(); EXPECT_TRUE(client_impl_.GetHasPotentialTransformAnimation( element_id_, ElementListType::ACTIVE)); EXPECT_FALSE(client_impl_.GetTransformIsCurrentlyAnimating( element_id_, ElementListType::ACTIVE)); } TEST_F(ElementAnimationsTest, ObserverNotifiedWhenOpacityAnimationChanges) { CreateTestLayer(true, true); AttachTimelineAnimationLayer(); CreateImplTimelineAndAnimation(); auto events = CreateEventsForTesting(); EXPECT_FALSE(client_.GetHasPotentialOpacityAnimation( element_id_, ElementListType::ACTIVE)); EXPECT_FALSE(client_.GetOpacityIsCurrentlyAnimating(element_id_, ElementListType::ACTIVE)); EXPECT_FALSE(client_impl_.GetHasPotentialOpacityAnimation( element_id_, ElementListType::PENDING)); EXPECT_FALSE(client_impl_.GetOpacityIsCurrentlyAnimating( element_id_, ElementListType::PENDING)); EXPECT_FALSE(client_impl_.GetHasPotentialOpacityAnimation( element_id_, ElementListType::ACTIVE)); EXPECT_FALSE(client_impl_.GetOpacityIsCurrentlyAnimating( element_id_, ElementListType::ACTIVE)); // Case 1: An animation that's allowed to run until its finish point. AddOpacityTransitionToAnimation(animation_.get(), 1.0, 0.f, 1.f, false /*use_timing_function*/); EXPECT_TRUE(client_.GetHasPotentialOpacityAnimation(element_id_, ElementListType::ACTIVE)); EXPECT_TRUE(client_.GetOpacityIsCurrentlyAnimating(element_id_, ElementListType::ACTIVE)); PushProperties(); EXPECT_TRUE(client_impl_.GetHasPotentialOpacityAnimation( element_id_, ElementListType::PENDING)); EXPECT_TRUE(client_impl_.GetOpacityIsCurrentlyAnimating( element_id_, ElementListType::PENDING)); EXPECT_FALSE(client_impl_.GetHasPotentialOpacityAnimation( element_id_, ElementListType::ACTIVE)); EXPECT_FALSE(client_impl_.GetOpacityIsCurrentlyAnimating( element_id_, ElementListType::ACTIVE)); animation_impl_->ActivateKeyframeEffects(); EXPECT_TRUE(client_impl_.GetHasPotentialOpacityAnimation( element_id_, ElementListType::PENDING)); EXPECT_TRUE(client_impl_.GetOpacityIsCurrentlyAnimating( element_id_, ElementListType::PENDING)); EXPECT_TRUE(client_impl_.GetHasPotentialOpacityAnimation( element_id_, ElementListType::ACTIVE)); EXPECT_TRUE(client_impl_.GetOpacityIsCurrentlyAnimating( element_id_, ElementListType::ACTIVE)); animation_impl_->Tick(kInitialTickTime); animation_impl_->UpdateState(true, events.get()); animation_->keyframe_effect()->NotifyKeyframeModelStarted(events->events_[0]); events->events_.clear(); // Finish the animation. animation_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(1000)); animation_->UpdateState(true, nullptr); EXPECT_FALSE(client_.GetHasPotentialOpacityAnimation( element_id_, ElementListType::ACTIVE)); EXPECT_FALSE(client_.GetOpacityIsCurrentlyAnimating(element_id_, ElementListType::ACTIVE)); PushProperties(); // Finished animations are pushed, but animations_impl hasn't yet ticked // at/past the end of the animation. EXPECT_FALSE(client_impl_.GetHasPotentialOpacityAnimation( element_id_, ElementListType::PENDING)); EXPECT_FALSE(client_impl_.GetOpacityIsCurrentlyAnimating( element_id_, ElementListType::PENDING)); EXPECT_TRUE(client_impl_.GetHasPotentialOpacityAnimation( element_id_, ElementListType::ACTIVE)); EXPECT_TRUE(client_impl_.GetOpacityIsCurrentlyAnimating( element_id_, ElementListType::ACTIVE)); animation_impl_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(1000)); animation_impl_->UpdateState(true, events.get()); EXPECT_FALSE(client_impl_.GetHasPotentialOpacityAnimation( element_id_, ElementListType::PENDING)); EXPECT_FALSE(client_impl_.GetOpacityIsCurrentlyAnimating( element_id_, ElementListType::PENDING)); EXPECT_FALSE(client_impl_.GetHasPotentialOpacityAnimation( element_id_, ElementListType::ACTIVE)); EXPECT_FALSE(client_impl_.GetOpacityIsCurrentlyAnimating( element_id_, ElementListType::ACTIVE)); // Case 2: An animation that's removed before it finishes. int keyframe_model_id = AddOpacityTransitionToAnimation( animation_.get(), 10.0, 0.f, 1.f, false /*use_timing_function*/); EXPECT_TRUE(client_.GetHasPotentialOpacityAnimation(element_id_, ElementListType::ACTIVE)); EXPECT_TRUE(client_.GetOpacityIsCurrentlyAnimating(element_id_, ElementListType::ACTIVE)); PushProperties(); EXPECT_TRUE(client_impl_.GetHasPotentialOpacityAnimation( element_id_, ElementListType::PENDING)); EXPECT_TRUE(client_impl_.GetOpacityIsCurrentlyAnimating( element_id_, ElementListType::PENDING)); EXPECT_FALSE(client_impl_.GetHasPotentialOpacityAnimation( element_id_, ElementListType::ACTIVE)); EXPECT_FALSE(client_impl_.GetOpacityIsCurrentlyAnimating( element_id_, ElementListType::ACTIVE)); animation_impl_->ActivateKeyframeEffects(); EXPECT_TRUE(client_impl_.GetHasPotentialOpacityAnimation( element_id_, ElementListType::ACTIVE)); EXPECT_TRUE(client_impl_.GetOpacityIsCurrentlyAnimating( element_id_, ElementListType::ACTIVE)); animation_impl_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(2000)); animation_impl_->UpdateState(true, events.get()); animation_->keyframe_effect()->NotifyKeyframeModelStarted(events->events_[0]); events->events_.clear(); animation_->RemoveKeyframeModel(keyframe_model_id); EXPECT_FALSE(client_.GetHasPotentialOpacityAnimation( element_id_, ElementListType::ACTIVE)); EXPECT_FALSE(client_.GetOpacityIsCurrentlyAnimating(element_id_, ElementListType::ACTIVE)); PushProperties(); EXPECT_FALSE(client_impl_.GetHasPotentialOpacityAnimation( element_id_, ElementListType::PENDING)); EXPECT_FALSE(client_impl_.GetOpacityIsCurrentlyAnimating( element_id_, ElementListType::PENDING)); EXPECT_TRUE(client_impl_.GetHasPotentialOpacityAnimation( element_id_, ElementListType::ACTIVE)); EXPECT_TRUE(client_impl_.GetOpacityIsCurrentlyAnimating( element_id_, ElementListType::ACTIVE)); animation_impl_->ActivateKeyframeEffects(); EXPECT_FALSE(client_impl_.GetHasPotentialOpacityAnimation( element_id_, ElementListType::ACTIVE)); EXPECT_FALSE(client_impl_.GetOpacityIsCurrentlyAnimating( element_id_, ElementListType::ACTIVE)); // Case 3: An animation that's aborted before it finishes. keyframe_model_id = AddOpacityTransitionToAnimation( animation_.get(), 10.0, 0.f, 0.5f, false /*use_timing_function*/); EXPECT_TRUE(client_.GetHasPotentialOpacityAnimation(element_id_, ElementListType::ACTIVE)); EXPECT_TRUE(client_.GetOpacityIsCurrentlyAnimating(element_id_, ElementListType::ACTIVE)); PushProperties(); EXPECT_TRUE(client_impl_.GetHasPotentialOpacityAnimation( element_id_, ElementListType::PENDING)); EXPECT_TRUE(client_impl_.GetOpacityIsCurrentlyAnimating( element_id_, ElementListType::PENDING)); EXPECT_FALSE(client_impl_.GetHasPotentialOpacityAnimation( element_id_, ElementListType::ACTIVE)); EXPECT_FALSE(client_impl_.GetOpacityIsCurrentlyAnimating( element_id_, ElementListType::ACTIVE)); animation_impl_->ActivateKeyframeEffects(); EXPECT_TRUE(client_impl_.GetHasPotentialOpacityAnimation( element_id_, ElementListType::ACTIVE)); EXPECT_TRUE(client_impl_.GetOpacityIsCurrentlyAnimating( element_id_, ElementListType::ACTIVE)); animation_impl_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(2000)); animation_impl_->UpdateState(true, events.get()); animation_->keyframe_effect()->NotifyKeyframeModelStarted(events->events_[0]); events->events_.clear(); animation_impl_->AbortKeyframeModels(TargetProperty::OPACITY, false); EXPECT_FALSE(client_impl_.GetHasPotentialOpacityAnimation( element_id_, ElementListType::PENDING)); EXPECT_FALSE(client_impl_.GetOpacityIsCurrentlyAnimating( element_id_, ElementListType::PENDING)); EXPECT_FALSE(client_impl_.GetHasPotentialOpacityAnimation( element_id_, ElementListType::ACTIVE)); EXPECT_FALSE(client_impl_.GetOpacityIsCurrentlyAnimating( element_id_, ElementListType::ACTIVE)); animation_impl_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(4000)); animation_impl_->UpdateState(true, events.get()); element_animations_->NotifyAnimationAborted(events->events_[0]); EXPECT_FALSE(client_.GetHasPotentialOpacityAnimation( element_id_, ElementListType::ACTIVE)); EXPECT_FALSE(client_.GetOpacityIsCurrentlyAnimating(element_id_, ElementListType::ACTIVE)); // Case 4 : An animation that's not in effect. keyframe_model_id = AddOpacityTransitionToAnimation( animation_.get(), 1.0, 0.f, 0.5f, false /*use_timing_function*/); animation_->keyframe_effect() ->GetKeyframeModelById(keyframe_model_id) ->set_time_offset(base::TimeDelta::FromMilliseconds(-10000)); animation_->keyframe_effect() ->GetKeyframeModelById(keyframe_model_id) ->set_fill_mode(KeyframeModel::FillMode::NONE); PushProperties(); EXPECT_TRUE(client_impl_.GetHasPotentialOpacityAnimation( element_id_, ElementListType::PENDING)); EXPECT_FALSE(client_impl_.GetOpacityIsCurrentlyAnimating( element_id_, ElementListType::PENDING)); EXPECT_FALSE(client_impl_.GetHasPotentialOpacityAnimation( element_id_, ElementListType::ACTIVE)); EXPECT_FALSE(client_impl_.GetOpacityIsCurrentlyAnimating( element_id_, ElementListType::ACTIVE)); animation_impl_->ActivateKeyframeEffects(); EXPECT_TRUE(client_impl_.GetHasPotentialOpacityAnimation( element_id_, ElementListType::ACTIVE)); EXPECT_FALSE(client_impl_.GetOpacityIsCurrentlyAnimating( element_id_, ElementListType::ACTIVE)); } TEST_F(ElementAnimationsTest, ObserverNotifiedWhenFilterAnimationChanges) { CreateTestLayer(true, true); AttachTimelineAnimationLayer(); CreateImplTimelineAndAnimation(); auto events = CreateEventsForTesting(); EXPECT_FALSE(client_.GetHasPotentialFilterAnimation(element_id_, ElementListType::ACTIVE)); EXPECT_FALSE(client_.GetFilterIsCurrentlyAnimating(element_id_, ElementListType::ACTIVE)); EXPECT_FALSE(client_impl_.GetHasPotentialFilterAnimation( element_id_, ElementListType::PENDING)); EXPECT_FALSE(client_impl_.GetFilterIsCurrentlyAnimating( element_id_, ElementListType::PENDING)); EXPECT_FALSE(client_impl_.GetHasPotentialFilterAnimation( element_id_, ElementListType::ACTIVE)); EXPECT_FALSE(client_impl_.GetFilterIsCurrentlyAnimating( element_id_, ElementListType::ACTIVE)); // Case 1: An animation that's allowed to run until its finish point. AddAnimatedFilterToAnimation(animation_.get(), 1.0, 0.f, 1.f); EXPECT_TRUE(client_.GetHasPotentialFilterAnimation(element_id_, ElementListType::ACTIVE)); EXPECT_TRUE(client_.GetFilterIsCurrentlyAnimating(element_id_, ElementListType::ACTIVE)); PushProperties(); EXPECT_TRUE(client_impl_.GetHasPotentialFilterAnimation( element_id_, ElementListType::PENDING)); EXPECT_TRUE(client_impl_.GetFilterIsCurrentlyAnimating( element_id_, ElementListType::PENDING)); EXPECT_FALSE(client_impl_.GetHasPotentialFilterAnimation( element_id_, ElementListType::ACTIVE)); EXPECT_FALSE(client_impl_.GetFilterIsCurrentlyAnimating( element_id_, ElementListType::ACTIVE)); animation_impl_->ActivateKeyframeEffects(); EXPECT_TRUE(client_impl_.GetHasPotentialFilterAnimation( element_id_, ElementListType::PENDING)); EXPECT_TRUE(client_impl_.GetFilterIsCurrentlyAnimating( element_id_, ElementListType::PENDING)); EXPECT_TRUE(client_impl_.GetHasPotentialFilterAnimation( element_id_, ElementListType::ACTIVE)); EXPECT_TRUE(client_impl_.GetFilterIsCurrentlyAnimating( element_id_, ElementListType::ACTIVE)); animation_impl_->Tick(kInitialTickTime); animation_impl_->UpdateState(true, events.get()); animation_->keyframe_effect()->NotifyKeyframeModelStarted(events->events_[0]); events->events_.clear(); // Finish the animation. animation_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(1000)); animation_->UpdateState(true, nullptr); EXPECT_FALSE(client_.GetHasPotentialFilterAnimation(element_id_, ElementListType::ACTIVE)); EXPECT_FALSE(client_.GetFilterIsCurrentlyAnimating(element_id_, ElementListType::ACTIVE)); PushProperties(); // Finished animations are pushed, but animations_impl hasn't yet ticked // at/past the end of the animation. EXPECT_FALSE(client_impl_.GetHasPotentialFilterAnimation( element_id_, ElementListType::PENDING)); EXPECT_FALSE(client_impl_.GetFilterIsCurrentlyAnimating( element_id_, ElementListType::PENDING)); EXPECT_TRUE(client_impl_.GetHasPotentialFilterAnimation( element_id_, ElementListType::ACTIVE)); EXPECT_TRUE(client_impl_.GetFilterIsCurrentlyAnimating( element_id_, ElementListType::ACTIVE)); animation_impl_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(1000)); animation_impl_->UpdateState(true, events.get()); EXPECT_FALSE(client_impl_.GetHasPotentialFilterAnimation( element_id_, ElementListType::PENDING)); EXPECT_FALSE(client_impl_.GetFilterIsCurrentlyAnimating( element_id_, ElementListType::PENDING)); EXPECT_FALSE(client_impl_.GetHasPotentialFilterAnimation( element_id_, ElementListType::ACTIVE)); EXPECT_FALSE(client_impl_.GetFilterIsCurrentlyAnimating( element_id_, ElementListType::ACTIVE)); // Case 2: An animation that's removed before it finishes. int keyframe_model_id = AddAnimatedFilterToAnimation(animation_.get(), 10.0, 0.f, 1.f); EXPECT_TRUE(client_.GetHasPotentialFilterAnimation(element_id_, ElementListType::ACTIVE)); EXPECT_TRUE(client_.GetFilterIsCurrentlyAnimating(element_id_, ElementListType::ACTIVE)); PushProperties(); EXPECT_TRUE(client_impl_.GetHasPotentialFilterAnimation( element_id_, ElementListType::PENDING)); EXPECT_TRUE(client_impl_.GetFilterIsCurrentlyAnimating( element_id_, ElementListType::PENDING)); EXPECT_FALSE(client_impl_.GetHasPotentialFilterAnimation( element_id_, ElementListType::ACTIVE)); EXPECT_FALSE(client_impl_.GetFilterIsCurrentlyAnimating( element_id_, ElementListType::ACTIVE)); animation_impl_->ActivateKeyframeEffects(); EXPECT_TRUE(client_impl_.GetHasPotentialFilterAnimation( element_id_, ElementListType::ACTIVE)); EXPECT_TRUE(client_impl_.GetFilterIsCurrentlyAnimating( element_id_, ElementListType::ACTIVE)); animation_impl_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(2000)); animation_impl_->UpdateState(true, events.get()); animation_->keyframe_effect()->NotifyKeyframeModelStarted(events->events_[0]); events->events_.clear(); animation_->RemoveKeyframeModel(keyframe_model_id); EXPECT_FALSE(client_.GetHasPotentialFilterAnimation(element_id_, ElementListType::ACTIVE)); EXPECT_FALSE(client_.GetFilterIsCurrentlyAnimating(element_id_, ElementListType::ACTIVE)); PushProperties(); EXPECT_FALSE(client_impl_.GetHasPotentialFilterAnimation( element_id_, ElementListType::PENDING)); EXPECT_FALSE(client_impl_.GetFilterIsCurrentlyAnimating( element_id_, ElementListType::PENDING)); EXPECT_TRUE(client_impl_.GetHasPotentialFilterAnimation( element_id_, ElementListType::ACTIVE)); EXPECT_TRUE(client_impl_.GetFilterIsCurrentlyAnimating( element_id_, ElementListType::ACTIVE)); animation_impl_->ActivateKeyframeEffects(); EXPECT_FALSE(client_impl_.GetHasPotentialFilterAnimation( element_id_, ElementListType::ACTIVE)); EXPECT_FALSE(client_impl_.GetFilterIsCurrentlyAnimating( element_id_, ElementListType::ACTIVE)); // Case 3: An animation that's aborted before it finishes. keyframe_model_id = AddAnimatedFilterToAnimation(animation_.get(), 10.0, 0.f, 0.5f); EXPECT_TRUE(client_.GetHasPotentialFilterAnimation(element_id_, ElementListType::ACTIVE)); EXPECT_TRUE(client_.GetFilterIsCurrentlyAnimating(element_id_, ElementListType::ACTIVE)); PushProperties(); EXPECT_TRUE(client_impl_.GetHasPotentialFilterAnimation( element_id_, ElementListType::PENDING)); EXPECT_TRUE(client_impl_.GetFilterIsCurrentlyAnimating( element_id_, ElementListType::PENDING)); EXPECT_FALSE(client_impl_.GetHasPotentialFilterAnimation( element_id_, ElementListType::ACTIVE)); EXPECT_FALSE(client_impl_.GetFilterIsCurrentlyAnimating( element_id_, ElementListType::ACTIVE)); animation_impl_->ActivateKeyframeEffects(); EXPECT_TRUE(client_impl_.GetHasPotentialFilterAnimation( element_id_, ElementListType::ACTIVE)); EXPECT_TRUE(client_impl_.GetFilterIsCurrentlyAnimating( element_id_, ElementListType::ACTIVE)); animation_impl_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(2000)); animation_impl_->UpdateState(true, events.get()); animation_->keyframe_effect()->NotifyKeyframeModelStarted(events->events_[0]); events->events_.clear(); animation_impl_->AbortKeyframeModels(TargetProperty::FILTER, false); EXPECT_FALSE(client_impl_.GetHasPotentialFilterAnimation( element_id_, ElementListType::PENDING)); EXPECT_FALSE(client_impl_.GetFilterIsCurrentlyAnimating( element_id_, ElementListType::PENDING)); EXPECT_FALSE(client_impl_.GetHasPotentialFilterAnimation( element_id_, ElementListType::ACTIVE)); EXPECT_FALSE(client_impl_.GetFilterIsCurrentlyAnimating( element_id_, ElementListType::ACTIVE)); animation_impl_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(4000)); animation_impl_->UpdateState(true, events.get()); element_animations_->NotifyAnimationAborted(events->events_[0]); EXPECT_FALSE(client_.GetHasPotentialFilterAnimation(element_id_, ElementListType::ACTIVE)); EXPECT_FALSE(client_.GetFilterIsCurrentlyAnimating(element_id_, ElementListType::ACTIVE)); // Case 4 : An animation that's not in effect. keyframe_model_id = AddAnimatedFilterToAnimation(animation_.get(), 1.0, 0.f, 0.5f); animation_->keyframe_effect() ->GetKeyframeModelById(keyframe_model_id) ->set_time_offset(base::TimeDelta::FromMilliseconds(-10000)); animation_->keyframe_effect() ->GetKeyframeModelById(keyframe_model_id) ->set_fill_mode(KeyframeModel::FillMode::NONE); PushProperties(); EXPECT_TRUE(client_impl_.GetHasPotentialFilterAnimation( element_id_, ElementListType::PENDING)); EXPECT_FALSE(client_impl_.GetFilterIsCurrentlyAnimating( element_id_, ElementListType::PENDING)); EXPECT_FALSE(client_impl_.GetHasPotentialFilterAnimation( element_id_, ElementListType::ACTIVE)); EXPECT_FALSE(client_impl_.GetFilterIsCurrentlyAnimating( element_id_, ElementListType::ACTIVE)); animation_impl_->ActivateKeyframeEffects(); EXPECT_TRUE(client_impl_.GetHasPotentialFilterAnimation( element_id_, ElementListType::ACTIVE)); EXPECT_FALSE(client_impl_.GetFilterIsCurrentlyAnimating( element_id_, ElementListType::ACTIVE)); } TEST_F(ElementAnimationsTest, ClippedOpacityValues) { CreateTestLayer(false, false); AttachTimelineAnimationLayer(); AddOpacityTransitionToAnimation(animation_.get(), 1, 1.f, 2.f, true); animation_->Tick(kInitialTickTime); EXPECT_EQ(1.f, client_.GetOpacity(element_id_, ElementListType::ACTIVE)); // Opacity values are clipped [0,1] animation_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(1000)); EXPECT_EQ(1.f, client_.GetOpacity(element_id_, ElementListType::ACTIVE)); } TEST_F(ElementAnimationsTest, ClippedNegativeOpacityValues) { CreateTestLayer(false, false); AttachTimelineAnimationLayer(); AddOpacityTransitionToAnimation(animation_.get(), 1, 0.f, -2.f, true); animation_->Tick(kInitialTickTime); EXPECT_EQ(0.f, client_.GetOpacity(element_id_, ElementListType::ACTIVE)); // Opacity values are clipped [0,1] animation_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(1000)); EXPECT_EQ(0.f, client_.GetOpacity(element_id_, ElementListType::ACTIVE)); } TEST_F(ElementAnimationsTest, PushedDeletedAnimationWaitsForActivation) { CreateTestLayer(true, true); AttachTimelineAnimationLayer(); CreateImplTimelineAndAnimation(); auto events = CreateEventsForTesting(); const int keyframe_model_id = AddOpacityTransitionToAnimation(animation_.get(), 1, 0.5f, 1.f, true); PushProperties(); animation_impl_->ActivateKeyframeEffects(); animation_impl_->Tick(kInitialTickTime); animation_impl_->UpdateState(true, events.get()); EXPECT_EQ(KeyframeModel::RUNNING, animation_impl_->keyframe_effect() ->GetKeyframeModelById(keyframe_model_id) ->run_state()); EXPECT_EQ(0.5f, client_impl_.GetOpacity(element_id_, ElementListType::PENDING)); EXPECT_EQ(0.5f, client_impl_.GetOpacity(element_id_, ElementListType::ACTIVE)); EXPECT_TRUE(animation_impl_->keyframe_effect() ->GetKeyframeModelById(keyframe_model_id) ->affects_pending_elements()); EXPECT_TRUE(animation_impl_->keyframe_effect() ->GetKeyframeModelById(keyframe_model_id) ->affects_active_elements()); // Delete the animation on the main-thread animations. animation_->RemoveKeyframeModel( animation_->GetKeyframeModel(TargetProperty::OPACITY)->id()); PushProperties(); // The animation should no longer affect pending elements. EXPECT_FALSE(animation_impl_->keyframe_effect() ->GetKeyframeModelById(keyframe_model_id) ->affects_pending_elements()); EXPECT_TRUE(animation_impl_->keyframe_effect() ->GetKeyframeModelById(keyframe_model_id) ->affects_active_elements()); animation_impl_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(500)); animation_impl_->UpdateState(true, events.get()); // Only the active observer should have been ticked. EXPECT_EQ(0.5f, client_impl_.GetOpacity(element_id_, ElementListType::PENDING)); EXPECT_EQ(0.75f, client_impl_.GetOpacity(element_id_, ElementListType::ACTIVE)); animation_impl_->ActivateKeyframeEffects(); events = CreateEventsForTesting(); animation_impl_->UpdateState(true, events.get()); // After Activation the animation doesn't affect neither active nor pending // thread. UpdateState for this animation would put the animation to wait for // deletion state. EXPECT_EQ(KeyframeModel::WAITING_FOR_DELETION, animation_impl_->keyframe_effect() ->GetKeyframeModelById(keyframe_model_id) ->run_state()); EXPECT_EQ(1u, events->events_.size()); // The animation is finished on impl thread, and main thread will delete it // during commit. animation_->animation_host()->SetAnimationEvents(std::move(events)); PushProperties(); EXPECT_FALSE(animation_impl_->keyframe_effect()->has_any_keyframe_model()); } // Tests that an animation that affects only active elements won't block // an animation that affects only pending elements from starting. TEST_F(ElementAnimationsTest, StartAnimationsAffectingDifferentObservers) { CreateTestLayer(true, true); AttachTimelineAnimationLayer(); CreateImplTimelineAndAnimation(); auto events = CreateEventsForTesting(); const int first_keyframe_model_id = AddOpacityTransitionToAnimation(animation_.get(), 1, 0.f, 1.f, true); PushProperties(); animation_impl_->ActivateKeyframeEffects(); animation_impl_->Tick(kInitialTickTime); animation_impl_->UpdateState(true, events.get()); // Remove the first animation from the main-thread animations, and add a // new animation affecting the same property. animation_->RemoveKeyframeModel( animation_->GetKeyframeModel(TargetProperty::OPACITY)->id()); const int second_keyframe_model_id = AddOpacityTransitionToAnimation(animation_.get(), 1, 1.f, 0.5f, true); PushProperties(); // The original animation should only affect active elements, and the new // animation should only affect pending elements. EXPECT_FALSE(animation_impl_->keyframe_effect() ->GetKeyframeModelById(first_keyframe_model_id) ->affects_pending_elements()); EXPECT_TRUE(animation_impl_->keyframe_effect() ->GetKeyframeModelById(first_keyframe_model_id) ->affects_active_elements()); EXPECT_TRUE(animation_impl_->keyframe_effect() ->GetKeyframeModelById(second_keyframe_model_id) ->affects_pending_elements()); EXPECT_FALSE(animation_impl_->keyframe_effect() ->GetKeyframeModelById(second_keyframe_model_id) ->affects_active_elements()); animation_impl_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(500)); animation_impl_->UpdateState(true, events.get()); // The original animation should still be running, and the new animation // should be starting. EXPECT_EQ(KeyframeModel::RUNNING, animation_impl_->keyframe_effect() ->GetKeyframeModelById(first_keyframe_model_id) ->run_state()); EXPECT_EQ(KeyframeModel::STARTING, animation_impl_->keyframe_effect() ->GetKeyframeModelById(second_keyframe_model_id) ->run_state()); // The active observer should have been ticked by the original animation, // and the pending observer should have been ticked by the new animation. EXPECT_EQ(1.f, client_impl_.GetOpacity(element_id_, ElementListType::PENDING)); EXPECT_EQ(0.5f, client_impl_.GetOpacity(element_id_, ElementListType::ACTIVE)); animation_impl_->ActivateKeyframeEffects(); // The original animation no longer affect either elements, and the new // animation should now affect both elements. EXPECT_FALSE(animation_impl_->keyframe_effect() ->GetKeyframeModelById(first_keyframe_model_id) ->affects_pending_elements()); EXPECT_FALSE(animation_impl_->keyframe_effect() ->GetKeyframeModelById(first_keyframe_model_id) ->affects_active_elements()); EXPECT_TRUE(animation_impl_->keyframe_effect() ->GetKeyframeModelById(second_keyframe_model_id) ->affects_pending_elements()); EXPECT_TRUE(animation_impl_->keyframe_effect() ->GetKeyframeModelById(second_keyframe_model_id) ->affects_active_elements()); animation_impl_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(1000)); animation_impl_->UpdateState(true, events.get()); // The original animation should be marked for waiting for deletion. EXPECT_EQ(KeyframeModel::WAITING_FOR_DELETION, animation_impl_->keyframe_effect() ->GetKeyframeModelById(first_keyframe_model_id) ->run_state()); // The new animation should be running, and the active observer should have // been ticked at the new animation's starting point. EXPECT_EQ(KeyframeModel::RUNNING, animation_impl_->keyframe_effect() ->GetKeyframeModelById(second_keyframe_model_id) ->run_state()); EXPECT_EQ(1.f, client_impl_.GetOpacity(element_id_, ElementListType::PENDING)); EXPECT_EQ(1.f, client_impl_.GetOpacity(element_id_, ElementListType::ACTIVE)); } TEST_F(ElementAnimationsTest, TestIsCurrentlyAnimatingProperty) { CreateTestLayer(false, false); AttachTimelineAnimationLayer(); // Create an animation that initially affects only pending elements. std::unique_ptr keyframe_model(CreateKeyframeModel( std::unique_ptr(new FakeFloatTransition(1.0, 0.f, 1.f)), 1, TargetProperty::OPACITY)); keyframe_model->set_affects_active_elements(false); animation_->AddKeyframeModel(std::move(keyframe_model)); animation_->Tick(kInitialTickTime); EXPECT_TRUE(animation_->keyframe_effect()->IsCurrentlyAnimatingProperty( TargetProperty::OPACITY, ElementListType::PENDING)); EXPECT_FALSE(animation_->keyframe_effect()->IsCurrentlyAnimatingProperty( TargetProperty::OPACITY, ElementListType::ACTIVE)); animation_->UpdateState(true, nullptr); EXPECT_TRUE(animation_->keyframe_effect()->HasTickingKeyframeModel()); EXPECT_TRUE(animation_->keyframe_effect()->IsCurrentlyAnimatingProperty( TargetProperty::OPACITY, ElementListType::PENDING)); EXPECT_FALSE(animation_->keyframe_effect()->IsCurrentlyAnimatingProperty( TargetProperty::OPACITY, ElementListType::ACTIVE)); EXPECT_FALSE(animation_->keyframe_effect()->IsCurrentlyAnimatingProperty( TargetProperty::FILTER, ElementListType::PENDING)); EXPECT_FALSE(animation_->keyframe_effect()->IsCurrentlyAnimatingProperty( TargetProperty::FILTER, ElementListType::ACTIVE)); animation_->ActivateKeyframeEffects(); EXPECT_TRUE(animation_->keyframe_effect()->IsCurrentlyAnimatingProperty( TargetProperty::OPACITY, ElementListType::PENDING)); EXPECT_TRUE(animation_->keyframe_effect()->IsCurrentlyAnimatingProperty( TargetProperty::OPACITY, ElementListType::ACTIVE)); EXPECT_FALSE(animation_->keyframe_effect()->IsCurrentlyAnimatingProperty( TargetProperty::FILTER, ElementListType::PENDING)); EXPECT_FALSE(animation_->keyframe_effect()->IsCurrentlyAnimatingProperty( TargetProperty::FILTER, ElementListType::ACTIVE)); animation_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(10)); animation_->UpdateState(true, nullptr); EXPECT_TRUE(animation_->keyframe_effect()->IsCurrentlyAnimatingProperty( TargetProperty::OPACITY, ElementListType::PENDING)); EXPECT_TRUE(animation_->keyframe_effect()->IsCurrentlyAnimatingProperty( TargetProperty::OPACITY, ElementListType::ACTIVE)); EXPECT_FALSE(animation_->keyframe_effect()->IsCurrentlyAnimatingProperty( TargetProperty::FILTER, ElementListType::PENDING)); EXPECT_FALSE(animation_->keyframe_effect()->IsCurrentlyAnimatingProperty( TargetProperty::FILTER, ElementListType::ACTIVE)); EXPECT_EQ(0.f, client_.GetOpacity(element_id_, ElementListType::ACTIVE)); // Tick past the end of the animation. animation_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(1100)); animation_->UpdateState(true, nullptr); EXPECT_FALSE(animation_->keyframe_effect()->IsCurrentlyAnimatingProperty( TargetProperty::OPACITY, ElementListType::PENDING)); EXPECT_FALSE(animation_->keyframe_effect()->IsCurrentlyAnimatingProperty( TargetProperty::OPACITY, ElementListType::ACTIVE)); EXPECT_FALSE(animation_->keyframe_effect()->IsCurrentlyAnimatingProperty( TargetProperty::FILTER, ElementListType::PENDING)); EXPECT_FALSE(animation_->keyframe_effect()->IsCurrentlyAnimatingProperty( TargetProperty::FILTER, ElementListType::ACTIVE)); EXPECT_EQ(1.f, client_.GetOpacity(element_id_, ElementListType::ACTIVE)); } TEST_F(ElementAnimationsTest, TestIsAnimatingPropertyTimeOffsetFillMode) { CreateTestLayer(false, false); AttachTimelineAnimationLayer(); // Create an animation that initially affects only pending elements, and has // a start delay of 2 seconds. std::unique_ptr keyframe_model(CreateKeyframeModel( std::unique_ptr(new FakeFloatTransition(1.0, 0.f, 1.f)), 1, TargetProperty::OPACITY)); keyframe_model->set_fill_mode(KeyframeModel::FillMode::NONE); keyframe_model->set_time_offset(TimeDelta::FromMilliseconds(-2000)); keyframe_model->set_affects_active_elements(false); animation_->AddKeyframeModel(std::move(keyframe_model)); animation_->Tick(kInitialTickTime); // Since the animation has a start delay, the elements it affects have a // potentially running transform animation but aren't currently animating // transform. EXPECT_TRUE(animation_->keyframe_effect()->IsPotentiallyAnimatingProperty( TargetProperty::OPACITY, ElementListType::PENDING)); EXPECT_FALSE(animation_->keyframe_effect()->IsPotentiallyAnimatingProperty( TargetProperty::OPACITY, ElementListType::ACTIVE)); EXPECT_FALSE(animation_->keyframe_effect()->IsCurrentlyAnimatingProperty( TargetProperty::OPACITY, ElementListType::PENDING)); EXPECT_FALSE(animation_->keyframe_effect()->IsCurrentlyAnimatingProperty( TargetProperty::OPACITY, ElementListType::ACTIVE)); EXPECT_TRUE(animation_->keyframe_effect()->HasTickingKeyframeModel()); EXPECT_FALSE(animation_->keyframe_effect()->IsPotentiallyAnimatingProperty( TargetProperty::FILTER, ElementListType::PENDING)); EXPECT_FALSE(animation_->keyframe_effect()->IsPotentiallyAnimatingProperty( TargetProperty::FILTER, ElementListType::ACTIVE)); animation_->ActivateKeyframeEffects(); EXPECT_TRUE(animation_->keyframe_effect()->IsPotentiallyAnimatingProperty( TargetProperty::OPACITY, ElementListType::PENDING)); EXPECT_TRUE(animation_->keyframe_effect()->IsPotentiallyAnimatingProperty( TargetProperty::OPACITY, ElementListType::ACTIVE)); EXPECT_FALSE(animation_->keyframe_effect()->IsCurrentlyAnimatingProperty( TargetProperty::OPACITY, ElementListType::PENDING)); EXPECT_FALSE(animation_->keyframe_effect()->IsCurrentlyAnimatingProperty( TargetProperty::OPACITY, ElementListType::ACTIVE)); EXPECT_TRUE(animation_->keyframe_effect()->HasTickingKeyframeModel()); EXPECT_FALSE(animation_->keyframe_effect()->IsPotentiallyAnimatingProperty( TargetProperty::FILTER, ElementListType::PENDING)); EXPECT_FALSE(animation_->keyframe_effect()->IsPotentiallyAnimatingProperty( TargetProperty::FILTER, ElementListType::ACTIVE)); animation_->UpdateState(true, nullptr); // Tick past the start delay. animation_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(2000)); animation_->UpdateState(true, nullptr); EXPECT_TRUE(animation_->keyframe_effect()->IsPotentiallyAnimatingProperty( TargetProperty::OPACITY, ElementListType::PENDING)); EXPECT_TRUE(animation_->keyframe_effect()->IsPotentiallyAnimatingProperty( TargetProperty::OPACITY, ElementListType::ACTIVE)); EXPECT_TRUE(animation_->keyframe_effect()->IsCurrentlyAnimatingProperty( TargetProperty::OPACITY, ElementListType::PENDING)); EXPECT_TRUE(animation_->keyframe_effect()->IsCurrentlyAnimatingProperty( TargetProperty::OPACITY, ElementListType::ACTIVE)); // After the animaton finishes, the elements it affects have neither a // potentially running transform animation nor a currently running transform // animation. animation_->Tick(kInitialTickTime + TimeDelta::FromMilliseconds(4000)); animation_->UpdateState(true, nullptr); EXPECT_FALSE(animation_->keyframe_effect()->IsPotentiallyAnimatingProperty( TargetProperty::OPACITY, ElementListType::PENDING)); EXPECT_FALSE(animation_->keyframe_effect()->IsPotentiallyAnimatingProperty( TargetProperty::OPACITY, ElementListType::ACTIVE)); EXPECT_FALSE(animation_->keyframe_effect()->IsCurrentlyAnimatingProperty( TargetProperty::OPACITY, ElementListType::PENDING)); EXPECT_FALSE(animation_->keyframe_effect()->IsCurrentlyAnimatingProperty( TargetProperty::OPACITY, ElementListType::ACTIVE)); } TEST_F(ElementAnimationsTest, DestroyTestMainLayerBeforePushProperties) { CreateTestLayer(false, false); AttachTimelineAnimationLayer(); EXPECT_EQ(0u, host_->ticking_animations_for_testing().size()); animation_->AddKeyframeModel(CreateKeyframeModel( std::unique_ptr(new FakeFloatTransition(1.0, 1.f, 0.5f)), 2, TargetProperty::OPACITY)); EXPECT_EQ(1u, host_->ticking_animations_for_testing().size()); DestroyTestMainLayer(); EXPECT_EQ(0u, host_->ticking_animations_for_testing().size()); PushProperties(); EXPECT_EQ(0u, host_->ticking_animations_for_testing().size()); EXPECT_EQ(0u, host_impl_->ticking_animations_for_testing().size()); } TEST_F(ElementAnimationsTest, RemoveAndReAddAnimationToTicking) { CreateTestLayer(false, false); AttachTimelineAnimationLayer(); EXPECT_EQ(0u, host_->ticking_animations_for_testing().size()); // Add an animation and ensure the animation is in the host's ticking // animations. Remove the animation using RemoveFromTicking(). animation_->AddKeyframeModel(CreateKeyframeModel( std::unique_ptr(new FakeFloatTransition(1.0, 1.f, 0.5f)), 2, TargetProperty::OPACITY)); ASSERT_EQ(1u, host_->ticking_animations_for_testing().size()); animation_->keyframe_effect()->RemoveFromTicking(); ASSERT_EQ(0u, host_->ticking_animations_for_testing().size()); // Ensure that adding a new animation will correctly update the ticking // animations list. animation_->AddKeyframeModel(CreateKeyframeModel( std::unique_ptr(new FakeFloatTransition(1.0, 1.f, 0.5f)), 2, TargetProperty::OPACITY)); EXPECT_EQ(1u, host_->ticking_animations_for_testing().size()); } TEST_F(ElementAnimationsTest, TickingKeyframeModelsCount) { CreateTestLayer(false, false); AttachTimelineAnimationLayer(); // Add an animation and ensure the animation is in the host's ticking // animations. animation_->AddKeyframeModel(CreateKeyframeModel( std::unique_ptr(new FakeFloatTransition(1.0, 1.f, 0.5f)), 2, TargetProperty::OPACITY)); EXPECT_EQ(1u, animation_->TickingKeyframeModelsCount()); EXPECT_EQ(1u, host_->CompositedAnimationsCount()); animation_->AddKeyframeModel(CreateKeyframeModel( std::unique_ptr(new FakeTransformTransition(1)), 1, TargetProperty::TRANSFORM)); EXPECT_EQ(2u, animation_->TickingKeyframeModelsCount()); EXPECT_EQ(2u, host_->CompositedAnimationsCount()); animation_->keyframe_effect()->RemoveFromTicking(); EXPECT_EQ(0u, host_->CompositedAnimationsCount()); } } // namespace } // namespace cc