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/css/css_animations.h"
32
33 #include <algorithm>
34 #include <bitset>
35
36 #include "third_party/blink/public/platform/platform.h"
37 #include "third_party/blink/renderer/bindings/core/v8/v8_computed_effect_timing.h"
38 #include "third_party/blink/renderer/core/animation/animation.h"
39 #include "third_party/blink/renderer/core/animation/compositor_animations.h"
40 #include "third_party/blink/renderer/core/animation/css/compositor_keyframe_value_factory.h"
41 #include "third_party/blink/renderer/core/animation/css/css_animation.h"
42 #include "third_party/blink/renderer/core/animation/css/css_keyframe_effect_model.h"
43 #include "third_party/blink/renderer/core/animation/css/css_scroll_timeline.h"
44 #include "third_party/blink/renderer/core/animation/css/css_transition.h"
45 #include "third_party/blink/renderer/core/animation/css_interpolation_types_map.h"
46 #include "third_party/blink/renderer/core/animation/document_animations.h"
47 #include "third_party/blink/renderer/core/animation/document_timeline.h"
48 #include "third_party/blink/renderer/core/animation/element_animations.h"
49 #include "third_party/blink/renderer/core/animation/inert_effect.h"
50 #include "third_party/blink/renderer/core/animation/interpolation.h"
51 #include "third_party/blink/renderer/core/animation/interpolation_environment.h"
52 #include "third_party/blink/renderer/core/animation/interpolation_type.h"
53 #include "third_party/blink/renderer/core/animation/keyframe_effect.h"
54 #include "third_party/blink/renderer/core/animation/keyframe_effect_model.h"
55 #include "third_party/blink/renderer/core/animation/scroll_timeline_offset.h"
56 #include "third_party/blink/renderer/core/animation/timing_calculations.h"
57 #include "third_party/blink/renderer/core/animation/transition_interpolation.h"
58 #include "third_party/blink/renderer/core/animation/worklet_animation_base.h"
59 #include "third_party/blink/renderer/core/css/css_keyframe_rule.h"
60 #include "third_party/blink/renderer/core/css/css_property_equality.h"
61 #include "third_party/blink/renderer/core/css/css_value_list.h"
62 #include "third_party/blink/renderer/core/css/parser/css_variable_parser.h"
63 #include "third_party/blink/renderer/core/css/properties/computed_style_utils.h"
64 #include "third_party/blink/renderer/core/css/properties/css_property.h"
65 #include "third_party/blink/renderer/core/css/property_registry.h"
66 #include "third_party/blink/renderer/core/css/resolver/css_to_style_map.h"
67 #include "third_party/blink/renderer/core/css/resolver/style_resolver.h"
68 #include "third_party/blink/renderer/core/css/style_engine.h"
69 #include "third_party/blink/renderer/core/dom/element.h"
70 #include "third_party/blink/renderer/core/dom/events/event_path.h"
71 #include "third_party/blink/renderer/core/dom/node_computed_style.h"
72 #include "third_party/blink/renderer/core/dom/pseudo_element.h"
73 #include "third_party/blink/renderer/core/dom/shadow_root.h"
74 #include "third_party/blink/renderer/core/events/animation_event.h"
75 #include "third_party/blink/renderer/core/events/transition_event.h"
76 #include "third_party/blink/renderer/core/frame/web_feature.h"
77 #include "third_party/blink/renderer/core/layout/layout_object.h"
78 #include "third_party/blink/renderer/core/paint/paint_layer.h"
79 #include "third_party/blink/renderer/core/style_property_shorthand.h"
80 #include "third_party/blink/renderer/platform/animation/timing_function.h"
81 #include "third_party/blink/renderer/platform/heap/heap.h"
82 #include "third_party/blink/renderer/platform/instrumentation/histogram.h"
83 #include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
84 #include "third_party/blink/renderer/platform/wtf/hash_set.h"
85
86 namespace blink {
87
88 using PropertySet = HashSet<const CSSProperty*>;
89
90 namespace {
91
92 // Processes keyframe rules, extracting the timing function and properties being
93 // animated for each keyframe. The extraction process is doing more work that
94 // strictly required for the setup to step 5 in the spec
95 // (https://drafts.csswg.org/css-animations-2/#keyframes) as an optimization
96 // to avoid needing to process each rule multiple times to extract different
97 // properties.
ProcessKeyframesRule(const StyleRuleKeyframes * keyframes_rule,const Document & document,const ComputedStyle * parent_style,TimingFunction * default_timing_function,WritingMode writing_mode,TextDirection text_direction)98 StringKeyframeVector ProcessKeyframesRule(
99 const StyleRuleKeyframes* keyframes_rule,
100 const Document& document,
101 const ComputedStyle* parent_style,
102 TimingFunction* default_timing_function,
103 WritingMode writing_mode,
104 TextDirection text_direction) {
105 StringKeyframeVector keyframes;
106 const HeapVector<Member<StyleRuleKeyframe>>& style_keyframes =
107 keyframes_rule->Keyframes();
108
109 for (wtf_size_t i = 0; i < style_keyframes.size(); ++i) {
110 const StyleRuleKeyframe* style_keyframe = style_keyframes[i].Get();
111 auto* keyframe = MakeGarbageCollected<StringKeyframe>();
112 const Vector<double>& offsets = style_keyframe->Keys();
113 DCHECK(!offsets.IsEmpty());
114 keyframe->SetOffset(offsets[0]);
115 keyframe->SetEasing(default_timing_function);
116 const CSSPropertyValueSet& properties = style_keyframe->Properties();
117 for (unsigned j = 0; j < properties.PropertyCount(); j++) {
118 // TODO(crbug.com/980160): Remove access to static Variable instance.
119 const CSSProperty& property =
120 CSSProperty::Get(properties.PropertyAt(j).Id());
121 if (property.PropertyID() == CSSPropertyID::kAnimationTimingFunction) {
122 const CSSValue& value = properties.PropertyAt(j).Value();
123 scoped_refptr<TimingFunction> timing_function;
124 if (value.IsInheritedValue() && parent_style->Animations()) {
125 timing_function = parent_style->Animations()->TimingFunctionList()[0];
126 } else if (auto* value_list = DynamicTo<CSSValueList>(value)) {
127 timing_function =
128 CSSToStyleMap::MapAnimationTimingFunction(value_list->Item(0));
129 } else {
130 DCHECK(value.IsCSSWideKeyword());
131 timing_function = CSSTimingData::InitialTimingFunction();
132 }
133 keyframe->SetEasing(std::move(timing_function));
134 } else if (!CSSAnimations::IsAnimationAffectingProperty(property)) {
135 // Map Logical to physical property name.
136 const CSSProperty& physical_property =
137 property.ResolveDirectionAwareProperty(text_direction,
138 writing_mode);
139 keyframe->SetCSSPropertyValue(physical_property,
140 properties.PropertyAt(j).Value());
141 }
142 }
143 keyframes.push_back(keyframe);
144 // The last keyframe specified at a given offset is used.
145 for (wtf_size_t j = 1; j < offsets.size(); ++j) {
146 keyframes.push_back(
147 To<StringKeyframe>(keyframe->CloneWithOffset(offsets[j])));
148 }
149 }
150
151 std::stable_sort(keyframes.begin(), keyframes.end(),
152 [](const Member<Keyframe>& a, const Member<Keyframe>& b) {
153 return a->CheckedOffset() < b->CheckedOffset();
154 });
155 return keyframes;
156 }
157
158 // Finds the index of a keyframe with matching offset and easing.
FindIndexOfMatchingKeyframe(const StringKeyframeVector & keyframes,wtf_size_t start_index,double offset,const TimingFunction & easing)159 base::Optional<int> FindIndexOfMatchingKeyframe(
160 const StringKeyframeVector& keyframes,
161 wtf_size_t start_index,
162 double offset,
163 const TimingFunction& easing) {
164 for (wtf_size_t i = start_index; i < keyframes.size(); i++) {
165 StringKeyframe* keyframe = keyframes[i];
166
167 // Keyframes are sorted by offset. Search can stop once we hit and offset
168 // that exceeds the target value.
169 if (offset < keyframe->CheckedOffset())
170 break;
171
172 if (easing.ToString() == keyframe->Easing().ToString())
173 return i;
174 }
175 return base::nullopt;
176 }
177
178 // Tests conditions for inserting a bounding keyframe, which are outlined in
179 // steps 6 and 7 of the spec for keyframe construction.
180 // https://drafts.csswg.org/css-animations-2/#keyframes
NeedsBoundaryKeyframe(StringKeyframe * candidate,double offset,const PropertySet & animated_properties,const PropertySet & bounding_properties,TimingFunction * default_timing_function)181 bool NeedsBoundaryKeyframe(StringKeyframe* candidate,
182 double offset,
183 const PropertySet& animated_properties,
184 const PropertySet& bounding_properties,
185 TimingFunction* default_timing_function) {
186 if (!candidate)
187 return true;
188
189 if (candidate->CheckedOffset() != offset)
190 return true;
191
192 if (bounding_properties.size() == animated_properties.size())
193 return false;
194
195 return candidate->Easing().ToString() != default_timing_function->ToString();
196 }
197
CreateKeyframeEffectModel(StyleResolver * resolver,const Element * animating_element,Element & element,const ComputedStyle * style,const ComputedStyle * parent_style,const AtomicString & name,TimingFunction * default_timing_function,size_t animation_index)198 StringKeyframeEffectModel* CreateKeyframeEffectModel(
199 StyleResolver* resolver,
200 const Element* animating_element,
201 Element& element,
202 const ComputedStyle* style,
203 const ComputedStyle* parent_style,
204 const AtomicString& name,
205 TimingFunction* default_timing_function,
206 size_t animation_index) {
207 // The algorithm for constructing string keyframes for a CSS animation is
208 // covered in the following spec:
209 // https://drafts.csswg.org/css-animations-2/#keyframes
210
211 // For a given target (pseudo-)element, element, animation name, and
212 // position of the animation in element’s animation-name list, keyframe
213 // objects are generated as follows:
214
215 // 1. Let default timing function be the timing function at the position
216 // of the resolved value of the animation-timing-function for element,
217 // repeating the list as necessary as described in CSS Animations 1 §4.2
218 // The animation-name property.
219
220 // 2. Find the last @keyframes at-rule in document order with <keyframes-name>
221 // matching name.
222 // If there is no @keyframes at-rule with <keyframes-name> matching name,
223 // abort this procedure. In this case no animation is generated, and any
224 // existing animation matching name is canceled.
225
226 const StyleRuleKeyframes* keyframes_rule =
227 resolver->FindKeyframesRule(&element, name);
228 DCHECK(keyframes_rule);
229
230 // 3. Let keyframes be an empty sequence of keyframe objects.
231 StringKeyframeVector keyframes;
232
233 // 4. Let animated properties be an empty set of longhand CSS property names.
234 PropertySet animated_properties;
235
236 // Start and end properties are also tracked to simplify the process of
237 // determining if the first and last keyframes are missing properties.
238 PropertySet start_properties;
239 PropertySet end_properties;
240
241 // Properties that have already been processed at the current keyframe.
242 PropertySet current_offset_properties;
243
244 // 5. Perform a stable sort of the keyframe blocks in the @keyframes rule by
245 // the offset specified in the keyframe selector, and iterate over the
246 // result in reverse applying the following steps:
247 keyframes = ProcessKeyframesRule(keyframes_rule, element.GetDocument(),
248 parent_style, default_timing_function,
249 style->GetWritingMode(), style->Direction());
250
251 double last_offset = 1;
252 wtf_size_t merged_frame_count = 0;
253 for (wtf_size_t i = keyframes.size(); i > 0; --i) {
254 // 5.1 Let keyframe offset be the value of the keyframe selector converted
255 // to a value in the range 0 ≤ keyframe offset ≤ 1.
256 int source_index = i - 1;
257 StringKeyframe* rule_keyframe = keyframes[source_index];
258 double keyframe_offset = rule_keyframe->CheckedOffset();
259
260 // 5.2 Let keyframe timing function be the value of the last valid
261 // declaration of animation-timing-function specified on the keyframe
262 // block, or, if there is no such valid declaration, default timing
263 // function.
264 const TimingFunction& easing = rule_keyframe->Easing();
265
266 // 5.3 After converting keyframe timing function to its canonical form (e.g.
267 // such that step-end becomes steps(1, end)) let keyframe refer to the
268 // existing keyframe in keyframes with matching keyframe offset and
269 // timing function, if any.
270 // If there is no such existing keyframe, let keyframe be a new empty
271 // keyframe with offset, keyframe offset, and timing function, keyframe
272 // timing function, and prepend it to keyframes.
273
274 // Prevent stomping a rule override by tracking properties applied at
275 // the current offset.
276 if (last_offset != keyframe_offset) {
277 current_offset_properties.clear();
278 last_offset = keyframe_offset;
279 }
280
281 // Avoid unnecessary creation of extra keyframes by merging into
282 // existing keyframes.
283 base::Optional<int> existing_keyframe_index = FindIndexOfMatchingKeyframe(
284 keyframes, source_index + merged_frame_count + 1, keyframe_offset,
285 easing);
286 int target_index;
287 if (existing_keyframe_index) {
288 // Merge keyframe propoerties.
289 target_index = existing_keyframe_index.value();
290 merged_frame_count++;
291 } else {
292 target_index = source_index + merged_frame_count;
293 if (target_index != source_index) {
294 // Move keyframe to fill the gap.
295 keyframes[target_index] = keyframes[source_index];
296 source_index = target_index;
297 }
298 }
299
300 // 5.4 Iterate over all declarations in the keyframe block and add them to
301 // keyframe such that:
302 // * All variable references are resolved to their current values.
303 // * Each shorthand property is expanded to its longhand subproperties.
304 // * All logical properties are converted to their equivalent physical
305 // properties.
306 // * For any expanded physical longhand properties that appear more than
307 // once, only the last declaration in source order is added.
308 // Note, since multiple keyframe blocks may specify the same keyframe
309 // offset, and since this algorithm iterates over these blocks in
310 // reverse, this implies that if any properties are encountered that
311 // have already added at this same keyframe offset, they should be
312 // skipped.
313 // * All property values are replaced with their computed values.
314 // 5.5 Add each physical longhand property name that was added to keyframe
315 // to animated properties.
316 StringKeyframe* keyframe = keyframes[target_index];
317 for (const auto& property : rule_keyframe->Properties()) {
318 const CSSProperty& css_property = property.GetCSSProperty();
319
320 // Since processing keyframes in reverse order, skipping properties that
321 // have already been inserted prevents overwriting a later merged
322 // keyframe.
323 if (current_offset_properties.Contains(&css_property))
324 continue;
325
326 if (source_index != target_index) {
327 keyframe->SetCSSPropertyValue(
328 css_property, rule_keyframe->CssPropertyValue(property));
329 }
330
331 current_offset_properties.insert(&css_property);
332 animated_properties.insert(&css_property);
333 if (keyframe_offset == 0)
334 start_properties.insert(&css_property);
335 else if (keyframe_offset == 1)
336 end_properties.insert(&css_property);
337 }
338 }
339
340 // Compact the vector of keyframes if any keyframes have been merged.
341 keyframes.EraseAt(0, merged_frame_count);
342
343 // 6. If there is no keyframe in keyframes with offset 0, or if amongst the
344 // keyframes in keyframes with offset 0 not all of the properties in
345 // animated properties are present,
346 //
347 // 6.1 Let initial keyframe be the keyframe in keyframes with offset 0 and
348 // timing function default timing function.
349 // 6.2 If there is no such keyframe, let initial keyframe be a new empty
350 // keyframe with offset 0, and timing function default timing function,
351 // and add it to keyframes after the last keyframe with offset 0.
352 // 6.3 For each property in animated properties that is not present in some
353 // other keyframe with offset 0, add the computed value of that property
354 // for element to the keyframe.
355 StringKeyframe* start_keyframe = keyframes.IsEmpty() ? nullptr : keyframes[0];
356 if (NeedsBoundaryKeyframe(start_keyframe, 0, animated_properties,
357 start_properties, default_timing_function)) {
358 start_keyframe = MakeGarbageCollected<StringKeyframe>();
359 start_keyframe->SetOffset(0);
360 start_keyframe->SetEasing(default_timing_function);
361 keyframes.push_front(start_keyframe);
362 }
363
364 // 7. Similarly, if there is no keyframe in keyframes with offset 1, or if
365 // amongst the keyframes in keyframes with offset 1 not all of the
366 // properties in animated properties are present,
367 //
368 // 7.1 Let final keyframe be the keyframe in keyframes with offset 1 and
369 // timing function default timing function.
370 // 7.2 If there is no such keyframe, let final keyframe be a new empty
371 // keyframe with offset 1, and timing function default timing function,
372 // and add it to keyframes after the last keyframe with offset 1.
373 // 7.3 For each property in animated properties that is not present in some
374 // other keyframe with offset 1, add the computed value of that property
375 // for element to the keyframe.
376 StringKeyframe* end_keyframe = keyframes[keyframes.size() - 1];
377 if (NeedsBoundaryKeyframe(end_keyframe, 1, animated_properties,
378 end_properties, default_timing_function)) {
379 end_keyframe = MakeGarbageCollected<StringKeyframe>();
380 end_keyframe->SetOffset(1);
381 end_keyframe->SetEasing(default_timing_function);
382 keyframes.push_back(end_keyframe);
383 }
384
385 DCHECK_GE(keyframes.size(), 2U);
386 DCHECK_EQ(keyframes.front()->CheckedOffset(), 0);
387 DCHECK_EQ(keyframes.back()->CheckedOffset(), 1);
388
389 auto* model = MakeGarbageCollected<CssKeyframeEffectModel>(
390 keyframes, EffectModel::kCompositeReplace, &start_keyframe->Easing());
391 if (animation_index > 0 && model->HasSyntheticKeyframes()) {
392 UseCounter::Count(element.GetDocument(),
393 WebFeature::kCSSAnimationsStackedNeutralKeyframe);
394 }
395 return model;
396 }
397
398 // Returns the start time of an animation given the start delay. A negative
399 // start delay results in the animation starting with non-zero progress.
StartTimeFromDelay(double start_delay)400 AnimationTimeDelta StartTimeFromDelay(double start_delay) {
401 return AnimationTimeDelta::FromSecondsD(start_delay < 0 ? -start_delay : 0);
402 }
403
404 // Timing functions for computing elapsed time of an event.
405
IntervalStart(const AnimationEffect & effect)406 AnimationTimeDelta IntervalStart(const AnimationEffect& effect) {
407 const double start_delay = effect.SpecifiedTiming().start_delay;
408 const double active_duration = effect.SpecifiedTiming().ActiveDuration();
409 return AnimationTimeDelta::FromSecondsD(
410 std::fmax(std::fmin(-start_delay, active_duration), 0.0));
411 }
412
IntervalEnd(const AnimationEffect & effect)413 AnimationTimeDelta IntervalEnd(const AnimationEffect& effect) {
414 const double start_delay = effect.SpecifiedTiming().start_delay;
415 const double end_delay = effect.SpecifiedTiming().end_delay;
416 const double active_duration = effect.SpecifiedTiming().ActiveDuration();
417 const double target_effect_end =
418 std::max(start_delay + active_duration + end_delay, 0.0);
419 return AnimationTimeDelta::FromSecondsD(std::max(
420 std::min(target_effect_end - start_delay, active_duration), 0.0));
421 }
422
IterationElapsedTime(const AnimationEffect & effect,double previous_iteration)423 AnimationTimeDelta IterationElapsedTime(const AnimationEffect& effect,
424 double previous_iteration) {
425 const double current_iteration = effect.CurrentIteration().value();
426 const double iteration_boundary = (previous_iteration > current_iteration)
427 ? current_iteration + 1
428 : current_iteration;
429 const double iteration_start = effect.SpecifiedTiming().iteration_start;
430 const AnimationTimeDelta iteration_duration =
431 effect.SpecifiedTiming().IterationDuration();
432 return iteration_duration * (iteration_boundary - iteration_start);
433 }
434
CreateCSSScrollTimeline(Element * element,const CSSScrollTimeline::Options & options)435 CSSScrollTimeline* CreateCSSScrollTimeline(
436 Element* element,
437 const CSSScrollTimeline::Options& options) {
438 if (!options.IsValid())
439 return nullptr;
440 auto* scroll_timeline =
441 MakeGarbageCollected<CSSScrollTimeline>(&element->GetDocument(), options);
442 // It's is not allowed for a style resolve to create timelines that
443 // needs timing updates (i.e. AnimationTimeline::NeedsAnimationTimingUpdate()
444 // must return false). Servicing animations after creation preserves this
445 // invariant by ensuring the last-update time of the timeline is equal to
446 // the current time.
447 scroll_timeline->ServiceAnimations(kTimingUpdateOnDemand);
448 return scroll_timeline;
449 }
450
FindMatchingCachedTimeline(Document & document,const AtomicString & name,const CSSScrollTimeline::Options & options)451 CSSScrollTimeline* FindMatchingCachedTimeline(
452 Document& document,
453 const AtomicString& name,
454 const CSSScrollTimeline::Options& options) {
455 auto* cached_timeline = DynamicTo<CSSScrollTimeline>(
456 document.GetDocumentAnimations().FindCachedCSSScrollTimeline(name));
457 if (cached_timeline && cached_timeline->Matches(options))
458 return cached_timeline;
459 return nullptr;
460 }
461
ComputeTimeline(Element * element,const StyleNameOrKeyword & timeline_name,StyleRuleScrollTimeline * rule,AnimationTimeline * existing_timeline)462 AnimationTimeline* ComputeTimeline(Element* element,
463 const StyleNameOrKeyword& timeline_name,
464 StyleRuleScrollTimeline* rule,
465 AnimationTimeline* existing_timeline) {
466 Document& document = element->GetDocument();
467 if (timeline_name.IsKeyword()) {
468 if (timeline_name.GetKeyword() == CSSValueID::kAuto)
469 return &document.Timeline();
470 DCHECK_EQ(timeline_name.GetKeyword(), CSSValueID::kNone);
471 return nullptr;
472 }
473 if (rule) {
474 CSSScrollTimeline::Options options(element, *rule);
475
476 const AtomicString& name = timeline_name.GetName().GetValue();
477 // When multiple animations refer to the same @scroll-timeline, the same
478 // CSSScrollTimeline instance should be shared.
479 if (auto* timeline = FindMatchingCachedTimeline(document, name, options))
480 return timeline;
481 // When the incoming options match the existing timeline (associated with
482 // an existing animation), we can continue to use the existing timeline,
483 // since creating a new timeline from the options would just yield an
484 // identical timeline.
485 if (auto* timeline = DynamicTo<CSSScrollTimeline>(existing_timeline)) {
486 if (timeline->Matches(options))
487 return existing_timeline;
488 }
489 if (auto* timeline = CreateCSSScrollTimeline(element, options))
490 return timeline;
491 }
492 return nullptr;
493 }
494
FindScrollTimelineRule(Document & document,const StyleNameOrKeyword & timeline_name)495 StyleRuleScrollTimeline* FindScrollTimelineRule(
496 Document& document,
497 const StyleNameOrKeyword& timeline_name) {
498 if (timeline_name.IsKeyword())
499 return nullptr;
500 return document.GetStyleEngine().FindScrollTimelineRule(
501 timeline_name.GetName().GetValue());
502 }
503
504 } // namespace
505
506 CSSAnimations::CSSAnimations() = default;
507
508 namespace {
509
GetKeyframeEffectModelBase(const AnimationEffect * effect)510 const KeyframeEffectModelBase* GetKeyframeEffectModelBase(
511 const AnimationEffect* effect) {
512 if (!effect)
513 return nullptr;
514 const EffectModel* model = nullptr;
515 if (auto* keyframe_effect = DynamicTo<KeyframeEffect>(effect))
516 model = keyframe_effect->Model();
517 else if (auto* inert_effect = DynamicTo<InertEffect>(effect))
518 model = inert_effect->Model();
519 if (!model || !model->IsKeyframeEffectModel())
520 return nullptr;
521 return To<KeyframeEffectModelBase>(model);
522 }
523
ComputedValuesEqual(const PropertyHandle & property,const ComputedStyle & a,const ComputedStyle & b)524 bool ComputedValuesEqual(const PropertyHandle& property,
525 const ComputedStyle& a,
526 const ComputedStyle& b) {
527 // If zoom hasn't changed, compare internal values (stored with zoom applied)
528 // for speed. Custom properties are never zoomed so they are checked here too.
529 if (a.EffectiveZoom() == b.EffectiveZoom() ||
530 property.IsCSSCustomProperty()) {
531 return CSSPropertyEquality::PropertiesEqual(property, a, b);
532 }
533
534 // If zoom has changed, we must construct and compare the unzoomed
535 // computed values.
536 if (property.GetCSSProperty().PropertyID() == CSSPropertyID::kTransform) {
537 // Transform lists require special handling in this case to deal with
538 // layout-dependent interpolation which does not yet have a CSSValue.
539 return a.Transform().Zoom(1 / a.EffectiveZoom()) ==
540 b.Transform().Zoom(1 / b.EffectiveZoom());
541 } else {
542 const CSSValue* a_val =
543 ComputedStyleUtils::ComputedPropertyValue(property.GetCSSProperty(), a);
544 const CSSValue* b_val =
545 ComputedStyleUtils::ComputedPropertyValue(property.GetCSSProperty(), b);
546 // Computed values can be null if not able to parse.
547 if (a_val && b_val)
548 return *a_val == *b_val;
549 // Fallback to the zoom-unaware comparator if either value could not be
550 // parsed.
551 return CSSPropertyEquality::PropertiesEqual(property, a, b);
552 }
553 }
554
555 } // namespace
556
CalculateCompositorAnimationUpdate(CSSAnimationUpdate & update,const Element * animating_element,Element & element,const ComputedStyle & style,const ComputedStyle * parent_style,bool was_viewport_resized)557 void CSSAnimations::CalculateCompositorAnimationUpdate(
558 CSSAnimationUpdate& update,
559 const Element* animating_element,
560 Element& element,
561 const ComputedStyle& style,
562 const ComputedStyle* parent_style,
563 bool was_viewport_resized) {
564 ElementAnimations* element_animations =
565 animating_element ? animating_element->GetElementAnimations() : nullptr;
566
567 // If the change in style is only due to the Blink-side animation update, we
568 // do not need to update the compositor-side animations. The compositor is
569 // already changing the same properties and as such this update would provide
570 // no new information.
571 if (!element_animations || element_animations->IsAnimationStyleChange())
572 return;
573
574 const ComputedStyle* old_style = animating_element->GetComputedStyle();
575 if (!old_style || old_style->IsEnsuredInDisplayNone() ||
576 !old_style->ShouldCompositeForCurrentAnimations()) {
577 return;
578 }
579
580 bool transform_zoom_changed =
581 old_style->HasCurrentTransformAnimation() &&
582 old_style->EffectiveZoom() != style.EffectiveZoom();
583
584 const auto& snapshot = [&](AnimationEffect* effect) {
585 const KeyframeEffectModelBase* keyframe_effect =
586 GetKeyframeEffectModelBase(effect);
587 if (!keyframe_effect)
588 return false;
589
590 if ((transform_zoom_changed || was_viewport_resized) &&
591 (keyframe_effect->Affects(PropertyHandle(GetCSSPropertyTransform())) ||
592 keyframe_effect->Affects(PropertyHandle(GetCSSPropertyTranslate()))))
593 keyframe_effect->InvalidateCompositorKeyframesSnapshot();
594
595 if (keyframe_effect->SnapshotAllCompositorKeyframesIfNecessary(
596 element, style, parent_style)) {
597 return true;
598 } else if (keyframe_effect->HasSyntheticKeyframes() &&
599 keyframe_effect->SnapshotNeutralCompositorKeyframes(
600 element, *old_style, style, parent_style)) {
601 return true;
602 }
603 return false;
604 };
605
606 for (auto& entry : element_animations->Animations()) {
607 Animation& animation = *entry.key;
608 if (snapshot(animation.effect()))
609 update.UpdateCompositorKeyframes(&animation);
610 }
611
612 for (auto& entry : element_animations->GetWorkletAnimations()) {
613 WorkletAnimationBase& animation = *entry;
614 if (snapshot(animation.GetEffect()))
615 animation.InvalidateCompositingState();
616 }
617 }
618
CalculateAnimationUpdate(CSSAnimationUpdate & update,const Element * animating_element,Element & element,const ComputedStyle & style,const ComputedStyle * parent_style,StyleResolver * resolver)619 void CSSAnimations::CalculateAnimationUpdate(CSSAnimationUpdate& update,
620 const Element* animating_element,
621 Element& element,
622 const ComputedStyle& style,
623 const ComputedStyle* parent_style,
624 StyleResolver* resolver) {
625 ElementAnimations* element_animations =
626 animating_element ? animating_element->GetElementAnimations() : nullptr;
627
628 bool is_animation_style_change =
629 element_animations && element_animations->IsAnimationStyleChange();
630
631 #if !DCHECK_IS_ON()
632 // If we're in an animation style change, no animations can have started, been
633 // cancelled or changed play state. When DCHECK is enabled, we verify this
634 // optimization.
635 if (is_animation_style_change) {
636 CalculateAnimationActiveInterpolations(update, animating_element);
637 return;
638 }
639 #endif
640
641 // Rebuild the keyframe model for a CSS animation if it may have been
642 // invalidated by a change to the text direction or writing mode.
643 const ComputedStyle* old_style =
644 animating_element ? animating_element->GetComputedStyle() : nullptr;
645 bool logical_property_mapping_change =
646 !old_style || old_style->Direction() != style.Direction() ||
647 old_style->GetWritingMode() != style.GetWritingMode();
648
649 if (logical_property_mapping_change && element_animations) {
650 // Update computed keyframes for any running animations that depend on
651 // logical properties.
652 for (auto& entry : element_animations->Animations()) {
653 Animation* animation = entry.key;
654 if (auto* keyframe_effect =
655 DynamicTo<KeyframeEffect>(animation->effect())) {
656 keyframe_effect->SetLogicalPropertyResolutionContext(
657 style.Direction(), style.GetWritingMode());
658 animation->UpdateIfNecessary();
659 }
660 }
661 }
662
663 const CSSAnimationData* animation_data = style.Animations();
664 const CSSAnimations* css_animations =
665 element_animations ? &element_animations->CssAnimations() : nullptr;
666
667 Vector<bool> cancel_running_animation_flags(
668 css_animations ? css_animations->running_animations_.size() : 0);
669 for (bool& flag : cancel_running_animation_flags)
670 flag = true;
671
672 if (animation_data && style.Display() != EDisplay::kNone) {
673 const Vector<AtomicString>& name_list = animation_data->NameList();
674 for (wtf_size_t i = 0; i < name_list.size(); ++i) {
675 AtomicString name = name_list[i];
676 if (name == CSSAnimationData::InitialName())
677 continue;
678
679 // Find n where this is the nth occurence of this animation name.
680 wtf_size_t name_index = 0;
681 for (wtf_size_t j = 0; j < i; j++) {
682 if (name_list[j] == name)
683 name_index++;
684 }
685
686 const bool is_paused =
687 CSSTimingData::GetRepeated(animation_data->PlayStateList(), i) ==
688 EAnimPlayState::kPaused;
689
690 Timing timing = animation_data->ConvertToTiming(i);
691 Timing specified_timing = timing;
692 scoped_refptr<TimingFunction> keyframe_timing_function =
693 timing.timing_function;
694 timing.timing_function = Timing().timing_function;
695
696 StyleRuleKeyframes* keyframes_rule =
697 resolver->FindKeyframesRule(&element, name);
698 if (!keyframes_rule)
699 continue; // Cancel the animation if there's no style rule for it.
700
701 const StyleNameOrKeyword& timeline_name = animation_data->GetTimeline(i);
702
703 StyleRuleScrollTimeline* scroll_timeline_rule =
704 FindScrollTimelineRule(element.GetDocument(), timeline_name);
705
706 const RunningAnimation* existing_animation = nullptr;
707 wtf_size_t existing_animation_index = 0;
708
709 if (css_animations) {
710 for (wtf_size_t j = 0; j < css_animations->running_animations_.size();
711 j++) {
712 const RunningAnimation& running_animation =
713 *css_animations->running_animations_[j];
714 if (running_animation.name == name &&
715 running_animation.name_index == name_index) {
716 existing_animation = &running_animation;
717 existing_animation_index = j;
718 break;
719 }
720 }
721 }
722
723 if (existing_animation) {
724 cancel_running_animation_flags[existing_animation_index] = false;
725
726 CSSAnimation* animation =
727 DynamicTo<CSSAnimation>(existing_animation->animation.Get());
728 animation->SetAnimationIndex(i);
729 const bool was_paused =
730 CSSTimingData::GetRepeated(existing_animation->play_state_list,
731 i) == EAnimPlayState::kPaused;
732
733 // Explicit calls to web-animation play controls override changes to
734 // play state via the animation-play-state style. Ensure that the new
735 // play state based on animation-play-state differs from the current
736 // play state and that the change is not blocked by a sticky state.
737 bool toggle_pause_state = false;
738 if (is_paused != was_paused && !animation->getIgnoreCSSPlayState()) {
739 if (animation->Paused() && !is_paused)
740 toggle_pause_state = true;
741 else if (animation->Playing() && is_paused)
742 toggle_pause_state = true;
743 }
744
745 bool will_be_playing =
746 toggle_pause_state ? animation->Paused() : animation->Playing();
747
748 AnimationTimeline* timeline = existing_animation->Timeline();
749 if (!is_animation_style_change && !animation->GetIgnoreCSSTimeline()) {
750 timeline = ComputeTimeline(&element, timeline_name,
751 scroll_timeline_rule, timeline);
752 }
753
754 if (keyframes_rule != existing_animation->style_rule ||
755 keyframes_rule->Version() !=
756 existing_animation->style_rule_version ||
757 existing_animation->specified_timing != specified_timing ||
758 is_paused != was_paused || logical_property_mapping_change ||
759 timeline != existing_animation->Timeline()) {
760 DCHECK(!is_animation_style_change);
761
762 base::Optional<TimelinePhase> inherited_phase;
763 base::Optional<double> inherited_time;
764
765 if (timeline) {
766 inherited_phase = base::make_optional(timeline->Phase());
767 inherited_time = animation->UnlimitedCurrentTime();
768
769 if (will_be_playing &&
770 ((timeline != existing_animation->Timeline()) ||
771 animation->ResetsCurrentTimeOnResume())) {
772 if (!timeline->IsMonotonicallyIncreasing())
773 inherited_time = timeline->CurrentTimeSeconds();
774 }
775 }
776
777 update.UpdateAnimation(
778 existing_animation_index, animation,
779 *MakeGarbageCollected<InertEffect>(
780 CreateKeyframeEffectModel(resolver, animating_element,
781 element, &style, parent_style, name,
782 keyframe_timing_function.get(), i),
783 timing, is_paused, inherited_time, inherited_phase),
784 specified_timing, keyframes_rule, timeline,
785 animation_data->PlayStateList());
786 if (toggle_pause_state)
787 update.ToggleAnimationIndexPaused(existing_animation_index);
788 }
789 } else {
790 DCHECK(!is_animation_style_change);
791 AnimationTimeline* timeline =
792 ComputeTimeline(&element, timeline_name, scroll_timeline_rule,
793 nullptr /* existing_timeline */);
794 base::Optional<TimelinePhase> inherited_phase;
795 base::Optional<double> inherited_time;
796 if (timeline) {
797 if (timeline->IsMonotonicallyIncreasing()) {
798 inherited_time = 0;
799 } else {
800 inherited_phase = base::make_optional(timeline->Phase());
801 inherited_time = timeline->CurrentTimeSeconds();
802 }
803 }
804 update.StartAnimation(
805 name, name_index, i,
806 *MakeGarbageCollected<InertEffect>(
807 CreateKeyframeEffectModel(resolver, animating_element, element,
808 &style, parent_style, name,
809 keyframe_timing_function.get(), i),
810 timing, is_paused, inherited_time, inherited_phase),
811 specified_timing, keyframes_rule, timeline,
812 animation_data->PlayStateList());
813 }
814 }
815 }
816
817 for (wtf_size_t i = 0; i < cancel_running_animation_flags.size(); i++) {
818 if (cancel_running_animation_flags[i]) {
819 DCHECK(css_animations && !is_animation_style_change);
820 update.CancelAnimation(
821 i, *css_animations->running_animations_[i]->animation);
822 }
823 }
824
825 CalculateAnimationActiveInterpolations(update, animating_element);
826 }
827
CreateEventDelegate(Element * element,const PropertyHandle & property_handle,const AnimationEffect::EventDelegate * old_event_delegate)828 AnimationEffect::EventDelegate* CSSAnimations::CreateEventDelegate(
829 Element* element,
830 const PropertyHandle& property_handle,
831 const AnimationEffect::EventDelegate* old_event_delegate) {
832 const CSSAnimations::TransitionEventDelegate* old_transition_delegate =
833 DynamicTo<CSSAnimations::TransitionEventDelegate>(old_event_delegate);
834 Timing::Phase previous_phase =
835 old_transition_delegate ? old_transition_delegate->getPreviousPhase()
836 : Timing::kPhaseNone;
837 return MakeGarbageCollected<TransitionEventDelegate>(element, property_handle,
838 previous_phase);
839 }
840
CreateEventDelegate(Element * element,const AtomicString & animation_name,const AnimationEffect::EventDelegate * old_event_delegate)841 AnimationEffect::EventDelegate* CSSAnimations::CreateEventDelegate(
842 Element* element,
843 const AtomicString& animation_name,
844 const AnimationEffect::EventDelegate* old_event_delegate) {
845 const CSSAnimations::AnimationEventDelegate* old_animation_delegate =
846 DynamicTo<CSSAnimations::AnimationEventDelegate>(old_event_delegate);
847 Timing::Phase previous_phase =
848 old_animation_delegate ? old_animation_delegate->getPreviousPhase()
849 : Timing::kPhaseNone;
850 base::Optional<double> previous_iteration =
851 old_animation_delegate ? old_animation_delegate->getPreviousIteration()
852 : base::nullopt;
853 return MakeGarbageCollected<AnimationEventDelegate>(
854 element, animation_name, previous_phase, previous_iteration);
855 }
856
SnapshotCompositorKeyframes(Element & element,CSSAnimationUpdate & update,const ComputedStyle & style,const ComputedStyle * parent_style)857 void CSSAnimations::SnapshotCompositorKeyframes(
858 Element& element,
859 CSSAnimationUpdate& update,
860 const ComputedStyle& style,
861 const ComputedStyle* parent_style) {
862 const auto& snapshot = [&element, &style,
863 parent_style](const AnimationEffect* effect) {
864 const KeyframeEffectModelBase* keyframe_effect =
865 GetKeyframeEffectModelBase(effect);
866 if (keyframe_effect) {
867 keyframe_effect->SnapshotAllCompositorKeyframesIfNecessary(element, style,
868 parent_style);
869 }
870 };
871
872 ElementAnimations* element_animations = element.GetElementAnimations();
873 if (element_animations) {
874 for (auto& entry : element_animations->Animations())
875 snapshot(entry.key->effect());
876 }
877
878 for (const auto& new_animation : update.NewAnimations())
879 snapshot(new_animation.effect.Get());
880
881 for (const auto& updated_animation : update.AnimationsWithUpdates())
882 snapshot(updated_animation.effect.Get());
883
884 for (const auto& new_transition : update.NewTransitions())
885 snapshot(new_transition.value->effect.Get());
886 }
887
MaybeApplyPendingUpdate(Element * element)888 void CSSAnimations::MaybeApplyPendingUpdate(Element* element) {
889 previous_active_interpolations_for_custom_animations_.clear();
890 previous_active_interpolations_for_standard_animations_.clear();
891 if (pending_update_.IsEmpty())
892 return;
893
894 previous_active_interpolations_for_custom_animations_.swap(
895 pending_update_.ActiveInterpolationsForCustomAnimations());
896 previous_active_interpolations_for_standard_animations_.swap(
897 pending_update_.ActiveInterpolationsForStandardAnimations());
898
899 for (wtf_size_t paused_index :
900 pending_update_.AnimationIndicesWithPauseToggled()) {
901 CSSAnimation* animation = DynamicTo<CSSAnimation>(
902 running_animations_[paused_index]->animation.Get());
903
904 if (animation->Paused()) {
905 animation->Unpause();
906 animation->resetIgnoreCSSPlayState();
907 } else {
908 animation->pause();
909 animation->resetIgnoreCSSPlayState();
910 }
911 if (animation->Outdated())
912 animation->Update(kTimingUpdateOnDemand);
913 }
914
915 for (const auto& animation : pending_update_.UpdatedCompositorKeyframes())
916 animation->SetCompositorPending(true);
917
918 for (const auto& entry : pending_update_.AnimationsWithUpdates()) {
919 if (entry.animation->effect()) {
920 auto* effect = To<KeyframeEffect>(entry.animation->effect());
921 if (!effect->GetIgnoreCSSKeyframes())
922 effect->SetModel(entry.effect->Model());
923 effect->UpdateSpecifiedTiming(entry.effect->SpecifiedTiming());
924 }
925 if (entry.animation->timeline() != entry.timeline) {
926 entry.animation->setTimeline(entry.timeline);
927 To<CSSAnimation>(*entry.animation).ResetIgnoreCSSTimeline();
928 }
929
930 running_animations_[entry.index]->Update(entry);
931 }
932
933 const Vector<wtf_size_t>& cancelled_indices =
934 pending_update_.CancelledAnimationIndices();
935 for (wtf_size_t i = cancelled_indices.size(); i-- > 0;) {
936 DCHECK(i == cancelled_indices.size() - 1 ||
937 cancelled_indices[i] < cancelled_indices[i + 1]);
938 Animation& animation =
939 *running_animations_[cancelled_indices[i]]->animation;
940 animation.ClearOwningElement();
941 if (animation.IsCSSAnimation() &&
942 !DynamicTo<CSSAnimation>(animation)->getIgnoreCSSPlayState())
943 animation.cancel();
944 animation.Update(kTimingUpdateOnDemand);
945 running_animations_.EraseAt(cancelled_indices[i]);
946 }
947
948 for (const auto& entry : pending_update_.NewAnimations()) {
949 const InertEffect* inert_animation = entry.effect.Get();
950 AnimationEventDelegate* event_delegate =
951 MakeGarbageCollected<AnimationEventDelegate>(element, entry.name);
952 auto* effect = MakeGarbageCollected<KeyframeEffect>(
953 element, inert_animation->Model(), inert_animation->SpecifiedTiming(),
954 KeyframeEffect::kDefaultPriority, event_delegate);
955 auto* animation = MakeGarbageCollected<CSSAnimation>(
956 element->GetExecutionContext(), entry.timeline, effect,
957 entry.position_index, entry.name);
958 animation->play();
959 if (inert_animation->Paused())
960 animation->pause();
961 animation->resetIgnoreCSSPlayState();
962 animation->Update(kTimingUpdateOnDemand);
963
964 running_animations_.push_back(
965 MakeGarbageCollected<RunningAnimation>(animation, entry));
966 }
967
968 // Track retargeted transitions that are running on the compositor in order
969 // to update their start times.
970 HashSet<PropertyHandle> retargeted_compositor_transitions;
971 for (const PropertyHandle& property :
972 pending_update_.CancelledTransitions()) {
973 DCHECK(transitions_.Contains(property));
974
975 Animation* animation = transitions_.Take(property)->animation;
976 auto* effect = To<KeyframeEffect>(animation->effect());
977 if (effect && effect->HasActiveAnimationsOnCompositor(property) &&
978 pending_update_.NewTransitions().find(property) !=
979 pending_update_.NewTransitions().end() &&
980 !animation->Limited()) {
981 retargeted_compositor_transitions.insert(property);
982 }
983 animation->ClearOwningElement();
984 animation->cancel();
985 // After cancellation, transitions must be downgraded or they'll fail
986 // to be considered when retriggering themselves. This can happen if
987 // the transition is captured through getAnimations then played.
988 effect = DynamicTo<KeyframeEffect>(animation->effect());
989 if (effect)
990 effect->DowngradeToNormal();
991 animation->Update(kTimingUpdateOnDemand);
992 }
993
994 for (const PropertyHandle& property : pending_update_.FinishedTransitions()) {
995 // This transition can also be cancelled and finished at the same time
996 if (transitions_.Contains(property)) {
997 Animation* animation = transitions_.Take(property)->animation;
998 // Transition must be downgraded
999 if (auto* effect = DynamicTo<KeyframeEffect>(animation->effect()))
1000 effect->DowngradeToNormal();
1001 }
1002 }
1003
1004 if (!pending_update_.NewTransitions().IsEmpty()) {
1005 element->GetDocument()
1006 .GetDocumentAnimations()
1007 .IncrementTrasitionGeneration();
1008 }
1009
1010 for (const auto& entry : pending_update_.NewTransitions()) {
1011 const CSSAnimationUpdate::NewTransition* new_transition = entry.value;
1012
1013 RunningTransition* running_transition =
1014 MakeGarbageCollected<RunningTransition>();
1015 running_transition->from = new_transition->from;
1016 running_transition->to = new_transition->to;
1017 running_transition->reversing_adjusted_start_value =
1018 new_transition->reversing_adjusted_start_value;
1019 running_transition->reversing_shortening_factor =
1020 new_transition->reversing_shortening_factor;
1021
1022 const PropertyHandle& property = new_transition->property;
1023 const InertEffect* inert_animation = new_transition->effect.Get();
1024 TransitionEventDelegate* event_delegate =
1025 MakeGarbageCollected<TransitionEventDelegate>(element, property);
1026
1027 KeyframeEffectModelBase* model = inert_animation->Model();
1028
1029 auto* transition_effect = MakeGarbageCollected<KeyframeEffect>(
1030 element, model, inert_animation->SpecifiedTiming(),
1031 KeyframeEffect::kTransitionPriority, event_delegate);
1032 auto* animation = MakeGarbageCollected<CSSTransition>(
1033 element->GetExecutionContext(), &(element->GetDocument().Timeline()),
1034 transition_effect,
1035 element->GetDocument().GetDocumentAnimations().TransitionGeneration(),
1036 property);
1037
1038 animation->play();
1039
1040 // Set the current time as the start time for retargeted transitions
1041 if (retargeted_compositor_transitions.Contains(property)) {
1042 animation->setStartTime(
1043 element->GetDocument().Timeline().CurrentTimeMilliseconds());
1044 }
1045 animation->Update(kTimingUpdateOnDemand);
1046 running_transition->animation = animation;
1047 transitions_.Set(property, running_transition);
1048 }
1049 ClearPendingUpdate();
1050 }
1051
CalculateTransitionUpdateForProperty(TransitionUpdateState & state,const PropertyHandle & property,size_t transition_index)1052 void CSSAnimations::CalculateTransitionUpdateForProperty(
1053 TransitionUpdateState& state,
1054 const PropertyHandle& property,
1055 size_t transition_index) {
1056 state.listed_properties.insert(property);
1057
1058 // FIXME: We should transition if an !important property changes even when an
1059 // animation is running, but this is a bit hard to do with the current
1060 // applyMatchedProperties system.
1061 if (property.IsCSSCustomProperty()) {
1062 if (state.update.ActiveInterpolationsForCustomAnimations().Contains(
1063 property) ||
1064 (state.animating_element->GetElementAnimations() &&
1065 state.animating_element->GetElementAnimations()
1066 ->CssAnimations()
1067 .previous_active_interpolations_for_custom_animations_.Contains(
1068 property))) {
1069 return;
1070 }
1071 } else if (state.update.ActiveInterpolationsForStandardAnimations().Contains(
1072 property) ||
1073 (state.animating_element->GetElementAnimations() &&
1074 state.animating_element->GetElementAnimations()
1075 ->CssAnimations()
1076 .previous_active_interpolations_for_standard_animations_
1077 .Contains(property))) {
1078 return;
1079 }
1080
1081 const RunningTransition* interrupted_transition = nullptr;
1082 if (state.active_transitions) {
1083 TransitionMap::const_iterator active_transition_iter =
1084 state.active_transitions->find(property);
1085 if (active_transition_iter != state.active_transitions->end()) {
1086 const RunningTransition* running_transition =
1087 active_transition_iter->value;
1088 if (ComputedValuesEqual(property, state.style, *running_transition->to)) {
1089 if (!state.transition_data) {
1090 if (!running_transition->animation->FinishedInternal()) {
1091 UseCounter::Count(
1092 state.animating_element->GetDocument(),
1093 WebFeature::kCSSTransitionCancelledByRemovingStyle);
1094 }
1095 // TODO(crbug.com/934700): Add a return to this branch to correctly
1096 // continue transitions under default settings (all 0s) in the absence
1097 // of a change in base computed style.
1098 } else {
1099 return;
1100 }
1101 }
1102 state.update.CancelTransition(property);
1103 DCHECK(!state.animating_element->GetElementAnimations() ||
1104 !state.animating_element->GetElementAnimations()
1105 ->IsAnimationStyleChange());
1106
1107 if (ComputedValuesEqual(
1108 property, state.style,
1109 *running_transition->reversing_adjusted_start_value)) {
1110 interrupted_transition = running_transition;
1111 }
1112 }
1113 }
1114
1115 // In the default configutation (transition: all 0s) we continue and cancel
1116 // transitions but do not start them.
1117 if (!state.transition_data)
1118 return;
1119
1120 const PropertyRegistry* registry =
1121 state.animating_element->GetDocument().GetPropertyRegistry();
1122 if (property.IsCSSCustomProperty()) {
1123 if (!registry || !registry->Registration(property.CustomPropertyName())) {
1124 return;
1125 }
1126 }
1127
1128 // Lazy evaluation of the before change style. We only need to update where
1129 // we are transitioning from if the final destination is changing.
1130 if (!state.before_change_style) {
1131 ElementAnimations* element_animations =
1132 state.animating_element->GetElementAnimations();
1133 if (element_animations) {
1134 const ComputedStyle* base_style = element_animations->BaseComputedStyle();
1135 if (base_style) {
1136 state.before_change_style =
1137 CalculateBeforeChangeStyle(state.animating_element, *base_style);
1138 }
1139 }
1140 // Use the style from the previous frame if no base style is found.
1141 // Elements that have not been animated will not have a base style.
1142 // Elements that were previously animated, but where all previously running
1143 // animations have stopped may also be missing a base style. In both cases,
1144 // the old style is equivalent to the base computed style.
1145 if (!state.before_change_style) {
1146 state.before_change_style =
1147 CalculateBeforeChangeStyle(state.animating_element, state.old_style);
1148 }
1149 }
1150
1151 if (ComputedValuesEqual(property, *state.before_change_style, state.style)) {
1152 return;
1153 }
1154
1155 CSSInterpolationTypesMap map(registry,
1156 state.animating_element->GetDocument());
1157 CSSInterpolationEnvironment old_environment(map, *state.before_change_style);
1158 CSSInterpolationEnvironment new_environment(map, state.style);
1159 const InterpolationType* transition_type = nullptr;
1160 InterpolationValue start = nullptr;
1161 InterpolationValue end = nullptr;
1162
1163 for (const auto& interpolation_type : map.Get(property)) {
1164 start = interpolation_type->MaybeConvertUnderlyingValue(old_environment);
1165 if (!start) {
1166 continue;
1167 }
1168 end = interpolation_type->MaybeConvertUnderlyingValue(new_environment);
1169 if (!end) {
1170 continue;
1171 }
1172 // Merge will only succeed if the two values are considered interpolable.
1173 if (interpolation_type->MaybeMergeSingles(start.Clone(), end.Clone())) {
1174 transition_type = interpolation_type.get();
1175 break;
1176 }
1177 }
1178
1179 // No smooth interpolation exists between these values so don't start a
1180 // transition.
1181 if (!transition_type) {
1182 return;
1183 }
1184
1185 // If we have multiple transitions on the same property, we will use the
1186 // last one since we iterate over them in order.
1187
1188 Timing timing = state.transition_data->ConvertToTiming(transition_index);
1189 // CSS Transitions always have a valid duration (i.e. the value 'auto' is not
1190 // supported), so iteration_duration will always be set.
1191 if (timing.start_delay + timing.iteration_duration->InSecondsF() <= 0) {
1192 // We may have started a transition in a prior CSSTransitionData update,
1193 // this CSSTransitionData update needs to override them.
1194 // TODO(alancutter): Just iterate over the CSSTransitionDatas in reverse and
1195 // skip any properties that have already been visited so we don't need to
1196 // "undo" work like this.
1197 state.update.UnstartTransition(property);
1198 return;
1199 }
1200
1201 const ComputedStyle* reversing_adjusted_start_value =
1202 state.before_change_style.get();
1203 double reversing_shortening_factor = 1;
1204 if (interrupted_transition) {
1205 AnimationEffect* effect = interrupted_transition->animation->effect();
1206 const base::Optional<double> interrupted_progress =
1207 effect ? effect->Progress() : base::nullopt;
1208 if (interrupted_progress) {
1209 reversing_adjusted_start_value = interrupted_transition->to.get();
1210 reversing_shortening_factor =
1211 clampTo((interrupted_progress.value() *
1212 interrupted_transition->reversing_shortening_factor) +
1213 (1 - interrupted_transition->reversing_shortening_factor),
1214 0.0, 1.0);
1215 timing.iteration_duration.value() *= reversing_shortening_factor;
1216 if (timing.start_delay < 0) {
1217 timing.start_delay *= reversing_shortening_factor;
1218 }
1219 }
1220 }
1221
1222 TransitionKeyframeVector keyframes;
1223
1224 TransitionKeyframe* start_keyframe =
1225 MakeGarbageCollected<TransitionKeyframe>(property);
1226 start_keyframe->SetValue(std::make_unique<TypedInterpolationValue>(
1227 *transition_type, start.interpolable_value->Clone(),
1228 start.non_interpolable_value));
1229 start_keyframe->SetOffset(0);
1230 keyframes.push_back(start_keyframe);
1231
1232 TransitionKeyframe* end_keyframe =
1233 MakeGarbageCollected<TransitionKeyframe>(property);
1234 end_keyframe->SetValue(std::make_unique<TypedInterpolationValue>(
1235 *transition_type, end.interpolable_value->Clone(),
1236 end.non_interpolable_value));
1237 end_keyframe->SetOffset(1);
1238 keyframes.push_back(end_keyframe);
1239
1240 if (property.GetCSSProperty().IsCompositableProperty()) {
1241 CompositorKeyframeValue* from = CompositorKeyframeValueFactory::Create(
1242 property, *state.before_change_style);
1243 CompositorKeyframeValue* to =
1244 CompositorKeyframeValueFactory::Create(property, state.style);
1245 start_keyframe->SetCompositorValue(from);
1246 end_keyframe->SetCompositorValue(to);
1247 }
1248
1249 auto* model = MakeGarbageCollected<TransitionKeyframeEffectModel>(keyframes);
1250 if (!state.cloned_style) {
1251 state.cloned_style = ComputedStyle::Clone(state.style);
1252 }
1253 state.update.StartTransition(
1254 property, state.before_change_style, state.cloned_style,
1255 reversing_adjusted_start_value, reversing_shortening_factor,
1256 *MakeGarbageCollected<InertEffect>(model, timing, false, 0,
1257 base::nullopt));
1258 DCHECK(!state.animating_element->GetElementAnimations() ||
1259 !state.animating_element->GetElementAnimations()
1260 ->IsAnimationStyleChange());
1261 }
1262
CalculateTransitionUpdateForCustomProperty(TransitionUpdateState & state,const CSSTransitionData::TransitionProperty & transition_property,size_t transition_index)1263 void CSSAnimations::CalculateTransitionUpdateForCustomProperty(
1264 TransitionUpdateState& state,
1265 const CSSTransitionData::TransitionProperty& transition_property,
1266 size_t transition_index) {
1267 if (transition_property.property_type !=
1268 CSSTransitionData::kTransitionUnknownProperty) {
1269 return;
1270 }
1271 if (!CSSVariableParser::IsValidVariableName(
1272 transition_property.property_string)) {
1273 return;
1274 }
1275 CalculateTransitionUpdateForProperty(
1276 state, PropertyHandle(transition_property.property_string),
1277 transition_index);
1278 }
1279
CalculateTransitionUpdateForStandardProperty(TransitionUpdateState & state,const CSSTransitionData::TransitionProperty & transition_property,size_t transition_index,const ComputedStyle & style)1280 void CSSAnimations::CalculateTransitionUpdateForStandardProperty(
1281 TransitionUpdateState& state,
1282 const CSSTransitionData::TransitionProperty& transition_property,
1283 size_t transition_index,
1284 const ComputedStyle& style) {
1285 if (transition_property.property_type !=
1286 CSSTransitionData::kTransitionKnownProperty) {
1287 return;
1288 }
1289
1290 CSSPropertyID resolved_id =
1291 resolveCSSPropertyID(transition_property.unresolved_property);
1292 bool animate_all = resolved_id == CSSPropertyID::kAll;
1293 const StylePropertyShorthand& property_list =
1294 animate_all ? PropertiesForTransitionAll()
1295 : shorthandForProperty(resolved_id);
1296 // If not a shorthand we only execute one iteration of this loop, and
1297 // refer to the property directly.
1298 for (unsigned i = 0; !i || i < property_list.length(); ++i) {
1299 CSSPropertyID longhand_id =
1300 property_list.length() ? property_list.properties()[i]->PropertyID()
1301 : resolved_id;
1302 DCHECK_GE(longhand_id, firstCSSProperty);
1303 const CSSProperty& property =
1304 CSSProperty::Get(longhand_id)
1305 .ResolveDirectionAwareProperty(style.Direction(),
1306 style.GetWritingMode());
1307 PropertyHandle property_handle = PropertyHandle(property);
1308
1309 if (!animate_all && !property.IsInterpolable()) {
1310 continue;
1311 }
1312
1313 CalculateTransitionUpdateForProperty(state, property_handle,
1314 transition_index);
1315 }
1316 }
1317
CalculateTransitionUpdate(CSSAnimationUpdate & update,PropertyPass property_pass,Element * animating_element,const ComputedStyle & style)1318 void CSSAnimations::CalculateTransitionUpdate(CSSAnimationUpdate& update,
1319 PropertyPass property_pass,
1320 Element* animating_element,
1321 const ComputedStyle& style) {
1322 if (!animating_element)
1323 return;
1324
1325 if (animating_element->GetDocument().FinishingOrIsPrinting())
1326 return;
1327
1328 ElementAnimations* element_animations =
1329 animating_element->GetElementAnimations();
1330 const TransitionMap* active_transitions =
1331 element_animations ? &element_animations->CssAnimations().transitions_
1332 : nullptr;
1333 const CSSTransitionData* transition_data = style.Transitions();
1334
1335 const bool animation_style_recalc =
1336 element_animations && element_animations->IsAnimationStyleChange();
1337
1338 HashSet<PropertyHandle> listed_properties;
1339 bool any_transition_had_transition_all = false;
1340 const ComputedStyle* old_style = animating_element->GetComputedStyle();
1341 if (!animation_style_recalc && style.Display() != EDisplay::kNone &&
1342 old_style && !old_style->IsEnsuredInDisplayNone()) {
1343 TransitionUpdateState state = {update,
1344 animating_element,
1345 *old_style,
1346 style,
1347 /*before_change_style=*/nullptr,
1348 /*cloned_style=*/nullptr,
1349 active_transitions,
1350 listed_properties,
1351 transition_data};
1352
1353 if (transition_data) {
1354 for (wtf_size_t transition_index = 0;
1355 transition_index < transition_data->PropertyList().size();
1356 ++transition_index) {
1357 const CSSTransitionData::TransitionProperty& transition_property =
1358 transition_data->PropertyList()[transition_index];
1359 if (transition_property.unresolved_property == CSSPropertyID::kAll) {
1360 any_transition_had_transition_all = true;
1361 }
1362 if (property_pass == PropertyPass::kCustom) {
1363 CalculateTransitionUpdateForCustomProperty(state, transition_property,
1364 transition_index);
1365 } else {
1366 DCHECK_EQ(property_pass, PropertyPass::kStandard);
1367 CalculateTransitionUpdateForStandardProperty(
1368 state, transition_property, transition_index, style);
1369 }
1370 }
1371 } else if (active_transitions && active_transitions->size()) {
1372 // !transition_data implies transition: all 0s
1373 any_transition_had_transition_all = true;
1374 if (property_pass == PropertyPass::kStandard) {
1375 CSSTransitionData::TransitionProperty default_property(
1376 CSSPropertyID::kAll);
1377 CalculateTransitionUpdateForStandardProperty(state, default_property, 0,
1378 style);
1379 }
1380 }
1381 }
1382
1383 if (active_transitions) {
1384 for (const auto& entry : *active_transitions) {
1385 const PropertyHandle& property = entry.key;
1386 if (property.IsCSSCustomProperty() !=
1387 (property_pass == PropertyPass::kCustom)) {
1388 continue;
1389 }
1390 if (!any_transition_had_transition_all && !animation_style_recalc &&
1391 !listed_properties.Contains(property)) {
1392 update.CancelTransition(property);
1393 } else if (entry.value->animation->FinishedInternal()) {
1394 update.FinishTransition(property);
1395 }
1396 }
1397 }
1398
1399 CalculateTransitionActiveInterpolations(update, property_pass,
1400 animating_element);
1401 }
1402
CalculateBeforeChangeStyle(Element * animating_element,const ComputedStyle & base_style)1403 scoped_refptr<const ComputedStyle> CSSAnimations::CalculateBeforeChangeStyle(
1404 Element* animating_element,
1405 const ComputedStyle& base_style) {
1406 ActiveInterpolationsMap interpolations_map;
1407 ElementAnimations* element_animations =
1408 animating_element->GetElementAnimations();
1409 if (element_animations) {
1410 const TransitionMap& transition_map =
1411 element_animations->CssAnimations().transitions_;
1412
1413 // Assemble list of animations in composite ordering.
1414 // TODO(crbug.com/1082401): Per spec, the before change style should include
1415 // all declarative animations. Currently, only including transitions.
1416 HeapVector<Member<Animation>> animations;
1417 for (const auto& entry : transition_map) {
1418 RunningTransition* transition = entry.value;
1419 Animation* animation = transition->animation;
1420 animations.push_back(animation);
1421 }
1422 std::sort(animations.begin(), animations.end(),
1423 [](Animation* a, Animation* b) {
1424 return Animation::HasLowerCompositeOrdering(
1425 a, b, Animation::CompareAnimationsOrdering::kPointerOrder);
1426 });
1427
1428 // Sample animations and add to the interpolations map.
1429 for (Animation* animation : animations) {
1430 base::Optional<double> current_time = animation->currentTime();
1431 if (!current_time)
1432 continue;
1433
1434 auto* effect = DynamicTo<KeyframeEffect>(animation->effect());
1435 if (!effect)
1436 continue;
1437
1438 auto* inert_animation_for_sampling = MakeGarbageCollected<InertEffect>(
1439 effect->Model(), effect->SpecifiedTiming(), false,
1440 current_time.value() / 1000, base::nullopt);
1441
1442 HeapVector<Member<Interpolation>> sample;
1443 inert_animation_for_sampling->Sample(sample);
1444
1445 for (const auto& interpolation : sample) {
1446 PropertyHandle handle = interpolation->GetProperty();
1447 auto interpolation_map_entry = interpolations_map.insert(
1448 handle, MakeGarbageCollected<ActiveInterpolations>());
1449 auto& active_interpolations =
1450 *interpolation_map_entry.stored_value->value;
1451 if (!interpolation->DependsOnUnderlyingValue())
1452 active_interpolations.clear();
1453 active_interpolations.push_back(interpolation);
1454 }
1455 }
1456 }
1457
1458 StyleResolver& resolver = animating_element->GetDocument().GetStyleResolver();
1459 return resolver.BeforeChangeStyleForTransitionUpdate(
1460 *animating_element, base_style, interpolations_map);
1461 }
1462
Cancel()1463 void CSSAnimations::Cancel() {
1464 for (const auto& running_animation : running_animations_) {
1465 running_animation->animation->cancel();
1466 running_animation->animation->Update(kTimingUpdateOnDemand);
1467 }
1468
1469 for (const auto& entry : transitions_) {
1470 entry.value->animation->cancel();
1471 entry.value->animation->Update(kTimingUpdateOnDemand);
1472 }
1473
1474 running_animations_.clear();
1475 transitions_.clear();
1476 ClearPendingUpdate();
1477 }
1478
1479 namespace {
1480
IsCustomPropertyHandle(const PropertyHandle & property)1481 bool IsCustomPropertyHandle(const PropertyHandle& property) {
1482 return property.IsCSSCustomProperty();
1483 }
1484
IsFontAffectingPropertyHandle(const PropertyHandle & property)1485 bool IsFontAffectingPropertyHandle(const PropertyHandle& property) {
1486 if (property.IsCSSCustomProperty() || !property.IsCSSProperty())
1487 return false;
1488 return property.GetCSSProperty().AffectsFont();
1489 }
1490
1491 // TODO(alancutter): CSS properties and presentation attributes may have
1492 // identical effects. By grouping them in the same set we introduce a bug where
1493 // arbitrary hash iteration will determine the order the apply in and thus which
1494 // one "wins". We should be more deliberate about the order of application in
1495 // the case of effect collisions.
1496 // Example: Both 'color' and 'svg-color' set the color on ComputedStyle but are
1497 // considered distinct properties in the ActiveInterpolationsMap.
IsStandardPropertyHandle(const PropertyHandle & property)1498 bool IsStandardPropertyHandle(const PropertyHandle& property) {
1499 return (property.IsCSSProperty() && !property.IsCSSCustomProperty()) ||
1500 property.IsPresentationAttribute();
1501 }
1502
AdoptActiveAnimationInterpolations(EffectStack * effect_stack,CSSAnimationUpdate & update,const HeapVector<Member<const InertEffect>> * new_animations,const HeapHashSet<Member<const Animation>> * suppressed_animations)1503 void AdoptActiveAnimationInterpolations(
1504 EffectStack* effect_stack,
1505 CSSAnimationUpdate& update,
1506 const HeapVector<Member<const InertEffect>>* new_animations,
1507 const HeapHashSet<Member<const Animation>>* suppressed_animations) {
1508 ActiveInterpolationsMap custom_interpolations(
1509 EffectStack::ActiveInterpolations(
1510 effect_stack, new_animations, suppressed_animations,
1511 KeyframeEffect::kDefaultPriority, IsCustomPropertyHandle));
1512 update.AdoptActiveInterpolationsForCustomAnimations(custom_interpolations);
1513
1514 ActiveInterpolationsMap standard_interpolations(
1515 EffectStack::ActiveInterpolations(
1516 effect_stack, new_animations, suppressed_animations,
1517 KeyframeEffect::kDefaultPriority, IsStandardPropertyHandle));
1518 update.AdoptActiveInterpolationsForStandardAnimations(
1519 standard_interpolations);
1520 }
1521
1522 } // namespace
1523
CalculateAnimationActiveInterpolations(CSSAnimationUpdate & update,const Element * animating_element)1524 void CSSAnimations::CalculateAnimationActiveInterpolations(
1525 CSSAnimationUpdate& update,
1526 const Element* animating_element) {
1527 ElementAnimations* element_animations =
1528 animating_element ? animating_element->GetElementAnimations() : nullptr;
1529 EffectStack* effect_stack =
1530 element_animations ? &element_animations->GetEffectStack() : nullptr;
1531
1532 if (update.NewAnimations().IsEmpty() &&
1533 update.SuppressedAnimations().IsEmpty()) {
1534 AdoptActiveAnimationInterpolations(effect_stack, update, nullptr, nullptr);
1535 return;
1536 }
1537
1538 HeapVector<Member<const InertEffect>> new_effects;
1539 for (const auto& new_animation : update.NewAnimations())
1540 new_effects.push_back(new_animation.effect);
1541
1542 // Animations with updates use a temporary InertEffect for the current frame.
1543 for (const auto& updated_animation : update.AnimationsWithUpdates())
1544 new_effects.push_back(updated_animation.effect);
1545
1546 AdoptActiveAnimationInterpolations(effect_stack, update, &new_effects,
1547 &update.SuppressedAnimations());
1548 }
1549
1550 namespace {
1551
PropertyFilter(CSSAnimations::PropertyPass property_pass)1552 EffectStack::PropertyHandleFilter PropertyFilter(
1553 CSSAnimations::PropertyPass property_pass) {
1554 if (property_pass == CSSAnimations::PropertyPass::kCustom) {
1555 return IsCustomPropertyHandle;
1556 }
1557 DCHECK_EQ(property_pass, CSSAnimations::PropertyPass::kStandard);
1558 return IsStandardPropertyHandle;
1559 }
1560
1561 } // namespace
1562
CalculateTransitionActiveInterpolations(CSSAnimationUpdate & update,PropertyPass property_pass,const Element * animating_element)1563 void CSSAnimations::CalculateTransitionActiveInterpolations(
1564 CSSAnimationUpdate& update,
1565 PropertyPass property_pass,
1566 const Element* animating_element) {
1567 ElementAnimations* element_animations =
1568 animating_element ? animating_element->GetElementAnimations() : nullptr;
1569 EffectStack* effect_stack =
1570 element_animations ? &element_animations->GetEffectStack() : nullptr;
1571
1572 ActiveInterpolationsMap active_interpolations_for_transitions;
1573 if (update.NewTransitions().IsEmpty() &&
1574 update.CancelledTransitions().IsEmpty()) {
1575 active_interpolations_for_transitions = EffectStack::ActiveInterpolations(
1576 effect_stack, nullptr, nullptr, KeyframeEffect::kTransitionPriority,
1577 PropertyFilter(property_pass));
1578 } else {
1579 HeapVector<Member<const InertEffect>> new_transitions;
1580 for (const auto& entry : update.NewTransitions())
1581 new_transitions.push_back(entry.value->effect.Get());
1582
1583 HeapHashSet<Member<const Animation>> cancelled_animations;
1584 if (!update.CancelledTransitions().IsEmpty()) {
1585 DCHECK(element_animations);
1586 const TransitionMap& transition_map =
1587 element_animations->CssAnimations().transitions_;
1588 for (const PropertyHandle& property : update.CancelledTransitions()) {
1589 DCHECK(transition_map.Contains(property));
1590 cancelled_animations.insert(
1591 transition_map.at(property)->animation.Get());
1592 }
1593 }
1594
1595 active_interpolations_for_transitions = EffectStack::ActiveInterpolations(
1596 effect_stack, &new_transitions, &cancelled_animations,
1597 KeyframeEffect::kTransitionPriority, PropertyFilter(property_pass));
1598 }
1599
1600 const ActiveInterpolationsMap& animations =
1601 property_pass == PropertyPass::kCustom
1602 ? update.ActiveInterpolationsForCustomAnimations()
1603 : update.ActiveInterpolationsForStandardAnimations();
1604 // Properties being animated by animations don't get values from transitions
1605 // applied.
1606 if (!animations.IsEmpty() &&
1607 !active_interpolations_for_transitions.IsEmpty()) {
1608 for (const auto& entry : animations)
1609 active_interpolations_for_transitions.erase(entry.key);
1610 }
1611
1612 if (property_pass == PropertyPass::kCustom) {
1613 update.AdoptActiveInterpolationsForCustomTransitions(
1614 active_interpolations_for_transitions);
1615 } else {
1616 DCHECK_EQ(property_pass, PropertyPass::kStandard);
1617 update.AdoptActiveInterpolationsForStandardTransitions(
1618 active_interpolations_for_transitions);
1619 }
1620 }
1621
GetEventTarget() const1622 EventTarget* CSSAnimations::AnimationEventDelegate::GetEventTarget() const {
1623 return &EventPath::EventTargetRespectingTargetRules(*animation_target_);
1624 }
1625
MaybeDispatch(Document::ListenerType listener_type,const AtomicString & event_name,const AnimationTimeDelta & elapsed_time)1626 void CSSAnimations::AnimationEventDelegate::MaybeDispatch(
1627 Document::ListenerType listener_type,
1628 const AtomicString& event_name,
1629 const AnimationTimeDelta& elapsed_time) {
1630 if (animation_target_->GetDocument().HasListenerType(listener_type)) {
1631 String pseudo_element_name = PseudoElement::PseudoElementNameForEvents(
1632 animation_target_->GetPseudoId());
1633 AnimationEvent* event = AnimationEvent::Create(
1634 event_name, name_, elapsed_time, pseudo_element_name);
1635 event->SetTarget(GetEventTarget());
1636 GetDocument().EnqueueAnimationFrameEvent(event);
1637 }
1638 }
1639
RequiresIterationEvents(const AnimationEffect & animation_node)1640 bool CSSAnimations::AnimationEventDelegate::RequiresIterationEvents(
1641 const AnimationEffect& animation_node) {
1642 return GetDocument().HasListenerType(Document::kAnimationIterationListener);
1643 }
1644
OnEventCondition(const AnimationEffect & animation_node,Timing::Phase current_phase)1645 void CSSAnimations::AnimationEventDelegate::OnEventCondition(
1646 const AnimationEffect& animation_node,
1647 Timing::Phase current_phase) {
1648 const base::Optional<double> current_iteration =
1649 animation_node.CurrentIteration();
1650
1651 // See http://drafts.csswg.org/css-animations-2/#event-dispatch
1652 // When multiple events are dispatched for a single phase transition,
1653 // the animationstart event is to be dispatched before the animationend
1654 // event.
1655
1656 // The following phase transitions trigger an animationstart event:
1657 // idle or before --> active or after
1658 // after --> active or before
1659 const bool phase_change = previous_phase_ != current_phase;
1660 const bool was_idle_or_before = (previous_phase_ == Timing::kPhaseNone ||
1661 previous_phase_ == Timing::kPhaseBefore);
1662 const bool is_active_or_after = (current_phase == Timing::kPhaseActive ||
1663 current_phase == Timing::kPhaseAfter);
1664 const bool is_active_or_before = (current_phase == Timing::kPhaseActive ||
1665 current_phase == Timing::kPhaseBefore);
1666 const bool was_after = (previous_phase_ == Timing::kPhaseAfter);
1667 if (phase_change && ((was_idle_or_before && is_active_or_after) ||
1668 (was_after && is_active_or_before))) {
1669 AnimationTimeDelta elapsed_time =
1670 was_after ? IntervalEnd(animation_node) : IntervalStart(animation_node);
1671 MaybeDispatch(Document::kAnimationStartListener,
1672 event_type_names::kAnimationstart, elapsed_time);
1673 }
1674
1675 // The following phase transitions trigger an animationend event:
1676 // idle, before or active--> after
1677 // active or after--> before
1678 const bool was_active_or_after = (previous_phase_ == Timing::kPhaseActive ||
1679 previous_phase_ == Timing::kPhaseAfter);
1680 const bool is_after = (current_phase == Timing::kPhaseAfter);
1681 const bool is_before = (current_phase == Timing::kPhaseBefore);
1682 if (phase_change && (is_after || (was_active_or_after && is_before))) {
1683 AnimationTimeDelta elapsed_time =
1684 is_after ? IntervalEnd(animation_node) : IntervalStart(animation_node);
1685 MaybeDispatch(Document::kAnimationEndListener,
1686 event_type_names::kAnimationend, elapsed_time);
1687 }
1688
1689 // The following phase transitions trigger an animationcalcel event:
1690 // not idle and not after --> idle
1691 if (phase_change && current_phase == Timing::kPhaseNone &&
1692 previous_phase_ != Timing::kPhaseAfter) {
1693 // TODO(crbug.com/1059968): Determine if animation direction or playback
1694 // rate factor into the calculation of the elapsed time.
1695 double cancel_time = animation_node.GetCancelTime();
1696 MaybeDispatch(Document::kAnimationCancelListener,
1697 event_type_names::kAnimationcancel,
1698 AnimationTimeDelta::FromSecondsD(cancel_time));
1699 }
1700
1701 if (!phase_change && current_phase == Timing::kPhaseActive &&
1702 previous_iteration_ != current_iteration) {
1703 // We fire only a single event for all iterations that terminate
1704 // between a single pair of samples. See http://crbug.com/275263. For
1705 // compatibility with the existing implementation, this event uses
1706 // the elapsedTime for the first iteration in question.
1707 DCHECK(previous_iteration_ && current_iteration);
1708 const AnimationTimeDelta elapsed_time =
1709 IterationElapsedTime(animation_node, previous_iteration_.value());
1710 MaybeDispatch(Document::kAnimationIterationListener,
1711 event_type_names::kAnimationiteration, elapsed_time);
1712 }
1713
1714 previous_iteration_ = current_iteration;
1715 previous_phase_ = current_phase;
1716 }
1717
Trace(Visitor * visitor) const1718 void CSSAnimations::AnimationEventDelegate::Trace(Visitor* visitor) const {
1719 visitor->Trace(animation_target_);
1720 AnimationEffect::EventDelegate::Trace(visitor);
1721 }
1722
GetEventTarget() const1723 EventTarget* CSSAnimations::TransitionEventDelegate::GetEventTarget() const {
1724 return &EventPath::EventTargetRespectingTargetRules(*transition_target_);
1725 }
1726
OnEventCondition(const AnimationEffect & animation_node,Timing::Phase current_phase)1727 void CSSAnimations::TransitionEventDelegate::OnEventCondition(
1728 const AnimationEffect& animation_node,
1729 Timing::Phase current_phase) {
1730 if (current_phase == previous_phase_)
1731 return;
1732
1733 if (GetDocument().HasListenerType(Document::kTransitionRunListener)) {
1734 if (previous_phase_ == Timing::kPhaseNone) {
1735 EnqueueEvent(
1736 event_type_names::kTransitionrun,
1737 StartTimeFromDelay(animation_node.SpecifiedTiming().start_delay));
1738 }
1739 }
1740
1741 if (GetDocument().HasListenerType(Document::kTransitionStartListener)) {
1742 if ((current_phase == Timing::kPhaseActive ||
1743 current_phase == Timing::kPhaseAfter) &&
1744 (previous_phase_ == Timing::kPhaseNone ||
1745 previous_phase_ == Timing::kPhaseBefore)) {
1746 EnqueueEvent(
1747 event_type_names::kTransitionstart,
1748 StartTimeFromDelay(animation_node.SpecifiedTiming().start_delay));
1749 } else if ((current_phase == Timing::kPhaseActive ||
1750 current_phase == Timing::kPhaseBefore) &&
1751 previous_phase_ == Timing::kPhaseAfter) {
1752 // If the transition is progressing backwards it is considered to have
1753 // started at the end position.
1754 EnqueueEvent(event_type_names::kTransitionstart,
1755 animation_node.SpecifiedTiming().IterationDuration());
1756 }
1757 }
1758
1759 if (GetDocument().HasListenerType(Document::kTransitionEndListener)) {
1760 if (current_phase == Timing::kPhaseAfter &&
1761 (previous_phase_ == Timing::kPhaseActive ||
1762 previous_phase_ == Timing::kPhaseBefore ||
1763 previous_phase_ == Timing::kPhaseNone)) {
1764 EnqueueEvent(event_type_names::kTransitionend,
1765 animation_node.SpecifiedTiming().IterationDuration());
1766 } else if (current_phase == Timing::kPhaseBefore &&
1767 (previous_phase_ == Timing::kPhaseActive ||
1768 previous_phase_ == Timing::kPhaseAfter)) {
1769 // If the transition is progressing backwards it is considered to have
1770 // ended at the start position.
1771 EnqueueEvent(
1772 event_type_names::kTransitionend,
1773 StartTimeFromDelay(animation_node.SpecifiedTiming().start_delay));
1774 }
1775 }
1776
1777 if (GetDocument().HasListenerType(Document::kTransitionCancelListener)) {
1778 if (current_phase == Timing::kPhaseNone &&
1779 previous_phase_ != Timing::kPhaseAfter) {
1780 // Per the css-transitions-2 spec, transitioncancel is fired with the
1781 // "active time of the animation at the moment it was cancelled,
1782 // calculated using a fill mode of both".
1783 base::Optional<AnimationTimeDelta> cancel_active_time =
1784 CalculateActiveTime(animation_node.SpecifiedTiming().ActiveDuration(),
1785 Timing::FillMode::BOTH,
1786 animation_node.LocalTime(), previous_phase_,
1787 animation_node.SpecifiedTiming());
1788 // Being the FillMode::BOTH the only possibility to get a null
1789 // cancel_active_time is that previous_phase_ is kPhaseNone. This cannot
1790 // happen because we know that current_phase == kPhaseNone and
1791 // current_phase != previous_phase_ (see early return at the beginning).
1792 DCHECK(cancel_active_time);
1793 EnqueueEvent(event_type_names::kTransitioncancel,
1794 cancel_active_time.value());
1795 }
1796 }
1797
1798 previous_phase_ = current_phase;
1799 }
1800
EnqueueEvent(const WTF::AtomicString & type,const AnimationTimeDelta & elapsed_time)1801 void CSSAnimations::TransitionEventDelegate::EnqueueEvent(
1802 const WTF::AtomicString& type,
1803 const AnimationTimeDelta& elapsed_time) {
1804 String property_name =
1805 property_.IsCSSCustomProperty()
1806 ? property_.CustomPropertyName()
1807 : property_.GetCSSProperty().GetPropertyNameString();
1808 String pseudo_element =
1809 PseudoElement::PseudoElementNameForEvents(GetPseudoId());
1810 TransitionEvent* event = TransitionEvent::Create(
1811 type, property_name, elapsed_time, pseudo_element);
1812 event->SetTarget(GetEventTarget());
1813 GetDocument().EnqueueAnimationFrameEvent(event);
1814 }
1815
Trace(Visitor * visitor) const1816 void CSSAnimations::TransitionEventDelegate::Trace(Visitor* visitor) const {
1817 visitor->Trace(transition_target_);
1818 AnimationEffect::EventDelegate::Trace(visitor);
1819 }
1820
PropertiesForTransitionAll()1821 const StylePropertyShorthand& CSSAnimations::PropertiesForTransitionAll() {
1822 DEFINE_STATIC_LOCAL(Vector<const CSSProperty*>, properties, ());
1823 DEFINE_STATIC_LOCAL(StylePropertyShorthand, property_shorthand, ());
1824 if (properties.IsEmpty()) {
1825 for (CSSPropertyID id : CSSPropertyIDList()) {
1826 // Avoid creating overlapping transitions with perspective-origin and
1827 // transition-origin.
1828 if (id == CSSPropertyID::kWebkitPerspectiveOriginX ||
1829 id == CSSPropertyID::kWebkitPerspectiveOriginY ||
1830 id == CSSPropertyID::kWebkitTransformOriginX ||
1831 id == CSSPropertyID::kWebkitTransformOriginY ||
1832 id == CSSPropertyID::kWebkitTransformOriginZ)
1833 continue;
1834 const CSSProperty& property = CSSProperty::Get(id);
1835 if (property.IsInterpolable())
1836 properties.push_back(&property);
1837 }
1838 property_shorthand = StylePropertyShorthand(
1839 CSSPropertyID::kInvalid, properties.begin(), properties.size());
1840 }
1841 return property_shorthand;
1842 }
1843
1844 // Properties that affect animations are not allowed to be affected by
1845 // animations. https://drafts.csswg.org/web-animations/#not-animatable-section
IsAnimationAffectingProperty(const CSSProperty & property)1846 bool CSSAnimations::IsAnimationAffectingProperty(const CSSProperty& property) {
1847 switch (property.PropertyID()) {
1848 case CSSPropertyID::kAnimation:
1849 case CSSPropertyID::kAnimationDelay:
1850 case CSSPropertyID::kAnimationDirection:
1851 case CSSPropertyID::kAnimationDuration:
1852 case CSSPropertyID::kAnimationFillMode:
1853 case CSSPropertyID::kAnimationIterationCount:
1854 case CSSPropertyID::kAnimationName:
1855 case CSSPropertyID::kAnimationPlayState:
1856 case CSSPropertyID::kAnimationTimeline:
1857 case CSSPropertyID::kAnimationTimingFunction:
1858 case CSSPropertyID::kContentVisibility:
1859 case CSSPropertyID::kContain:
1860 case CSSPropertyID::kDirection:
1861 case CSSPropertyID::kDisplay:
1862 case CSSPropertyID::kTextCombineUpright:
1863 case CSSPropertyID::kTextOrientation:
1864 case CSSPropertyID::kTransition:
1865 case CSSPropertyID::kTransitionDelay:
1866 case CSSPropertyID::kTransitionDuration:
1867 case CSSPropertyID::kTransitionProperty:
1868 case CSSPropertyID::kTransitionTimingFunction:
1869 case CSSPropertyID::kUnicodeBidi:
1870 case CSSPropertyID::kWebkitWritingMode:
1871 case CSSPropertyID::kWillChange:
1872 case CSSPropertyID::kWritingMode:
1873 return true;
1874 default:
1875 return false;
1876 }
1877 }
1878
IsAffectedByKeyframesFromScope(const Element & element,const TreeScope & tree_scope)1879 bool CSSAnimations::IsAffectedByKeyframesFromScope(
1880 const Element& element,
1881 const TreeScope& tree_scope) {
1882 // Animated elements are affected by @keyframes rules from the same scope
1883 // and from their shadow sub-trees if they are shadow hosts.
1884 if (element.GetTreeScope() == tree_scope)
1885 return true;
1886 if (!IsShadowHost(element))
1887 return false;
1888 if (tree_scope.RootNode() == tree_scope.GetDocument())
1889 return false;
1890 return To<ShadowRoot>(tree_scope.RootNode()).host() == element;
1891 }
1892
IsAnimatingCustomProperties(const ElementAnimations * element_animations)1893 bool CSSAnimations::IsAnimatingCustomProperties(
1894 const ElementAnimations* element_animations) {
1895 return element_animations &&
1896 element_animations->GetEffectStack().AffectsProperties(
1897 IsCustomPropertyHandle);
1898 }
1899
IsAnimatingStandardProperties(const ElementAnimations * element_animations,const CSSBitset * bitset,KeyframeEffect::Priority priority)1900 bool CSSAnimations::IsAnimatingStandardProperties(
1901 const ElementAnimations* element_animations,
1902 const CSSBitset* bitset,
1903 KeyframeEffect::Priority priority) {
1904 if (!element_animations || !bitset)
1905 return false;
1906 return element_animations->GetEffectStack().AffectsProperties(*bitset,
1907 priority);
1908 }
1909
IsAnimatingFontAffectingProperties(const ElementAnimations * element_animations)1910 bool CSSAnimations::IsAnimatingFontAffectingProperties(
1911 const ElementAnimations* element_animations) {
1912 return element_animations &&
1913 element_animations->GetEffectStack().AffectsProperties(
1914 IsFontAffectingPropertyHandle);
1915 }
1916
IsAnimatingRevert(const ElementAnimations * element_animations)1917 bool CSSAnimations::IsAnimatingRevert(
1918 const ElementAnimations* element_animations) {
1919 return element_animations && element_animations->GetEffectStack().HasRevert();
1920 }
1921
Trace(Visitor * visitor) const1922 void CSSAnimations::Trace(Visitor* visitor) const {
1923 visitor->Trace(transitions_);
1924 visitor->Trace(pending_update_);
1925 visitor->Trace(running_animations_);
1926 visitor->Trace(previous_active_interpolations_for_standard_animations_);
1927 visitor->Trace(previous_active_interpolations_for_custom_animations_);
1928 }
1929
1930 } // namespace blink
1931