1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "cc/animation/animation_host.h"
6
7 #include "base/memory/ptr_util.h"
8 #include "cc/animation/animation_id_provider.h"
9 #include "cc/animation/animation_timeline.h"
10 #include "cc/animation/scroll_timeline.h"
11 #include "cc/animation/worklet_animation.h"
12 #include "cc/test/animation_test_common.h"
13 #include "cc/test/animation_timelines_test_common.h"
14 #include "cc/test/mock_layer_tree_mutator.h"
15 #include "cc/trees/scroll_node.h"
16 #include "cc/trees/transform_node.h"
17 #include "testing/gmock/include/gmock/gmock.h"
18 #include "testing/gtest/include/gtest/gtest.h"
19
20 using ::testing::InvokeWithoutArgs;
21 using ::testing::Mock;
22 using ::testing::NiceMock;
23 using ::testing::Return;
24 using ::testing::_;
25
26 namespace cc {
27 namespace {
28
29 class AnimationHostTest : public AnimationTimelinesTest {
30 public:
31 AnimationHostTest() = default;
32 ~AnimationHostTest() override = default;
33
AttachWorkletAnimation()34 void AttachWorkletAnimation() {
35 client_.RegisterElementId(element_id_, ElementListType::ACTIVE);
36 client_impl_.RegisterElementId(element_id_, ElementListType::PENDING);
37 client_impl_.RegisterElementId(element_id_, ElementListType::ACTIVE);
38
39 worklet_animation_ = WorkletAnimation::Create(
40 worklet_animation_id_, "test_name", 1, nullptr, nullptr);
41 int cc_id = worklet_animation_->id();
42 worklet_animation_->AttachElement(element_id_);
43 host_->AddAnimationTimeline(timeline_);
44 timeline_->AttachAnimation(worklet_animation_);
45
46 host_->PushPropertiesTo(host_impl_);
47 timeline_impl_ = host_impl_->GetTimelineById(timeline_id_);
48 worklet_animation_impl_ =
49 ToWorkletAnimation(timeline_impl_->GetAnimationById(cc_id));
50 }
51
SetOutputState(base::TimeDelta local_time)52 void SetOutputState(base::TimeDelta local_time) {
53 MutatorOutputState::AnimationState state(worklet_animation_id_);
54 state.local_times.push_back(local_time);
55 worklet_animation_impl_->SetOutputState(state);
56 }
57
58 scoped_refptr<WorkletAnimation> worklet_animation_;
59 scoped_refptr<WorkletAnimation> worklet_animation_impl_;
60 WorkletAnimationId worklet_animation_id_{11, 12};
61 };
62
63 // See Animation tests on layer registration/unregistration in
64 // animation_unittest.cc.
65
TEST_F(AnimationHostTest,SyncTimelinesAddRemove)66 TEST_F(AnimationHostTest, SyncTimelinesAddRemove) {
67 std::unique_ptr<AnimationHost> host(
68 AnimationHost::CreateForTesting(ThreadInstance::MAIN));
69 std::unique_ptr<AnimationHost> host_impl(
70 AnimationHost::CreateForTesting(ThreadInstance::IMPL));
71
72 const int timeline_id = AnimationIdProvider::NextTimelineId();
73 scoped_refptr<AnimationTimeline> timeline(
74 AnimationTimeline::Create(timeline_id));
75 host->AddAnimationTimeline(timeline.get());
76 EXPECT_TRUE(timeline->animation_host());
77
78 EXPECT_FALSE(host_impl->GetTimelineById(timeline_id));
79
80 host->PushPropertiesTo(host_impl.get());
81
82 scoped_refptr<AnimationTimeline> timeline_impl =
83 host_impl->GetTimelineById(timeline_id);
84 EXPECT_TRUE(timeline_impl);
85 EXPECT_EQ(timeline_impl->id(), timeline_id);
86
87 host->PushPropertiesTo(host_impl.get());
88 EXPECT_EQ(timeline_impl, host_impl->GetTimelineById(timeline_id));
89
90 host->RemoveAnimationTimeline(timeline.get());
91 EXPECT_FALSE(timeline->animation_host());
92
93 host->PushPropertiesTo(host_impl.get());
94 EXPECT_FALSE(host_impl->GetTimelineById(timeline_id));
95
96 EXPECT_FALSE(timeline_impl->animation_host());
97 }
98
TEST_F(AnimationHostTest,ImplOnlyTimeline)99 TEST_F(AnimationHostTest, ImplOnlyTimeline) {
100 std::unique_ptr<AnimationHost> host(
101 AnimationHost::CreateForTesting(ThreadInstance::MAIN));
102 std::unique_ptr<AnimationHost> host_impl(
103 AnimationHost::CreateForTesting(ThreadInstance::IMPL));
104
105 const int timeline_id1 = AnimationIdProvider::NextTimelineId();
106 const int timeline_id2 = AnimationIdProvider::NextTimelineId();
107
108 scoped_refptr<AnimationTimeline> timeline(
109 AnimationTimeline::Create(timeline_id1));
110 scoped_refptr<AnimationTimeline> timeline_impl(
111 AnimationTimeline::Create(timeline_id2));
112 timeline_impl->set_is_impl_only(true);
113
114 host->AddAnimationTimeline(timeline.get());
115 host_impl->AddAnimationTimeline(timeline_impl.get());
116
117 host->PushPropertiesTo(host_impl.get());
118
119 EXPECT_TRUE(host->GetTimelineById(timeline_id1));
120 EXPECT_TRUE(host_impl->GetTimelineById(timeline_id2));
121 }
122
TEST_F(AnimationHostTest,ImplOnlyScrollAnimationUpdateTargetIfDetached)123 TEST_F(AnimationHostTest, ImplOnlyScrollAnimationUpdateTargetIfDetached) {
124 client_.RegisterElementId(element_id_, ElementListType::ACTIVE);
125 client_impl_.RegisterElementId(element_id_, ElementListType::PENDING);
126
127 gfx::ScrollOffset target_offset(0., 2.);
128 gfx::ScrollOffset current_offset(0., 1.);
129 host_impl_->ImplOnlyScrollAnimationCreate(element_id_, target_offset,
130 current_offset, base::TimeDelta(),
131 base::TimeDelta());
132
133 gfx::Vector2dF scroll_delta(0, 0.5);
134 gfx::ScrollOffset max_scroll_offset(0., 3.);
135
136 base::TimeTicks time;
137
138 time += base::TimeDelta::FromSecondsD(0.1);
139 EXPECT_TRUE(host_impl_->ImplOnlyScrollAnimationUpdateTarget(
140 scroll_delta, max_scroll_offset, time, base::TimeDelta()));
141
142 // Detach all animations from layers and timelines.
143 host_impl_->ClearMutators();
144
145 time += base::TimeDelta::FromSecondsD(0.1);
146 EXPECT_FALSE(host_impl_->ImplOnlyScrollAnimationUpdateTarget(
147 scroll_delta, max_scroll_offset, time, base::TimeDelta()));
148 }
149
150 // Tests that verify interaction of AnimationHost with LayerTreeMutator.
151
TEST_F(AnimationHostTest,FastLayerTreeMutatorUpdateTakesEffectInSameFrame)152 TEST_F(AnimationHostTest, FastLayerTreeMutatorUpdateTakesEffectInSameFrame) {
153 AttachWorkletAnimation();
154
155 const float start_opacity = .7f;
156 const float end_opacity = .3f;
157 const double duration = 1.;
158
159 const float expected_opacity =
160 start_opacity + (end_opacity - start_opacity) / 2;
161 AddOpacityTransitionToAnimation(worklet_animation_.get(), duration,
162 start_opacity, end_opacity, true);
163
164 base::TimeDelta local_time = base::TimeDelta::FromSecondsD(duration / 2);
165
166 MockLayerTreeMutator* mock_mutator = new NiceMock<MockLayerTreeMutator>();
167 host_impl_->SetLayerTreeMutator(
168 base::WrapUnique<LayerTreeMutator>(mock_mutator));
169 ON_CALL(*mock_mutator, HasMutators()).WillByDefault(Return(true));
170 ON_CALL(*mock_mutator, MutateRef(_))
171 .WillByDefault(InvokeWithoutArgs(
172 [this, local_time]() { this->SetOutputState(local_time); }));
173
174 // Push the opacity animation to the impl thread.
175 host_->PushPropertiesTo(host_impl_);
176 host_impl_->ActivateAnimations(nullptr);
177
178 // Ticking host should cause layer tree mutator to update output state which
179 // should take effect in the same animation frame.
180 TickAnimationsTransferEvents(base::TimeTicks(), 0u);
181
182 // Emulate behavior in PrepareToDraw. Animation worklet updates are best
183 // effort, and the animation tick is deferred until draw to allow time for the
184 // updates to arrive.
185 host_impl_->TickWorkletAnimations();
186
187 TestLayer* layer =
188 client_.FindTestLayer(element_id_, ElementListType::ACTIVE);
189 EXPECT_FALSE(layer->is_property_mutated(TargetProperty::OPACITY));
190 client_impl_.ExpectOpacityPropertyMutated(
191 element_id_, ElementListType::ACTIVE, expected_opacity);
192 }
193
TEST_F(AnimationHostTest,LayerTreeMutatorsIsMutatedWithCorrectInputState)194 TEST_F(AnimationHostTest, LayerTreeMutatorsIsMutatedWithCorrectInputState) {
195 AttachWorkletAnimation();
196
197 MockLayerTreeMutator* mock_mutator = new NiceMock<MockLayerTreeMutator>();
198 host_impl_->SetLayerTreeMutator(
199 base::WrapUnique<LayerTreeMutator>(mock_mutator));
200 ON_CALL(*mock_mutator, HasMutators()).WillByDefault(Return(true));
201
202 const float start_opacity = .7f;
203 const float end_opacity = .3f;
204 const double duration = 1.;
205
206 AddOpacityTransitionToAnimation(worklet_animation_.get(), duration,
207 start_opacity, end_opacity, true);
208
209 host_->PushPropertiesTo(host_impl_);
210 host_impl_->ActivateAnimations(nullptr);
211
212 EXPECT_CALL(*mock_mutator, MutateRef(_));
213
214 base::TimeTicks time;
215 time += base::TimeDelta::FromSecondsD(0.1);
216 TickAnimationsTransferEvents(time, 0u);
217 }
218
TEST_F(AnimationHostTest,LayerTreeMutatorsIsMutatedOnlyWhenInputChanges)219 TEST_F(AnimationHostTest, LayerTreeMutatorsIsMutatedOnlyWhenInputChanges) {
220 AttachWorkletAnimation();
221
222 MockLayerTreeMutator* mock_mutator = new NiceMock<MockLayerTreeMutator>();
223 host_impl_->SetLayerTreeMutator(
224 base::WrapUnique<LayerTreeMutator>(mock_mutator));
225 ON_CALL(*mock_mutator, HasMutators()).WillByDefault(Return(true));
226
227 const float start_opacity = .7f;
228 const float end_opacity = .3f;
229 const double duration = 1.;
230
231 AddOpacityTransitionToAnimation(worklet_animation_.get(), duration,
232 start_opacity, end_opacity, true);
233
234 host_->PushPropertiesTo(host_impl_);
235 host_impl_->ActivateAnimations(nullptr);
236
237 EXPECT_CALL(*mock_mutator, MutateRef(_)).Times(1);
238
239 base::TimeTicks time;
240 time += base::TimeDelta::FromSecondsD(0.1);
241 TickAnimationsTransferEvents(time, 0u);
242
243 // The time has not changed which means worklet animation input is the same.
244 // Ticking animations again should not result in mutator being asked to
245 // mutate.
246 TickAnimationsTransferEvents(time, 0u);
247 }
248
249 class MockAnimation : public Animation {
250 public:
MockAnimation(int id)251 explicit MockAnimation(int id) : Animation(id) {}
252 MOCK_METHOD1(Tick, void(base::TimeTicks monotonic_time));
253
254 private:
~MockAnimation()255 ~MockAnimation() {}
256 };
257
Animation1TimeEquals20(MutatorInputState * input)258 bool Animation1TimeEquals20(MutatorInputState* input) {
259 std::unique_ptr<AnimationWorkletInput> in = input->TakeWorkletState(333);
260 return in && in->added_and_updated_animations.size() == 1 &&
261 in->added_and_updated_animations[0]
262 .worklet_animation_id.animation_id == 22 &&
263 in->added_and_updated_animations[0].current_time == 20;
264 }
265
CreateScrollingNodeForElement(ElementId element_id,PropertyTrees * property_trees)266 void CreateScrollingNodeForElement(ElementId element_id,
267 PropertyTrees* property_trees) {
268 // A scrolling node in cc has a corresponding transform node (See
269 // |ScrollNode::transform_id|). This setup here creates both nodes and links
270 // them as they would normally be. This more complete setup is necessary here
271 // because ScrollTimeline depends on both nodes for its calculations.
272 TransformNode transform_node;
273 transform_node.scrolls = true;
274 int transform_node_id =
275 property_trees->transform_tree.Insert(transform_node, 0);
276 property_trees->element_id_to_transform_node_index[element_id] =
277 transform_node_id;
278
279 ScrollNode scroll_node;
280 scroll_node.scrollable = true;
281 // Setup scroll dimention to be 100x100.
282 scroll_node.bounds = gfx::Size(200, 200);
283 scroll_node.container_bounds = gfx::Size(100, 100);
284 scroll_node.element_id = element_id;
285 scroll_node.transform_id = transform_node_id;
286
287 int scroll_node_id = property_trees->scroll_tree.Insert(scroll_node, 0);
288 property_trees->element_id_to_scroll_node_index[element_id] = scroll_node_id;
289 }
290
SetScrollOffset(PropertyTrees * property_trees,ElementId element_id,gfx::ScrollOffset offset)291 void SetScrollOffset(PropertyTrees* property_trees,
292 ElementId element_id,
293 gfx::ScrollOffset offset) {
294 // Update both scroll and transform trees
295 property_trees->scroll_tree.SetScrollOffset(element_id, offset);
296 TransformNode* transform_node =
297 property_trees->transform_tree.FindNodeFromElementId(element_id);
298 transform_node->scroll_offset = offset;
299 transform_node->needs_local_transform_update = true;
300 }
301
TEST_F(AnimationHostTest,LayerTreeMutatorUpdateReflectsScrollAnimations)302 TEST_F(AnimationHostTest, LayerTreeMutatorUpdateReflectsScrollAnimations) {
303 ElementId element_id = element_id_;
304 int animation_id1 = 11;
305 int animation_id2 = 12;
306 WorkletAnimationId worklet_animation_id{333, 22};
307
308 client_.RegisterElementId(element_id, ElementListType::ACTIVE);
309 client_impl_.RegisterElementId(element_id, ElementListType::PENDING);
310 client_impl_.RegisterElementId(element_id, ElementListType::ACTIVE);
311 host_impl_->AddAnimationTimeline(timeline_);
312
313 PropertyTrees property_trees;
314 property_trees.is_main_thread = false;
315 property_trees.is_active = true;
316 CreateScrollingNodeForElement(element_id, &property_trees);
317
318 // Set an initial scroll value.
319 SetScrollOffset(&property_trees, element_id, gfx::ScrollOffset(10, 10));
320
321 scoped_refptr<MockAnimation> mock_scroll_animation(
322 new MockAnimation(animation_id1));
323 EXPECT_CALL(*mock_scroll_animation, Tick(_))
324 .WillOnce(InvokeWithoutArgs([&]() {
325 // Scroll to 20% of the max value.
326 SetScrollOffset(&property_trees, element_id, gfx::ScrollOffset(20, 20));
327 }));
328
329 // Ensure scroll animation is ticking.
330 timeline_->AttachAnimation(mock_scroll_animation);
331 host_impl_->AddToTicking(mock_scroll_animation);
332
333 // Create scroll timeline that links scroll animation and worklet animation
334 // together. Use timerange so that we have 1:1 time & scroll mapping.
335 auto scroll_timeline =
336 ScrollTimeline::Create(element_id, ScrollTimeline::ScrollDown,
337 base::nullopt, base::nullopt, 100);
338
339 // Create a worklet animation that is bound to the scroll timeline.
340 scoped_refptr<WorkletAnimation> worklet_animation(
341 new WorkletAnimation(animation_id2, worklet_animation_id, "test_name", 1,
342 nullptr, nullptr, true));
343 host_impl_->AddAnimationTimeline(scroll_timeline);
344 scroll_timeline->AttachAnimation(worklet_animation);
345
346 worklet_animation->AttachElement(element_id);
347
348 AddOpacityTransitionToAnimation(worklet_animation.get(), 1, .7f, .3f, true);
349
350 MockLayerTreeMutator* mock_mutator = new NiceMock<MockLayerTreeMutator>();
351 host_impl_->SetLayerTreeMutator(
352 base::WrapUnique<LayerTreeMutator>(mock_mutator));
353 ON_CALL(*mock_mutator, HasMutators()).WillByDefault(Return(true));
354 EXPECT_CALL(*mock_mutator,
355 MutateRef(::testing::Truly(Animation1TimeEquals20)))
356 .Times(1);
357
358 // Ticking host should cause scroll animation to scroll which should also be
359 // reflected in the input of the layer tree mutator in the same animation
360 // frame.
361 host_impl_->TickAnimations(base::TimeTicks(), property_trees.scroll_tree,
362 false);
363 }
364
365 } // namespace
366 } // namespace cc
367