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