1 // Copyright 2014 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 "third_party/blink/renderer/core/animation/effect_stack.h"
6 
7 #include <memory>
8 #include "testing/gtest/include/gtest/gtest.h"
9 #include "third_party/blink/renderer/core/animation/animation_clock.h"
10 #include "third_party/blink/renderer/core/animation/animation_test_helper.h"
11 #include "third_party/blink/renderer/core/animation/document_timeline.h"
12 #include "third_party/blink/renderer/core/animation/element_animations.h"
13 #include "third_party/blink/renderer/core/animation/interpolable_length.h"
14 #include "third_party/blink/renderer/core/animation/invalidatable_interpolation.h"
15 #include "third_party/blink/renderer/core/animation/keyframe_effect_model.h"
16 #include "third_party/blink/renderer/core/animation/pending_animations.h"
17 #include "third_party/blink/renderer/core/animation/string_keyframe.h"
18 #include "third_party/blink/renderer/core/testing/page_test_base.h"
19 #include "third_party/blink/renderer/platform/heap/heap.h"
20 
21 namespace blink {
22 
23 class AnimationEffectStackTest : public PageTestBase {
24  protected:
SetUp()25   void SetUp() override {
26     PageTestBase::SetUp(IntSize());
27     GetDocument().GetAnimationClock().ResetTimeForTesting();
28     timeline = GetDocument().Timeline();
29     element = GetDocument().CreateElementForBinding("foo");
30   }
31 
Play(KeyframeEffect * effect,double start_time)32   Animation* Play(KeyframeEffect* effect, double start_time) {
33     Animation* animation = timeline->Play(effect);
34     animation->setStartTime(start_time * 1000, false);
35     animation->Update(kTimingUpdateOnDemand);
36     return animation;
37   }
38 
UpdateTimeline(base::TimeDelta time)39   void UpdateTimeline(base::TimeDelta time) {
40     GetDocument().GetAnimationClock().UpdateTime(
41         GetDocument().Timeline().ZeroTime() + time);
42     timeline->ServiceAnimations(kTimingUpdateForAnimationFrame);
43   }
44 
SampledEffectCount()45   size_t SampledEffectCount() {
46     return element->EnsureElementAnimations()
47         .GetEffectStack()
48         .sampled_effects_.size();
49   }
50 
MakeEffectModel(CSSPropertyID id,const String & value)51   KeyframeEffectModelBase* MakeEffectModel(CSSPropertyID id,
52                                            const String& value) {
53     StringKeyframeVector keyframes(2);
54     keyframes[0] = MakeGarbageCollected<StringKeyframe>();
55     keyframes[0]->SetOffset(0.0);
56     keyframes[0]->SetCSSPropertyValue(
57         id, value, SecureContextMode::kInsecureContext, nullptr);
58     keyframes[1] = MakeGarbageCollected<StringKeyframe>();
59     keyframes[1]->SetOffset(1.0);
60     keyframes[1]->SetCSSPropertyValue(
61         id, value, SecureContextMode::kInsecureContext, nullptr);
62     return MakeGarbageCollected<StringKeyframeEffectModel>(keyframes);
63   }
64 
MakeInertEffect(KeyframeEffectModelBase * effect)65   InertEffect* MakeInertEffect(KeyframeEffectModelBase* effect) {
66     Timing timing;
67     timing.fill_mode = Timing::FillMode::BOTH;
68     return MakeGarbageCollected<InertEffect>(effect, timing, false, 0);
69   }
70 
MakeKeyframeEffect(KeyframeEffectModelBase * effect,double duration=10)71   KeyframeEffect* MakeKeyframeEffect(KeyframeEffectModelBase* effect,
72                                      double duration = 10) {
73     Timing timing;
74     timing.fill_mode = Timing::FillMode::BOTH;
75     timing.iteration_duration = AnimationTimeDelta::FromSecondsD(duration);
76     return MakeGarbageCollected<KeyframeEffect>(element.Get(), effect, timing);
77   }
78 
GetFontSizeValue(const ActiveInterpolationsMap & active_interpolations)79   double GetFontSizeValue(
80       const ActiveInterpolationsMap& active_interpolations) {
81     const ActiveInterpolations& interpolations =
82         active_interpolations.at(PropertyHandle(GetCSSPropertyFontSize()));
83     EnsureInterpolatedValueCached(interpolations, GetDocument(), element);
84 
85     const auto* typed_value =
86         To<InvalidatableInterpolation>(*interpolations.at(0))
87             .GetCachedValueForTesting();
88     // font-size is stored as an |InterpolableLength|; here we assume pixels.
89     EXPECT_TRUE(typed_value->GetInterpolableValue().IsLength());
90     const InterpolableLength& length =
91         To<InterpolableLength>(typed_value->GetInterpolableValue());
92     return length.CreateCSSValue(kValueRangeAll)->GetDoubleValue();
93   }
94 
GetZIndexValue(const ActiveInterpolationsMap & active_interpolations)95   double GetZIndexValue(const ActiveInterpolationsMap& active_interpolations) {
96     const ActiveInterpolations& interpolations =
97         active_interpolations.at(PropertyHandle(GetCSSPropertyZIndex()));
98     EnsureInterpolatedValueCached(interpolations, GetDocument(), element);
99 
100     const auto* typed_value =
101         To<InvalidatableInterpolation>(*interpolations.at(0))
102             .GetCachedValueForTesting();
103     // z-index is stored as a straight number value.
104     EXPECT_TRUE(typed_value->GetInterpolableValue().IsNumber());
105     return To<InterpolableNumber>(&typed_value->GetInterpolableValue())
106         ->Value();
107   }
108 
109   Persistent<DocumentTimeline> timeline;
110   Persistent<Element> element;
111 };
112 
TEST_F(AnimationEffectStackTest,ElementAnimationsSorted)113 TEST_F(AnimationEffectStackTest, ElementAnimationsSorted) {
114   Play(MakeKeyframeEffect(MakeEffectModel(CSSPropertyID::kFontSize, "1px")),
115        10);
116   Play(MakeKeyframeEffect(MakeEffectModel(CSSPropertyID::kFontSize, "2px")),
117        15);
118   Play(MakeKeyframeEffect(MakeEffectModel(CSSPropertyID::kFontSize, "3px")), 5);
119   ActiveInterpolationsMap result = EffectStack::ActiveInterpolations(
120       &element->GetElementAnimations()->GetEffectStack(), nullptr, nullptr,
121       KeyframeEffect::kDefaultPriority);
122   EXPECT_EQ(1u, result.size());
123   EXPECT_EQ(GetFontSizeValue(result), 3);
124 }
125 
TEST_F(AnimationEffectStackTest,NewAnimations)126 TEST_F(AnimationEffectStackTest, NewAnimations) {
127   Play(MakeKeyframeEffect(MakeEffectModel(CSSPropertyID::kFontSize, "1px")),
128        15);
129   Play(MakeKeyframeEffect(MakeEffectModel(CSSPropertyID::kZIndex, "2")), 10);
130   HeapVector<Member<const InertEffect>> new_animations;
131   InertEffect* inert1 =
132       MakeInertEffect(MakeEffectModel(CSSPropertyID::kFontSize, "3px"));
133   InertEffect* inert2 =
134       MakeInertEffect(MakeEffectModel(CSSPropertyID::kZIndex, "4"));
135   new_animations.push_back(inert1);
136   new_animations.push_back(inert2);
137   ActiveInterpolationsMap result = EffectStack::ActiveInterpolations(
138       &element->GetElementAnimations()->GetEffectStack(), &new_animations,
139       nullptr, KeyframeEffect::kDefaultPriority);
140   EXPECT_EQ(2u, result.size());
141   EXPECT_EQ(GetFontSizeValue(result), 3);
142   EXPECT_EQ(GetZIndexValue(result), 4);
143 }
144 
TEST_F(AnimationEffectStackTest,CancelledAnimations)145 TEST_F(AnimationEffectStackTest, CancelledAnimations) {
146   HeapHashSet<Member<const Animation>> cancelled_animations;
147   Animation* animation = Play(
148       MakeKeyframeEffect(MakeEffectModel(CSSPropertyID::kFontSize, "1px")), 0);
149   cancelled_animations.insert(animation);
150   Play(MakeKeyframeEffect(MakeEffectModel(CSSPropertyID::kZIndex, "2")), 0);
151   ActiveInterpolationsMap result = EffectStack::ActiveInterpolations(
152       &element->GetElementAnimations()->GetEffectStack(), nullptr,
153       &cancelled_animations, KeyframeEffect::kDefaultPriority);
154   EXPECT_EQ(1u, result.size());
155   EXPECT_EQ(GetZIndexValue(result), 2);
156 }
157 
TEST_F(AnimationEffectStackTest,ClearedEffectsRemoved)158 TEST_F(AnimationEffectStackTest, ClearedEffectsRemoved) {
159   Animation* animation = Play(
160       MakeKeyframeEffect(MakeEffectModel(CSSPropertyID::kFontSize, "1px")), 10);
161   ActiveInterpolationsMap result = EffectStack::ActiveInterpolations(
162       &element->GetElementAnimations()->GetEffectStack(), nullptr, nullptr,
163       KeyframeEffect::kDefaultPriority);
164   EXPECT_EQ(1u, result.size());
165   EXPECT_EQ(GetFontSizeValue(result), 1);
166 
167   animation->setEffect(nullptr);
168   result = EffectStack::ActiveInterpolations(
169       &element->GetElementAnimations()->GetEffectStack(), nullptr, nullptr,
170       KeyframeEffect::kDefaultPriority);
171   EXPECT_EQ(0u, result.size());
172 }
173 
TEST_F(AnimationEffectStackTest,ForwardsFillDiscarding)174 TEST_F(AnimationEffectStackTest, ForwardsFillDiscarding) {
175   Play(MakeKeyframeEffect(MakeEffectModel(CSSPropertyID::kFontSize, "1px")), 2);
176   Play(MakeKeyframeEffect(MakeEffectModel(CSSPropertyID::kFontSize, "2px")), 6);
177   Play(MakeKeyframeEffect(MakeEffectModel(CSSPropertyID::kFontSize, "3px")), 4);
178   GetDocument().GetPendingAnimations().Update(nullptr);
179 
180   // Because we will be forcing a naive GC that assumes there are no Oilpan
181   // objects on the stack (e.g. passes BlinkGC::kNoHeapPointersOnStack), we have
182   // to keep the ActiveInterpolationsMap in a Persistent.
183   Persistent<ActiveInterpolationsMap> interpolations;
184 
185   UpdateTimeline(base::TimeDelta::FromSeconds(11));
186   ThreadState::Current()->CollectAllGarbageForTesting();
187   interpolations = MakeGarbageCollected<ActiveInterpolationsMap>(
188       EffectStack::ActiveInterpolations(
189           &element->GetElementAnimations()->GetEffectStack(), nullptr, nullptr,
190           KeyframeEffect::kDefaultPriority));
191   EXPECT_EQ(1u, interpolations->size());
192   EXPECT_EQ(GetFontSizeValue(*interpolations), 3);
193   EXPECT_EQ(3u, SampledEffectCount());
194 
195   UpdateTimeline(base::TimeDelta::FromSeconds(13));
196   ThreadState::Current()->CollectAllGarbageForTesting();
197   interpolations = MakeGarbageCollected<ActiveInterpolationsMap>(
198       EffectStack::ActiveInterpolations(
199           &element->GetElementAnimations()->GetEffectStack(), nullptr, nullptr,
200           KeyframeEffect::kDefaultPriority));
201   EXPECT_EQ(1u, interpolations->size());
202   EXPECT_EQ(GetFontSizeValue(*interpolations), 3);
203   EXPECT_EQ(3u, SampledEffectCount());
204 
205   UpdateTimeline(base::TimeDelta::FromSeconds(15));
206   ThreadState::Current()->CollectAllGarbageForTesting();
207   interpolations = MakeGarbageCollected<ActiveInterpolationsMap>(
208       EffectStack::ActiveInterpolations(
209           &element->GetElementAnimations()->GetEffectStack(), nullptr, nullptr,
210           KeyframeEffect::kDefaultPriority));
211   EXPECT_EQ(1u, interpolations->size());
212   EXPECT_EQ(GetFontSizeValue(*interpolations), 3);
213   EXPECT_EQ(2u, SampledEffectCount());
214 
215   UpdateTimeline(base::TimeDelta::FromSeconds(17));
216   ThreadState::Current()->CollectAllGarbageForTesting();
217   interpolations = MakeGarbageCollected<ActiveInterpolationsMap>(
218       EffectStack::ActiveInterpolations(
219           &element->GetElementAnimations()->GetEffectStack(), nullptr, nullptr,
220           KeyframeEffect::kDefaultPriority));
221   EXPECT_EQ(1u, interpolations->size());
222   EXPECT_EQ(GetFontSizeValue(*interpolations), 3);
223   EXPECT_EQ(1u, SampledEffectCount());
224 }
225 
226 }  // namespace blink
227