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