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/animation.h"
32
33 #include <limits>
34 #include <memory>
35
36 #include "base/metrics/histogram_macros.h"
37 #include "third_party/blink/public/platform/platform.h"
38 #include "third_party/blink/public/platform/task_type.h"
39 #include "third_party/blink/renderer/core/animation/animation_timeline.h"
40 #include "third_party/blink/renderer/core/animation/css/css_animations.h"
41 #include "third_party/blink/renderer/core/animation/document_timeline.h"
42 #include "third_party/blink/renderer/core/animation/element_animations.h"
43 #include "third_party/blink/renderer/core/animation/keyframe_effect.h"
44 #include "third_party/blink/renderer/core/animation/pending_animations.h"
45 #include "third_party/blink/renderer/core/animation/scroll_timeline.h"
46 #include "third_party/blink/renderer/core/animation/scroll_timeline_util.h"
47 #include "third_party/blink/renderer/core/css/properties/css_property_ref.h"
48 #include "third_party/blink/renderer/core/css/resolver/style_resolver.h"
49 #include "third_party/blink/renderer/core/css/style_change_reason.h"
50 #include "third_party/blink/renderer/core/dom/document.h"
51 #include "third_party/blink/renderer/core/dom/dom_node_ids.h"
52 #include "third_party/blink/renderer/core/events/animation_playback_event.h"
53 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
54 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
55 #include "third_party/blink/renderer/core/frame/web_feature.h"
56 #include "third_party/blink/renderer/core/inspector/inspector_trace_events.h"
57 #include "third_party/blink/renderer/core/paint/paint_layer.h"
58 #include "third_party/blink/renderer/core/probe/core_probes.h"
59 #include "third_party/blink/renderer/platform/animation/compositor_animation.h"
60 #include "third_party/blink/renderer/platform/animation/compositor_animation_timeline.h"
61 #include "third_party/blink/renderer/platform/bindings/microtask.h"
62 #include "third_party/blink/renderer/platform/bindings/script_forbidden_scope.h"
63 #include "third_party/blink/renderer/platform/heap/heap.h"
64 #include "third_party/blink/renderer/platform/heap/persistent.h"
65 #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
66 #include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
67 #include "third_party/blink/renderer/platform/wtf/math_extras.h"
68
69 namespace blink {
70
71 namespace {
72
73 enum PseudoPriority { kNone, kMarker, kBefore, kOther, kAfter };
74
NextSequenceNumber()75 unsigned NextSequenceNumber() {
76 static unsigned next = 0;
77 return ++next;
78 }
79
SecondsToMilliseconds(double seconds)80 double SecondsToMilliseconds(double seconds) {
81 return seconds * 1000;
82 }
83
MillisecondsToSeconds(double milliseconds)84 double MillisecondsToSeconds(double milliseconds) {
85 return milliseconds / 1000;
86 }
87
Max(base::Optional<double> a,double b)88 double Max(base::Optional<double> a, double b) {
89 if (a.has_value())
90 return std::max(a.value(), b);
91 return b;
92 }
93
Min(base::Optional<double> a,double b)94 double Min(base::Optional<double> a, double b) {
95 if (a.has_value())
96 return std::min(a.value(), b);
97 return b;
98 }
99
ConvertStringtoPriority(const String & pseudo)100 PseudoPriority ConvertStringtoPriority(const String& pseudo) {
101 if (pseudo.IsNull())
102 return PseudoPriority::kNone;
103 if (pseudo == "::marker")
104 return PseudoPriority::kMarker;
105 if (pseudo == "::before")
106 return PseudoPriority::kBefore;
107 if (pseudo == "::after")
108 return PseudoPriority::kAfter;
109 return PseudoPriority::kOther;
110 }
111
AnimationPriority(const Animation & animation)112 Animation::AnimationClassPriority AnimationPriority(
113 const Animation& animation) {
114 // According to the spec:
115 // https://drafts.csswg.org/web-animations/#animation-class,
116 // CSS tranisiton has a lower composite order than the CSS animation, and CSS
117 // animation has a lower composite order than other animations. Thus,CSS
118 // transitions are to appear before CSS animations and CSS animations are to
119 // appear before other animations
120 // TODO: When animations are disassociated from their element they are sorted
121 // by their sequence number, i.e. kDefaultPriority. See
122 // https://drafts.csswg.org/css-animations-2/#animation-composite-order and
123 // https://drafts.csswg.org/css-transitions-2/#animation-composite-order
124 Animation::AnimationClassPriority priority;
125 if (animation.IsCSSTransition() && animation.IsOwned())
126 priority = Animation::AnimationClassPriority::kCssTransitionPriority;
127 else if (animation.IsCSSAnimation() && animation.IsOwned())
128 priority = Animation::AnimationClassPriority::kCssAnimationPriority;
129 else
130 priority = Animation::AnimationClassPriority::kDefaultPriority;
131 return priority;
132 }
133
RecordCompositorAnimationFailureReasons(CompositorAnimations::FailureReasons failure_reasons)134 void RecordCompositorAnimationFailureReasons(
135 CompositorAnimations::FailureReasons failure_reasons) {
136 // UMA_HISTOGRAM_ENUMERATION requires that the enum_max must be strictly
137 // greater than the sample value. kFailureReasonCount doesn't include the
138 // kNoFailure value but the histograms do so adding the +1 is necessary.
139 // TODO(dcheng): Fix https://crbug.com/705169 so this isn't needed.
140 constexpr uint32_t kFailureReasonEnumMax =
141 CompositorAnimations::kFailureReasonCount + 1;
142
143 if (failure_reasons == CompositorAnimations::kNoFailure) {
144 UMA_HISTOGRAM_ENUMERATION(
145 "Blink.Animation.CompositedAnimationFailureReason",
146 CompositorAnimations::kNoFailure, kFailureReasonEnumMax);
147 return;
148 }
149
150 for (uint32_t i = 0; i < CompositorAnimations::kFailureReasonCount; i++) {
151 unsigned val = 1 << i;
152 if (failure_reasons & val) {
153 UMA_HISTOGRAM_ENUMERATION(
154 "Blink.Animation.CompositedAnimationFailureReason", i + 1,
155 kFailureReasonEnumMax);
156 }
157 }
158 }
159 } // namespace
160
Create(AnimationEffect * effect,AnimationTimeline * timeline,ExceptionState & exception_state)161 Animation* Animation::Create(AnimationEffect* effect,
162 AnimationTimeline* timeline,
163 ExceptionState& exception_state) {
164 DCHECK(timeline);
165 if (!IsA<DocumentTimeline>(timeline) && !timeline->IsScrollTimeline()) {
166 exception_state.ThrowDOMException(DOMExceptionCode::kNotSupportedError,
167 "Invalid timeline. Animation requires a "
168 "DocumentTimeline or ScrollTimeline");
169 return nullptr;
170 }
171 DCHECK(IsA<DocumentTimeline>(timeline) || timeline->IsScrollTimeline());
172
173 auto* context = timeline->GetDocument()->GetExecutionContext();
174 return MakeGarbageCollected<Animation>(context, timeline, effect);
175 }
176
Create(ExecutionContext * execution_context,AnimationEffect * effect,ExceptionState & exception_state)177 Animation* Animation::Create(ExecutionContext* execution_context,
178 AnimationEffect* effect,
179 ExceptionState& exception_state) {
180 Document* document = To<LocalDOMWindow>(execution_context)->document();
181 return Create(effect, &document->Timeline(), exception_state);
182 }
183
Create(ExecutionContext * execution_context,AnimationEffect * effect,AnimationTimeline * timeline,ExceptionState & exception_state)184 Animation* Animation::Create(ExecutionContext* execution_context,
185 AnimationEffect* effect,
186 AnimationTimeline* timeline,
187 ExceptionState& exception_state) {
188 if (!timeline) {
189 Animation* animation =
190 MakeGarbageCollected<Animation>(execution_context, nullptr, effect);
191 return animation;
192 }
193
194 return Create(effect, timeline, exception_state);
195 }
196
Animation(ExecutionContext * execution_context,AnimationTimeline * timeline,AnimationEffect * content)197 Animation::Animation(ExecutionContext* execution_context,
198 AnimationTimeline* timeline,
199 AnimationEffect* content)
200 : ExecutionContextLifecycleObserver(execution_context),
201 reported_play_state_(kIdle),
202 playback_rate_(1),
203 start_time_(),
204 hold_time_(),
205 sequence_number_(NextSequenceNumber()),
206 content_(content),
207 timeline_(timeline),
208 replace_state_(kActive),
209 is_paused_for_testing_(false),
210 is_composited_animation_disabled_for_testing_(false),
211 pending_pause_(false),
212 pending_play_(false),
213 pending_finish_notification_(false),
214 has_queued_microtask_(false),
215 outdated_(false),
216 finished_(true),
217 compositor_state_(nullptr),
218 compositor_pending_(false),
219 compositor_group_(0),
220 effect_suppressed_(false) {
221 if (content_) {
222 if (content_->GetAnimation()) {
223 content_->GetAnimation()->cancel();
224 content_->GetAnimation()->setEffect(nullptr);
225 }
226 content_->Attach(this);
227 }
228 document_ = timeline_ ? timeline_->GetDocument()
229 : To<LocalDOMWindow>(execution_context)->document();
230 DCHECK(document_);
231
232 if (timeline_)
233 timeline_->AnimationAttached(this);
234 else
235 document_->Timeline().AnimationAttached(this);
236
237 probe::DidCreateAnimation(document_, sequence_number_);
238 }
239
~Animation()240 Animation::~Animation() {
241 // Verify that compositor_animation_ has been disposed of.
242 DCHECK(!compositor_animation_);
243 }
244
Dispose()245 void Animation::Dispose() {
246 if (timeline_)
247 timeline_->AnimationDetached(this);
248 DestroyCompositorAnimation();
249 // If the DocumentTimeline and its Animation objects are
250 // finalized by the same GC, we have to eagerly clear out
251 // this Animation object's compositor animation registration.
252 DCHECK(!compositor_animation_);
253 }
254
EffectEnd() const255 double Animation::EffectEnd() const {
256 return content_ ? content_->SpecifiedTiming().EndTimeInternal() : 0;
257 }
258
Limited(base::Optional<double> current_time) const259 bool Animation::Limited(base::Optional<double> current_time) const {
260 return (EffectivePlaybackRate() < 0 && current_time <= 0) ||
261 (EffectivePlaybackRate() > 0 && current_time >= EffectEnd());
262 }
263
GetDocument() const264 Document* Animation::GetDocument() const {
265 return document_;
266 }
267
TimelineTime() const268 base::Optional<double> Animation::TimelineTime() const {
269 return timeline_ ? timeline_->CurrentTime() : base::nullopt;
270 }
271
272 // https://drafts.csswg.org/web-animations/#setting-the-current-time-of-an-animation.
setCurrentTimeForBinding(base::Optional<double> new_current_time,ExceptionState & exception_state)273 void Animation::setCurrentTimeForBinding(
274 base::Optional<double> new_current_time,
275 ExceptionState& exception_state) {
276 // TODO(crbug.com/924159): Update this after we add support for inactive
277 // timelines and unresolved timeline.currentTime
278 if (!new_current_time) {
279 // If the current time is resolved, then throw a TypeError.
280 if (CurrentTimeInternal()) {
281 exception_state.ThrowTypeError(
282 "currentTime may not be changed from resolved to unresolved");
283 }
284 return;
285 }
286
287 SetCurrentTimeInternal(MillisecondsToSeconds(new_current_time.value()));
288
289 // Synchronously resolve pending pause task.
290 if (pending_pause_) {
291 hold_time_ = MillisecondsToSeconds(new_current_time.value());
292 ApplyPendingPlaybackRate();
293 start_time_ = base::nullopt;
294 pending_pause_ = false;
295 if (ready_promise_)
296 ResolvePromiseMaybeAsync(ready_promise_.Get());
297 }
298
299 // Update the finished state.
300 UpdateFinishedState(UpdateType::kDiscontinuous, NotificationType::kAsync);
301
302 SetCompositorPending(/*effect_changed=*/false);
303
304 // Notify of potential state change.
305 NotifyProbe();
306 }
307
setCurrentTimeForBinding(double new_current_time,bool is_null,ExceptionState & exception_state)308 void Animation::setCurrentTimeForBinding(double new_current_time,
309 bool is_null,
310 ExceptionState& exception_state) {
311 setCurrentTimeForBinding(
312 is_null ? base::nullopt : base::make_optional(new_current_time),
313 exception_state);
314 }
315
setCurrentTime(double new_current_time,bool is_null,ExceptionState & exception_state)316 void Animation::setCurrentTime(double new_current_time,
317 bool is_null,
318 ExceptionState& exception_state) {
319 setCurrentTimeForBinding(
320 is_null ? base::nullopt : base::make_optional(new_current_time),
321 exception_state);
322 }
323
324 // https://drafts.csswg.org/web-animations/#setting-the-current-time-of-an-animation
325 // See steps for silently setting the current time. The preliminary step of
326 // handling an unresolved time are to be handled by the caller.
SetCurrentTimeInternal(double new_current_time)327 void Animation::SetCurrentTimeInternal(double new_current_time) {
328 DCHECK(std::isfinite(new_current_time));
329
330 base::Optional<double> previous_start_time = start_time_;
331 base::Optional<double> previous_hold_time = hold_time_;
332
333 // Update either the hold time or the start time.
334 if (hold_time_ || !start_time_ || !timeline_ || !timeline_->IsActive() ||
335 playback_rate_ == 0)
336 hold_time_ = new_current_time;
337 else
338 start_time_ = CalculateStartTime(new_current_time);
339
340 // Preserve invariant that we can only set a start time or a hold time in the
341 // absence of an active timeline.
342 if (!timeline_ || !timeline_->IsActive())
343 start_time_ = base::nullopt;
344
345 // Reset the previous current time.
346 previous_current_time_ = base::nullopt;
347
348 if (previous_start_time != start_time_ || previous_hold_time != hold_time_)
349 SetOutdated();
350 }
351
startTime() const352 base::Optional<double> Animation::startTime() const {
353 return start_time_
354 ? base::make_optional(SecondsToMilliseconds(start_time_.value()))
355 : base::nullopt;
356 }
357
startTime(bool & is_null) const358 double Animation::startTime(bool& is_null) const {
359 base::Optional<double> result = startTime();
360 is_null = !result;
361 return result.value_or(0);
362 }
363
364 // https://drafts.csswg.org/web-animations/#the-current-time-of-an-animation
currentTimeForBinding() const365 base::Optional<double> Animation::currentTimeForBinding() const {
366 // 1. If the animation’s hold time is resolved,
367 // The current time is the animation’s hold time.
368 if (hold_time_.has_value())
369 return SecondsToMilliseconds(hold_time_.value());
370
371 // 2. If any of the following are true:
372 // * the animation has no associated timeline, or
373 // * the associated timeline is inactive, or
374 // * the animation’s start time is unresolved.
375 // The current time is an unresolved time value.
376 if (!timeline_ || !timeline_->IsActive() || !start_time_)
377 return base::nullopt;
378
379 // 3. Otherwise,
380 // current time = (timeline time - start time) × playback rate
381 base::Optional<double> timeline_time = timeline_->CurrentTimeSeconds();
382
383 // An active timeline should always have a value, and since inactive timeline
384 // is handled in step 2 above, make sure that timeline_time has a value.
385 DCHECK(timeline_time.has_value());
386
387 double current_time =
388 (timeline_time.value() - start_time_.value()) * playback_rate_;
389 return SecondsToMilliseconds(current_time);
390 }
391
currentTimeForBinding(bool & is_null)392 double Animation::currentTimeForBinding(bool& is_null) {
393 base::Optional<double> result = currentTimeForBinding();
394 is_null = !result;
395 return result.value_or(0);
396 }
397
currentTime() const398 double Animation::currentTime() const {
399 return currentTimeForBinding().value_or(Timing::NullValue());
400 }
401
currentTime(bool & is_null)402 double Animation::currentTime(bool& is_null) {
403 base::Optional<double> result = currentTimeForBinding();
404 is_null = !result;
405 return result.value_or(0);
406 }
407
CurrentTimeInternal() const408 base::Optional<double> Animation::CurrentTimeInternal() const {
409 return hold_time_ ? hold_time_ : CalculateCurrentTime();
410 }
411
UnlimitedCurrentTime() const412 base::Optional<double> Animation::UnlimitedCurrentTime() const {
413 return CalculateAnimationPlayState() == kPaused || !start_time_
414 ? CurrentTimeInternal()
415 : CalculateCurrentTime();
416 }
417
playState() const418 String Animation::playState() const {
419 return PlayStateString();
420 }
421
PreCommit(int compositor_group,const PaintArtifactCompositor * paint_artifact_compositor,bool start_on_compositor)422 bool Animation::PreCommit(
423 int compositor_group,
424 const PaintArtifactCompositor* paint_artifact_compositor,
425 bool start_on_compositor) {
426 // TODO(crbug.com/916117): Revisit this condition as part of handling
427 // inactive timelines work.
428 if (timeline_ && timeline_->IsScrollTimeline() && !timeline_->IsActive())
429 return false;
430
431 bool soft_change =
432 compositor_state_ &&
433 (Paused() || compositor_state_->playback_rate != EffectivePlaybackRate());
434 bool hard_change =
435 compositor_state_ && (compositor_state_->effect_changed ||
436 compositor_state_->start_time != start_time_ ||
437 !compositor_state_->start_time || !start_time_);
438
439 // FIXME: softChange && !hardChange should generate a Pause/ThenStart,
440 // not a Cancel, but we can't communicate these to the compositor yet.
441
442 bool changed = soft_change || hard_change;
443 bool should_cancel = (!Playing() && compositor_state_) || changed;
444 bool should_start = Playing() && (!compositor_state_ || changed);
445
446 if (start_on_compositor && should_cancel && should_start &&
447 compositor_state_ && compositor_state_->pending_action == kStart) {
448 // Restarting but still waiting for a start time.
449 return false;
450 }
451
452 if (should_cancel) {
453 CancelAnimationOnCompositor();
454 compositor_state_ = nullptr;
455 }
456
457 DCHECK(!compositor_state_ || compositor_state_->start_time);
458
459 if (should_start) {
460 compositor_group_ = compositor_group;
461 if (start_on_compositor) {
462 CompositorAnimations::FailureReasons failure_reasons =
463 CheckCanStartAnimationOnCompositor(paint_artifact_compositor);
464 RecordCompositorAnimationFailureReasons(failure_reasons);
465
466 if (failure_reasons == CompositorAnimations::kNoFailure) {
467 CreateCompositorAnimation();
468 StartAnimationOnCompositor(paint_artifact_compositor);
469 compositor_state_ = std::make_unique<CompositorState>(*this);
470 } else {
471 CancelIncompatibleAnimationsOnCompositor();
472 }
473 }
474 }
475
476 return true;
477 }
478
PostCommit(double timeline_time)479 void Animation::PostCommit(double timeline_time) {
480 compositor_pending_ = false;
481
482 if (!compositor_state_ || compositor_state_->pending_action == kNone)
483 return;
484
485 DCHECK_EQ(kStart, compositor_state_->pending_action);
486 if (compositor_state_->start_time) {
487 DCHECK_EQ(start_time_.value(), compositor_state_->start_time.value());
488 compositor_state_->pending_action = kNone;
489 }
490 }
491
HasLowerCompositeOrdering(const Animation * animation1,const Animation * animation2,CompareAnimationsOrdering compare_animation_type)492 bool Animation::HasLowerCompositeOrdering(
493 const Animation* animation1,
494 const Animation* animation2,
495 CompareAnimationsOrdering compare_animation_type) {
496 AnimationClassPriority priority1 = AnimationPriority(*animation1);
497 AnimationClassPriority priority2 = AnimationPriority(*animation2);
498 if (priority1 != priority2)
499 return priority1 < priority2;
500
501 // If the the animation class is CssAnimation or CssTransition, then first
502 // compare the owning element of animation1 and animation2, sort two of them
503 // by tree order of their conrresponding owning element
504 // The specs:
505 // https://drafts.csswg.org/css-animations-2/#animation-composite-order
506 // https://drafts.csswg.org/css-transitions-2/#animation-composite-order
507 if (priority1 != kDefaultPriority && animation1->effect() &&
508 animation2->effect()) {
509 // TODO(crbug.com/1043778): Implement and use OwningElement on CSSAnimation
510 // and CSSTransition.
511 auto* effect1 = DynamicTo<KeyframeEffect>(animation1->effect());
512 auto* effect2 = DynamicTo<KeyframeEffect>(animation2->effect());
513 Element* target1 = effect1->target();
514 Element* target2 = effect2->target();
515
516 // The tree position comparison would take a longer time, thus affec the
517 // performance. We only do it when it comes to getAnimation.
518 if (*target1 != *target2) {
519 if (compare_animation_type == CompareAnimationsOrdering::kTreeOrder) {
520 return target1->compareDocumentPosition(target2) &
521 Node::kDocumentPositionFollowing;
522 } else {
523 return target1 < target2;
524 }
525 }
526
527 // A pseudo-element has a higher composite ordering than its originating
528 // element, but lower than the originating element's children.
529 // Two pseudo-elements sharing the same originating element are sorted
530 // as follows:
531 // ::marker
532 // ::before
533 // other pseudo-elements (ordered by selector)
534 // ::after
535 const String& pseudo1 = effect1->pseudoElement();
536 const String& pseudo2 = effect2->pseudoElement();
537 PseudoPriority priority1 = ConvertStringtoPriority(pseudo1);
538 PseudoPriority priority2 = ConvertStringtoPriority(pseudo2);
539
540 if (priority1 != priority2)
541 return priority1 < priority2;
542 if (priority1 == kOther && pseudo1 != pseudo2)
543 return CodeUnitCompareLessThan(pseudo1, pseudo2);
544
545 // For two animatiions with the same target (including the pseudo-element
546 // selector) compare the SequenceNumber for now.
547 // TODO(crbug.com/1045835): Sort animation1 and animation2 based on their
548 // position in the computed value of "animation-name" property for
549 // CSSAnimations and transition property for CSSTransitions.
550 return animation1->SequenceNumber() < animation2->SequenceNumber();
551 }
552 // If the anmiations are not-CSS WebAnimation just compare them via generation
553 // time/ sequence number.
554 return animation1->SequenceNumber() < animation2->SequenceNumber();
555 }
556
NotifyReady(double ready_time)557 void Animation::NotifyReady(double ready_time) {
558 // Complete the pending updates prior to updating the compositor state in
559 // order to ensure a correct start time for the compositor state without the
560 // need to duplicate the calculations.
561 if (pending_play_)
562 CommitPendingPlay(ready_time);
563 else if (pending_pause_)
564 CommitPendingPause(ready_time);
565
566 if (compositor_state_ && compositor_state_->pending_action == kStart) {
567 DCHECK(!compositor_state_->start_time);
568 compositor_state_->pending_action = kNone;
569 compositor_state_->start_time = start_time_;
570 }
571
572 // Notify of change to play state.
573 NotifyProbe();
574 }
575
576 // Microtask for playing an animation.
577 // Refer to Step 8.3 'pending play task' in
578 // https://drafts.csswg.org/web-animations/#playing-an-animation-section.
CommitPendingPlay(double ready_time)579 void Animation::CommitPendingPlay(double ready_time) {
580 DCHECK(!Timing::IsNull(ready_time));
581 DCHECK(start_time_ || hold_time_);
582 DCHECK(pending_play_);
583 pending_play_ = false;
584
585 // Update hold and start time.
586 if (hold_time_) {
587 // A: If animation’s hold time is resolved,
588 // A.1. Apply any pending playback rate on animation.
589 // A.2. Let new start time be the result of evaluating:
590 // ready time - hold time / playback rate for animation.
591 // If the playback rate is zero, let new start time be simply ready
592 // time.
593 // A.3. Set the start time of animation to new start time.
594 // A.4. If animation’s playback rate is not 0, make animation’s hold time
595 // unresolved.
596 ApplyPendingPlaybackRate();
597 if (playback_rate_ == 0) {
598 start_time_ = ready_time;
599 } else {
600 start_time_ = ready_time - hold_time_.value() / playback_rate_;
601 hold_time_ = base::nullopt;
602 }
603 } else if (start_time_ && pending_playback_rate_) {
604 // B: If animation’s start time is resolved and animation has a pending
605 // playback rate,
606 // B.1. Let current time to match be the result of evaluating:
607 // (ready time - start time) × playback rate for animation.
608 // B.2 Apply any pending playback rate on animation.
609 // B.3 If animation’s playback rate is zero, let animation’s hold time be
610 // current time to match.
611 // B.4 Let new start time be the result of evaluating:
612 // ready time - current time to match / playback rate for animation.
613 // If the playback rate is zero, let new start time be simply ready
614 // time.
615 // B.5 Set the start time of animation to new start time.
616 double current_time_to_match =
617 (ready_time - start_time_.value()) * playback_rate_;
618 ApplyPendingPlaybackRate();
619 if (playback_rate_ == 0) {
620 hold_time_ = current_time_to_match;
621 start_time_ = ready_time;
622 } else {
623 start_time_ = ready_time - current_time_to_match / playback_rate_;
624 }
625 }
626
627 // 8.4 Resolve animation’s current ready promise with animation.
628 if (ready_promise_ &&
629 ready_promise_->GetState() == AnimationPromise::kPending)
630 ResolvePromiseMaybeAsync(ready_promise_.Get());
631
632 // 8.5 Run the procedure to update an animation’s finished state for
633 // animation with the did seek flag set to false, and the synchronously
634 // notify flag set to false.
635 UpdateFinishedState(UpdateType::kContinuous, NotificationType::kAsync);
636 }
637
638 // Microtask for pausing an animation.
639 // Refer to step 7 'pending pause task' in
640 // https://drafts.csswg.org/web-animations-1/#pausing-an-animation-section
CommitPendingPause(double ready_time)641 void Animation::CommitPendingPause(double ready_time) {
642 DCHECK(pending_pause_);
643 pending_pause_ = false;
644
645 // 1. Let ready time be the time value of the timeline associated with
646 // animation at the moment when the user agent completed processing
647 // necessary to suspend playback of animation’s associated effect.
648 // 2. If animation’s start time is resolved and its hold time is not resolved,
649 // let animation’s hold time be the result of evaluating
650 // (ready time - start time) × playback rate.
651 if (start_time_ && !hold_time_)
652 hold_time_ = (ready_time - start_time_.value()) * playback_rate_;
653
654 // 3. Apply any pending playback rate on animation.
655 // 4. Make animation’s start time unresolved.
656 ApplyPendingPlaybackRate();
657 start_time_ = base::nullopt;
658
659 // 5. Resolve animation’s current ready promise with animation.
660 if (ready_promise_ &&
661 ready_promise_->GetState() == AnimationPromise::kPending)
662 ResolvePromiseMaybeAsync(ready_promise_.Get());
663
664 // 6. Run the procedure to update an animation’s finished state for animation
665 // with the did seek flag set to false (continuous), and the synchronously
666 // notify flag set to false.
667 UpdateFinishedState(UpdateType::kContinuous, NotificationType::kAsync);
668 }
669
Affects(const Element & element,const CSSProperty & property) const670 bool Animation::Affects(const Element& element,
671 const CSSProperty& property) const {
672 const auto* effect = DynamicTo<KeyframeEffect>(content_.Get());
673 if (!effect)
674 return false;
675
676 return (effect->EffectTarget() == &element) &&
677 effect->Affects(PropertyHandle(property));
678 }
679
CalculateStartTime(double current_time) const680 base::Optional<double> Animation::CalculateStartTime(
681 double current_time) const {
682 base::Optional<double> start_time;
683 if (timeline_) {
684 base::Optional<double> timeline_time = timeline_->CurrentTimeSeconds();
685 if (timeline_time)
686 start_time = timeline_time.value() - current_time / playback_rate_;
687 // TODO(crbug.com/916117): Handle NaN time for scroll-linked animations.
688 DCHECK(start_time || timeline_->IsScrollTimeline());
689 }
690 return start_time;
691 }
692
CalculateCurrentTime() const693 base::Optional<double> Animation::CalculateCurrentTime() const {
694 if (!start_time_ || !timeline_ || !timeline_->IsActive())
695 return base::nullopt;
696 base::Optional<double> timeline_time = timeline_->CurrentTimeSeconds();
697
698 if (!timeline_time) {
699 // timeline_time can be null only when the timeline is inactive
700 DCHECK(!timeline_->IsActive());
701 return base::nullopt;
702 }
703
704 return (timeline_time.value() - start_time_.value()) * playback_rate_;
705 }
706
707 // https://drafts.csswg.org/web-animations/#setting-the-start-time-of-an-animation
setStartTime(base::Optional<double> start_time_ms,ExceptionState & exception_state)708 void Animation::setStartTime(base::Optional<double> start_time_ms,
709 ExceptionState& exception_state) {
710 // TODO(crbug.com/916117): Implement setting start time for scroll-linked
711 // animations.
712 if (timeline_ && timeline_->IsScrollTimeline()) {
713 exception_state.ThrowDOMException(
714 DOMExceptionCode::kNotSupportedError,
715 "Scroll-linked WebAnimation currently does not support setting start"
716 " time.");
717 return;
718 }
719
720 bool had_start_time = start_time_.has_value();
721
722 // 1. Let timeline time be the current time value of the timeline that
723 // animation is associated with. If there is no timeline associated with
724 // animation or the associated timeline is inactive, let the timeline time
725 // be unresolved.
726 base::Optional<double> timeline_time = timeline_ && timeline_->IsActive()
727 ? timeline_->CurrentTimeSeconds()
728 : base::nullopt;
729
730 // 2. If timeline time is unresolved and new start time is resolved, make
731 // animation’s hold time unresolved.
732 // This preserves the invariant that when we don’t have an active timeline it
733 // is only possible to set either the start time or the animation’s current
734 // time.
735 if (!timeline_time && start_time_ms)
736 hold_time_ = base::nullopt;
737
738 // 3. Let previous current time be animation’s current time.
739 base::Optional<double> previous_current_time = CurrentTimeInternal();
740
741 // 4. Apply any pending playback rate on animation.
742 ApplyPendingPlaybackRate();
743
744 // 5. Set animation’s start time to new start time.
745 base::Optional<double> new_start_time;
746 if (start_time_ms)
747 new_start_time = MillisecondsToSeconds(start_time_ms.value());
748 start_time_ = new_start_time;
749
750 // 6. Update animation’s hold time based on the first matching condition from
751 // the following,
752 // 6a If new start time is resolved,
753 // If animation’s playback rate is not zero, make animation’s hold time
754 // unresolved.
755 // 6b Otherwise (new start time is unresolved),
756 // Set animation’s hold time to previous current time even if previous
757 // current time is unresolved.
758 if (start_time_) {
759 if (playback_rate_ != 0)
760 hold_time_ = base::nullopt;
761 } else {
762 hold_time_ = previous_current_time;
763 }
764
765 // 7. If animation has a pending play task or a pending pause task, cancel
766 // that task and resolve animation’s current ready promise with animation.
767 if (PendingInternal()) {
768 pending_pause_ = false;
769 pending_play_ = false;
770 if (ready_promise_ &&
771 ready_promise_->GetState() == AnimationPromise::kPending)
772 ResolvePromiseMaybeAsync(ready_promise_.Get());
773 }
774
775 // 8. Run the procedure to update an animation’s finished state for animation
776 // with the did seek flag set to true (discontinuous), and the
777 // synchronously notify flag set to false (async).
778 UpdateFinishedState(UpdateType::kDiscontinuous, NotificationType::kAsync);
779
780 // Update user agent.
781 base::Optional<double> new_current_time = CurrentTimeInternal();
782 if (previous_current_time != new_current_time) {
783 SetOutdated();
784 } else if (!had_start_time && start_time_) {
785 // Even though this animation is not outdated, time to effect change is
786 // infinity until start time is set.
787 ForceServiceOnNextFrame();
788 }
789 SetCompositorPending(/*effect_changed=*/false);
790
791 NotifyProbe();
792 }
793
setStartTime(double start_time_ms,bool is_null,ExceptionState & exception_state)794 void Animation::setStartTime(double start_time_ms,
795 bool is_null,
796 ExceptionState& exception_state) {
797 setStartTime(is_null ? base::nullopt : base::make_optional(start_time_ms),
798 exception_state);
799 }
800
801 // https://drafts.csswg.org/web-animations-1/#setting-the-associated-effect
setEffect(AnimationEffect * new_effect)802 void Animation::setEffect(AnimationEffect* new_effect) {
803 // 1. Let old effect be the current associated effect of animation, if any.
804 AnimationEffect* old_effect = content_;
805
806 // 2. If new effect is the same object as old effect, abort this procedure.
807 if (new_effect == old_effect)
808 return;
809
810 // 3. If animation has a pending pause task, reschedule that task to run as
811 // soon as animation is ready.
812 // 4. If animation has a pending play task, reschedule that task to run as
813 // soon as animation is ready to play new effect.
814 // No special action required for a reschedule. The pending_pause_ and
815 // pending_play_ flags remain unchanged.
816
817 // 5. If new effect is not null and if new effect is the associated effect of
818 // another previous animation, run the procedure to set the associated
819 // effect of an animation (this procedure) on previous animation passing
820 // null as new effect.
821 if (new_effect && new_effect->GetAnimation())
822 new_effect->GetAnimation()->setEffect(nullptr);
823
824 // 6. Let the associated effect of the animation be the new effect.
825 if (old_effect)
826 old_effect->Detach();
827 content_ = new_effect;
828 if (new_effect)
829 new_effect->Attach(this);
830 SetOutdated();
831
832 // 7. Run the procedure to update an animation’s finished state for animation
833 // with the did seek flag set to false (continuous), and the synchronously
834 // notify flag set to false (async).
835 UpdateFinishedState(UpdateType::kContinuous, NotificationType::kAsync);
836
837 SetCompositorPending(/*effect_change=*/true);
838
839 // Notify of a potential state change.
840 NotifyProbe();
841
842 // The effect is no longer associated with CSS properties.
843 if (new_effect) {
844 new_effect->SetIgnoreCssTimingProperties();
845 if (KeyframeEffect* keyframe_effect = DynamicTo<KeyframeEffect>(new_effect))
846 keyframe_effect->SetIgnoreCSSKeyframes();
847 }
848
849 // The remaining steps are for handling CSS animation and transition events.
850 // Both use an event delegate to dispatch events, which must be reattached to
851 // the new effect.
852
853 // When the animation no longer has an associated effect, calls to
854 // Animation::Update will no longer update the animation timing and,
855 // consequently, do not trigger animation or transition events.
856 // Each transitionrun or transitionstart requires a corresponding
857 // transitionend or transitioncancel.
858 // https://drafts.csswg.org/css-transitions-2/#event-dispatch
859 // Similarly, each animationstart requires a corresponding animationend or
860 // animationcancel.
861 // https://drafts.csswg.org/css-animations-2/#event-dispatch
862 AnimationEffect::EventDelegate* old_event_delegate =
863 old_effect ? old_effect->GetEventDelegate() : nullptr;
864 if (!new_effect && old_effect && old_event_delegate) {
865 // If the animation|transition has no target effect, the timing phase is set
866 // according to the first matching condition from below:
867 // If the current time is unresolved,
868 // The timing phase is ‘idle’.
869 // If current time < 0,
870 // The timing phase is ‘before’.
871 // Otherwise,
872 // The timing phase is ‘after’.
873 base::Optional<double> current_time = CurrentTimeInternal();
874 Timing::Phase phase;
875 if (!current_time)
876 phase = Timing::kPhaseNone;
877 else if (current_time < 0)
878 phase = Timing::kPhaseBefore;
879 else
880 phase = Timing::kPhaseAfter;
881 old_event_delegate->OnEventCondition(*old_effect, phase);
882 return;
883 }
884
885 if (!new_effect || !old_effect)
886 return;
887
888 // Use the original target for event targeting.
889 Element* target = To<KeyframeEffect>(old_effect)->target();
890 if (!target)
891 return;
892
893 // Attach an event delegate to the new effect.
894 AnimationEffect::EventDelegate* new_event_delegate =
895 CreateEventDelegate(target, old_event_delegate);
896 new_effect->SetEventDelegate(new_event_delegate);
897
898 // Force an update to the timing model to ensure correct ordering of
899 // animation or transition events.
900 Update(kTimingUpdateOnDemand);
901 }
902
PlayStateString() const903 String Animation::PlayStateString() const {
904 return PlayStateString(CalculateAnimationPlayState());
905 }
906
PlayStateString(AnimationPlayState play_state)907 const char* Animation::PlayStateString(AnimationPlayState play_state) {
908 switch (play_state) {
909 case kIdle:
910 return "idle";
911 case kPending:
912 return "pending";
913 case kRunning:
914 return "running";
915 case kPaused:
916 return "paused";
917 case kFinished:
918 return "finished";
919 default:
920 NOTREACHED();
921 return "";
922 }
923 }
924
925 // https://drafts.csswg.org/web-animations/#play-states
CalculateAnimationPlayState() const926 Animation::AnimationPlayState Animation::CalculateAnimationPlayState() const {
927 // 1. All of the following conditions are true:
928 // * The current time of animation is unresolved, and
929 // * animation does not have either a pending play task or a pending pause
930 // task,
931 // then idle.
932 if (!CurrentTimeInternal() && !PendingInternal())
933 return kIdle;
934
935 // 2. Either of the following conditions are true:
936 // * animation has a pending pause task, or
937 // * both the start time of animation is unresolved and it does not have a
938 // pending play task,
939 // then paused.
940 if (pending_pause_ || (!start_time_ && !pending_play_))
941 return kPaused;
942
943 // 3. For animation, current time is resolved and either of the following
944 // conditions are true:
945 // * animation’s effective playback rate > 0 and current time ≥ target
946 // effect end; or
947 // * animation’s effective playback rate < 0 and current time ≤ 0,
948 // then finished.
949 if (Limited())
950 return kFinished;
951
952 // 4. Otherwise
953 return kRunning;
954 }
955
PendingInternal() const956 bool Animation::PendingInternal() const {
957 return pending_pause_ || pending_play_;
958 }
959
pending() const960 bool Animation::pending() const {
961 return PendingInternal();
962 }
963
964 // https://drafts.csswg.org/web-animations-1/#reset-an-animations-pending-tasks.
ResetPendingTasks()965 void Animation::ResetPendingTasks() {
966 // 1. If animation does not have a pending play task or a pending pause task,
967 // abort this procedure.
968 if (!PendingInternal())
969 return;
970
971 // 2. If animation has a pending play task, cancel that task.
972 // 3. If animation has a pending pause task, cancel that task.
973 pending_play_ = false;
974 pending_pause_ = false;
975
976 // 4. Apply any pending playback rate on animation.
977 ApplyPendingPlaybackRate();
978
979 // 5. Reject animation’s current ready promise with a DOMException named
980 // "AbortError".
981 // 6. Let animation’s current ready promise be the result of creating a new
982 // resolved Promise object with value animation in the relevant Realm of
983 // animation.
984 if (ready_promise_)
985 RejectAndResetPromiseMaybeAsync(ready_promise_.Get());
986 }
987
988 // ----------------------------------------------
989 // Pause methods.
990 // ----------------------------------------------
991
992 // https://drafts.csswg.org/web-animations/#pausing-an-animation-section
pause(ExceptionState & exception_state)993 void Animation::pause(ExceptionState& exception_state) {
994 // TODO(crbug.com/916117): Implement pause for scroll-linked animations.
995 if (timeline_ && timeline_->IsScrollTimeline()) {
996 exception_state.ThrowDOMException(
997 DOMExceptionCode::kNotSupportedError,
998 "Scroll-linked WebAnimation currently does not support pause.");
999 return;
1000 }
1001
1002 // 1. If animation has a pending pause task, abort these steps.
1003 // 2. If the play state of animation is paused, abort these steps.
1004 if (pending_pause_ || CalculateAnimationPlayState() == kPaused)
1005 return;
1006
1007 // 3. If the animation’s current time is unresolved, perform the steps
1008 // according to the first matching condition from below:
1009 // 3a. If animation’s playback rate is ≥ 0,
1010 // Let animation’s hold time be zero.
1011 // 3b. Otherwise,
1012 // If associated effect end for animation is positive infinity, throw an
1013 // "InvalidStateError" DOMException and abort these steps. Otherwise,
1014 // let animation’s hold time be associated effect end.
1015 base::Optional<double> current_time = CurrentTimeInternal();
1016 if (!current_time) {
1017 if (playback_rate_ >= 0) {
1018 hold_time_ = 0;
1019 } else {
1020 if (EffectEnd() == std::numeric_limits<double>::infinity()) {
1021 exception_state.ThrowDOMException(
1022 DOMExceptionCode::kInvalidStateError,
1023 "Cannot play reversed Animation with infinite target effect end.");
1024 return;
1025 }
1026 hold_time_ = EffectEnd();
1027 }
1028 }
1029
1030 // 4. Let has pending ready promise be a boolean flag that is initially false.
1031 // 5. If animation has a pending play task, cancel that task and let has
1032 // pending ready promise be true.
1033 // 6. If has pending ready promise is false, set animation’s current ready
1034 // promise to a new promise in the relevant Realm of animation.
1035 if (pending_play_)
1036 pending_play_ = false;
1037 else if (ready_promise_)
1038 ready_promise_->Reset();
1039
1040 // 7. Schedule a task to be executed at the first possible moment after the
1041 // user agent has performed any processing necessary to suspend the
1042 // playback of animation’s associated effect, if any.
1043 pending_pause_ = true;
1044 pending_play_ = false;
1045
1046 SetOutdated();
1047 SetCompositorPending(false);
1048
1049 // 8. Run the procedure to update an animation’s finished state for animation
1050 // with the did seek flag set to false (continuous) , and thesynchronously
1051 // notify flag set to false.
1052 UpdateFinishedState(UpdateType::kContinuous, NotificationType::kAsync);
1053
1054 NotifyProbe();
1055 }
1056
1057 // ----------------------------------------------
1058 // Play methods.
1059 // ----------------------------------------------
1060
1061 // Refer to the unpause operation in the following spec:
1062 // https://drafts.csswg.org/css-animations-1/#animation-play-state
Unpause()1063 void Animation::Unpause() {
1064 if (CalculateAnimationPlayState() != kPaused)
1065 return;
1066 PlayInternal(AutoRewind::kDisabled, ASSERT_NO_EXCEPTION);
1067 }
1068
1069 // https://drafts.csswg.org/web-animations/#programming-interface.
play(ExceptionState & exception_state)1070 void Animation::play(ExceptionState& exception_state) {
1071 // Begin or resume playback of the animation by running the procedure to
1072 // play an animation passing true as the value of the auto-rewind flag.
1073 PlayInternal(AutoRewind::kEnabled, exception_state);
1074 }
1075
1076 // https://drafts.csswg.org/web-animations/#playing-an-animation-section.
PlayInternal(AutoRewind auto_rewind,ExceptionState & exception_state)1077 void Animation::PlayInternal(AutoRewind auto_rewind,
1078 ExceptionState& exception_state) {
1079 // 1. Let aborted pause be a boolean flag that is true if animation has a
1080 // pending pause task, and false otherwise.
1081 // 2. Let has pending ready promise be a boolean flag that is initially false.
1082 bool aborted_pause = pending_pause_;
1083 bool has_pending_ready_promise = false;
1084
1085 // 3. Perform the steps corresponding to the first matching condition from the
1086 // following, if any:
1087 //
1088 // 3a If animation’s effective playback rate > 0, the auto-rewind flag is true
1089 // and either animation’s:
1090 // current time is unresolved, or
1091 // current time < zero, or
1092 // current time ≥ target effect end,
1093 // Set animation’s hold time to zero.
1094 //
1095 // 3b If animation’s effective playback rate < 0, the auto-rewind flag is true
1096 // and either animation’s:
1097 // current time is unresolved, or
1098 // current time ≤ zero, or
1099 // current time > target effect end,
1100 // If target effect end is positive infinity, throw an "InvalidStateError"
1101 // DOMException and abort these steps. Otherwise, set animation’s hold time
1102 // to target effect end.
1103 //
1104 // 3c If animation’s effective playback rate = 0 and animation’s current time
1105 // is unresolved,
1106 // Set animation’s hold time to zero.
1107 double effective_playback_rate = EffectivePlaybackRate();
1108 base::Optional<double> current_time = CurrentTimeInternal();
1109
1110 // TODO(crbug.com/1012073): This should be able to be extracted into a
1111 // function in AnimationTimeline that each child class can override for their
1112 // own special behavior.
1113 double initial_hold_time = 0;
1114 if (timeline_ && timeline_->IsScrollTimeline() && timeline_->IsActive()) {
1115 base::Optional<double> timeline_time = timeline_->CurrentTimeSeconds();
1116 if (timeline_time) {
1117 // TODO(crbug.com/924159): Once inactive timelines are supported we need
1118 // to re-evaluate if it is desired behavior to adjust the hold time when
1119 // playback rate is set before play().
1120 initial_hold_time = timeline_time.value() * effective_playback_rate;
1121 }
1122 }
1123
1124 if (effective_playback_rate > 0 && auto_rewind == AutoRewind::kEnabled &&
1125 (!current_time || current_time < 0 || current_time >= EffectEnd())) {
1126 hold_time_ = initial_hold_time;
1127 } else if (effective_playback_rate < 0 &&
1128 auto_rewind == AutoRewind::kEnabled &&
1129 (!current_time || current_time <= 0 ||
1130 current_time > EffectEnd())) {
1131 if (EffectEnd() == std::numeric_limits<double>::infinity()) {
1132 exception_state.ThrowDOMException(
1133 DOMExceptionCode::kInvalidStateError,
1134 "Cannot play reversed Animation with infinite target effect end.");
1135 return;
1136 }
1137 hold_time_ = initial_hold_time + EffectEnd();
1138 } else if (effective_playback_rate == 0 && !current_time) {
1139 hold_time_ = initial_hold_time;
1140 }
1141
1142 // 4. If animation has a pending play task or a pending pause task,
1143 // 4.1 Cancel that task.
1144 // 4.2 Set has pending ready promise to true.
1145 if (pending_play_ || pending_pause_) {
1146 pending_play_ = pending_pause_ = false;
1147 has_pending_ready_promise = true;
1148 }
1149
1150 // 5. If the following three conditions are all satisfied:
1151 // animation’s hold time is unresolved, and
1152 // aborted pause is false, and
1153 // animation does not have a pending playback rate,
1154 // abort this procedure.
1155 if (!hold_time_ && !aborted_pause && !pending_playback_rate_)
1156 return;
1157
1158 // 6. If animation’s hold time is resolved, let its start time be unresolved.
1159 if (hold_time_)
1160 start_time_ = base::nullopt;
1161
1162 // 7. If has pending ready promise is false, let animation’s current ready
1163 // promise be a new promise in the relevant Realm of animation.
1164 if (ready_promise_ && !has_pending_ready_promise)
1165 ready_promise_->Reset();
1166
1167 // 8. Schedule a task to run as soon as animation is ready.
1168 pending_play_ = true;
1169 finished_ = false;
1170 SetOutdated();
1171 SetCompositorPending(/*effect_changed=*/false);
1172
1173 // 9. Run the procedure to update an animation’s finished state for animation
1174 // with the did seek flag set to false, and the synchronously notify flag
1175 // set to false.
1176 // Boolean valued arguments replaced with enumerated values for clarity.
1177 UpdateFinishedState(UpdateType::kContinuous, NotificationType::kAsync);
1178
1179 // Notify change to pending play or finished state.
1180 NotifyProbe();
1181 }
1182
1183 // https://drafts.csswg.org/web-animations/#reversing-an-animation-section
reverse(ExceptionState & exception_state)1184 void Animation::reverse(ExceptionState& exception_state) {
1185 // TODO(crbug.com/916117): Implement reverse for scroll-linked animations.
1186 if (timeline_ && timeline_->IsScrollTimeline()) {
1187 exception_state.ThrowDOMException(
1188 DOMExceptionCode::kNotSupportedError,
1189 "Scroll-linked WebAnimation currently does not support reverse.");
1190 return;
1191 }
1192
1193 // 1. If there is no timeline associated with animation, or the associated
1194 // timeline is inactive throw an "InvalidStateError" DOMException and abort
1195 // these steps.
1196 if (!timeline_ || !timeline_->IsActive()) {
1197 exception_state.ThrowDOMException(
1198 DOMExceptionCode::kInvalidStateError,
1199 "Cannot reverse an animation with no active timeline");
1200 return;
1201 }
1202
1203 // 2. Let original pending playback rate be animation’s pending playback rate.
1204 // 3. Let animation’s pending playback rate be the additive inverse of its
1205 // effective playback rate (i.e. -effective playback rate).
1206 base::Optional<double> original_pending_playback_rate =
1207 pending_playback_rate_;
1208 pending_playback_rate_ = -EffectivePlaybackRate();
1209
1210 // Resolve precision issue at zero.
1211 if (pending_playback_rate_.value() == -0)
1212 pending_playback_rate_ = 0;
1213
1214 // 4. Run the steps to play an animation for animation with the auto-rewind
1215 // flag set to true.
1216 // If the steps to play an animation throw an exception, set animation’s
1217 // pending playback rate to original pending playback rate and propagate
1218 // the exception.
1219 PlayInternal(AutoRewind::kEnabled, exception_state);
1220 if (exception_state.HadException())
1221 pending_playback_rate_ = original_pending_playback_rate;
1222 }
1223
1224 // ----------------------------------------------
1225 // Finish methods.
1226 // ----------------------------------------------
1227
1228 // https://drafts.csswg.org/web-animations/#finishing-an-animation-section
finish(ExceptionState & exception_state)1229 void Animation::finish(ExceptionState& exception_state) {
1230 if (!EffectivePlaybackRate()) {
1231 exception_state.ThrowDOMException(
1232 DOMExceptionCode::kInvalidStateError,
1233 "Cannot finish Animation with a playbackRate of 0.");
1234 return;
1235 }
1236 if (EffectivePlaybackRate() > 0 &&
1237 EffectEnd() == std::numeric_limits<double>::infinity()) {
1238 exception_state.ThrowDOMException(
1239 DOMExceptionCode::kInvalidStateError,
1240 "Cannot finish Animation with an infinite target effect end.");
1241 return;
1242 }
1243
1244 ApplyPendingPlaybackRate();
1245
1246 double new_current_time = playback_rate_ < 0 ? 0 : EffectEnd();
1247 SetCurrentTimeInternal(new_current_time);
1248
1249 if (!start_time_ && timeline_ && timeline_->IsActive())
1250 start_time_ = CalculateStartTime(new_current_time);
1251
1252 if (pending_pause_ && start_time_) {
1253 hold_time_ = base::nullopt;
1254 pending_pause_ = false;
1255 if (ready_promise_)
1256 ResolvePromiseMaybeAsync(ready_promise_.Get());
1257 }
1258 if (pending_play_ && start_time_) {
1259 pending_play_ = false;
1260 if (ready_promise_)
1261 ResolvePromiseMaybeAsync(ready_promise_.Get());
1262 }
1263
1264 SetOutdated();
1265 UpdateFinishedState(UpdateType::kDiscontinuous, NotificationType::kSync);
1266
1267 // Notify of change to finished state.
1268 NotifyProbe();
1269 }
1270
UpdateFinishedState(UpdateType update_type,NotificationType notification_type)1271 void Animation::UpdateFinishedState(UpdateType update_type,
1272 NotificationType notification_type) {
1273 bool did_seek = update_type == UpdateType::kDiscontinuous;
1274 // 1. Calculate the unconstrained current time. The dependency on did_seek is
1275 // required to accommodate timelines that may change direction. Without this
1276 // distinction, a once-finished animation would remain finished even when its
1277 // timeline progresses in the opposite direction.
1278 base::Optional<double> unconstrained_current_time =
1279 did_seek ? CurrentTimeInternal() : CalculateCurrentTime();
1280
1281 // 2. Conditionally update the hold time.
1282 if (unconstrained_current_time && start_time_ && !pending_play_ &&
1283 !pending_pause_) {
1284 // Can seek outside the bounds of the active effect. Set the hold time to
1285 // the unconstrained value of the current time in the event that this update
1286 // is the result of explicitly setting the current time and the new time
1287 // is out of bounds. An update due to a time tick should not snap the hold
1288 // value back to the boundary if previously set outside the normal effect
1289 // boundary. The value of previous current time is used to retain this
1290 // value.
1291 double playback_rate = EffectivePlaybackRate();
1292 if (playback_rate > 0 && unconstrained_current_time >= EffectEnd()) {
1293 hold_time_ = did_seek ? unconstrained_current_time
1294 : Max(previous_current_time_, EffectEnd());
1295 } else if (playback_rate < 0 && unconstrained_current_time <= 0) {
1296 hold_time_ = did_seek ? unconstrained_current_time
1297 : Min(previous_current_time_, 0);
1298 // Hack for resolving precision issue at zero.
1299 if (hold_time_.value() == -0)
1300 hold_time_ = 0;
1301 } else if (playback_rate != 0) {
1302 // Update start time and reset hold time.
1303 if (did_seek && hold_time_)
1304 start_time_ = CalculateStartTime(hold_time_.value());
1305 hold_time_ = base::nullopt;
1306 }
1307 }
1308
1309 // 3. Set the previous current time.
1310 previous_current_time_ = CurrentTimeInternal();
1311
1312 // 4. Set the current finished state.
1313 AnimationPlayState play_state = CalculateAnimationPlayState();
1314 if (play_state == kFinished) {
1315 // 5. Setup finished notification.
1316 if (notification_type == NotificationType::kSync)
1317 CommitFinishNotification();
1318 else
1319 ScheduleAsyncFinish();
1320 } else {
1321 // 6. If not finished but the current finished promise is already resolved,
1322 // create a new promise.
1323 finished_ = pending_finish_notification_ = false;
1324 if (finished_promise_ &&
1325 finished_promise_->GetState() == AnimationPromise::kResolved) {
1326 finished_promise_->Reset();
1327 }
1328 }
1329 }
1330
ScheduleAsyncFinish()1331 void Animation::ScheduleAsyncFinish() {
1332 // Run a task to handle the finished promise and event as a microtask. With
1333 // the exception of an explicit call to Animation::finish, it is important to
1334 // apply these updates asynchronously as it is possible to enter the finished
1335 // state temporarily.
1336 pending_finish_notification_ = true;
1337 if (!has_queued_microtask_) {
1338 Microtask::EnqueueMicrotask(
1339 WTF::Bind(&Animation::AsyncFinishMicrotask, WrapWeakPersistent(this)));
1340 has_queued_microtask_ = true;
1341 }
1342 }
1343
AsyncFinishMicrotask()1344 void Animation::AsyncFinishMicrotask() {
1345 // Resolve the finished promise and queue the finished event only if the
1346 // animation is still in a pending finished state. It is possible that the
1347 // transition was only temporary.
1348 if (pending_finish_notification_) {
1349 // A pending play or pause must resolve before the finish promise.
1350 if (PendingInternal() && timeline_)
1351 NotifyReady(timeline_->CurrentTimeSeconds().value_or(0));
1352 CommitFinishNotification();
1353 }
1354
1355 // This is a once callback and needs to be re-armed.
1356 has_queued_microtask_ = false;
1357 }
1358
1359 // Refer to 'finished notification steps' in
1360 // https://drafts.csswg.org/web-animations-1/#updating-the-finished-state
CommitFinishNotification()1361 void Animation::CommitFinishNotification() {
1362 pending_finish_notification_ = false;
1363
1364 // 1. If animation’s play state is not equal to finished, abort these steps.
1365 if (CalculateAnimationPlayState() != kFinished)
1366 return;
1367
1368 // 2. Resolve animation’s current finished promise object with animation.
1369 if (finished_promise_ &&
1370 finished_promise_->GetState() == AnimationPromise::kPending) {
1371 ResolvePromiseMaybeAsync(finished_promise_.Get());
1372 }
1373
1374 // 3. Create an AnimationPlaybackEvent, finishEvent.
1375 QueueFinishedEvent();
1376 }
1377
1378 // https://drafts.csswg.org/web-animations/#setting-the-playback-rate-of-an-animation
updatePlaybackRate(double playback_rate,ExceptionState & exception_state)1379 void Animation::updatePlaybackRate(double playback_rate,
1380 ExceptionState& exception_state) {
1381 // TODO(crbug.com/916117): Implement updatePlaybackRate for scroll-linked
1382 // animations.
1383 if (timeline_ && timeline_->IsScrollTimeline()) {
1384 exception_state.ThrowDOMException(
1385 DOMExceptionCode::kNotSupportedError,
1386 "Scroll-linked WebAnimation currently does not support"
1387 " updatePlaybackRate.");
1388 return;
1389 }
1390
1391 // 1. Let previous play state be animation’s play state.
1392 // 2. Let animation’s pending playback rate be new playback rate.
1393 AnimationPlayState play_state = CalculateAnimationPlayState();
1394 pending_playback_rate_ = playback_rate;
1395
1396 // 3. Perform the steps corresponding to the first matching condition from
1397 // below:
1398 //
1399 // 3a If animation has a pending play task or a pending pause task,
1400 // Abort these steps.
1401 if (PendingInternal())
1402 return;
1403
1404 switch (play_state) {
1405 // 3b If previous play state is idle or paused,
1406 // Apply any pending playback rate on animation.
1407 case kIdle:
1408 case kPaused:
1409 ApplyPendingPlaybackRate();
1410 break;
1411
1412 // 3c If previous play state is finished,
1413 // 3c.1 Let the unconstrained current time be the result of calculating
1414 // the current time of animation substituting an unresolved time
1415 // value for the hold time.
1416 // 3c.2 Let animation’s start time be the result of evaluating the
1417 // following expression:
1418 // timeline time - (unconstrained current time / pending playback rate)
1419 // Where timeline time is the current time value of the timeline associated
1420 // with animation.
1421 // 3c.3 If pending playback rate is zero, let animation’s start time be
1422 // timeline time.
1423 // 3c.4 Apply any pending playback rate on animation.
1424 // 3c.5 Run the procedure to update an animation’s finished state for
1425 // animation with the did seek flag set to false, and the
1426 // synchronously notify flag set to false.
1427 case kFinished: {
1428 base::Optional<double> unconstrained_current_time =
1429 CalculateCurrentTime();
1430 base::Optional<double> timeline_time =
1431 timeline_ ? timeline_->CurrentTimeSeconds() : base::nullopt;
1432 if (playback_rate) {
1433 if (timeline_time) {
1434 start_time_ = (timeline_time && unconstrained_current_time)
1435 ? base::make_optional<double>(
1436 (timeline_time.value() -
1437 unconstrained_current_time.value()) /
1438 playback_rate)
1439 : base::nullopt;
1440 }
1441 } else {
1442 start_time_ = timeline_time;
1443 }
1444 ApplyPendingPlaybackRate();
1445 UpdateFinishedState(UpdateType::kContinuous, NotificationType::kAsync);
1446 SetCompositorPending(false);
1447 SetOutdated();
1448 NotifyProbe();
1449 break;
1450 }
1451
1452 // 3d Otherwise,
1453 // Run the procedure to play an animation for animation with the
1454 // auto-rewind flag set to false.
1455 case kRunning:
1456 PlayInternal(AutoRewind::kDisabled, exception_state);
1457 break;
1458
1459 case kUnset:
1460 case kPending:
1461 NOTREACHED();
1462 }
1463 }
1464
finished(ScriptState * script_state)1465 ScriptPromise Animation::finished(ScriptState* script_state) {
1466 if (!finished_promise_) {
1467 finished_promise_ = MakeGarbageCollected<AnimationPromise>(
1468 ExecutionContext::From(script_state));
1469 // Do not report unhandled rejections of the finished promise.
1470 finished_promise_->MarkAsHandled();
1471
1472 // Defer resolving the finished promise if the finish notification task is
1473 // pending. The finished state could change before the next microtask
1474 // checkpoint.
1475 if (CalculateAnimationPlayState() == kFinished &&
1476 !pending_finish_notification_)
1477 finished_promise_->Resolve(this);
1478 }
1479 return finished_promise_->Promise(script_state->World());
1480 }
1481
ready(ScriptState * script_state)1482 ScriptPromise Animation::ready(ScriptState* script_state) {
1483 // Check for a pending state change prior to checking the ready promise, since
1484 // the pending check may force a style flush, which in turn could trigger a
1485 // reset of the ready promise when resolving a change to the
1486 // animationPlayState style.
1487 bool is_pending = pending();
1488 if (!ready_promise_) {
1489 ready_promise_ = MakeGarbageCollected<AnimationPromise>(
1490 ExecutionContext::From(script_state));
1491 // Do not report unhandled rejections of the ready promise.
1492 ready_promise_->MarkAsHandled();
1493 if (!is_pending)
1494 ready_promise_->Resolve(this);
1495 }
1496 return ready_promise_->Promise(script_state->World());
1497 }
1498
InterfaceName() const1499 const AtomicString& Animation::InterfaceName() const {
1500 return event_target_names::kAnimation;
1501 }
1502
GetExecutionContext() const1503 ExecutionContext* Animation::GetExecutionContext() const {
1504 return ExecutionContextLifecycleObserver::GetExecutionContext();
1505 }
1506
HasPendingActivity() const1507 bool Animation::HasPendingActivity() const {
1508 bool has_pending_promise =
1509 finished_promise_ &&
1510 finished_promise_->GetState() == AnimationPromise::kPending;
1511
1512 return pending_finished_event_ || pending_cancelled_event_ ||
1513 pending_remove_event_ || has_pending_promise ||
1514 (!finished_ && HasEventListeners(event_type_names::kFinish));
1515 }
1516
ContextDestroyed()1517 void Animation::ContextDestroyed() {
1518 finished_ = true;
1519 pending_finished_event_ = nullptr;
1520 pending_cancelled_event_ = nullptr;
1521 pending_remove_event_ = nullptr;
1522 }
1523
DispatchEventInternal(Event & event)1524 DispatchEventResult Animation::DispatchEventInternal(Event& event) {
1525 if (pending_finished_event_ == &event)
1526 pending_finished_event_ = nullptr;
1527 if (pending_cancelled_event_ == &event)
1528 pending_cancelled_event_ = nullptr;
1529 if (pending_remove_event_ == &event)
1530 pending_remove_event_ = nullptr;
1531 return EventTargetWithInlineData::DispatchEventInternal(event);
1532 }
1533
playbackRate() const1534 double Animation::playbackRate() const {
1535 return playback_rate_;
1536 }
1537
EffectivePlaybackRate() const1538 double Animation::EffectivePlaybackRate() const {
1539 return pending_playback_rate_.value_or(playback_rate_);
1540 }
1541
ApplyPendingPlaybackRate()1542 void Animation::ApplyPendingPlaybackRate() {
1543 if (pending_playback_rate_) {
1544 playback_rate_ = pending_playback_rate_.value();
1545 pending_playback_rate_ = base::nullopt;
1546 }
1547 }
1548
setPlaybackRate(double playback_rate,ExceptionState & exception_state)1549 void Animation::setPlaybackRate(double playback_rate,
1550 ExceptionState& exception_state) {
1551 // TODO(crbug.com/924159): Update this after we add support for inactive
1552 // timelines and unresolved timeline.currentTime
1553
1554 base::Optional<double> start_time_before = start_time_;
1555
1556 // 1. Clear any pending playback rate on animation.
1557 // 2. Let previous time be the value of the current time of animation before
1558 // changing the playback rate.
1559 // 3. Set the playback rate to new playback rate.
1560 // 4. If previous time is resolved, set the current time of animation to
1561 // previous time
1562 pending_playback_rate_ = base::nullopt;
1563 double previous_current_time = currentTime();
1564 playback_rate_ = playback_rate;
1565 if (!Timing::IsNull(previous_current_time)) {
1566 setCurrentTime(previous_current_time, false, exception_state);
1567 }
1568
1569 // Adds a UseCounter to check if setting playbackRate causes a compensatory
1570 // seek forcing a change in start_time_
1571 if (start_time_before && start_time_ != start_time_before &&
1572 CalculateAnimationPlayState() != kFinished) {
1573 UseCounter::Count(GetExecutionContext(),
1574 WebFeature::kAnimationSetPlaybackRateCompensatorySeek);
1575 }
1576
1577 SetCompositorPending(false);
1578 SetOutdated();
1579 NotifyProbe();
1580 }
1581
ClearOutdated()1582 void Animation::ClearOutdated() {
1583 if (!outdated_)
1584 return;
1585 outdated_ = false;
1586 if (timeline_)
1587 timeline_->ClearOutdatedAnimation(this);
1588 }
1589
SetOutdated()1590 void Animation::SetOutdated() {
1591 if (outdated_)
1592 return;
1593 outdated_ = true;
1594 if (timeline_)
1595 timeline_->SetOutdatedAnimation(this);
1596 }
1597
ForceServiceOnNextFrame()1598 void Animation::ForceServiceOnNextFrame() {
1599 if (timeline_)
1600 timeline_->ScheduleServiceOnNextFrame();
1601 }
1602
1603 CompositorAnimations::FailureReasons
CheckCanStartAnimationOnCompositor(const PaintArtifactCompositor * paint_artifact_compositor) const1604 Animation::CheckCanStartAnimationOnCompositor(
1605 const PaintArtifactCompositor* paint_artifact_compositor) const {
1606 CompositorAnimations::FailureReasons reasons =
1607 CheckCanStartAnimationOnCompositorInternal();
1608
1609 if (auto* keyframe_effect = DynamicTo<KeyframeEffect>(content_.Get())) {
1610 reasons |= keyframe_effect->CheckCanStartAnimationOnCompositor(
1611 paint_artifact_compositor, playback_rate_);
1612 }
1613 return reasons;
1614 }
1615
1616 CompositorAnimations::FailureReasons
CheckCanStartAnimationOnCompositorInternal() const1617 Animation::CheckCanStartAnimationOnCompositorInternal() const {
1618 CompositorAnimations::FailureReasons reasons =
1619 CompositorAnimations::kNoFailure;
1620
1621 if (is_composited_animation_disabled_for_testing_)
1622 reasons |= CompositorAnimations::kAcceleratedAnimationsDisabled;
1623
1624 if (EffectSuppressed())
1625 reasons |= CompositorAnimations::kEffectSuppressedByDevtools;
1626
1627 // An Animation with zero playback rate will produce no visual output, so
1628 // there is no reason to composite it.
1629 if (EffectivePlaybackRate() == 0)
1630 reasons |= CompositorAnimations::kInvalidAnimationOrEffect;
1631
1632 if (!CurrentTimeInternal())
1633 reasons |= CompositorAnimations::kInvalidAnimationOrEffect;
1634
1635 // Cannot composite an infinite duration animation with a negative playback
1636 // rate. TODO(crbug.com/1029167): Fix calculation of compositor timing to
1637 // enable compositing provided the iteration duration is finite. Having an
1638 // infinite number of iterations in the animation should not impede the
1639 // ability to composite the animation.
1640 if (std::isinf(EffectEnd()) && EffectivePlaybackRate() < 0)
1641 reasons |= CompositorAnimations::kInvalidAnimationOrEffect;
1642
1643 // An Animation without a timeline effectively isn't playing, so there is no
1644 // reason to composite it. Additionally, mutating the timeline playback rate
1645 // is a debug feature available via devtools; we don't support this on the
1646 // compositor currently and there is no reason to do so.
1647 auto* document_timeline = DynamicTo<DocumentTimeline>(*timeline_);
1648 if (!document_timeline || document_timeline->PlaybackRate() != 1)
1649 reasons |= CompositorAnimations::kInvalidAnimationOrEffect;
1650
1651 // An Animation without an effect cannot produce a visual, so there is no
1652 // reason to composite it.
1653 if (!IsA<KeyframeEffect>(content_.Get()))
1654 reasons |= CompositorAnimations::kInvalidAnimationOrEffect;
1655
1656 // An Animation that is not playing will not produce a visual, so there is no
1657 // reason to composite it.
1658 if (!Playing())
1659 reasons |= CompositorAnimations::kInvalidAnimationOrEffect;
1660
1661 // TODO(crbug.com/916117): Support accelerated scroll linked animations.
1662 if (timeline_->IsScrollTimeline())
1663 reasons |= CompositorAnimations::kInvalidAnimationOrEffect;
1664
1665 return reasons;
1666 }
1667
StartAnimationOnCompositor(const PaintArtifactCompositor * paint_artifact_compositor)1668 void Animation::StartAnimationOnCompositor(
1669 const PaintArtifactCompositor* paint_artifact_compositor) {
1670 DCHECK_EQ(CheckCanStartAnimationOnCompositor(paint_artifact_compositor),
1671 CompositorAnimations::kNoFailure);
1672 DCHECK(IsA<DocumentTimeline>(*timeline_));
1673
1674 bool reversed = EffectivePlaybackRate() < 0;
1675
1676 base::Optional<double> start_time = base::nullopt;
1677 double time_offset = 0;
1678 // Start the animation on the compositor with either a start time or time
1679 // offset. The start time is used for synchronous updates where the
1680 // compositor start time must be in precise alignment with the specified time
1681 // (e.g. after calling setStartTime). Asynchronous updates such as updating
1682 // the playback rate preserve current time even if the start time is set.
1683 // Asynchronous updates have an associated pending play or pending pause
1684 // task associated with them.
1685 if (start_time_ && !PendingInternal()) {
1686 start_time = To<DocumentTimeline>(*timeline_)
1687 .ZeroTime()
1688 .since_origin()
1689 .InSecondsF() +
1690 start_time_.value();
1691 if (reversed) {
1692 start_time =
1693 start_time.value() - (EffectEnd() / fabs(EffectivePlaybackRate()));
1694 }
1695 } else {
1696 base::Optional<double> current_time = CurrentTimeInternal();
1697 DCHECK(current_time);
1698 time_offset =
1699 reversed ? EffectEnd() - current_time.value() : current_time.value();
1700 time_offset = time_offset / fabs(EffectivePlaybackRate());
1701 }
1702
1703 DCHECK(!start_time || !Timing::IsNull(start_time.value()));
1704 DCHECK_NE(compositor_group_, 0);
1705 DCHECK(To<KeyframeEffect>(content_.Get()));
1706 DCHECK(std::isfinite(time_offset));
1707 To<KeyframeEffect>(content_.Get())
1708 ->StartAnimationOnCompositor(compositor_group_, start_time,
1709 base::TimeDelta::FromSecondsD(time_offset),
1710 EffectivePlaybackRate());
1711 }
1712
1713 // TODO(crbug.com/960944): Rename to SetPendingCommit. This method handles both
1714 // composited and non-composited animations. The use of 'compositor' in the name
1715 // is confusing.
SetCompositorPending(bool effect_changed)1716 void Animation::SetCompositorPending(bool effect_changed) {
1717 // Cannot play an animation with a null timeline.
1718 // TODO(crbug.com/827626) Revisit once timelines are mutable as there will be
1719 // work to do if the timeline is reset.
1720 if (!timeline_)
1721 return;
1722
1723 // FIXME: KeyframeEffect could notify this directly?
1724 if (!HasActiveAnimationsOnCompositor()) {
1725 DestroyCompositorAnimation();
1726 compositor_state_.reset();
1727 }
1728 if (effect_changed && compositor_state_) {
1729 compositor_state_->effect_changed = true;
1730 }
1731 if (compositor_pending_ || is_paused_for_testing_) {
1732 return;
1733 }
1734 // In general, we need to update the compositor-side if anything has changed
1735 // on the blink version of the animation. There is also an edge case; if
1736 // neither the compositor nor blink side have a start time we still have to
1737 // sync them. This can happen if the blink side animation was started, the
1738 // compositor side hadn't started on its side yet, and then the blink side
1739 // start time was cleared (e.g. by setting current time).
1740 if (PendingInternal() || !compositor_state_ ||
1741 compositor_state_->effect_changed ||
1742 compositor_state_->playback_rate != EffectivePlaybackRate() ||
1743 compositor_state_->start_time != start_time_ ||
1744 !compositor_state_->start_time || !start_time_) {
1745 compositor_pending_ = true;
1746 document_->GetPendingAnimations().Add(this);
1747 }
1748 }
1749
CancelAnimationOnCompositor()1750 void Animation::CancelAnimationOnCompositor() {
1751 if (HasActiveAnimationsOnCompositor()) {
1752 To<KeyframeEffect>(content_.Get())
1753 ->CancelAnimationOnCompositor(GetCompositorAnimation());
1754 }
1755
1756 DestroyCompositorAnimation();
1757 }
1758
RestartAnimationOnCompositor()1759 void Animation::RestartAnimationOnCompositor() {
1760 if (!HasActiveAnimationsOnCompositor())
1761 return;
1762 if (To<KeyframeEffect>(content_.Get())
1763 ->CancelAnimationOnCompositor(GetCompositorAnimation()))
1764 SetCompositorPending(true);
1765 }
1766
CancelIncompatibleAnimationsOnCompositor()1767 void Animation::CancelIncompatibleAnimationsOnCompositor() {
1768 if (auto* keyframe_effect = DynamicTo<KeyframeEffect>(content_.Get()))
1769 keyframe_effect->CancelIncompatibleAnimationsOnCompositor();
1770 }
1771
HasActiveAnimationsOnCompositor()1772 bool Animation::HasActiveAnimationsOnCompositor() {
1773 auto* keyframe_effect = DynamicTo<KeyframeEffect>(content_.Get());
1774 if (!keyframe_effect)
1775 return false;
1776
1777 return keyframe_effect->HasActiveAnimationsOnCompositor();
1778 }
1779
1780 // Update current time of the animation. Refer to step 1 in:
1781 // https://drafts.csswg.org/web-animations/#update-animations-and-send-events
Update(TimingUpdateReason reason)1782 bool Animation::Update(TimingUpdateReason reason) {
1783 // Due to the hierarchical nature of the timing model, updating the current
1784 // time of an animation also involves:
1785 // * Running the update an animation’s finished state procedure.
1786 // * Queueing animation events.
1787 if (!timeline_)
1788 return false;
1789
1790 ClearOutdated();
1791 bool idle = CalculateAnimationPlayState() == kIdle;
1792 if (!idle)
1793 UpdateFinishedState(UpdateType::kContinuous, NotificationType::kAsync);
1794
1795 if (content_) {
1796 base::Optional<double> inherited_time = idle || !timeline_->CurrentTime()
1797 ? base::nullopt
1798 : CurrentTimeInternal();
1799
1800 // Special case for end-exclusivity when playing backwards.
1801 if (inherited_time == 0 && EffectivePlaybackRate() < 0)
1802 inherited_time = -1;
1803
1804 content_->UpdateInheritedTime(inherited_time, reason);
1805 // After updating the animation time if the animation is no longer current
1806 // blink will no longer composite the element (see
1807 // CompositingReasonFinder::RequiresCompositingFor*Animation). We cancel any
1808 // running compositor animation so that we don't try to animate the
1809 // non-existent element on the compositor.
1810 if (!content_->IsCurrent())
1811 CancelAnimationOnCompositor();
1812 }
1813
1814 if (reason == kTimingUpdateForAnimationFrame) {
1815 if (idle || CalculateAnimationPlayState() == kFinished) {
1816 // TODO(crbug.com/1029348): Per spec, we should have a microtask
1817 // checkpoint right after the update cycle. Once this is fixed we should
1818 // no longer need to force a synchronous resolution here.
1819 AsyncFinishMicrotask();
1820 finished_ = true;
1821 }
1822 }
1823
1824 DCHECK(!outdated_);
1825 NotifyProbe();
1826
1827 return !finished_ || TimeToEffectChange();
1828 }
1829
QueueFinishedEvent()1830 void Animation::QueueFinishedEvent() {
1831 const AtomicString& event_type = event_type_names::kFinish;
1832 if (GetExecutionContext() && HasEventListeners(event_type)) {
1833 base::Optional<double> event_current_time = CurrentTimeInternal();
1834 if (event_current_time)
1835 event_current_time = SecondsToMilliseconds(event_current_time.value());
1836 // TODO(crbug.com/916117): Handle NaN values for scroll-linked animations.
1837 pending_finished_event_ = MakeGarbageCollected<AnimationPlaybackEvent>(
1838 event_type, event_current_time, TimelineTime());
1839 pending_finished_event_->SetTarget(this);
1840 pending_finished_event_->SetCurrentTarget(this);
1841 document_->EnqueueAnimationFrameEvent(pending_finished_event_);
1842 }
1843 }
1844
UpdateIfNecessary()1845 void Animation::UpdateIfNecessary() {
1846 // Update is a no-op if there is no timeline_, and will not reset the outdated
1847 // state in this case.
1848 if (!timeline_)
1849 return;
1850
1851 if (Outdated())
1852 Update(kTimingUpdateOnDemand);
1853 DCHECK(!Outdated());
1854 }
1855
EffectInvalidated()1856 void Animation::EffectInvalidated() {
1857 SetOutdated();
1858 UpdateFinishedState(UpdateType::kContinuous, NotificationType::kAsync);
1859 // FIXME: Needs to consider groups when added.
1860 SetCompositorPending(true);
1861 }
1862
IsEventDispatchAllowed() const1863 bool Animation::IsEventDispatchAllowed() const {
1864 return Paused() || start_time_;
1865 }
1866
TimeToEffectChange()1867 base::Optional<AnimationTimeDelta> Animation::TimeToEffectChange() {
1868 DCHECK(!outdated_);
1869 if (!start_time_ || hold_time_ || !playback_rate_)
1870 return base::nullopt;
1871
1872 if (!content_) {
1873 base::Optional<double> current_time = CurrentTimeInternal();
1874 DCHECK(current_time);
1875 return AnimationTimeDelta::FromSecondsD(-current_time.value() /
1876 playback_rate_);
1877 }
1878
1879 double result =
1880 playback_rate_ > 0
1881 ? content_->TimeToForwardsEffectChange().InSecondsF() / playback_rate_
1882 : content_->TimeToReverseEffectChange().InSecondsF() /
1883 -playback_rate_;
1884
1885 return !HasActiveAnimationsOnCompositor() &&
1886 content_->GetPhase() == Timing::kPhaseActive
1887 ? AnimationTimeDelta()
1888 : AnimationTimeDelta::FromSecondsD(result);
1889 }
1890
cancel()1891 void Animation::cancel() {
1892 double current_time_before_cancel = CurrentTimeInternal().value_or(0);
1893 AnimationPlayState initial_play_state = CalculateAnimationPlayState();
1894 if (initial_play_state != kIdle) {
1895 ResetPendingTasks();
1896
1897 if (finished_promise_) {
1898 if (finished_promise_->GetState() == AnimationPromise::kPending)
1899 RejectAndResetPromiseMaybeAsync(finished_promise_.Get());
1900 else
1901 finished_promise_->Reset();
1902 }
1903
1904 const AtomicString& event_type = event_type_names::kCancel;
1905 if (GetExecutionContext() && HasEventListeners(event_type)) {
1906 base::Optional<double> event_current_time = base::nullopt;
1907 // TODO(crbug.com/916117): Handle NaN values for scroll-linked
1908 // animations.
1909 pending_cancelled_event_ = MakeGarbageCollected<AnimationPlaybackEvent>(
1910 event_type, event_current_time, TimelineTime());
1911 pending_cancelled_event_->SetTarget(this);
1912 pending_cancelled_event_->SetCurrentTarget(this);
1913 document_->EnqueueAnimationFrameEvent(pending_cancelled_event_);
1914 }
1915 } else {
1916 // Quietly reset without rejecting promises.
1917 pending_playback_rate_ = base::nullopt;
1918 pending_pause_ = pending_play_ = false;
1919 }
1920
1921 hold_time_ = base::nullopt;
1922 start_time_ = base::nullopt;
1923
1924 // Apply changes synchronously.
1925 SetCompositorPending(/*effect_changed=*/false);
1926 SetOutdated();
1927
1928 // Force dispatch of canceled event.
1929 if (content_)
1930 content_->SetCancelTime(current_time_before_cancel);
1931 Update(kTimingUpdateOnDemand);
1932
1933 // Notify of change to canceled state.
1934 NotifyProbe();
1935 }
1936
CreateCompositorAnimation()1937 void Animation::CreateCompositorAnimation() {
1938 if (Platform::Current()->IsThreadedAnimationEnabled() &&
1939 !compositor_animation_) {
1940 compositor_animation_ = CompositorAnimationHolder::Create(this);
1941 AttachCompositorTimeline();
1942 }
1943
1944 AttachCompositedLayers();
1945 }
1946
DestroyCompositorAnimation()1947 void Animation::DestroyCompositorAnimation() {
1948 DetachCompositedLayers();
1949
1950 if (compositor_animation_) {
1951 DetachCompositorTimeline();
1952 compositor_animation_->Detach();
1953 compositor_animation_ = nullptr;
1954 }
1955 }
1956
AttachCompositorTimeline()1957 void Animation::AttachCompositorTimeline() {
1958 DCHECK(compositor_animation_);
1959
1960 // Register ourselves on the compositor timeline. This will cause our cc-side
1961 // animation animation to be registered.
1962 CompositorAnimationTimeline* compositor_timeline =
1963 timeline_ ? timeline_->EnsureCompositorTimeline() : nullptr;
1964 if (!compositor_timeline)
1965 return;
1966
1967 compositor_timeline->AnimationAttached(*this);
1968 if (compositor_timeline->GetAnimationTimeline()->IsScrollTimeline())
1969 document_->AttachCompositorTimeline(compositor_timeline);
1970 }
1971
DetachCompositorTimeline()1972 void Animation::DetachCompositorTimeline() {
1973 DCHECK(compositor_animation_);
1974
1975 CompositorAnimationTimeline* compositor_timeline =
1976 timeline_ ? timeline_->CompositorTimeline() : nullptr;
1977 if (!compositor_timeline)
1978 return;
1979
1980 compositor_timeline->AnimationDestroyed(*this);
1981
1982 if (compositor_timeline->GetAnimationTimeline()->IsScrollTimeline())
1983 document_->DetachCompositorTimeline(compositor_timeline);
1984 }
1985
UpdateCompositorScrollTimeline()1986 void Animation::UpdateCompositorScrollTimeline() {
1987 if (!compositor_animation_ || !timeline_)
1988 return;
1989 Node* scroll_source = To<ScrollTimeline>(*timeline_).ResolvedScrollSource();
1990 LayoutBox* box = scroll_source ? scroll_source->GetLayoutBox() : nullptr;
1991
1992 base::Optional<double> start_scroll_offset;
1993 base::Optional<double> end_scroll_offset;
1994 if (box) {
1995 double current_offset;
1996 double max_offset;
1997 To<ScrollTimeline>(*timeline_)
1998 .GetCurrentAndMaxOffset(box, current_offset, max_offset);
1999
2000 double resolved_start_scroll_offset = 0;
2001 double resolved_end_scroll_offset = max_offset;
2002 To<ScrollTimeline>(*timeline_)
2003 .ResolveScrollStartAndEnd(box, max_offset, resolved_start_scroll_offset,
2004 resolved_end_scroll_offset);
2005 start_scroll_offset = resolved_start_scroll_offset;
2006 end_scroll_offset = resolved_end_scroll_offset;
2007 }
2008 compositor_animation_->GetAnimation()->UpdateScrollTimeline(
2009 scroll_timeline_util::GetCompositorScrollElementId(scroll_source),
2010 start_scroll_offset, end_scroll_offset);
2011 }
2012
AttachCompositedLayers()2013 void Animation::AttachCompositedLayers() {
2014 if (!compositor_animation_)
2015 return;
2016
2017 DCHECK(content_);
2018 DCHECK(IsA<KeyframeEffect>(*content_));
2019
2020 To<KeyframeEffect>(content_.Get())->AttachCompositedLayers();
2021 }
2022
DetachCompositedLayers()2023 void Animation::DetachCompositedLayers() {
2024 if (compositor_animation_ &&
2025 compositor_animation_->GetAnimation()->IsElementAttached())
2026 compositor_animation_->GetAnimation()->DetachElement();
2027 }
2028
NotifyAnimationStarted(double monotonic_time,int group)2029 void Animation::NotifyAnimationStarted(double monotonic_time, int group) {
2030 document_->GetPendingAnimations().NotifyCompositorAnimationStarted(
2031 monotonic_time, group);
2032 }
2033
AddedEventListener(const AtomicString & event_type,RegisteredEventListener & registered_listener)2034 void Animation::AddedEventListener(
2035 const AtomicString& event_type,
2036 RegisteredEventListener& registered_listener) {
2037 EventTargetWithInlineData::AddedEventListener(event_type,
2038 registered_listener);
2039 if (event_type == event_type_names::kFinish)
2040 UseCounter::Count(GetExecutionContext(), WebFeature::kAnimationFinishEvent);
2041 }
2042
PauseForTesting(double pause_time)2043 void Animation::PauseForTesting(double pause_time) {
2044 // Do not restart a canceled animation.
2045 if (CalculateAnimationPlayState() == kIdle)
2046 return;
2047
2048 // Pause a running animation, or update the hold time of a previously paused
2049 // animation.
2050 SetCurrentTimeInternal(pause_time);
2051 if (HasActiveAnimationsOnCompositor()) {
2052 base::Optional<double> current_time = CurrentTimeInternal();
2053 DCHECK(current_time);
2054 To<KeyframeEffect>(content_.Get())
2055 ->PauseAnimationForTestingOnCompositor(
2056 base::TimeDelta::FromSecondsD(current_time.value()));
2057 }
2058
2059 // Do not wait for animation ready to lock in the hold time. Otherwise,
2060 // the pause won't take effect until the next frame and the hold time will
2061 // potentially drift.
2062 is_paused_for_testing_ = true;
2063 pending_pause_ = false;
2064 pending_play_ = false;
2065 hold_time_ = pause_time;
2066 start_time_ = base::nullopt;
2067 }
2068
SetEffectSuppressed(bool suppressed)2069 void Animation::SetEffectSuppressed(bool suppressed) {
2070 effect_suppressed_ = suppressed;
2071 if (suppressed)
2072 CancelAnimationOnCompositor();
2073 }
2074
DisableCompositedAnimationForTesting()2075 void Animation::DisableCompositedAnimationForTesting() {
2076 is_composited_animation_disabled_for_testing_ = true;
2077 CancelAnimationOnCompositor();
2078 }
2079
InvalidateKeyframeEffect(const TreeScope & tree_scope)2080 void Animation::InvalidateKeyframeEffect(const TreeScope& tree_scope) {
2081 auto* keyframe_effect = DynamicTo<KeyframeEffect>(content_.Get());
2082 if (!keyframe_effect)
2083 return;
2084
2085 Element* target = keyframe_effect->EffectTarget();
2086
2087 // TODO(alancutter): Remove dependency of this function on CSSAnimations.
2088 // This function makes the incorrect assumption that the animation uses
2089 // @keyframes for its effect model when it may instead be using JS provided
2090 // keyframes.
2091 if (target &&
2092 CSSAnimations::IsAffectedByKeyframesFromScope(*target, tree_scope)) {
2093 target->SetNeedsStyleRecalc(kLocalStyleChange,
2094 StyleChangeReasonForTracing::Create(
2095 style_change_reason::kStyleSheetChange));
2096 }
2097 }
2098
ResolvePromiseMaybeAsync(AnimationPromise * promise)2099 void Animation::ResolvePromiseMaybeAsync(AnimationPromise* promise) {
2100 if (ScriptForbiddenScope::IsScriptForbidden()) {
2101 GetExecutionContext()
2102 ->GetTaskRunner(TaskType::kDOMManipulation)
2103 ->PostTask(FROM_HERE,
2104 WTF::Bind(&AnimationPromise::Resolve<Animation*>,
2105 WrapPersistent(promise), WrapPersistent(this)));
2106 } else {
2107 promise->Resolve(this);
2108 }
2109 }
2110
RejectAndResetPromise(AnimationPromise * promise)2111 void Animation::RejectAndResetPromise(AnimationPromise* promise) {
2112 promise->Reject(
2113 MakeGarbageCollected<DOMException>(DOMExceptionCode::kAbortError));
2114 promise->Reset();
2115 }
2116
RejectAndResetPromiseMaybeAsync(AnimationPromise * promise)2117 void Animation::RejectAndResetPromiseMaybeAsync(AnimationPromise* promise) {
2118 if (ScriptForbiddenScope::IsScriptForbidden()) {
2119 GetExecutionContext()
2120 ->GetTaskRunner(TaskType::kDOMManipulation)
2121 ->PostTask(FROM_HERE,
2122 WTF::Bind(&Animation::RejectAndResetPromise,
2123 WrapPersistent(this), WrapPersistent(promise)));
2124 } else {
2125 RejectAndResetPromise(promise);
2126 }
2127 }
2128
NotifyProbe()2129 void Animation::NotifyProbe() {
2130 AnimationPlayState old_play_state = reported_play_state_;
2131 AnimationPlayState new_play_state =
2132 PendingInternal() ? kPending : CalculateAnimationPlayState();
2133
2134 if (old_play_state != new_play_state) {
2135 if (!PendingInternal()) {
2136 probe::AnimationPlayStateChanged(document_, this, old_play_state,
2137 new_play_state);
2138 }
2139 reported_play_state_ = new_play_state;
2140
2141 bool was_active = old_play_state == kPending || old_play_state == kRunning;
2142 bool is_active = new_play_state == kPending || new_play_state == kRunning;
2143
2144 if (!was_active && is_active) {
2145 TRACE_EVENT_NESTABLE_ASYNC_BEGIN1(
2146 "blink.animations,devtools.timeline,benchmark,rail", "Animation",
2147 this, "data", inspector_animation_event::Data(*this));
2148 } else if (was_active && !is_active) {
2149 TRACE_EVENT_NESTABLE_ASYNC_END1(
2150 "blink.animations,devtools.timeline,benchmark,rail", "Animation",
2151 this, "endData", inspector_animation_state_event::Data(*this));
2152 } else {
2153 TRACE_EVENT_NESTABLE_ASYNC_INSTANT1(
2154 "blink.animations,devtools.timeline,benchmark,rail", "Animation",
2155 this, "data", inspector_animation_state_event::Data(*this));
2156 }
2157 }
2158 }
2159
2160 // -------------------------------------
2161 // Replacement of animations
2162 // -------------------------------------
2163
2164 // https://drafts.csswg.org/web-animations-1/#removing-replaced-animations
IsReplaceable()2165 bool Animation::IsReplaceable() {
2166 // An animation is replaceable if all of the following conditions are true:
2167
2168 // 1. The existence of the animation is not prescribed by markup. That is, it
2169 // is not a CSS animation with an owning element, nor a CSS transition with
2170 // an owning element.
2171 if (IsCSSAnimation() || IsCSSTransition()) {
2172 // TODO(crbug.com/981905): Add OwningElement method to Animation and
2173 // override in CssAnimations and CssTransitions. Only bail here if the
2174 // animation has an owning element.
2175 return false;
2176 }
2177
2178 // 2. The animation's play state is finished.
2179 if (CalculateAnimationPlayState() != kFinished)
2180 return false;
2181
2182 // 3. The animation's replace state is not removed.
2183 if (replace_state_ == kRemoved)
2184 return false;
2185
2186 // 4. The animation is associated with a monotonically increasing timeline.
2187 if (!timeline_ || timeline_->IsScrollTimeline())
2188 return false;
2189
2190 // 5. The animation has an associated effect.
2191 if (!content_ || !content_->IsKeyframeEffect())
2192 return false;
2193
2194 // 6. The animation's associated effect is in effect.
2195 if (!content_->IsInEffect())
2196 return false;
2197
2198 // 7. The animation's associated effect has an effect target.
2199 Element* target = To<KeyframeEffect>(content_.Get())->target();
2200 if (!target)
2201 return false;
2202
2203 return true;
2204 }
2205
2206 // https://drafts.csswg.org/web-animations-1/#removing-replaced-animations
RemoveReplacedAnimation()2207 void Animation::RemoveReplacedAnimation() {
2208 DCHECK(IsReplaceable());
2209
2210 // To remove a replaced animation, perform the following steps:
2211 // 1. Set animation’s replace state to removed.
2212 // 2. Create an AnimationPlaybackEvent, removeEvent.
2213 // 3. Set removeEvent’s type attribute to remove.
2214 // 4. Set removeEvent’s currentTime attribute to the current time of
2215 // animation.
2216 // 5. Set removeEvent’s timelineTime attribute to the current time of the
2217 // timeline with which animation is associated.
2218 //
2219 // If animation has a document for timing, then append removeEvent to its
2220 // document for timing's pending animation event queue along with its target,
2221 // animation. For the scheduled event time, use the result of applying the
2222 // procedure to convert timeline time to origin-relative time to the current
2223 // time of the timeline with which animation is associated.
2224 replace_state_ = kRemoved;
2225 const AtomicString& event_type = event_type_names::kRemove;
2226 if (GetExecutionContext() && HasEventListeners(event_type)) {
2227 base::Optional<double> event_current_time = CurrentTimeInternal();
2228 if (event_current_time)
2229 event_current_time = SecondsToMilliseconds(event_current_time.value());
2230 pending_remove_event_ = MakeGarbageCollected<AnimationPlaybackEvent>(
2231 event_type, event_current_time, TimelineTime());
2232 pending_remove_event_->SetTarget(this);
2233 pending_remove_event_->SetCurrentTarget(this);
2234 document_->EnqueueAnimationFrameEvent(pending_remove_event_);
2235 }
2236
2237 // Force timing update to clear the effect.
2238 if (content_)
2239 content_->Invalidate();
2240 Update(kTimingUpdateOnDemand);
2241 }
2242
persist()2243 void Animation::persist() {
2244 if (replace_state_ == kPersisted)
2245 return;
2246
2247 replace_state_ = kPersisted;
2248
2249 // Force timing update to reapply the effect.
2250 if (content_)
2251 content_->Invalidate();
2252 Update(kTimingUpdateOnDemand);
2253 }
2254
replaceState()2255 String Animation::replaceState() {
2256 switch (replace_state_) {
2257 case kActive:
2258 return "active";
2259
2260 case kRemoved:
2261 return "removed";
2262
2263 case kPersisted:
2264 return "persisted";
2265
2266 default:
2267 NOTREACHED();
2268 return "";
2269 }
2270 }
2271
2272 // https://drafts.csswg.org/web-animations-1/#dom-animation-commitstyles
commitStyles(ExceptionState & exception_state)2273 void Animation::commitStyles(ExceptionState& exception_state) {
2274 Element* target = content_ && content_->IsKeyframeEffect()
2275 ? To<KeyframeEffect>(effect())->target()
2276 : nullptr;
2277
2278 // 1. If target is not an element capable of having a style attribute
2279 // (for example, it is a pseudo-element or is an element in a document
2280 // format for which style attributes are not defined) throw a
2281 // "NoModificationAllowedError" DOMException and abort these steps.
2282 if (!target || !target->IsStyledElement() ||
2283 !To<KeyframeEffect>(effect())->pseudoElement().IsEmpty()) {
2284 exception_state.ThrowDOMException(
2285 DOMExceptionCode::kNoModificationAllowedError,
2286 "Animation not associated with a styled target element");
2287 return;
2288 }
2289 // 2. If, after applying any pending style changes, target is not being
2290 // rendered, throw an "InvalidStateError" DOMException and abort these
2291 // steps.
2292 target->GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kJavaScript);
2293 if (!target->GetLayoutObject()) {
2294 exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
2295 "Target element is not rendered.");
2296 return;
2297 }
2298
2299 // 3. Let inline style be the result of getting the CSS declaration block
2300 // corresponding to target’s style attribute. If target does not have a
2301 // style attribute, let inline style be a new empty CSS declaration block
2302 // with the readonly flag unset and owner node set to target.
2303 CSSStyleDeclaration* inline_style = target->style();
2304
2305 // 4. Let targeted properties be the set of physical longhand properties
2306 // that are a target property for at least one animation effect
2307 // associated with animation whose effect target is target.
2308 PropertyHandleSet animation_properties =
2309 To<KeyframeEffect>(effect())->Model()->Properties();
2310
2311 // 5. For each property, property, in targeted properties:
2312 // 5.1 Let partialEffectStack be a copy of the effect stack for property
2313 // on target.
2314 // 5.2 If animation’s replace state is removed, add all animation effects
2315 // associated with animation whose effect target is target and which
2316 // include property as a target property to partialEffectStack.
2317 // 5.3 Remove from partialEffectStack any animation effects whose
2318 // associated animation has a higher composite order than animation.
2319 // 5.4 Let effect value be the result of calculating the result of
2320 // partialEffectStack for property using target’s computed style
2321 // (see § 5.4.3 Calculating the result of an effect stack).
2322 // 5.5 Set a CSS declaration property for effect value in inline style.
2323 // 6. Update style attribute for inline style.
2324 ActiveInterpolationsMap interpolations_map =
2325 To<KeyframeEffect>(effect())->InterpolationsForCommitStyles();
2326 StyleResolver& resolver = target->GetDocument().EnsureStyleResolver();
2327 scoped_refptr<ComputedStyle> style =
2328 resolver.StyleForInterpolations(*target, interpolations_map);
2329
2330 for (const auto& property : animation_properties) {
2331 if (!property.IsCSSProperty())
2332 continue;
2333
2334 CSSPropertyRef ref(property.GetCSSPropertyName(), target->GetDocument());
2335 const CSSValue* value = ref.GetProperty().CSSValueFromComputedStyle(
2336 *style, target->GetLayoutObject(), false);
2337 inline_style->setProperty(target->GetExecutionContext(),
2338 property.GetCSSPropertyName().ToAtomicString(),
2339 value->CssText(), "", ASSERT_NO_EXCEPTION);
2340 }
2341 }
2342
Trace(Visitor * visitor)2343 void Animation::Trace(Visitor* visitor) {
2344 visitor->Trace(content_);
2345 visitor->Trace(document_);
2346 visitor->Trace(timeline_);
2347 visitor->Trace(pending_finished_event_);
2348 visitor->Trace(pending_cancelled_event_);
2349 visitor->Trace(pending_remove_event_);
2350 visitor->Trace(finished_promise_);
2351 visitor->Trace(ready_promise_);
2352 visitor->Trace(compositor_animation_);
2353 EventTargetWithInlineData::Trace(visitor);
2354 ExecutionContextLifecycleObserver::Trace(visitor);
2355 }
2356
2357 Animation::CompositorAnimationHolder*
Create(Animation * animation)2358 Animation::CompositorAnimationHolder::Create(Animation* animation) {
2359 return MakeGarbageCollected<CompositorAnimationHolder>(animation);
2360 }
2361
CompositorAnimationHolder(Animation * animation)2362 Animation::CompositorAnimationHolder::CompositorAnimationHolder(
2363 Animation* animation)
2364 : animation_(animation) {
2365 compositor_animation_ = CompositorAnimation::Create();
2366 compositor_animation_->SetAnimationDelegate(animation_);
2367 }
2368
Dispose()2369 void Animation::CompositorAnimationHolder::Dispose() {
2370 if (!animation_)
2371 return;
2372 animation_->Dispose();
2373 DCHECK(!animation_);
2374 DCHECK(!compositor_animation_);
2375 }
2376
Detach()2377 void Animation::CompositorAnimationHolder::Detach() {
2378 DCHECK(compositor_animation_);
2379 compositor_animation_->SetAnimationDelegate(nullptr);
2380 animation_ = nullptr;
2381 compositor_animation_.reset();
2382 }
2383 } // namespace blink
2384