3b8790aa3d9ed265d176dfc7a1b5f44865273b77
[WebKit-https.git] / Source / WebCore / animation / WebAnimation.cpp
1 /*
2  * Copyright (C) 2017 Apple 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
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "WebAnimation.h"
28
29 #include "AnimationEffect.h"
30 #include "AnimationPlaybackEvent.h"
31 #include "AnimationTimeline.h"
32 #include "CSSComputedStyleDeclaration.h"
33 #include "DOMPromiseProxy.h"
34 #include "DeclarativeAnimation.h"
35 #include "Document.h"
36 #include "DocumentTimeline.h"
37 #include "EventLoop.h"
38 #include "EventNames.h"
39 #include "InspectorInstrumentation.h"
40 #include "JSWebAnimation.h"
41 #include "KeyframeEffect.h"
42 #include "KeyframeEffectStack.h"
43 #include "Logging.h"
44 #include "RenderElement.h"
45 #include "StyledElement.h"
46 #include "WebAnimationUtilities.h"
47 #include <wtf/IsoMallocInlines.h>
48 #include <wtf/Optional.h>
49 #include <wtf/text/TextStream.h>
50 #include <wtf/text/WTFString.h>
51
52 namespace WebCore {
53
54 WTF_MAKE_ISO_ALLOCATED_IMPL(WebAnimation);
55
56 Ref<WebAnimation> WebAnimation::create(Document& document, AnimationEffect* effect)
57 {
58     auto result = adoptRef(*new WebAnimation(document));
59     result->setEffect(effect);
60     result->setTimeline(&document.timeline());
61     return result;
62 }
63
64 Ref<WebAnimation> WebAnimation::create(Document& document, AnimationEffect* effect, AnimationTimeline* timeline)
65 {
66     auto result = adoptRef(*new WebAnimation(document));
67     result->setEffect(effect);
68     if (timeline)
69         result->setTimeline(timeline);
70     return result;
71 }
72
73 WebAnimation::WebAnimation(Document& document)
74     : ActiveDOMObject(document)
75     , m_readyPromise(makeUniqueRef<ReadyPromise>(*this, &WebAnimation::readyPromiseResolve))
76     , m_finishedPromise(makeUniqueRef<FinishedPromise>(*this, &WebAnimation::finishedPromiseResolve))
77 {
78     m_readyPromise->resolve(*this);
79     suspendIfNeeded();
80 }
81
82 WebAnimation::~WebAnimation()
83 {
84     InspectorInstrumentation::willDestroyWebAnimation(*this);
85
86     if (m_timeline)
87         m_timeline->forgetAnimation(this);
88 }
89
90 void WebAnimation::contextDestroyed()
91 {
92     InspectorInstrumentation::willDestroyWebAnimation(*this);
93
94     ActiveDOMObject::contextDestroyed();
95 }
96
97 void WebAnimation::remove()
98 {
99     // This object could be deleted after either clearing the effect or timeline relationship.
100     auto protectedThis = makeRef(*this);
101     setEffectInternal(nullptr);
102     setTimelineInternal(nullptr);
103 }
104
105 void WebAnimation::suspendEffectInvalidation()
106 {
107     ++m_suspendCount;
108 }
109
110 void WebAnimation::unsuspendEffectInvalidation()
111 {
112     ASSERT(m_suspendCount > 0);
113     --m_suspendCount;
114 }
115
116 void WebAnimation::effectTimingDidChange()
117 {
118     timingDidChange(DidSeek::No, SynchronouslyNotify::Yes);
119 }
120
121 void WebAnimation::setEffect(RefPtr<AnimationEffect>&& newEffect)
122 {
123     // 3.4.3. Setting the target effect of an animation
124     // https://drafts.csswg.org/web-animations-1/#setting-the-target-effect
125
126     // 1. Let old effect be the current target effect of animation, if any.
127     auto oldEffect = m_effect;
128
129     // 2. If new effect is the same object as old effect, abort this procedure.
130     if (newEffect == oldEffect)
131         return;
132
133     // 3. If animation has a pending pause task, reschedule that task to run as soon as animation is ready.
134     if (hasPendingPauseTask())
135         m_timeToRunPendingPauseTask = TimeToRunPendingTask::WhenReady;
136
137     // 4. If animation has a pending play task, reschedule that task to run as soon as animation is ready to play new effect.
138     if (hasPendingPlayTask())
139         m_timeToRunPendingPlayTask = TimeToRunPendingTask::WhenReady;
140
141     // 5. If new effect is not null and if new effect is the target effect of another animation, previous animation, run the
142     // procedure to set the target effect of an animation (this procedure) on previous animation passing null as new effect.
143     if (newEffect && newEffect->animation())
144         newEffect->animation()->setEffect(nullptr);
145
146     // 6. Let the target effect of animation be new effect.
147     // In the case of a declarative animation, we don't want to remove the animation from the relevant maps because
148     // while the effect was set via the API, the element still has a transition or animation set up and we must
149     // not break the timeline-to-animation relationship.
150
151     invalidateEffect();
152
153     // This object could be deleted after clearing the effect relationship.
154     auto protectedThis = makeRef(*this);
155     setEffectInternal(WTFMove(newEffect), isDeclarativeAnimation());
156
157     // 7. Run the procedure to update an animation's finished state for animation with the did seek flag set to false,
158     // and the synchronously notify flag set to false.
159     timingDidChange(DidSeek::No, SynchronouslyNotify::No);
160
161     invalidateEffect();
162 }
163
164 void WebAnimation::setEffectInternal(RefPtr<AnimationEffect>&& newEffect, bool doNotRemoveFromTimeline)
165 {
166     if (m_effect == newEffect)
167         return;
168
169     auto oldEffect = std::exchange(m_effect, WTFMove(newEffect));
170
171     Element* previousTarget = nullptr;
172     if (is<KeyframeEffect>(oldEffect))
173         previousTarget = downcast<KeyframeEffect>(oldEffect.get())->target();
174
175     Element* newTarget = nullptr;
176     if (is<KeyframeEffect>(m_effect))
177         newTarget = downcast<KeyframeEffect>(m_effect.get())->target();
178
179     // Update the effect-to-animation relationships and the timeline's animation map.
180     if (oldEffect) {
181         oldEffect->setAnimation(nullptr);
182         if (!doNotRemoveFromTimeline && m_timeline && previousTarget && previousTarget != newTarget)
183             m_timeline->animationWasRemovedFromElement(*this, *previousTarget);
184         updateRelevance();
185     }
186
187     if (m_effect) {
188         m_effect->setAnimation(this);
189         if (m_timeline && newTarget && previousTarget != newTarget)
190             m_timeline->animationWasAddedToElement(*this, *newTarget);
191     }
192
193     InspectorInstrumentation::didChangeWebAnimationEffect(*this);
194 }
195
196 void WebAnimation::setTimeline(RefPtr<AnimationTimeline>&& timeline)
197 {
198     // 3.4.1. Setting the timeline of an animation
199     // https://drafts.csswg.org/web-animations-1/#setting-the-timeline
200
201     // 2. If new timeline is the same object as old timeline, abort this procedure.
202     if (timeline == m_timeline)
203         return;
204
205     // 4. If the animation start time of animation is resolved, make animation's hold time unresolved.
206     if (m_startTime)
207         m_holdTime = WTF::nullopt;
208
209     if (is<KeyframeEffect>(m_effect)) {
210         auto* keyframeEffect = downcast<KeyframeEffect>(m_effect.get());
211         auto* target = keyframeEffect->target();
212         if (target) {
213             // In the case of a declarative animation, we don't want to remove the animation from the relevant maps because
214             // while the timeline was set via the API, the element still has a transition or animation set up and we must
215             // not break the relationship.
216             if (m_timeline && !isDeclarativeAnimation())
217                 m_timeline->animationWasRemovedFromElement(*this, *target);
218             if (timeline)
219                 timeline->animationWasAddedToElement(*this, *target);
220         }
221     }
222
223     // This object could be deleted after clearing the timeline relationship.
224     auto protectedThis = makeRef(*this);
225     setTimelineInternal(WTFMove(timeline));
226
227     setSuspended(is<DocumentTimeline>(m_timeline) && downcast<DocumentTimeline>(*m_timeline).animationsAreSuspended());
228
229     // 5. Run the procedure to update an animation's finished state for animation with the did seek flag set to false,
230     // and the synchronously notify flag set to false.
231     timingDidChange(DidSeek::No, SynchronouslyNotify::No);
232
233     invalidateEffect();
234 }
235
236 void WebAnimation::setTimelineInternal(RefPtr<AnimationTimeline>&& timeline)
237 {
238     if (m_timeline == timeline)
239         return;
240
241     if (m_timeline)
242         m_timeline->removeAnimation(*this);
243
244     m_timeline = WTFMove(timeline);
245
246     if (m_effect)
247         m_effect->animationTimelineDidChange(m_timeline.get());
248 }
249
250 void WebAnimation::effectTargetDidChange(Element* previousTarget, Element* newTarget)
251 {
252     if (!m_timeline)
253         return;
254
255     if (previousTarget)
256         m_timeline->animationWasRemovedFromElement(*this, *previousTarget);
257
258     if (newTarget)
259         m_timeline->animationWasAddedToElement(*this, *newTarget);
260 }
261
262 Optional<double> WebAnimation::startTime() const
263 {
264     if (!m_startTime)
265         return WTF::nullopt;
266     return secondsToWebAnimationsAPITime(m_startTime.value());
267 }
268
269 void WebAnimation::setStartTime(Optional<double> startTime)
270 {
271     // 3.4.6 The procedure to set the start time of animation, animation, to new start time, is as follows:
272     // https://drafts.csswg.org/web-animations/#setting-the-start-time-of-an-animation
273
274     Optional<Seconds> newStartTime;
275     if (!startTime)
276         newStartTime = WTF::nullopt;
277     else
278         newStartTime = Seconds::fromMilliseconds(startTime.value());
279
280     // 1. Let timeline time be the current time value of the timeline that animation is associated with. If
281     //    there is no timeline associated with animation or the associated timeline is inactive, let the timeline
282     //    time be unresolved.
283     auto timelineTime = m_timeline ? m_timeline->currentTime() : WTF::nullopt;
284
285     // 2. If timeline time is unresolved and new start time is resolved, make animation's hold time unresolved.
286     if (!timelineTime && newStartTime)
287         m_holdTime = WTF::nullopt;
288
289     // 3. Let previous current time be animation's current time.
290     auto previousCurrentTime = currentTime();
291
292     // 4. Apply any pending playback rate on animation.
293     applyPendingPlaybackRate();
294
295     // 5. Set animation's start time to new start time.
296     m_startTime = newStartTime;
297
298     // 6. Update animation's hold time based on the first matching condition from the following,
299     if (newStartTime) {
300         // If new start time is resolved,
301         // If animation's playback rate is not zero, make animation's hold time unresolved.
302         if (m_playbackRate)
303             m_holdTime = WTF::nullopt;
304     } else {
305         // Otherwise (new start time is unresolved),
306         // Set animation's hold time to previous current time even if previous current time is unresolved.
307         m_holdTime = previousCurrentTime;
308     }
309
310     // 7. If animation has a pending play task or a pending pause task, cancel that task and resolve animation's current ready promise with animation.
311     if (pending()) {
312         m_timeToRunPendingPauseTask = TimeToRunPendingTask::NotScheduled;
313         m_timeToRunPendingPlayTask = TimeToRunPendingTask::NotScheduled;
314         m_readyPromise->resolve(*this);
315     }
316
317     // 8. Run the procedure to update an animation's finished state for animation with the did seek flag set to true, and the synchronously notify flag set to false.
318     timingDidChange(DidSeek::Yes, SynchronouslyNotify::No);
319
320     invalidateEffect();
321 }
322
323 Optional<double> WebAnimation::bindingsCurrentTime() const
324 {
325     auto time = currentTime();
326     if (!time)
327         return WTF::nullopt;
328     return secondsToWebAnimationsAPITime(time.value());
329 }
330
331 ExceptionOr<void> WebAnimation::setBindingsCurrentTime(Optional<double> currentTime)
332 {
333     if (!currentTime)
334         return setCurrentTime(WTF::nullopt);
335     return setCurrentTime(Seconds::fromMilliseconds(currentTime.value()));
336 }
337
338 Optional<Seconds> WebAnimation::currentTime() const
339 {
340     return currentTime(RespectHoldTime::Yes);
341 }
342
343 Optional<Seconds> WebAnimation::currentTime(RespectHoldTime respectHoldTime) const
344 {
345     // 3.4.4. The current time of an animation
346     // https://drafts.csswg.org/web-animations-1/#the-current-time-of-an-animation
347
348     // The current time is calculated from the first matching condition from below:
349
350     // If the animation's hold time is resolved, the current time is the animation's hold time.
351     if (respectHoldTime == RespectHoldTime::Yes && m_holdTime)
352         return m_holdTime;
353
354     // If any of the following are true:
355     //     1. the animation has no associated timeline, or
356     //     2. the associated timeline is inactive, or
357     //     3. the animation's start time is unresolved.
358     // The current time is an unresolved time value.
359     if (!m_timeline || !m_timeline->currentTime() || !m_startTime)
360         return WTF::nullopt;
361
362     // Otherwise, current time = (timeline time - start time) * playback rate
363     return (m_timeline->currentTime().value() - m_startTime.value()) * m_playbackRate;
364 }
365
366 ExceptionOr<void> WebAnimation::silentlySetCurrentTime(Optional<Seconds> seekTime)
367 {
368     LOG_WITH_STREAM(Animations, stream << "WebAnimation " << this << " silentlySetCurrentTime " << seekTime);
369
370     // 3.4.5. Setting the current time of an animation
371     // https://drafts.csswg.org/web-animations-1/#setting-the-current-time-of-an-animation
372
373     // 1. If seek time is an unresolved time value, then perform the following steps.
374     if (!seekTime) {
375         // 1. If the current time is resolved, then throw a TypeError.
376         if (currentTime())
377             return Exception { TypeError };
378         // 2. Abort these steps.
379         return { };
380     }
381
382     // 2. Update either animation's hold time or start time as follows:
383     // If any of the following conditions are true:
384     //     - animation's hold time is resolved, or
385     //     - animation's start time is unresolved, or
386     //     - animation has no associated timeline or the associated timeline is inactive, or
387     //     - animation's playback rate is 0,
388     // Set animation's hold time to seek time.
389     // Otherwise, set animation's start time to the result of evaluating timeline time - (seek time / playback rate)
390     // where timeline time is the current time value of timeline associated with animation.
391     if (m_holdTime || !m_startTime || !m_timeline || !m_timeline->currentTime() || !m_playbackRate)
392         m_holdTime = seekTime;
393     else
394         m_startTime = m_timeline->currentTime().value() - (seekTime.value() / m_playbackRate);
395
396     // 3. If animation has no associated timeline or the associated timeline is inactive, make animation's start time unresolved.
397     if (!m_timeline || !m_timeline->currentTime())
398         m_startTime = WTF::nullopt;
399
400     // 4. Make animation's previous current time unresolved.
401     m_previousCurrentTime = WTF::nullopt;
402
403     return { };
404 }
405
406 ExceptionOr<void> WebAnimation::setCurrentTime(Optional<Seconds> seekTime)
407 {
408     LOG_WITH_STREAM(Animations, stream << "WebAnimation " << this << " setCurrentTime " << seekTime);
409
410     // 3.4.5. Setting the current time of an animation
411     // https://drafts.csswg.org/web-animations-1/#setting-the-current-time-of-an-animation
412
413     // 1. Run the steps to silently set the current time of animation to seek time.
414     auto silentResult = silentlySetCurrentTime(seekTime);
415     if (silentResult.hasException())
416         return silentResult.releaseException();
417
418     // 2. If animation has a pending pause task, synchronously complete the pause operation by performing the following steps:
419     if (hasPendingPauseTask()) {
420         // 1. Set animation's hold time to seek time.
421         m_holdTime = seekTime;
422         // 2. Apply any pending playback rate to animation.
423         applyPendingPlaybackRate();
424         // 3. Make animation's start time unresolved.
425         m_startTime = WTF::nullopt;
426         // 4. Cancel the pending pause task.
427         m_timeToRunPendingPauseTask = TimeToRunPendingTask::NotScheduled;
428         // 5. Resolve animation's current ready promise with animation.
429         m_readyPromise->resolve(*this);
430     }
431
432     // 3. Run the procedure to update an animation's finished state for animation with the did seek flag set to true, and the synchronously notify flag set to false.
433     timingDidChange(DidSeek::Yes, SynchronouslyNotify::No);
434
435     if (m_effect)
436         m_effect->animationDidSeek();
437
438     invalidateEffect();
439
440     return { };
441 }
442
443 double WebAnimation::effectivePlaybackRate() const
444 {
445     // https://drafts.csswg.org/web-animations/#effective-playback-rate
446     // The effective playback rate of an animation is its pending playback rate, if set, otherwise it is the animation's playback rate.
447     return (m_pendingPlaybackRate ? m_pendingPlaybackRate.value() : m_playbackRate);
448 }
449
450 void WebAnimation::setPlaybackRate(double newPlaybackRate)
451 {
452     // 3.4.17.1. Updating the playback rate of an animation
453     // https://drafts.csswg.org/web-animations-1/#updating-the-playback-rate-of-an-animation
454
455     // 1. Clear any pending playback rate on animation.
456     m_pendingPlaybackRate = WTF::nullopt;
457     
458     // 2. Let previous time be the value of the current time of animation before changing the playback rate.
459     auto previousTime = currentTime();
460
461     // 3. Set the playback rate to new playback rate.
462     m_playbackRate = newPlaybackRate;
463
464     // 4. If previous time is resolved, set the current time of animation to previous time.
465     if (previousTime)
466         setCurrentTime(previousTime);
467 }
468
469 void WebAnimation::updatePlaybackRate(double newPlaybackRate)
470 {
471     // https://drafts.csswg.org/web-animations/#seamlessly-update-the-playback-rate
472
473     // The procedure to seamlessly update the playback rate an animation, animation, to new playback rate preserving its current time is as follows:
474
475     // 1. Let previous play state be animation's play state.
476     //    Note: It is necessary to record the play state before updating animation's effective playback rate since, in the following logic,
477     //    we want to immediately apply the pending playback rate of animation if it is currently finished regardless of whether or not it will
478     //    still be finished after we apply the pending playback rate.
479     auto previousPlayState = playState();
480
481     // 2. Let animation's pending playback rate be new playback rate.
482     m_pendingPlaybackRate = newPlaybackRate;
483
484     // 3. Perform the steps corresponding to the first matching condition from below:
485     if (pending()) {
486         // If animation has a pending play task or a pending pause task,
487         // Abort these steps.
488         // Note: The different types of pending tasks will apply the pending playback rate when they run so there is no further action required in this case.
489         return;
490     }
491
492     if (previousPlayState == PlayState::Idle || previousPlayState == PlayState::Paused) {
493         // If previous play state is idle or paused,
494         // Apply any pending playback rate on animation.
495         applyPendingPlaybackRate();
496     } else if (previousPlayState == PlayState::Finished) {
497         // If previous play state is finished,
498         // 1. Let the unconstrained current time be the result of calculating the current time of animation substituting an unresolved time value for the hold time.
499         auto unconstrainedCurrentTime = currentTime(RespectHoldTime::No);
500         // 2. Let animation's start time be the result of evaluating the following expression:
501         // timeline time - (unconstrained current time / pending playback rate)
502         // Where timeline time is the current time value of the timeline associated with animation.
503         // If pending playback rate is zero, let animation's start time be timeline time.
504         auto newStartTime = m_timeline->currentTime().value();
505         if (m_pendingPlaybackRate)
506             newStartTime -= (unconstrainedCurrentTime.value() / m_pendingPlaybackRate.value());
507         m_startTime = newStartTime;
508         // 3. Apply any pending playback rate on animation.
509         applyPendingPlaybackRate();
510         // 4. Run the procedure to update an animation's finished state for animation with the did seek flag set to false, and the synchronously notify flag set to false.
511         timingDidChange(DidSeek::No, SynchronouslyNotify::No);
512
513         invalidateEffect();
514     } else {
515         // Otherwise,
516         // Run the procedure to play an animation for animation with the auto-rewind flag set to false.
517         play(AutoRewind::No);
518     }
519 }
520
521 void WebAnimation::applyPendingPlaybackRate()
522 {
523     // https://drafts.csswg.org/web-animations/#apply-any-pending-playback-rate
524
525     // 1. If animation does not have a pending playback rate, abort these steps.
526     if (!m_pendingPlaybackRate)
527         return;
528
529     // 2. Set animation's playback rate to its pending playback rate.
530     m_playbackRate = m_pendingPlaybackRate.value();
531
532     // 3. Clear animation's pending playback rate.
533     m_pendingPlaybackRate = WTF::nullopt;
534 }
535
536 auto WebAnimation::playState() const -> PlayState
537 {
538     // 3.5.19 Play states
539     // https://drafts.csswg.org/web-animations/#play-states
540
541     // The play state of animation, animation, at a given moment is the state corresponding to the
542     // first matching condition from the following:
543
544     // The current time of animation is unresolved, and animation does not have either a pending
545     // play task or a pending pause task,
546     // → idle
547     auto animationCurrentTime = currentTime();
548     if (!animationCurrentTime && !pending())
549         return PlayState::Idle;
550
551     // Animation has a pending pause task, or both the start time of animation is unresolved and it does not
552     // have a pending play task,
553     // → paused
554     if (hasPendingPauseTask() || (!m_startTime && !hasPendingPlayTask()))
555         return PlayState::Paused;
556
557     // For animation, current time is resolved and either of the following conditions are true:
558     // animation's effective playback rate > 0 and current time ≥ target effect end; or
559     // animation's effective playback rate < 0 and current time ≤ 0,
560     // → finished
561     if (animationCurrentTime && ((effectivePlaybackRate() > 0 && (*animationCurrentTime + timeEpsilon) >= effectEndTime()) || (effectivePlaybackRate() < 0 && (*animationCurrentTime - timeEpsilon) <= 0_s)))
562         return PlayState::Finished;
563
564     // Otherwise → running
565     return PlayState::Running;
566 }
567
568 Seconds WebAnimation::effectEndTime() const
569 {
570     // The target effect end of an animation is equal to the end time of the animation's target effect.
571     // If the animation has no target effect, the target effect end is zero.
572     return m_effect ? m_effect->endTime() : 0_s;
573 }
574
575 void WebAnimation::cancel()
576 {
577     cancel(Silently::No);
578     invalidateEffect();
579 }
580
581 void WebAnimation::cancel(Silently silently)
582 {
583     LOG_WITH_STREAM(Animations, stream << "WebAnimation " << this << " cancel(silently " << (silently == Silently::Yes) << ") (current time is " << currentTime() << ")");
584
585     // 3.4.16. Canceling an animation
586     // https://drafts.csswg.org/web-animations-1/#canceling-an-animation-section
587     //
588     // An animation can be canceled which causes the current time to become unresolved hence removing any effects caused by the target effect.
589     //
590     // The procedure to cancel an animation for animation is as follows:
591     //
592     // 1. If animation's play state is not idle, perform the following steps:
593     if (playState() != PlayState::Idle) {
594         // 1. Run the procedure to reset an animation's pending tasks on animation.
595         resetPendingTasks(silently);
596
597         // 2. Reject the current finished promise with a DOMException named "AbortError".
598         if (silently == Silently::No && !m_finishedPromise->isFulfilled())
599             m_finishedPromise->reject(Exception { AbortError });
600
601         // 3. Let current finished promise be a new (pending) Promise object.
602         m_finishedPromise = makeUniqueRef<FinishedPromise>(*this, &WebAnimation::finishedPromiseResolve);
603
604         // 4. Create an AnimationPlaybackEvent, cancelEvent.
605         // 5. Set cancelEvent's type attribute to cancel.
606         // 6. Set cancelEvent's currentTime to null.
607         // 7. Let timeline time be the current time of the timeline with which animation is associated. If animation is not associated with an
608         //    active timeline, let timeline time be n unresolved time value.
609         // 8. Set cancelEvent's timelineTime to timeline time. If timeline time is unresolved, set it to null.
610         // 9. If animation has a document for timing, then append cancelEvent to its document for timing's pending animation event queue along
611         //    with its target, animation. If animation is associated with an active timeline that defines a procedure to convert timeline times
612         //    to origin-relative time, let the scheduled event time be the result of applying that procedure to timeline time. Otherwise, the
613         //    scheduled event time is an unresolved time value.
614         // Otherwise, queue a task to dispatch cancelEvent at animation. The task source for this task is the DOM manipulation task source.
615         if (silently == Silently::No)
616             enqueueAnimationPlaybackEvent(eventNames().cancelEvent, WTF::nullopt, m_timeline ? m_timeline->currentTime() : WTF::nullopt);
617     }
618
619     // 2. Make animation's hold time unresolved.
620     m_holdTime = WTF::nullopt;
621
622     // 3. Make animation's start time unresolved.
623     m_startTime = WTF::nullopt;
624
625     timingDidChange(DidSeek::No, SynchronouslyNotify::No);
626
627     invalidateEffect();
628
629     if (m_effect)
630         m_effect->animationWasCanceled();
631 }
632
633 void WebAnimation::enqueueAnimationPlaybackEvent(const AtomString& type, Optional<Seconds> currentTime, Optional<Seconds> timelineTime)
634 {
635     auto event = AnimationPlaybackEvent::create(type, currentTime, timelineTime);
636     event->setTarget(this);
637
638     if (is<DocumentTimeline>(m_timeline)) {
639         // If animation has a document for timing, then append event to its document for timing's pending animation event queue along
640         // with its target, animation. If animation is associated with an active timeline that defines a procedure to convert timeline times
641         // to origin-relative time, let the scheduled event time be the result of applying that procedure to timeline time. Otherwise, the
642         // scheduled event time is an unresolved time value.
643         downcast<DocumentTimeline>(*m_timeline).enqueueAnimationPlaybackEvent(WTFMove(event));
644     } else {
645         // Otherwise, queue a task to dispatch event at animation. The task source for this task is the DOM manipulation task source.
646         queueTaskToDispatchEvent(*this, TaskSource::DOMManipulation, WTFMove(event));
647     }
648 }
649
650 void WebAnimation::resetPendingTasks(Silently silently)
651 {
652     // The procedure to reset an animation's pending tasks for animation is as follows:
653     // https://drafts.csswg.org/web-animations-1/#reset-an-animations-pending-tasks
654     //
655     // 1. If animation does not have a pending play task or a pending pause task, abort this procedure.
656     if (!pending())
657         return;
658
659     // 2. If animation has a pending play task, cancel that task.
660     if (hasPendingPlayTask())
661         m_timeToRunPendingPlayTask = TimeToRunPendingTask::NotScheduled;
662
663     // 3. If animation has a pending pause task, cancel that task.
664     if (hasPendingPauseTask())
665         m_timeToRunPendingPauseTask = TimeToRunPendingTask::NotScheduled;
666
667     // 4. Apply any pending playback rate on animation.
668     applyPendingPlaybackRate();
669
670     // 5. Reject animation's current ready promise with a DOMException named "AbortError".
671     if (silently == Silently::No)
672         m_readyPromise->reject(Exception { AbortError });
673
674     // 6. Let animation's current ready promise be the result of creating a new resolved Promise object.
675     m_readyPromise = makeUniqueRef<ReadyPromise>(*this, &WebAnimation::readyPromiseResolve);
676     m_readyPromise->resolve(*this);
677 }
678
679 ExceptionOr<void> WebAnimation::finish()
680 {
681     LOG_WITH_STREAM(Animations, stream << "WebAnimation " << this << " finish (current time is " << currentTime() << ")");
682
683     // 3.4.15. Finishing an animation
684     // https://drafts.csswg.org/web-animations-1/#finishing-an-animation-section
685
686     // An animation can be advanced to the natural end of its current playback direction by using the procedure to finish an animation for animation defined below:
687     //
688     // 1. If animation's effective playback rate is zero, or if animation's effective playback rate > 0 and target effect end is infinity, throw an InvalidStateError and abort these steps.
689     if (!effectivePlaybackRate() || (effectivePlaybackRate() > 0 && effectEndTime() == Seconds::infinity()))
690         return Exception { InvalidStateError };
691
692     // 2. Apply any pending playback rate to animation.
693     applyPendingPlaybackRate();
694
695     // 3. Set limit as follows:
696     // If animation playback rate > 0, let limit be target effect end.
697     // Otherwise, let limit be zero.
698     auto limit = m_playbackRate > 0 ? effectEndTime() : 0_s;
699
700     // 4. Silently set the current time to limit.
701     silentlySetCurrentTime(limit);
702
703     // 5. If animation's start time is unresolved and animation has an associated active timeline, let the start time be the result of
704     //    evaluating timeline time - (limit / playback rate) where timeline time is the current time value of the associated timeline.
705     if (!m_startTime && m_timeline && m_timeline->currentTime())
706         m_startTime = m_timeline->currentTime().value() - (limit / m_playbackRate);
707
708     // 6. If there is a pending pause task and start time is resolved,
709     if (hasPendingPauseTask() && m_startTime) {
710         // 1. Let the hold time be unresolved.
711         m_holdTime = WTF::nullopt;
712         // 2. Cancel the pending pause task.
713         m_timeToRunPendingPauseTask = TimeToRunPendingTask::NotScheduled;
714         // 3. Resolve the current ready promise of animation with animation.
715         m_readyPromise->resolve(*this);
716     }
717
718     // 7. If there is a pending play task and start time is resolved, cancel that task and resolve the current ready promise of animation with animation.
719     if (hasPendingPlayTask() && m_startTime) {
720         m_timeToRunPendingPlayTask = TimeToRunPendingTask::NotScheduled;
721         m_readyPromise->resolve(*this);
722     }
723
724     // 8. Run the procedure to update an animation's finished state animation with the did seek flag set to true, and the synchronously notify flag set to true.
725     timingDidChange(DidSeek::Yes, SynchronouslyNotify::Yes);
726
727     invalidateEffect();
728
729     return { };
730 }
731
732 void WebAnimation::timingDidChange(DidSeek didSeek, SynchronouslyNotify synchronouslyNotify, Silently silently)
733 {
734     m_shouldSkipUpdatingFinishedStateWhenResolving = false;
735     updateFinishedState(didSeek, synchronouslyNotify);
736
737     if (is<KeyframeEffect>(m_effect)) {
738         updateRelevance();
739         downcast<KeyframeEffect>(*m_effect).animationTimingDidChange();
740     }
741
742     if (silently == Silently::No && m_timeline)
743         m_timeline->animationTimingDidChange(*this);
744 };
745
746 void WebAnimation::invalidateEffect()
747 {
748     if (!isEffectInvalidationSuspended() && m_effect)
749         m_effect->invalidate();
750 }
751
752 void WebAnimation::updateFinishedState(DidSeek didSeek, SynchronouslyNotify synchronouslyNotify)
753 {
754     // 3.4.14. Updating the finished state
755     // https://drafts.csswg.org/web-animations-1/#updating-the-finished-state
756
757     // 1. Let the unconstrained current time be the result of calculating the current time substituting an unresolved time value
758     // for the hold time if did seek is false. If did seek is true, the unconstrained current time is equal to the current time.
759     auto unconstrainedCurrentTime = currentTime(didSeek == DidSeek::Yes ? RespectHoldTime::Yes : RespectHoldTime::No);
760     auto endTime = effectEndTime();
761
762     // 2. If all three of the following conditions are true,
763     //    - the unconstrained current time is resolved, and
764     //    - animation's start time is resolved, and
765     //    - animation does not have a pending play task or a pending pause task,
766     if (unconstrainedCurrentTime && m_startTime && !pending()) {
767         // then update animation's hold time based on the first matching condition for animation from below, if any:
768         if (m_playbackRate > 0 && unconstrainedCurrentTime >= endTime) {
769             // If animation playback rate > 0 and unconstrained current time is greater than or equal to target effect end,
770             // If did seek is true, let the hold time be the value of unconstrained current time.
771             if (didSeek == DidSeek::Yes)
772                 m_holdTime = unconstrainedCurrentTime;
773             // If did seek is false, let the hold time be the maximum value of previous current time and target effect end. If the previous current time is unresolved, let the hold time be target effect end.
774             else if (!m_previousCurrentTime)
775                 m_holdTime = endTime;
776             else
777                 m_holdTime = std::max(m_previousCurrentTime.value(), endTime);
778         } else if (m_playbackRate < 0 && unconstrainedCurrentTime <= 0_s) {
779             // If animation playback rate < 0 and unconstrained current time is less than or equal to 0,
780             // If did seek is true, let the hold time be the value of unconstrained current time.
781             if (didSeek == DidSeek::Yes)
782                 m_holdTime = unconstrainedCurrentTime;
783             // If did seek is false, let the hold time be the minimum value of previous current time and zero. If the previous current time is unresolved, let the hold time be zero.
784             else if (!m_previousCurrentTime)
785                 m_holdTime = 0_s;
786             else
787                 m_holdTime = std::min(m_previousCurrentTime.value(), 0_s);
788         } else if (m_playbackRate && m_timeline && m_timeline->currentTime()) {
789             // If animation playback rate ≠ 0, and animation is associated with an active timeline,
790             // Perform the following steps:
791             // 1. If did seek is true and the hold time is resolved, let animation's start time be equal to the result of evaluating timeline time - (hold time / playback rate)
792             //    where timeline time is the current time value of timeline associated with animation.
793             if (didSeek == DidSeek::Yes && m_holdTime)
794                 m_startTime = m_timeline->currentTime().value() - (m_holdTime.value() / m_playbackRate);
795             // 2. Let the hold time be unresolved.
796             m_holdTime = WTF::nullopt;
797         }
798     }
799
800     // 3. Set the previous current time of animation be the result of calculating its current time.
801     m_previousCurrentTime = currentTime();
802
803     // 4. Let current finished state be true if the play state of animation is finished. Otherwise, let it be false.
804     auto currentFinishedState = playState() == PlayState::Finished;
805
806     // 5. If current finished state is true and the current finished promise is not yet resolved, perform the following steps:
807     if (currentFinishedState && !m_finishedPromise->isFulfilled()) {
808         if (synchronouslyNotify == SynchronouslyNotify::Yes) {
809             // If synchronously notify is true, cancel any queued microtask to run the finish notification steps for this animation,
810             // and run the finish notification steps immediately.
811             m_finishNotificationStepsMicrotaskPending = false;
812             finishNotificationSteps();
813         } else if (!m_finishNotificationStepsMicrotaskPending) {
814             // Otherwise, if synchronously notify is false, queue a microtask to run finish notification steps for animation unless there
815             // is already a microtask queued to run those steps for animation.
816             m_finishNotificationStepsMicrotaskPending = true;
817             if (auto* context = scriptExecutionContext()) {
818                 context->eventLoop().queueMicrotask([this, protectedThis = makeRef(*this)] {
819                     if (m_finishNotificationStepsMicrotaskPending) {
820                         m_finishNotificationStepsMicrotaskPending = false;
821                         finishNotificationSteps();
822                     }
823                 });
824             }
825         }
826     }
827
828     // 6. If current finished state is false and animation's current finished promise is already resolved, set animation's current
829     // finished promise to a new (pending) Promise object.
830     if (!currentFinishedState && m_finishedPromise->isFulfilled())
831         m_finishedPromise = makeUniqueRef<FinishedPromise>(*this, &WebAnimation::finishedPromiseResolve);
832
833     updateRelevance();
834 }
835
836 void WebAnimation::finishNotificationSteps()
837 {
838     // 3.4.14. Updating the finished state
839     // https://drafts.csswg.org/web-animations-1/#finish-notification-steps
840
841     // Let finish notification steps refer to the following procedure:
842     // 1. If animation's play state is not equal to finished, abort these steps.
843     if (playState() != PlayState::Finished)
844         return;
845
846     // 2. Resolve animation's current finished promise object with animation.
847     m_finishedPromise->resolve(*this);
848
849     // 3. Create an AnimationPlaybackEvent, finishEvent.
850     // 4. Set finishEvent's type attribute to finish.
851     // 5. Set finishEvent's currentTime attribute to the current time of animation.
852     // 6. Set finishEvent's timelineTime attribute to the current time of the timeline with which animation is associated.
853     //    If animation is not associated with a timeline, or the timeline is inactive, let timelineTime be null.
854     // 7. If animation has a document for timing, then append finishEvent to its document for timing's pending animation event
855     //    queue along with its target, animation. For the scheduled event time, use the result of converting animation's target
856     //    effect end to an origin-relative time.
857     //    Otherwise, queue a task to dispatch finishEvent at animation. The task source for this task is the DOM manipulation task source.
858     enqueueAnimationPlaybackEvent(eventNames().finishEvent, currentTime(), m_timeline ? m_timeline->currentTime() : WTF::nullopt);
859 }
860
861 ExceptionOr<void> WebAnimation::play()
862 {
863     return play(AutoRewind::Yes);
864 }
865
866 ExceptionOr<void> WebAnimation::play(AutoRewind autoRewind)
867 {
868     LOG_WITH_STREAM(Animations, stream << "WebAnimation " << this << " play(autoRewind " << (autoRewind == AutoRewind::Yes) << ") (current time is " << currentTime() << ")");
869
870     // 3.4.10. Playing an animation
871     // https://drafts.csswg.org/web-animations-1/#play-an-animation
872
873     auto localTime = currentTime();
874     auto endTime = effectEndTime();
875
876     // 1. Let aborted pause be a boolean flag that is true if animation has a pending pause task, and false otherwise.
877     bool abortedPause = hasPendingPauseTask();
878
879     // 2. Let has pending ready promise be a boolean flag that is initially false.
880     bool hasPendingReadyPromise = false;
881
882     // 3. Perform the steps corresponding to the first matching condition from the following, if any:
883     if (effectivePlaybackRate() > 0 && autoRewind == AutoRewind::Yes && (!localTime || localTime.value() < 0_s || localTime.value() >= endTime)) {
884         // If animation's effective playback rate > 0, the auto-rewind flag is true and either animation's:
885         //     - current time is unresolved, or
886         //     - current time < zero, or
887         //     - current time ≥ target effect end,
888         // Set animation's hold time to zero.
889         m_holdTime = 0_s;
890     } else if (effectivePlaybackRate() < 0 && autoRewind == AutoRewind::Yes && (!localTime || localTime.value() <= 0_s || localTime.value() > endTime)) {
891         // If animation's effective playback rate < 0, the auto-rewind flag is true and either animation's:
892         //     - current time is unresolved, or
893         //     - current time ≤ zero, or
894         //     - current time > target effect end
895         // If target effect end is positive infinity, throw an InvalidStateError and abort these steps.
896         if (endTime == Seconds::infinity())
897             return Exception { InvalidStateError };
898         m_holdTime = endTime;
899     } else if (!effectivePlaybackRate() && !localTime) {
900         // If animation's effective playback rate = 0 and animation's current time is unresolved,
901         // Set animation's hold time to zero.
902         m_holdTime = 0_s;
903     }
904
905     // 4. If animation has a pending play task or a pending pause task,
906     if (pending()) {
907         // 1. Cancel that task.
908         m_timeToRunPendingPauseTask = TimeToRunPendingTask::NotScheduled;
909         m_timeToRunPendingPlayTask = TimeToRunPendingTask::NotScheduled;
910         // 2. Set has pending ready promise to true.
911         hasPendingReadyPromise = true;
912     }
913
914     // 5. If the following three conditions are all satisfied:
915     //    - animation's hold time is unresolved, and
916     //    - aborted pause is false, and
917     //    - animation does not have a pending playback rate,
918     // abort this procedure.
919     if (!m_holdTime && !abortedPause && !m_pendingPlaybackRate)
920         return { };
921
922     // 6. If animation's hold time is resolved, let its start time be unresolved.
923     if (m_holdTime)
924         m_startTime = WTF::nullopt;
925
926     // 7. If has pending ready promise is false, let animation's current ready promise be a new (pending) Promise object.
927     if (!hasPendingReadyPromise)
928         m_readyPromise = makeUniqueRef<ReadyPromise>(*this, &WebAnimation::readyPromiseResolve);
929
930     // 8. Schedule a task to run as soon as animation is ready.
931     m_timeToRunPendingPlayTask = TimeToRunPendingTask::WhenReady;
932
933     // 9. Run the procedure to update an animation's finished state for animation with the did seek flag set to false, and the synchronously notify flag set to false.
934     timingDidChange(DidSeek::No, SynchronouslyNotify::No);
935
936     invalidateEffect();
937
938     return { };
939 }
940
941 void WebAnimation::runPendingPlayTask()
942 {
943     LOG_WITH_STREAM(Animations, stream << "WebAnimation " << this << " runPendingPlayTask (current time is " << currentTime() << ")");
944
945     // 3.4.10. Playing an animation, step 8.
946     // https://drafts.csswg.org/web-animations-1/#play-an-animation
947
948     m_timeToRunPendingPlayTask = TimeToRunPendingTask::NotScheduled;
949
950     // 1. Assert that at least one of animation's start time or hold time is resolved.
951     ASSERT(m_startTime || m_holdTime);
952
953     // 2. Let ready time be the time value of the timeline associated with animation at the moment when animation became ready.
954     auto readyTime = m_timeline->currentTime();
955
956     // 3. Perform the steps corresponding to the first matching condition below, if any:
957     if (m_holdTime) {
958         // If animation's hold time is resolved,
959         // 1. Apply any pending playback rate on animation.
960         applyPendingPlaybackRate();
961         // 2. Let new start time be the result of evaluating ready time - hold time / animation playback rate for animation.
962         // If the animation playback rate is zero, let new start time be simply ready time.
963         // FIXME: Implementation cannot guarantee an active timeline at the point of this async dispatch.
964         // Subsequently, the resulting readyTime value can be null. Unify behavior between C++17 and
965         // C++14 builds (the latter using WTF's Optional) and avoid null Optional dereferencing
966         // by defaulting to a Seconds(0) value. See https://bugs.webkit.org/show_bug.cgi?id=186189.
967         auto newStartTime = readyTime.valueOr(0_s);
968         if (m_playbackRate)
969             newStartTime -= m_holdTime.value() / m_playbackRate;
970         // 3. Set the start time of animation to new start time.
971         m_startTime = newStartTime;
972         // 4. If animation's playback rate is not 0, make animation's hold time unresolved.
973         if (m_playbackRate)
974             m_holdTime = WTF::nullopt;
975     } else if (m_startTime && m_pendingPlaybackRate) {
976         // If animation's start time is resolved and animation has a pending playback rate,
977         // 1. Let current time to match be the result of evaluating (ready time - start time) × playback rate for animation.
978         auto currentTimeToMatch = (readyTime.valueOr(0_s) - m_startTime.value()) * m_playbackRate;
979         // 2. Apply any pending playback rate on animation.
980         applyPendingPlaybackRate();
981         // 3. If animation's playback rate is zero, let animation's hold time be current time to match.
982         if (m_playbackRate)
983             m_holdTime = currentTimeToMatch;
984         // 4. Let new start time be the result of evaluating ready time - current time to match / playback rate for animation.
985         // If the playback rate is zero, let new start time be simply ready time.
986         auto newStartTime = readyTime.valueOr(0_s);
987         if (m_playbackRate)
988             newStartTime -= currentTimeToMatch / m_playbackRate;
989         // 5. Set the start time of animation to new start time.
990         m_startTime = newStartTime;
991     }
992
993     // 4. Resolve animation's current ready promise with animation.
994     if (!m_readyPromise->isFulfilled())
995         m_readyPromise->resolve(*this);
996
997     // 5. Run the procedure to update an animation's finished state for animation with the did seek flag set to false, and the synchronously notify flag set to false.
998     timingDidChange(DidSeek::No, SynchronouslyNotify::No, Silently::Yes);
999
1000     invalidateEffect();
1001 }
1002
1003 ExceptionOr<void> WebAnimation::pause()
1004 {
1005     LOG_WITH_STREAM(Animations, stream << "WebAnimation " << this << " pause (current time is " << currentTime() << ")");
1006
1007     // 3.4.11. Pausing an animation
1008     // https://drafts.csswg.org/web-animations-1/#pause-an-animation
1009
1010     // 1. If animation has a pending pause task, abort these steps.
1011     if (hasPendingPauseTask())
1012         return { };
1013
1014     // 2. If the play state of animation is paused, abort these steps.
1015     if (playState() == PlayState::Paused)
1016         return { };
1017
1018     auto localTime = currentTime();
1019
1020     // 3. If the animation's current time is unresolved, perform the steps according to the first matching condition from below:
1021     if (!localTime) {
1022         if (m_playbackRate >= 0) {
1023             // If animation's playback rate is ≥ 0, let animation's hold time be zero.
1024             m_holdTime = 0_s;
1025         } else if (effectEndTime() == Seconds::infinity()) {
1026             // Otherwise, if target effect end for animation is positive infinity, throw an InvalidStateError and abort these steps.
1027             return Exception { InvalidStateError };
1028         } else {
1029             // Otherwise, let animation's hold time be target effect end.
1030             m_holdTime = effectEndTime();
1031         }
1032     }
1033
1034     // 4. Let has pending ready promise be a boolean flag that is initially false.
1035     bool hasPendingReadyPromise = false;
1036
1037     // 5. If animation has a pending play task, cancel that task and let has pending ready promise be true.
1038     if (hasPendingPlayTask()) {
1039         m_timeToRunPendingPlayTask = TimeToRunPendingTask::NotScheduled;
1040         hasPendingReadyPromise = true;
1041     }
1042
1043     // 6. If has pending ready promise is false, set animation's current ready promise to a new (pending) Promise object.
1044     if (!hasPendingReadyPromise)
1045         m_readyPromise = makeUniqueRef<ReadyPromise>(*this, &WebAnimation::readyPromiseResolve);
1046
1047     // 7. Schedule a task to be executed at the first possible moment after the user agent has performed any processing necessary
1048     //    to suspend the playback of animation's target effect, if any.
1049     m_timeToRunPendingPauseTask = TimeToRunPendingTask::ASAP;
1050
1051     // 8. Run the procedure to update an animation's finished state for animation with the did seek flag set to false, and the synchronously notify flag set to false.
1052     timingDidChange(DidSeek::No, SynchronouslyNotify::No);
1053
1054     invalidateEffect();
1055
1056     return { };
1057 }
1058
1059 ExceptionOr<void> WebAnimation::reverse()
1060 {
1061     LOG_WITH_STREAM(Animations, stream << "WebAnimation " << this << " reverse (current time is " << currentTime() << ")");
1062
1063     // 3.4.18. Reversing an animation
1064     // https://drafts.csswg.org/web-animations-1/#reverse-an-animation
1065
1066     // The procedure to reverse an animation of animation animation is as follows:
1067
1068     // 1. If there is no timeline associated with animation, or the associated timeline is inactive
1069     //    throw an InvalidStateError and abort these steps.
1070     if (!m_timeline || !m_timeline->currentTime())
1071         return Exception { InvalidStateError };
1072
1073     // 2. Let original pending playback rate be animation's pending playback rate.
1074     auto originalPendingPlaybackRate = m_pendingPlaybackRate;
1075
1076     // 3. Let animation's pending playback rate be the additive inverse of its effective playback rate (i.e. -effective playback rate).
1077     m_pendingPlaybackRate = -effectivePlaybackRate();
1078
1079     // 4. Run the steps to play an animation for animation with the auto-rewind flag set to true.
1080     auto playResult = play(AutoRewind::Yes);
1081
1082     // If the steps to play an animation throw an exception, set animation's pending playback rate to original
1083     // pending playback rate and propagate the exception.
1084     if (playResult.hasException()) {
1085         m_pendingPlaybackRate = originalPendingPlaybackRate;
1086         return playResult.releaseException();
1087     }
1088
1089     return { };
1090 }
1091
1092 void WebAnimation::runPendingPauseTask()
1093 {
1094     LOG_WITH_STREAM(Animations, stream << "WebAnimation " << this << " runPendingPauseTask (current time is " << currentTime() << ")");
1095
1096     // 3.4.11. Pausing an animation, step 7.
1097     // https://drafts.csswg.org/web-animations-1/#pause-an-animation
1098
1099     m_timeToRunPendingPauseTask = TimeToRunPendingTask::NotScheduled;
1100
1101     // 1. Let ready time be the time value of the timeline associated with animation at the moment when the user agent
1102     //    completed processing necessary to suspend playback of animation's target effect.
1103     auto readyTime = m_timeline->currentTime();
1104     auto animationStartTime = m_startTime;
1105
1106     // 2. If animation's start time is resolved and its hold time is not resolved, let animation's hold time be the result of
1107     //    evaluating (ready time - start time) × playback rate.
1108     //    Note: The hold time might be already set if the animation is finished, or if the animation is pending, waiting to begin
1109     //    playback. In either case we want to preserve the hold time as we enter the paused state.
1110     if (animationStartTime && !m_holdTime) {
1111         // FIXME: Implementation cannot guarantee an active timeline at the point of this async dispatch.
1112         // Subsequently, the resulting readyTime value can be null. Unify behavior between C++17 and
1113         // C++14 builds (the latter using WTF's Optional) and avoid null Optional dereferencing
1114         // by defaulting to a Seconds(0) value. See https://bugs.webkit.org/show_bug.cgi?id=186189.
1115         m_holdTime = (readyTime.valueOr(0_s) - animationStartTime.value()) * m_playbackRate;
1116     }
1117
1118     // 3. Apply any pending playback rate on animation.
1119     applyPendingPlaybackRate();
1120
1121     // 4. Make animation's start time unresolved.
1122     m_startTime = WTF::nullopt;
1123
1124     // 5. Resolve animation's current ready promise with animation.
1125     if (!m_readyPromise->isFulfilled())
1126         m_readyPromise->resolve(*this);
1127
1128     // 6. Run the procedure to update an animation's finished state for animation with the did seek flag set to false, and the
1129     //    synchronously notify flag set to false.
1130     timingDidChange(DidSeek::No, SynchronouslyNotify::No, Silently::Yes);
1131
1132     invalidateEffect();
1133 }
1134
1135 bool WebAnimation::isRunningAccelerated() const
1136 {
1137     return is<KeyframeEffect>(m_effect) && downcast<KeyframeEffect>(*m_effect).isRunningAccelerated();
1138 }
1139
1140 bool WebAnimation::needsTick() const
1141 {
1142     return pending() || playState() == PlayState::Running;
1143 }
1144
1145 void WebAnimation::tick()
1146 {
1147     updateFinishedState(DidSeek::No, SynchronouslyNotify::Yes);
1148     m_shouldSkipUpdatingFinishedStateWhenResolving = true;
1149
1150     // Run pending tasks, if any.
1151     if (hasPendingPauseTask())
1152         runPendingPauseTask();
1153     if (hasPendingPlayTask())
1154         runPendingPlayTask();
1155
1156     invalidateEffect();
1157 }
1158
1159 void WebAnimation::resolve(RenderStyle& targetStyle)
1160 {
1161     if (!m_shouldSkipUpdatingFinishedStateWhenResolving)
1162         updateFinishedState(DidSeek::No, SynchronouslyNotify::Yes);
1163     m_shouldSkipUpdatingFinishedStateWhenResolving = false;
1164
1165     if (m_effect)
1166         m_effect->apply(targetStyle);
1167 }
1168
1169 void WebAnimation::setSuspended(bool isSuspended)
1170 {
1171     if (m_isSuspended == isSuspended)
1172         return;
1173
1174     m_isSuspended = isSuspended;
1175
1176     if (m_effect && playState() == PlayState::Running)
1177         m_effect->animationSuspensionStateDidChange(isSuspended);
1178 }
1179
1180 void WebAnimation::acceleratedStateDidChange()
1181 {
1182     if (is<DocumentTimeline>(m_timeline))
1183         downcast<DocumentTimeline>(*m_timeline).animationAcceleratedRunningStateDidChange(*this);
1184 }
1185
1186 void WebAnimation::applyPendingAcceleratedActions()
1187 {
1188     if (is<KeyframeEffect>(m_effect))
1189         downcast<KeyframeEffect>(*m_effect).applyPendingAcceleratedActions();
1190 }
1191
1192 WebAnimation& WebAnimation::readyPromiseResolve()
1193 {
1194     return *this;
1195 }
1196
1197 WebAnimation& WebAnimation::finishedPromiseResolve()
1198 {
1199     return *this;
1200 }
1201
1202 const char* WebAnimation::activeDOMObjectName() const
1203 {
1204     return "Animation";
1205 }
1206
1207 void WebAnimation::suspend(ReasonForSuspension)
1208 {
1209     setSuspended(true);
1210 }
1211
1212 void WebAnimation::resume()
1213 {
1214     setSuspended(false);
1215 }
1216
1217 void WebAnimation::stop()
1218 {
1219     ActiveDOMObject::stop();
1220     removeAllEventListeners();
1221 }
1222
1223 bool WebAnimation::hasPendingActivity() const
1224 {
1225     // Keep the JS wrapper alive if the animation is considered relevant or could become relevant again by virtue of having a timeline.
1226     return m_timeline || m_isRelevant || ActiveDOMObject::hasPendingActivity();
1227 }
1228
1229 void WebAnimation::updateRelevance()
1230 {
1231     m_isRelevant = computeRelevance();
1232 }
1233
1234 bool WebAnimation::computeRelevance()
1235 {
1236     // To be listed in getAnimations() an animation needs a target effect which is current or in effect.
1237     if (!m_effect)
1238         return false;
1239
1240     auto timing = m_effect->getBasicTiming();
1241
1242     // An animation effect is in effect if its active time is not unresolved.
1243     if (timing.activeTime)
1244         return true;
1245
1246     // An animation effect is current if either of the following conditions is true:
1247     // - the animation effect is in the before phase, or
1248     // - the animation effect is in play.
1249
1250     // An animation effect is in play if all of the following conditions are met:
1251     // - the animation effect is in the active phase, and
1252     // - the animation effect is associated with an animation that is not finished.
1253     return timing.phase == AnimationEffectPhase::Before || (timing.phase == AnimationEffectPhase::Active && playState() != PlayState::Finished);
1254 }
1255
1256 bool WebAnimation::isReplaceable() const
1257 {
1258     // An animation is replaceable if all of the following conditions are true:
1259     // https://drafts.csswg.org/web-animations/#removing-replaced-animations
1260
1261     // The existence of the animation is not prescribed by markup. That is, it is not a CSS animation with an owning element,
1262     // nor a CSS transition with an owning element.
1263     if (isDeclarativeAnimation() && downcast<DeclarativeAnimation>(this)->owningElement())
1264         return false;
1265
1266     // The animation's play state is finished.
1267     if (playState() != PlayState::Finished)
1268         return false;
1269
1270     // The animation's replace state is not removed.
1271     if (m_replaceState == ReplaceState::Removed)
1272         return false;
1273
1274     // The animation is associated with a monotonically increasing timeline.
1275     if (!m_timeline)
1276         return false;
1277
1278     // The animation has an associated target effect.
1279     if (!m_effect)
1280         return false;
1281
1282     // The target effect associated with the animation is in effect.
1283     if (!m_effect->getBasicTiming().activeTime)
1284         return false;
1285
1286     // The target effect has an associated target element.
1287     if (!is<KeyframeEffect>(m_effect) || !downcast<KeyframeEffect>(m_effect.get())->target())
1288         return false;
1289
1290     return true;
1291 }
1292
1293 void WebAnimation::persist()
1294 {
1295     auto previousReplaceState = std::exchange(m_replaceState, ReplaceState::Persisted);
1296
1297     if (previousReplaceState == ReplaceState::Removed && m_timeline) {
1298         if (is<KeyframeEffect>(m_effect)) {
1299             auto& keyframeEffect = downcast<KeyframeEffect>(*m_effect);
1300             auto& target = *keyframeEffect.target();
1301             m_timeline->animationWasAddedToElement(*this, target);
1302             target.ensureKeyframeEffectStack().addEffect(keyframeEffect);
1303         }
1304     }
1305 }
1306
1307 ExceptionOr<void> WebAnimation::commitStyles()
1308 {
1309     // https://drafts.csswg.org/web-animations-1/#commit-computed-styles
1310
1311     // 1. Let targets be the set of all effect targets for animation effects associated with animation.
1312     auto* effect = is<KeyframeEffect>(m_effect) ? downcast<KeyframeEffect>(m_effect.get()) : nullptr;
1313     auto* target = effect ? effect->target() : nullptr;
1314
1315     // 2. For each target in targets:
1316     //
1317     // 2.1 If target is not an element capable of having a style attribute (for example, it is a pseudo-element or is an element in a
1318     // document format for which style attributes are not defined) throw a "NoModificationAllowedError" DOMException and abort these steps.
1319     if (!is<StyledElement>(target))
1320         return Exception { NoModificationAllowedError };
1321
1322     auto& styledElement = downcast<StyledElement>(*target);
1323
1324     // 2.2 If, after applying any pending style changes, target is not being rendered, throw an "InvalidStateError" DOMException and abort these steps.
1325     styledElement.document().updateStyleIfNeeded();
1326     auto* renderer = styledElement.renderer();
1327     if (!renderer)
1328         return Exception { InvalidStateError };
1329
1330     // 2.3 Let inline style be the result of getting the CSS declaration block corresponding to target’s style attribute. If target does not have a style
1331     // attribute, let inline style be a new empty CSS declaration block with the readonly flag unset and owner node set to target.
1332
1333     // 2.4 Let targeted properties be the set of physical longhand properties that are a target property for at least one animation effect associated with
1334     // animation whose effect target is target.
1335
1336     auto& style = renderer->style();
1337     auto computedStyleExtractor = ComputedStyleExtractor(&styledElement);
1338     auto inlineStyle = styledElement.document().createCSSStyleDeclaration();
1339     inlineStyle->setCssText(styledElement.getAttribute("style"));
1340
1341     auto& keyframeStack = styledElement.ensureKeyframeEffectStack();
1342     auto* cssAnimationList = keyframeStack.cssAnimationList();
1343
1344     // 2.5 For each property, property, in targeted properties:
1345     for (auto property : effect->animatedProperties()) {
1346         // 1. Let partialEffectStack be a copy of the effect stack for property on target.
1347         // 2. If animation's replace state is removed, add all animation effects associated with animation whose effect target is target and which include
1348         // property as a target property to partialEffectStack.
1349         // 3. Remove from partialEffectStack any animation effects whose associated animation has a higher composite order than animation.
1350         // 4. Let effect value be the result of calculating the result of partialEffectStack for property using target's computed style (see § 5.4.3 Calculating
1351         // the result of an effect stack).
1352         // 5. Set a CSS declaration property for effect value in inline style.
1353         // 6. Update style attribute for inline style.
1354
1355         // We actually perform those steps in a different way: instead of building a copy of the effect stack and then removing stuff, we iterate through the
1356         // effect stack and stop when we've found this animation's effect or when we've found an effect associated with an animation with a higher composite order.
1357         auto animatedStyle = RenderStyle::clonePtr(style);
1358         for (const auto& effectInStack : keyframeStack.sortedEffects()) {
1359             if (effectInStack->animation() != this && !compareAnimationsByCompositeOrder(*effectInStack->animation(), *this, cssAnimationList))
1360                 break;
1361             if (effectInStack->animatedProperties().contains(property))
1362                 effectInStack->animation()->resolve(*animatedStyle);
1363             if (effectInStack->animation() == this)
1364                 break;
1365         }
1366         if (m_replaceState == ReplaceState::Removed)
1367             effect->animation()->resolve(*animatedStyle);
1368         if (auto cssValue = computedStyleExtractor.valueForPropertyInStyle(*animatedStyle, property, renderer))
1369             inlineStyle->setPropertyInternal(property, cssValue->cssText(), false);
1370     }
1371
1372     styledElement.setAttribute("style", inlineStyle->cssText());
1373
1374     return { };
1375 }
1376
1377 Seconds WebAnimation::timeToNextTick() const
1378 {
1379     // Any animation that is pending needs immediate resolution.
1380     if (pending())
1381         return 0_s;
1382
1383     // If we're not running, or time is not advancing for this animation, there's no telling when we'll end.
1384     auto playbackRate = effectivePlaybackRate();
1385     if (playState() != PlayState::Running || !playbackRate)
1386         return Seconds::infinity();
1387
1388     auto& effect = *this->effect();
1389     auto timing = effect.getBasicTiming();
1390     switch (timing.phase) {
1391     case AnimationEffectPhase::Before:
1392         // The animation is in its "before" phase, in this case we can wait until it enters its "active" phase.
1393         return (effect.delay() - timing.localTime.value()) / playbackRate;
1394     case AnimationEffectPhase::Active:
1395         // Non-accelerated animations in the "active" phase will need to update their animated value at the immediate next opportunity.
1396         if (!isRunningAccelerated())
1397             return 0_s;
1398         // Accelerated CSS Animations need to trigger "animationiteration" events, in this case we can wait until the next iteration.
1399         if (isCSSAnimation()) {
1400             if (auto iterationProgress = effect.getComputedTiming().simpleIterationProgress)
1401                 return effect.iterationDuration() * (1 - *iterationProgress);
1402         }
1403         // Accelerated animations in the "active" phase can wait until they ended.
1404         return (effect.endTime() - timing.localTime.value()) / playbackRate;
1405     case AnimationEffectPhase::After:
1406         // The animation is in its after phase, which means it will no longer update its value, so it doens't need a tick.
1407         return Seconds::infinity();
1408     case AnimationEffectPhase::Idle:
1409         ASSERT_NOT_REACHED();
1410         return Seconds::infinity();
1411     }
1412
1413     ASSERT_NOT_REACHED();
1414     return Seconds::infinity();
1415 }
1416
1417 } // namespace WebCore