1 /*
2 * Copyright (C) 2013 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 * * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31 #include "third_party/blink/renderer/core/animation/keyframe_effect_model.h"
32
33 #include <limits>
34 #include <utility>
35
36 #include "third_party/blink/renderer/core/animation/animation_effect.h"
37 #include "third_party/blink/renderer/core/animation/compositor_animations.h"
38 #include "third_party/blink/renderer/core/css/css_property_equality.h"
39 #include "third_party/blink/renderer/core/css/property_registry.h"
40 #include "third_party/blink/renderer/core/css/resolver/style_resolver.h"
41 #include "third_party/blink/renderer/core/dom/document.h"
42 #include "third_party/blink/renderer/core/frame/web_feature.h"
43 #include "third_party/blink/renderer/core/style/computed_style.h"
44 #include "third_party/blink/renderer/platform/animation/animation_utilities.h"
45 #include "third_party/blink/renderer/platform/geometry/float_box.h"
46 #include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
47 #include "third_party/blink/renderer/platform/transforms/transformation_matrix.h"
48 #include "third_party/blink/renderer/platform/wtf/text/string_hash.h"
49
50 namespace blink {
51
Properties() const52 PropertyHandleSet KeyframeEffectModelBase::Properties() const {
53 PropertyHandleSet result;
54 for (const auto& keyframe : keyframes_) {
55 for (const auto& property : keyframe->Properties())
56 result.insert(property);
57 }
58 return result;
59 }
60
61 template <class K>
SetFrames(HeapVector<K> & keyframes)62 void KeyframeEffectModelBase::SetFrames(HeapVector<K>& keyframes) {
63 // TODO(samli): Should also notify/invalidate the animation
64 keyframes_.clear();
65 keyframes_.AppendVector(keyframes);
66 ClearCachedData();
67 }
68
69 template CORE_EXPORT void KeyframeEffectModelBase::SetFrames(
70 HeapVector<Member<Keyframe>>& keyframes);
71 template CORE_EXPORT void KeyframeEffectModelBase::SetFrames(
72 HeapVector<Member<StringKeyframe>>& keyframes);
73
SetComposite(CompositeOperation composite)74 void KeyframeEffectModelBase::SetComposite(CompositeOperation composite) {
75 composite_ = composite;
76 ClearCachedData();
77 }
78
Sample(int iteration,double fraction,AnimationTimeDelta iteration_duration,HeapVector<Member<Interpolation>> & result) const79 bool KeyframeEffectModelBase::Sample(
80 int iteration,
81 double fraction,
82 AnimationTimeDelta iteration_duration,
83 HeapVector<Member<Interpolation>>& result) const {
84 DCHECK_GE(iteration, 0);
85 EnsureKeyframeGroups();
86 EnsureInterpolationEffectPopulated();
87
88 bool changed = iteration != last_iteration_ || fraction != last_fraction_ ||
89 iteration_duration != last_iteration_duration_;
90 last_iteration_ = iteration;
91 last_fraction_ = fraction;
92 last_iteration_duration_ = iteration_duration;
93 interpolation_effect_->GetActiveInterpolations(fraction, result);
94 return changed;
95 }
96
97 namespace {
98
99 static const size_t num_compositable_properties = 7;
100
CompositableProperties()101 const CSSProperty** CompositableProperties() {
102 static const CSSProperty*
103 kCompositableProperties[num_compositable_properties] = {
104 &GetCSSPropertyOpacity(), &GetCSSPropertyRotate(),
105 &GetCSSPropertyScale(), &GetCSSPropertyTransform(),
106 &GetCSSPropertyTranslate(), &GetCSSPropertyFilter(),
107 &GetCSSPropertyBackdropFilter()};
108 return kCompositableProperties;
109 }
110
111 } // namespace
112
SnapshotNeutralCompositorKeyframes(Element & element,const ComputedStyle & old_style,const ComputedStyle & new_style,const ComputedStyle * parent_style) const113 bool KeyframeEffectModelBase::SnapshotNeutralCompositorKeyframes(
114 Element& element,
115 const ComputedStyle& old_style,
116 const ComputedStyle& new_style,
117 const ComputedStyle* parent_style) const {
118 ShouldSnapshotPropertyCallback should_snapshot_property_callback =
119 [&old_style, &new_style](const PropertyHandle& property) {
120 return !CSSPropertyEquality::PropertiesEqual(property, old_style,
121 new_style);
122 };
123 ShouldSnapshotKeyframeCallback should_snapshot_keyframe_callback =
124 [](const PropertySpecificKeyframe& keyframe) {
125 return keyframe.IsNeutral();
126 };
127
128 return SnapshotCompositableProperties(element, new_style, parent_style,
129 should_snapshot_property_callback,
130 should_snapshot_keyframe_callback);
131 }
132
SnapshotAllCompositorKeyframesIfNecessary(Element & element,const ComputedStyle & base_style,const ComputedStyle * parent_style) const133 bool KeyframeEffectModelBase::SnapshotAllCompositorKeyframesIfNecessary(
134 Element& element,
135 const ComputedStyle& base_style,
136 const ComputedStyle* parent_style) const {
137 if (!needs_compositor_keyframes_snapshot_)
138 return false;
139 needs_compositor_keyframes_snapshot_ = false;
140
141 bool has_neutral_compositable_keyframe = false;
142 ShouldSnapshotPropertyCallback should_snapshot_property_callback =
143 [](const PropertyHandle& property) { return true; };
144 ShouldSnapshotKeyframeCallback should_snapshot_keyframe_callback =
145 [&has_neutral_compositable_keyframe](
146 const PropertySpecificKeyframe& keyframe) mutable {
147 has_neutral_compositable_keyframe |= keyframe.IsNeutral();
148 return true;
149 };
150
151 bool updated = SnapshotCompositableProperties(
152 element, base_style, parent_style, should_snapshot_property_callback,
153 should_snapshot_keyframe_callback);
154
155 if (updated && has_neutral_compositable_keyframe) {
156 UseCounter::Count(element.GetDocument(),
157 WebFeature::kSyntheticKeyframesInCompositedCSSAnimation);
158 }
159 return updated;
160 }
161
SnapshotCompositableProperties(Element & element,const ComputedStyle & computed_style,const ComputedStyle * parent_style,ShouldSnapshotPropertyCallback should_snapshot_property_callback,ShouldSnapshotKeyframeCallback should_snapshot_keyframe_callback) const162 bool KeyframeEffectModelBase::SnapshotCompositableProperties(
163 Element& element,
164 const ComputedStyle& computed_style,
165 const ComputedStyle* parent_style,
166 ShouldSnapshotPropertyCallback should_snapshot_property_callback,
167 ShouldSnapshotKeyframeCallback should_snapshot_keyframe_callback) const {
168 EnsureKeyframeGroups();
169 bool updated = false;
170 static const CSSProperty** compositable_properties = CompositableProperties();
171 for (size_t i = 0; i < num_compositable_properties; i++) {
172 updated |= SnapshotCompositorKeyFrames(
173 PropertyHandle(*compositable_properties[i]), element, computed_style,
174 parent_style, should_snapshot_property_callback,
175 should_snapshot_keyframe_callback);
176 }
177
178 // Custom properties need to be handled separately, since not all values
179 // can be animated. Need to resolve the value of each custom property to
180 // ensure that it can be animated.
181 const PropertyRegistry* property_registry =
182 element.GetDocument().GetPropertyRegistry();
183 if (!property_registry)
184 return updated;
185
186 for (const AtomicString& name : computed_style.GetVariableNames()) {
187 if (property_registry->WasReferenced(name)) {
188 // This variable has been referenced as a property value at least once
189 // during style resolution in the document. Animating this property on
190 // the compositor could introduce misalignment in frame synchronization.
191 //
192 // TODO(kevers): For non-inherited properites, check if referenced in
193 // computed style. References elsewhere in the document should not prevent
194 // compositing.
195 continue;
196 }
197 updated |= SnapshotCompositorKeyFrames(
198 PropertyHandle(name), element, computed_style, parent_style,
199 should_snapshot_property_callback, should_snapshot_keyframe_callback);
200 }
201 return updated;
202 }
203
SnapshotCompositorKeyFrames(const PropertyHandle & property,Element & element,const ComputedStyle & computed_style,const ComputedStyle * parent_style,ShouldSnapshotPropertyCallback should_snapshot_property_callback,ShouldSnapshotKeyframeCallback should_snapshot_keyframe_callback) const204 bool KeyframeEffectModelBase::SnapshotCompositorKeyFrames(
205 const PropertyHandle& property,
206 Element& element,
207 const ComputedStyle& computed_style,
208 const ComputedStyle* parent_style,
209 ShouldSnapshotPropertyCallback should_snapshot_property_callback,
210 ShouldSnapshotKeyframeCallback should_snapshot_keyframe_callback) const {
211 if (!should_snapshot_property_callback(property))
212 return false;
213
214 PropertySpecificKeyframeGroup* keyframe_group =
215 keyframe_groups_->at(property);
216 if (!keyframe_group)
217 return false;
218
219 bool updated = false;
220 for (auto& keyframe : keyframe_group->keyframes_) {
221 if (!should_snapshot_keyframe_callback(*keyframe))
222 continue;
223
224 updated |= keyframe->PopulateCompositorKeyframeValue(
225 property, element, computed_style, parent_style);
226 }
227 return updated;
228 }
229
230 template <class K>
GetComputedOffsets(const HeapVector<K> & keyframes)231 Vector<double> KeyframeEffectModelBase::GetComputedOffsets(
232 const HeapVector<K>& keyframes) {
233 // To avoid having to create two vectors when converting from the nullable
234 // offsets to the non-nullable computed offsets, we keep the convention in
235 // this function that std::numeric_limits::quiet_NaN() represents null.
236 double last_offset = 0;
237 Vector<double> result;
238 result.ReserveCapacity(keyframes.size());
239
240 for (const auto& keyframe : keyframes) {
241 base::Optional<double> offset = keyframe->Offset();
242 if (offset) {
243 DCHECK_GE(offset.value(), 0);
244 DCHECK_LE(offset.value(), 1);
245 DCHECK_GE(offset.value(), last_offset);
246 last_offset = offset.value();
247 }
248 result.push_back(offset.value_or(std::numeric_limits<double>::quiet_NaN()));
249 }
250
251 if (result.IsEmpty())
252 return result;
253
254 if (std::isnan(result.back()))
255 result.back() = 1;
256
257 if (result.size() > 1 && std::isnan(result[0])) {
258 result.front() = 0;
259 }
260
261 wtf_size_t last_index = 0;
262 last_offset = result.front();
263 for (wtf_size_t i = 1; i < result.size(); ++i) {
264 double offset = result[i];
265 if (!std::isnan(offset)) {
266 for (wtf_size_t j = 1; j < i - last_index; ++j) {
267 result[last_index + j] =
268 last_offset + (offset - last_offset) * j / (i - last_index);
269 }
270 last_index = i;
271 last_offset = offset;
272 }
273 }
274
275 return result;
276 }
277
278 template CORE_EXPORT Vector<double> KeyframeEffectModelBase::GetComputedOffsets(
279 const HeapVector<Member<Keyframe>>& keyframes);
280 template CORE_EXPORT Vector<double> KeyframeEffectModelBase::GetComputedOffsets(
281 const HeapVector<Member<StringKeyframe>>& keyframes);
282
IsTransformRelatedEffect() const283 bool KeyframeEffectModelBase::IsTransformRelatedEffect() const {
284 return Affects(PropertyHandle(GetCSSPropertyTransform())) ||
285 Affects(PropertyHandle(GetCSSPropertyRotate())) ||
286 Affects(PropertyHandle(GetCSSPropertyScale())) ||
287 Affects(PropertyHandle(GetCSSPropertyTranslate()));
288 }
289
SetLogicalPropertyResolutionContext(TextDirection text_direction,WritingMode writing_mode)290 bool KeyframeEffectModelBase::SetLogicalPropertyResolutionContext(
291 TextDirection text_direction,
292 WritingMode writing_mode) {
293 bool changed = false;
294 for (wtf_size_t i = 0; i < keyframes_.size(); i++) {
295 if (auto* string_keyframe = DynamicTo<StringKeyframe>(*keyframes_[i])) {
296 if (string_keyframe->HasLogicalProperty()) {
297 string_keyframe->SetLogicalPropertyResolutionContext(text_direction,
298 writing_mode);
299 changed = true;
300 }
301 }
302 }
303 if (changed)
304 ClearCachedData();
305 return changed;
306 }
307
Trace(Visitor * visitor) const308 void KeyframeEffectModelBase::Trace(Visitor* visitor) const {
309 visitor->Trace(keyframes_);
310 visitor->Trace(keyframe_groups_);
311 visitor->Trace(interpolation_effect_);
312 EffectModel::Trace(visitor);
313 }
314
EnsureKeyframeGroups() const315 void KeyframeEffectModelBase::EnsureKeyframeGroups() const {
316 if (keyframe_groups_)
317 return;
318
319 keyframe_groups_ = MakeGarbageCollected<KeyframeGroupMap>();
320 scoped_refptr<TimingFunction> zero_offset_easing = default_keyframe_easing_;
321 Vector<double> computed_offsets = GetComputedOffsets(keyframes_);
322 DCHECK_EQ(computed_offsets.size(), keyframes_.size());
323 for (wtf_size_t i = 0; i < keyframes_.size(); i++) {
324 double computed_offset = computed_offsets[i];
325 const auto& keyframe = keyframes_[i];
326
327 if (computed_offset == 0)
328 zero_offset_easing = &keyframe->Easing();
329
330 for (const PropertyHandle& property : keyframe->Properties()) {
331 Member<PropertySpecificKeyframeGroup>& group =
332 keyframe_groups_->insert(property, nullptr).stored_value->value;
333 if (!group)
334 group = MakeGarbageCollected<PropertySpecificKeyframeGroup>();
335
336 Keyframe::PropertySpecificKeyframe* property_specific_keyframe =
337 keyframe->CreatePropertySpecificKeyframe(property, composite_,
338 computed_offset);
339 has_revert_ |= property_specific_keyframe->IsRevert();
340 group->AppendKeyframe(property_specific_keyframe);
341 }
342 }
343
344 // Add synthetic keyframes.
345 has_synthetic_keyframes_ = false;
346 for (const auto& entry : *keyframe_groups_) {
347 if (entry.value->AddSyntheticKeyframeIfRequired(zero_offset_easing))
348 has_synthetic_keyframes_ = true;
349
350 entry.value->RemoveRedundantKeyframes();
351 }
352 }
353
HasNonVariableProperty() const354 bool KeyframeEffectModelBase::HasNonVariableProperty() const {
355 for (const auto& keyframe : keyframes_) {
356 for (const auto& property : keyframe->Properties()) {
357 if (!property.IsCSSProperty() ||
358 property.GetCSSProperty().PropertyID() != CSSPropertyID::kVariable)
359 return true;
360 }
361 }
362 return false;
363 }
364
EnsureInterpolationEffectPopulated() const365 void KeyframeEffectModelBase::EnsureInterpolationEffectPopulated() const {
366 if (interpolation_effect_->IsPopulated())
367 return;
368
369 for (const auto& entry : *keyframe_groups_) {
370 const PropertySpecificKeyframeVector& keyframes = entry.value->Keyframes();
371 for (wtf_size_t i = 0; i < keyframes.size() - 1; i++) {
372 wtf_size_t start_index = i;
373 wtf_size_t end_index = i + 1;
374 double start_offset = keyframes[start_index]->Offset();
375 double end_offset = keyframes[end_index]->Offset();
376 double apply_from = start_offset;
377 double apply_to = end_offset;
378
379 if (i == 0) {
380 apply_from = -std::numeric_limits<double>::infinity();
381 DCHECK_EQ(start_offset, 0.0);
382 if (end_offset == 0.0) {
383 DCHECK_NE(keyframes[end_index + 1]->Offset(), 0.0);
384 end_index = start_index;
385 }
386 }
387 if (i == keyframes.size() - 2) {
388 apply_to = std::numeric_limits<double>::infinity();
389 DCHECK_EQ(end_offset, 1.0);
390 if (start_offset == 1.0) {
391 DCHECK_NE(keyframes[start_index - 1]->Offset(), 1.0);
392 start_index = end_index;
393 }
394 }
395
396 if (apply_from != apply_to) {
397 interpolation_effect_->AddInterpolationsFromKeyframes(
398 entry.key, *keyframes[start_index], *keyframes[end_index],
399 apply_from, apply_to);
400 }
401 // else the interpolation will never be used in sampling
402 }
403 }
404
405 interpolation_effect_->SetPopulated();
406 }
407
ClearCachedData()408 void KeyframeEffectModelBase::ClearCachedData() {
409 keyframe_groups_ = nullptr;
410 interpolation_effect_->Clear();
411 last_fraction_ = std::numeric_limits<double>::quiet_NaN();
412 needs_compositor_keyframes_snapshot_ = true;
413 }
414
IsReplaceOnly() const415 bool KeyframeEffectModelBase::IsReplaceOnly() const {
416 EnsureKeyframeGroups();
417 for (const auto& entry : *keyframe_groups_) {
418 for (const auto& keyframe : entry.value->Keyframes()) {
419 if (keyframe->Composite() != EffectModel::kCompositeReplace)
420 return false;
421 }
422 }
423 return true;
424 }
425
AppendKeyframe(Keyframe::PropertySpecificKeyframe * keyframe)426 void KeyframeEffectModelBase::PropertySpecificKeyframeGroup::AppendKeyframe(
427 Keyframe::PropertySpecificKeyframe* keyframe) {
428 DCHECK(keyframes_.IsEmpty() ||
429 keyframes_.back()->Offset() <= keyframe->Offset());
430 keyframes_.push_back(std::move(keyframe));
431 }
432
433 void KeyframeEffectModelBase::PropertySpecificKeyframeGroup::
RemoveRedundantKeyframes()434 RemoveRedundantKeyframes() {
435 // As an optimization, removes interior keyframes that have the same offset
436 // as both their neighbours, as they will never be used by sample().
437 // Note that synthetic keyframes must be added before this method is
438 // called.
439 DCHECK_GE(keyframes_.size(), 2U);
440 for (int i = keyframes_.size() - 2; i > 0; --i) {
441 double offset = keyframes_[i]->Offset();
442 bool has_same_offset_as_previous_neighbor =
443 keyframes_[i - 1]->Offset() == offset;
444 bool has_same_offset_as_next_neighbor =
445 keyframes_[i + 1]->Offset() == offset;
446 if (has_same_offset_as_previous_neighbor &&
447 has_same_offset_as_next_neighbor)
448 keyframes_.EraseAt(i);
449 }
450 DCHECK_GE(keyframes_.size(), 2U);
451 }
452
453 bool KeyframeEffectModelBase::PropertySpecificKeyframeGroup::
AddSyntheticKeyframeIfRequired(scoped_refptr<TimingFunction> zero_offset_easing)454 AddSyntheticKeyframeIfRequired(
455 scoped_refptr<TimingFunction> zero_offset_easing) {
456 DCHECK(!keyframes_.IsEmpty());
457
458 bool added_synthetic_keyframe = false;
459
460 if (keyframes_.front()->Offset() != 0.0) {
461 keyframes_.insert(0, keyframes_.front()->NeutralKeyframe(
462 0, std::move(zero_offset_easing)));
463 added_synthetic_keyframe = true;
464 }
465 if (keyframes_.back()->Offset() != 1.0) {
466 AppendKeyframe(keyframes_.back()->NeutralKeyframe(1, nullptr));
467 added_synthetic_keyframe = true;
468 }
469
470 return added_synthetic_keyframe;
471 }
472
473 } // namespace blink
474