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