6bc1a9b92575aa056dad453a19834d694a9e0c86
[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 "AnimationEffectReadOnly.h"
30 #include "AnimationEffectTimingReadOnly.h"
31 #include "AnimationPlaybackEvent.h"
32 #include "AnimationTimeline.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/text/WTFString.h>
41
42 namespace WebCore {
43
44 Ref<WebAnimation> WebAnimation::create(Document& document, AnimationEffectReadOnly* effect)
45 {
46     auto result = adoptRef(*new WebAnimation(document));
47     result->setEffect(effect);
48     result->setTimeline(&document.timeline());
49     return result;
50 }
51
52 Ref<WebAnimation> WebAnimation::create(Document& document, AnimationEffectReadOnly* effect, AnimationTimeline* timeline)
53 {
54     auto result = adoptRef(*new WebAnimation(document));
55     result->setEffect(effect);
56     if (timeline)
57         result->setTimeline(timeline);
58     return result;
59 }
60
61 WebAnimation::WebAnimation(Document& document)
62     : ActiveDOMObject(&document)
63     , m_readyPromise(*this, &WebAnimation::readyPromiseResolve)
64     , m_finishedPromise(*this, &WebAnimation::finishedPromiseResolve)
65 {
66     suspendIfNeeded();
67 }
68
69 WebAnimation::~WebAnimation()
70 {
71     if (m_timeline)
72         m_timeline->removeAnimation(*this);
73 }
74
75 void WebAnimation::timingModelDidChange()
76 {
77     if (m_effect)
78         m_effect->invalidate();
79     if (m_timeline)
80         m_timeline->timingModelDidChange();
81 }
82
83 void WebAnimation::setEffect(RefPtr<AnimationEffectReadOnly>&& effect)
84 {
85     // 3.4.3. Setting the target effect of an animation
86     // https://drafts.csswg.org/web-animations-1/#setting-the-target-effect
87
88     // 2. If new effect is the same object as old effect, abort this procedure.
89     if (effect == m_effect)
90         return;
91
92     // 3. If new effect is null and old effect is not null, run the procedure to reset an animation's pending tasks on animation.
93     if (!effect && m_effect)
94         resetPendingTasks();
95
96     if (m_effect) {
97         m_effect->setAnimation(nullptr);
98
99         // Update the Element to Animation map.
100         if (m_timeline && is<KeyframeEffect>(m_effect)) {
101             auto* keyframeEffect = downcast<KeyframeEffect>(m_effect.get());
102             auto* target = keyframeEffect->target();
103             if (target)
104                 m_timeline->animationWasRemovedFromElement(*this, *target);
105         }
106     }
107
108     if (effect) {
109         // An animation effect can only be associated with a single animation.
110         if (effect->animation())
111             effect->animation()->setEffect(nullptr);
112
113         effect->setAnimation(this);
114
115         if (m_timeline && is<KeyframeEffect>(effect)) {
116             auto* keyframeEffect = downcast<KeyframeEffect>(effect.get());
117             auto* target = keyframeEffect->target();
118             if (target)
119                 m_timeline->animationWasAddedToElement(*this, *target);
120         }
121     }
122
123     m_effect = WTFMove(effect);
124 }
125
126 void WebAnimation::setTimeline(RefPtr<AnimationTimeline>&& timeline)
127 {
128     // 3.4.1. Setting the timeline of an animation
129     // https://drafts.csswg.org/web-animations-1/#setting-the-timeline
130
131     // 2. If new timeline is the same object as old timeline, abort this procedure.
132     if (timeline == m_timeline)
133         return;
134
135     // 4. If the animation start time of animation is resolved, make animation’s hold time unresolved.
136     if (startTime())
137         m_holdTime = std::nullopt;
138
139     if (m_timeline)
140         m_timeline->removeAnimation(*this);
141
142     if (timeline)
143         timeline->addAnimation(*this);
144
145     if (is<KeyframeEffect>(m_effect)) {
146         auto* keyframeEffect = downcast<KeyframeEffect>(m_effect.get());
147         auto* target = keyframeEffect->target();
148         if (target) {
149             if (m_timeline)
150                 m_timeline->animationWasRemovedFromElement(*this, *target);
151             if (timeline)
152                 timeline->animationWasAddedToElement(*this, *target);
153         }
154     }
155
156     m_timeline = WTFMove(timeline);
157
158     updatePendingTasks();
159
160     // 5. Run the procedure to update an animation’s finished state for animation with the did seek flag set to false,
161     // and the synchronously notify flag set to false.
162     updateFinishedState(DidSeek::No, SynchronouslyNotify::No);
163 }
164
165 void WebAnimation::effectTargetDidChange(Element* previousTarget, Element* newTarget)
166 {
167     if (!m_timeline)
168         return;
169
170     if (previousTarget)
171         m_timeline->animationWasRemovedFromElement(*this, *previousTarget);
172
173     if (newTarget)
174         m_timeline->animationWasAddedToElement(*this, *newTarget);
175 }
176
177 std::optional<double> WebAnimation::bindingsStartTime() const
178 {
179     if (!m_startTime)
180         return std::nullopt;
181     return secondsToWebAnimationsAPITime(m_startTime.value());
182 }
183
184 void WebAnimation::setBindingsStartTime(std::optional<double> startTime)
185 {
186     if (!startTime)
187         setStartTime(std::nullopt);
188     else
189         setStartTime(Seconds::fromMilliseconds(startTime.value()));
190 }
191
192 std::optional<Seconds> WebAnimation::startTime() const
193 {
194     return m_startTime;
195 }
196
197 void WebAnimation::setStartTime(std::optional<Seconds> startTime)
198 {
199     if (startTime == m_startTime)
200         return;
201
202     m_startTime = startTime;
203     timingModelDidChange();
204 }
205
206 std::optional<double> WebAnimation::bindingsCurrentTime() const
207 {
208     auto time = currentTime();
209     if (!time)
210         return std::nullopt;
211     return secondsToWebAnimationsAPITime(time.value());
212 }
213
214 ExceptionOr<void> WebAnimation::setBindingsCurrentTime(std::optional<double> currentTime)
215 {
216     if (!currentTime)
217         return setCurrentTime(std::nullopt);
218     return setCurrentTime(Seconds::fromMilliseconds(currentTime.value()));
219 }
220
221 std::optional<Seconds> WebAnimation::currentTime() const
222 {
223     return currentTime(RespectHoldTime::Yes);
224 }
225
226 std::optional<Seconds> WebAnimation::currentTime(RespectHoldTime respectHoldTime) const
227 {
228     // 3.4.4. The current time of an animation
229     // https://drafts.csswg.org/web-animations-1/#the-current-time-of-an-animation
230
231     // The current time is calculated from the first matching condition from below:
232
233     // If the animation's hold time is resolved, the current time is the animation's hold time.
234     if (respectHoldTime == RespectHoldTime::Yes && m_holdTime)
235         return m_holdTime;
236
237     // If any of the following are true:
238     //     1. the animation has no associated timeline, or
239     //     2. the associated timeline is inactive, or
240     //     3. the animation's start time is unresolved.
241     // The current time is an unresolved time value.
242     if (!m_timeline || !m_timeline->currentTime() || !m_startTime)
243         return std::nullopt;
244
245     // Otherwise, current time = (timeline time - start time) * playback rate
246     return (m_timeline->currentTime().value() - m_startTime.value()) * m_playbackRate;
247 }
248
249 ExceptionOr<void> WebAnimation::silentlySetCurrentTime(std::optional<Seconds> seekTime)
250 {
251     // 3.4.5. Setting the current time of an animation
252     // https://drafts.csswg.org/web-animations-1/#setting-the-current-time-of-an-animation
253
254     // 1. If seek time is an unresolved time value, then perform the following steps.
255     if (!seekTime) {
256         // 1. If the current time is resolved, then throw a TypeError.
257         if (currentTime())
258             return Exception { TypeError };
259         // 2. Abort these steps.
260         return { };
261     }
262
263     // 2. Update either animation's hold time or start time as follows:
264     // If any of the following conditions are true:
265     //     - animation's hold time is resolved, or
266     //     - animation's start time is unresolved, or
267     //     - animation has no associated timeline or the associated timeline is inactive, or
268     //     - animation's playback rate is 0,
269     // Set animation's hold time to seek time.
270     // Otherwise, set animation's start time to the result of evaluating timeline time - (seek time / playback rate)
271     // where timeline time is the current time value of timeline associated with animation.
272     if (m_holdTime || !startTime() || !m_timeline || !m_timeline->currentTime() || !m_playbackRate)
273         m_holdTime = seekTime;
274     else
275         setStartTime(m_timeline->currentTime().value() - (seekTime.value() / m_playbackRate));
276
277     // 3. If animation has no associated timeline or the associated timeline is inactive, make animation's start time unresolved.
278     if (!m_timeline || !m_timeline->currentTime())
279         setStartTime(std::nullopt);
280
281     // 4. Make animation's previous current time unresolved.
282     m_previousCurrentTime = std::nullopt;
283
284     return { };
285 }
286
287 ExceptionOr<void> WebAnimation::setCurrentTime(std::optional<Seconds> seekTime)
288 {
289     // 3.4.5. Setting the current time of an animation
290     // https://drafts.csswg.org/web-animations-1/#setting-the-current-time-of-an-animation
291
292     // 1. Run the steps to silently set the current time of animation to seek time.
293     auto silentResult = silentlySetCurrentTime(seekTime);
294     if (silentResult.hasException())
295         return silentResult.releaseException();
296
297     // 2. If animation has a pending pause task, synchronously complete the pause operation by performing the following steps:
298     if (hasPendingPauseTask()) {
299         // 1. Set animation's hold time to seek time.
300         m_holdTime = seekTime;
301         // 2. Make animation's start time unresolved.
302         setStartTime(std::nullopt);
303         // 3. Cancel the pending pause task.
304         setTimeToRunPendingPauseTask(TimeToRunPendingTask::NotScheduled);
305         // 4. Resolve animation's current ready promise with animation.
306         m_readyPromise.resolve(*this);
307     }
308
309     // 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.
310     updateFinishedState(DidSeek::Yes, SynchronouslyNotify::No);
311
312     return { };
313 }
314
315 void WebAnimation::setPlaybackRate(double newPlaybackRate, Silently silently)
316 {
317     // 3.4.17.1. Updating the playback rate of an animation
318     // https://drafts.csswg.org/web-animations-1/#updating-the-playback-rate-of-an-animation
319
320     if (m_playbackRate == newPlaybackRate)
321         return;
322
323     // The procedure to set the animation playback rate of an animation, animation to new playback rate is as follows.
324     // The procedure to silently set the animation playback rate of animation, animation to new playback rate is identical
325     // to the above procedure except that rather than invoking the procedure to set the current time in the final step,
326     // the procedure to silently set the current time is invoked instead.
327
328     // 1. Let previous time be the value of the current time of animation before changing the playback rate.
329     auto previousTime = currentTime();
330
331     // 2. Set the playback rate to new playback rate.
332     m_playbackRate = newPlaybackRate;
333
334     // 3. If previous time is resolved, set the current time of animation to previous time.
335     // Changes to the playback rate trigger a compensatory seek so that that the animation's current time
336     // is unaffected by the change to the playback rate.
337     if (!previousTime)
338         return;
339
340     if (silently == Silently::Yes)
341         silentlySetCurrentTime(previousTime);
342     else
343         setCurrentTime(previousTime);
344 }
345
346 auto WebAnimation::playState() const -> PlayState
347 {
348     // Section 3.5.19. Play states
349
350     // Animation has a pending play task or a pending pause task → pending
351     if (pending())
352         return PlayState::Pending;
353
354     // The current time of animation is unresolved → idle
355     auto animationCurrentTime = currentTime();
356     if (!animationCurrentTime)
357         return PlayState::Idle;
358
359     // The start time of animation is unresolved → paused
360     if (!startTime())
361         return PlayState::Paused;
362
363     // For animation, animation playback rate > 0 and current time ≥ target effect end; or
364     // animation playback rate < 0 and current time ≤ 0 → finished
365     if ((m_playbackRate > 0 && animationCurrentTime.value() >= effectEndTime()) || (m_playbackRate < 0 && animationCurrentTime.value() <= 0_s))
366         return PlayState::Finished;
367
368     // Otherwise → running
369     return PlayState::Running;
370 }
371
372 Seconds WebAnimation::effectEndTime() const
373 {
374     // The target effect end of an animation is equal to the end time of the animation's target effect.
375     // If the animation has no target effect, the target effect end is zero.
376     return m_effect ? m_effect->timing()->endTime() : 0_s;
377 }
378
379 void WebAnimation::cancel()
380 {
381     // 3.4.16. Canceling an animation
382     // https://drafts.csswg.org/web-animations-1/#canceling-an-animation-section
383     //
384     // An animation can be canceled which causes the current time to become unresolved hence removing any effects caused by the target effect.
385     //
386     // The procedure to cancel an animation for animation is as follows:
387     //
388     // 1. If animation's play state is not idle, perform the following steps:
389     if (playState() != PlayState::Idle) {
390         // 1. Run the procedure to reset an animation's pending tasks on animation.
391         resetPendingTasks();
392
393         // 2. Reject the current finished promise with a DOMException named "AbortError".
394         m_finishedPromise.reject(Exception { AbortError });
395
396         // 3. Let current finished promise be a new (pending) Promise object.
397         m_finishedPromise.clear();
398
399         // 4. Create an AnimationPlaybackEvent, cancelEvent.
400         // 5. Set cancelEvent's type attribute to cancel.
401         // 6. Set cancelEvent's currentTime to null.
402         // 7. Let timeline time be the current time of the timeline with which animation is associated. If animation is not associated with an
403         //    active timeline, let timeline time be n unresolved time value.
404         // 8. Set cancelEvent's timelineTime to timeline time. If timeline time is unresolved, set it to null.
405         // 9. If animation has a document for timing, then append cancelEvent to its document for timing's pending animation event queue along
406         //    with its target, animation. If animation is associated with an active timeline that defines a procedure to convert timeline times
407         //    to origin-relative time, let the scheduled event time be the result of applying that procedure to timeline time. Otherwise, the
408         //    scheduled event time is an unresolved time value.
409         // Otherwise, queue a task to dispatch cancelEvent at animation. The task source for this task is the DOM manipulation task source.
410         enqueueAnimationPlaybackEvent(eventNames().cancelEvent, std::nullopt, m_timeline ? m_timeline->currentTime() : std::nullopt);
411     }
412
413     // 2. Make animation's hold time unresolved.
414     m_holdTime = std::nullopt;
415
416     // 3. Make animation's start time unresolved.
417     setStartTime(std::nullopt);
418 }
419
420 void WebAnimation::enqueueAnimationPlaybackEvent(const AtomicString& type, std::optional<Seconds> currentTime, std::optional<Seconds> timelineTime)
421 {
422     auto event = AnimationPlaybackEvent::create(type, currentTime, timelineTime);
423     event->setTarget(this);
424
425     if (is<DocumentTimeline>(m_timeline)) {
426         // If animation has a document for timing, then append event to its document for timing's pending animation event queue along
427         // with its target, animation. If animation is associated with an active timeline that defines a procedure to convert timeline times
428         // to origin-relative time, let the scheduled event time be the result of applying that procedure to timeline time. Otherwise, the
429         // scheduled event time is an unresolved time value.
430         downcast<DocumentTimeline>(*m_timeline).enqueueAnimationPlaybackEvent(WTFMove(event));
431     } else {
432         // Otherwise, queue a task to dispatch event at animation. The task source for this task is the DOM manipulation task source.
433         callOnMainThread([this, pendingActivity = makePendingActivity(*this), event = WTFMove(event)]() {
434             if (!m_isStopped)
435                 this->dispatchEvent(event);
436         });
437     }
438 }
439
440 void WebAnimation::resetPendingTasks()
441 {
442     // The procedure to reset an animation's pending tasks for animation is as follows:
443     // https://drafts.csswg.org/web-animations-1/#reset-an-animations-pending-tasks
444     //
445     // 1. If animation does not have a pending play task or a pending pause task, abort this procedure.
446     if (!pending())
447         return;
448
449     // 2. If animation has a pending play task, cancel that task.
450     if (hasPendingPlayTask())
451         setTimeToRunPendingPlayTask(TimeToRunPendingTask::NotScheduled);
452
453     // 3. If animation has a pending pause task, cancel that task.
454     if (hasPendingPauseTask())
455         setTimeToRunPendingPauseTask(TimeToRunPendingTask::NotScheduled);
456
457     // 4. Reject animation's current ready promise with a DOMException named "AbortError".
458     m_readyPromise.reject(Exception { AbortError });
459
460     // 5. Let animation's current ready promise be the result of creating a new resolved Promise object.
461     m_readyPromise.clear();
462     m_readyPromise.resolve(*this);
463 }
464
465 ExceptionOr<void> WebAnimation::finish()
466 {
467     // 3.4.15. Finishing an animation
468     // https://drafts.csswg.org/web-animations-1/#finishing-an-animation-section
469
470     // 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:
471     //
472     // 1. If animation playback rate is zero, or if animation playback rate > 0 and target effect end is infinity, throw an InvalidStateError and abort these steps.
473     if (!m_playbackRate || (m_playbackRate > 0 && effectEndTime() == Seconds::infinity()))
474         return Exception { InvalidStateError };
475
476     // 2. Set limit as follows:
477     // If animation playback rate > 0, let limit be target effect end.
478     // Otherwise, let limit be zero.
479     auto limit = m_playbackRate > 0 ? effectEndTime() : 0_s;
480
481     // 3. Silently set the current time to limit.
482     silentlySetCurrentTime(limit);
483
484     // 4. If animation's start time is unresolved and animation has an associated active timeline, let the start time be the result of
485     //    evaluating timeline time - (limit / playback rate) where timeline time is the current time value of the associated timeline.
486     if (!startTime() && m_timeline && m_timeline->currentTime())
487         setStartTime(m_timeline->currentTime().value() - (limit / m_playbackRate));
488
489     // 5. If there is a pending pause task and start time is resolved,
490     if (hasPendingPauseTask() && startTime()) {
491         // 1. Let the hold time be unresolved.
492         m_holdTime = std::nullopt;
493         // 2. Cancel the pending pause task.
494         setTimeToRunPendingPauseTask(TimeToRunPendingTask::NotScheduled);
495         // 3. Resolve the current ready promise of animation with animation.
496         m_readyPromise.resolve(*this);
497     }
498
499     // 6. 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.
500     if (hasPendingPlayTask() && startTime()) {
501         setTimeToRunPendingPlayTask(TimeToRunPendingTask::NotScheduled);
502         m_readyPromise.resolve(*this);
503     }
504
505     // 7. 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.
506     updateFinishedState(DidSeek::Yes, SynchronouslyNotify::Yes);
507
508     return { };
509 }
510
511 void WebAnimation::updateFinishedState(DidSeek didSeek, SynchronouslyNotify synchronouslyNotify)
512 {
513     // 3.4.14. Updating the finished state
514     // https://drafts.csswg.org/web-animations-1/#updating-the-finished-state
515
516     // 1. Let the unconstrained current time be the result of calculating the current time substituting an unresolved time value
517     // for the hold time if did seek is false. If did seek is true, the unconstrained current time is equal to the current time.
518     auto unconstrainedCurrentTime = currentTime(didSeek == DidSeek::Yes ? RespectHoldTime::Yes : RespectHoldTime::No);
519     auto endTime = effectEndTime();
520
521     // 2. If all three of the following conditions are true,
522     //    - the unconstrained current time is resolved, and
523     //    - animation's start time is resolved, and
524     //    - animation does not have a pending play task or a pending pause task,
525     if (unconstrainedCurrentTime && startTime() && !pending()) {
526         // then update animation's hold time based on the first matching condition for animation from below, if any:
527         if (m_playbackRate > 0 && unconstrainedCurrentTime >= endTime) {
528             // If animation playback rate > 0 and unconstrained current time is greater than or equal to target effect end,
529             // If did seek is true, let the hold time be the value of unconstrained current time.
530             if (didSeek == DidSeek::Yes)
531                 m_holdTime = unconstrainedCurrentTime;
532             // 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.
533             else if (!m_previousCurrentTime)
534                 m_holdTime = endTime;
535             else
536                 m_holdTime = std::max(m_previousCurrentTime.value(), endTime);
537         } else if (m_playbackRate < 0 && unconstrainedCurrentTime <= 0_s) {
538             // If animation playback rate < 0 and unconstrained current time is less than or equal to 0,
539             // If did seek is true, let the hold time be the value of unconstrained current time.
540             if (didSeek == DidSeek::Yes)
541                 m_holdTime = unconstrainedCurrentTime;
542             // 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.
543             else if (!m_previousCurrentTime)
544                 m_holdTime = 0_s;
545             else
546                 m_holdTime = std::min(m_previousCurrentTime.value(), 0_s);
547         } else if (m_playbackRate && m_timeline && m_timeline->currentTime()) {
548             // If animation playback rate ≠ 0, and animation is associated with an active timeline,
549             // Perform the following steps:
550             // 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)
551             //    where timeline time is the current time value of timeline associated with animation.
552             if (didSeek == DidSeek::Yes && m_holdTime)
553                 setStartTime(m_timeline->currentTime().value() - (m_holdTime.value() / m_playbackRate));
554             // 2. Let the hold time be unresolved.
555             m_holdTime = std::nullopt;
556         }
557     }
558
559     // 3. Set the previous current time of animation be the result of calculating its current time.
560     m_previousCurrentTime = currentTime();
561
562     // 4. Let current finished state be true if the play state of animation is finished. Otherwise, let it be false.
563     auto currentFinishedState = playState() == PlayState::Finished;
564
565     // 5. If current finished state is true and the current finished promise is not yet resolved, perform the following steps:
566     if (currentFinishedState && !m_finishedPromise.isFulfilled()) {
567         if (synchronouslyNotify == SynchronouslyNotify::Yes) {
568             // If synchronously notify is true, cancel any queued microtask to run the finish notification steps for this animation,
569             // and run the finish notification steps immediately.
570             m_finishNotificationStepsMicrotaskPending = false;
571             finishNotificationSteps();
572         } else if (!m_finishNotificationStepsMicrotaskPending) {
573             // Otherwise, if synchronously notify is false, queue a microtask to run finish notification steps for animation unless there
574             // is already a microtask queued to run those steps for animation.
575             m_finishNotificationStepsMicrotaskPending = true;
576             scheduleMicrotaskIfNeeded();
577         }
578     }
579
580     // 6. If current finished state is false and animation's current finished promise is already resolved, set animation's current
581     // finished promise to a new (pending) Promise object.
582     if (!currentFinishedState && m_finishedPromise.isFulfilled())
583         m_finishedPromise.clear();
584 }
585
586 void WebAnimation::scheduleMicrotaskIfNeeded()
587 {
588     if (m_scheduledMicrotask)
589         return;
590
591     m_scheduledMicrotask = true;
592     MicrotaskQueue::mainThreadQueue().append(std::make_unique<VoidMicrotask>(std::bind(&WebAnimation::performMicrotask, this)));
593 }
594
595 void WebAnimation::performMicrotask()
596 {
597     m_scheduledMicrotask = false;
598     if (!m_finishNotificationStepsMicrotaskPending)
599         return;
600
601     m_finishNotificationStepsMicrotaskPending = false;
602     finishNotificationSteps();
603 }
604
605 void WebAnimation::finishNotificationSteps()
606 {
607     // 3.4.14. Updating the finished state
608     // https://drafts.csswg.org/web-animations-1/#finish-notification-steps
609
610     // Let finish notification steps refer to the following procedure:
611     // 1. If animation's play state is not equal to finished, abort these steps.
612     if (playState() != PlayState::Finished)
613         return;
614
615     // 2. Resolve animation's current finished promise object with animation.
616     m_finishedPromise.resolve(*this);
617
618     // 3. Create an AnimationPlaybackEvent, finishEvent.
619     // 4. Set finishEvent's type attribute to finish.
620     // 5. Set finishEvent's currentTime attribute to the current time of animation.
621     // 6. Set finishEvent's timelineTime attribute to the current time of the timeline with which animation is associated.
622     //    If animation is not associated with a timeline, or the timeline is inactive, let timelineTime be null.
623     // 7. If animation has a document for timing, then append finishEvent to its document for timing's pending animation event
624     //    queue along with its target, animation. For the scheduled event time, use the result of converting animation's target
625     //    effect end to an origin-relative time.
626     //    Otherwise, queue a task to dispatch finishEvent at animation. The task source for this task is the DOM manipulation task source.
627     enqueueAnimationPlaybackEvent(eventNames().finishEvent, currentTime(), m_timeline ? m_timeline->currentTime() : std::nullopt);
628 }
629
630 ExceptionOr<void> WebAnimation::play()
631 {
632     return play(AutoRewind::Yes);
633 }
634
635 ExceptionOr<void> WebAnimation::play(AutoRewind autoRewind)
636 {
637     // 3.4.10. Playing an animation
638     // https://drafts.csswg.org/web-animations-1/#play-an-animation
639
640     auto localTime = currentTime();
641     auto endTime = effectEndTime();
642
643     // 1. Let aborted pause be a boolean flag that is true if animation has a pending pause task, and false otherwise.
644     bool abortedPause = hasPendingPauseTask();
645
646     // 2. Let has pending ready promise be a boolean flag that is initially false.
647     bool hasPendingReadyPromise = false;
648
649     // 3. Perform the steps corresponding to the first matching condition from the following, if any:
650     if (m_playbackRate > 0 && autoRewind == AutoRewind::Yes && (!localTime || localTime.value() < 0_s || localTime.value() >= endTime)) {
651         // If animation playback rate > 0, the auto-rewind flag is true and either animation's:
652         //     - current time is unresolved, or
653         //     - current time < zero, or
654         //     - current time ≥ target effect end,
655         // Set animation's hold time to zero.
656         m_holdTime = 0_s;
657     } else if (m_playbackRate < 0 && autoRewind == AutoRewind::Yes && (!localTime || localTime.value() <= 0_s || localTime.value() > endTime)) {
658         // If animation playback rate < 0, the auto-rewind flag is true and either animation's:
659         //     - current time is unresolved, or
660         //     - current time ≤ zero, or
661         //     - current time > target effect end
662         // If target effect end is positive infinity, throw an InvalidStateError and abort these steps.
663         if (endTime == Seconds::infinity())
664             return Exception { InvalidStateError };
665         m_holdTime = endTime;
666     } else if (!m_playbackRate && !localTime) {
667         // If animation playback rate = 0 and animation's current time is unresolved,
668         // Set animation's hold time to zero.
669         m_holdTime = 0_s;
670     }
671
672     // 4. If animation has a pending play task or a pending pause task,
673     if (hasPendingPauseTask()) {
674         // 1. Cancel that task.
675         setTimeToRunPendingPlayTask(TimeToRunPendingTask::NotScheduled);
676         // 2. Set has pending ready promise to true.
677         hasPendingReadyPromise = true;
678     }
679
680     // 5. If animation's hold time is unresolved and aborted pause is false, abort this procedure.
681     if (!m_holdTime && !abortedPause)
682         return { };
683
684     // 6. If animation's hold time is resolved, let its start time be unresolved.
685     if (m_holdTime)
686         setStartTime(std::nullopt);
687
688     // 7. If has pending ready promise is false, let animation's current ready promise be a new (pending) Promise object.
689     if (!hasPendingReadyPromise)
690         m_readyPromise.clear();
691
692     // 8. Schedule a task to run as soon as animation is ready.
693     setTimeToRunPendingPlayTask(TimeToRunPendingTask::WhenReady);
694
695     // 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.
696     updateFinishedState(DidSeek::No, SynchronouslyNotify::No);
697
698     return { };
699 }
700
701 void WebAnimation::setTimeToRunPendingPlayTask(TimeToRunPendingTask timeToRunPendingTask)
702 {
703     m_timeToRunPendingPlayTask = timeToRunPendingTask;
704     updatePendingTasks();
705 }
706
707 void WebAnimation::runPendingPlayTask()
708 {
709     // 3.4.10. Playing an animation, step 8.
710     // https://drafts.csswg.org/web-animations-1/#play-an-animation
711
712     m_timeToRunPendingPlayTask = TimeToRunPendingTask::NotScheduled;
713
714     // 1. Let ready time be the time value of the timeline associated with animation at the moment when animation became ready.
715     auto readyTime = m_timeline->currentTime();
716
717     // 2. If animation's start time is unresolved, perform the following steps:
718     if (!startTime()) {
719         // 1. Let new start time be the result of evaluating ready time - hold time / animation playback rate for animation.
720         // If the animation playback rate is zero, let new start time be simply ready time.
721         auto newStartTime = readyTime.value();
722         if (m_playbackRate)
723             newStartTime -= m_holdTime.value() / m_playbackRate;
724         // 2. If animation's playback rate is not 0, make animation's hold time unresolved.
725         if (m_playbackRate)
726             m_holdTime = std::nullopt;
727         // 3. Set the animation start time of animation to new start time.
728         setStartTime(newStartTime);
729     }
730
731     // 3. Resolve animation's current ready promise with animation.
732     m_readyPromise.resolve(*this);
733
734     // 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.
735     updateFinishedState(DidSeek::No, SynchronouslyNotify::No);
736 }
737
738 ExceptionOr<void> WebAnimation::pause()
739 {
740     // 3.4.11. Pausing an animation
741     // https://drafts.csswg.org/web-animations-1/#pause-an-animation
742
743     // 1. If animation has a pending pause task, abort these steps.
744     if (hasPendingPauseTask())
745         return { };
746
747     // 2. If the play state of animation is paused, abort these steps.
748     if (playState() == PlayState::Paused)
749         return { };
750
751     auto localTime = currentTime();
752
753     // 3. If the animation's current time is unresolved, perform the steps according to the first matching condition from below:
754     if (!localTime) {
755         if (m_playbackRate >= 0) {
756             // If animation's playback rate is ≥ 0, let animation's hold time be zero.
757             m_holdTime = 0_s;
758         } else if (effectEndTime() == Seconds::infinity()) {
759             // Otherwise, if target effect end for animation is positive infinity, throw an InvalidStateError and abort these steps.
760             return Exception { InvalidStateError };
761         } else {
762             // Otherwise, let animation's hold time be target effect end.
763             m_holdTime = effectEndTime();
764         }
765     }
766
767     // 4. Let has pending ready promise be a boolean flag that is initially false.
768     bool hasPendingReadyPromise = false;
769
770     // 5. If animation has a pending play task, cancel that task and let has pending ready promise be true.
771     if (hasPendingPlayTask()) {
772         setTimeToRunPendingPlayTask(TimeToRunPendingTask::NotScheduled);
773         hasPendingReadyPromise = true;
774     }
775
776     // 6. If has pending ready promise is false, set animation's current ready promise to a new (pending) Promise object.
777     if (!hasPendingReadyPromise)
778         m_readyPromise.clear();
779
780     // 7. Schedule a task to be executed at the first possible moment after the user agent has performed any processing necessary
781     //    to suspend the playback of animation's target effect, if any.
782     setTimeToRunPendingPauseTask(TimeToRunPendingTask::ASAP);
783
784     // 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.
785     updateFinishedState(DidSeek::No, SynchronouslyNotify::No);
786
787     return { };
788 }
789
790 ExceptionOr<void> WebAnimation::reverse()
791 {
792     // 3.4.18. Reversing an animation
793     // https://drafts.csswg.org/web-animations-1/#reverse-an-animation
794
795     // The procedure to reverse an animation of animation animation is as follows:
796
797     // 1. If there is no timeline associated with animation, or the associated timeline is inactive
798     //    throw an InvalidStateError and abort these steps.
799     if (!m_timeline || !m_timeline->currentTime())
800         return Exception { InvalidStateError };
801
802     // 2. Silently set the animation playback rate of animation to −animation playback rate.
803     //    This must be done silently or else we may end up resolving the current ready promise when
804     //    we do the compensatory seek despite the fact that we will most likely still have a pending
805     //    task queued at the end of the operation.
806     setPlaybackRate(-m_playbackRate, Silently::Yes);
807
808     // 3. Run the steps to play an animation for animation with the auto-rewind flag set to true.
809     auto playResult = play(AutoRewind::Yes);
810
811     // If the steps to play an animation throw an exception, restore the original animation playback rate
812     // by re-running the procedure to silently set the animation playback rate with the original animation
813     // playback rate.
814     if (playResult.hasException()) {
815         setPlaybackRate(-m_playbackRate, Silently::Yes);
816         return playResult.releaseException();
817     }
818
819     return { };
820 }
821
822 void WebAnimation::setTimeToRunPendingPauseTask(TimeToRunPendingTask timeToRunPendingTask)
823 {
824     m_timeToRunPendingPauseTask = timeToRunPendingTask;
825     updatePendingTasks();
826 }
827
828 void WebAnimation::runPendingPauseTask()
829 {
830     // 3.4.11. Pausing an animation, step 7.
831     // https://drafts.csswg.org/web-animations-1/#pause-an-animation
832
833     m_timeToRunPendingPauseTask = TimeToRunPendingTask::NotScheduled;
834
835     // 1. Let ready time be the time value of the timeline associated with animation at the moment when the user agent
836     //    completed processing necessary to suspend playback of animation's target effect.
837     auto readyTime = m_timeline->currentTime();
838     auto animationStartTime = startTime();
839
840     // 2. If animation's start time is resolved and its hold time is not resolved, let animation's hold time be the result of
841     //    evaluating (ready time - start time) × playback rate.
842     //    Note: The hold time might be already set if the animation is finished, or if the animation is pending, waiting to begin
843     //    playback. In either case we want to preserve the hold time as we enter the paused state.
844     if (animationStartTime && !m_holdTime)
845         m_holdTime = (readyTime.value() - animationStartTime.value()) * m_playbackRate;
846
847     // 3. Make animation's start time unresolved.
848     setStartTime(std::nullopt);
849
850     // 4. Resolve animation's current ready promise with animation.
851     m_readyPromise.resolve(*this);
852
853     // 5. Run the procedure to update an animation's finished state for animation with the did seek flag set to false, and the
854     //    synchronously notify flag set to false.
855     updateFinishedState(DidSeek::No, SynchronouslyNotify::No);
856 }
857
858 void WebAnimation::updatePendingTasks()
859 {
860     if (m_timeToRunPendingPauseTask == TimeToRunPendingTask::ASAP && m_timeline)
861         runPendingPauseTask();
862
863     // FIXME: This should only happen if we're ready, at the moment we think we're ready if we have a timeline.
864     if (m_timeToRunPendingPlayTask == TimeToRunPendingTask::WhenReady && m_timeline)
865         runPendingPlayTask();
866 }
867
868 Seconds WebAnimation::timeToNextRequiredTick(Seconds timelineTime) const
869 {
870     if (!m_timeline || !m_startTime || !m_effect || !m_playbackRate)
871         return Seconds::infinity();
872
873     auto startTime = m_startTime.value();
874     auto endTime = startTime + (m_effect->timing()->iterationDuration() / m_playbackRate);
875
876     // If we haven't started yet, return the interval until our active start time.
877     auto activeStartTime = std::min(startTime, endTime);
878     if (timelineTime <= activeStartTime)
879         return activeStartTime - timelineTime;
880
881     // If we're in the middle of our active duration, we want to be called as soon as possible.
882     auto activeEndTime = std::max(startTime, endTime);
883     if (timelineTime <= activeEndTime)
884         return 0_ms;
885
886     // If none of the previous cases match, then we're already past our active duration
887     // and do not need scheduling.
888     return Seconds::infinity();
889 }
890
891 void WebAnimation::resolve(RenderStyle& targetStyle)
892 {
893     if (m_effect)
894         m_effect->apply(targetStyle);
895 }
896
897 void WebAnimation::acceleratedRunningStateDidChange()
898 {
899     if (is<DocumentTimeline>(m_timeline))
900         downcast<DocumentTimeline>(*m_timeline).animationAcceleratedRunningStateDidChange(*this);
901 }
902
903 void WebAnimation::startOrStopAccelerated()
904 {
905     if (is<KeyframeEffect>(m_effect))
906         downcast<KeyframeEffect>(*m_effect).startOrStopAccelerated();
907 }
908
909 WebAnimation& WebAnimation::readyPromiseResolve()
910 {
911     return *this;
912 }
913
914 WebAnimation& WebAnimation::finishedPromiseResolve()
915 {
916     return *this;
917 }
918
919 String WebAnimation::description()
920 {
921     return "Animation";
922 }
923
924 const char* WebAnimation::activeDOMObjectName() const
925 {
926     return "Animation";
927 }
928
929 bool WebAnimation::canSuspendForDocumentSuspension() const
930 {
931     return !hasPendingActivity();
932 }
933
934 void WebAnimation::stop()
935 {
936     m_isStopped = true;
937     removeAllEventListeners();
938 }
939
940 } // namespace WebCore