[Web Animations] Make imported/mozilla/css-animations/test_event-dispatch.html pass...
[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     if (m_timeToRunPendingPlayTask == timeToRunPendingTask)
788         return;
789
790     m_timeToRunPendingPlayTask = timeToRunPendingTask;
791     updatePendingTasks();
792 }
793
794 void WebAnimation::runPendingPlayTask()
795 {
796     // 3.4.10. Playing an animation, step 8.
797     // https://drafts.csswg.org/web-animations-1/#play-an-animation
798
799     m_timeToRunPendingPlayTask = TimeToRunPendingTask::NotScheduled;
800
801     // 1. Assert that at least one of animation’s start time or hold time is resolved.
802     ASSERT(m_startTime || m_holdTime);
803
804     // 2. Let ready time be the time value of the timeline associated with animation at the moment when animation became ready.
805     auto readyTime = m_timeline->currentTime();
806
807     // 3. If animation's start time is unresolved, perform the following steps:
808     if (!m_startTime) {
809         // 1. Let new start time be the result of evaluating ready time - hold time / animation playback rate for animation.
810         // If the animation playback rate is zero, let new start time be simply ready time.
811         auto newStartTime = readyTime.value();
812         if (m_playbackRate)
813             newStartTime -= m_holdTime.value() / m_playbackRate;
814         // 2. If animation's playback rate is not 0, make animation's hold time unresolved.
815         if (m_playbackRate)
816             setHoldTime(std::nullopt);
817         // 3. Set the animation start time of animation to new start time.
818         setStartTime(newStartTime);
819     }
820
821     // 4. Resolve animation's current ready promise with animation.
822     if (!m_readyPromise->isFulfilled())
823         m_readyPromise->resolve(*this);
824
825     // 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.
826     updateFinishedState(DidSeek::No, SynchronouslyNotify::No);
827 }
828
829 ExceptionOr<void> WebAnimation::pause()
830 {
831     // 3.4.11. Pausing an animation
832     // https://drafts.csswg.org/web-animations-1/#pause-an-animation
833
834     // 1. If animation has a pending pause task, abort these steps.
835     if (hasPendingPauseTask())
836         return { };
837
838     // 2. If the play state of animation is paused, abort these steps.
839     if (playState() == PlayState::Paused)
840         return { };
841
842     auto localTime = currentTime();
843
844     // 3. If the animation's current time is unresolved, perform the steps according to the first matching condition from below:
845     if (!localTime) {
846         if (m_playbackRate >= 0) {
847             // If animation's playback rate is ≥ 0, let animation's hold time be zero.
848             setHoldTime(0_s);
849         } else if (effectEndTime() == Seconds::infinity()) {
850             // Otherwise, if target effect end for animation is positive infinity, throw an InvalidStateError and abort these steps.
851             return Exception { InvalidStateError };
852         } else {
853             // Otherwise, let animation's hold time be target effect end.
854             setHoldTime(effectEndTime());
855         }
856     }
857
858     // 4. Let has pending ready promise be a boolean flag that is initially false.
859     bool hasPendingReadyPromise = false;
860
861     // 5. If animation has a pending play task, cancel that task and let has pending ready promise be true.
862     if (hasPendingPlayTask()) {
863         setTimeToRunPendingPlayTask(TimeToRunPendingTask::NotScheduled);
864         hasPendingReadyPromise = true;
865     }
866
867     // 6. If has pending ready promise is false, set animation's current ready promise to a new (pending) Promise object.
868     if (!hasPendingReadyPromise)
869         m_readyPromise = makeUniqueRef<ReadyPromise>(*this, &WebAnimation::readyPromiseResolve);
870
871     // 7. Schedule a task to be executed at the first possible moment after the user agent has performed any processing necessary
872     //    to suspend the playback of animation's target effect, if any.
873     setTimeToRunPendingPauseTask(TimeToRunPendingTask::ASAP);
874
875     // 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.
876     updateFinishedState(DidSeek::No, SynchronouslyNotify::No);
877
878     return { };
879 }
880
881 ExceptionOr<void> WebAnimation::reverse()
882 {
883     // 3.4.18. Reversing an animation
884     // https://drafts.csswg.org/web-animations-1/#reverse-an-animation
885
886     // The procedure to reverse an animation of animation animation is as follows:
887
888     // 1. If there is no timeline associated with animation, or the associated timeline is inactive
889     //    throw an InvalidStateError and abort these steps.
890     if (!m_timeline || !m_timeline->currentTime())
891         return Exception { InvalidStateError };
892
893     // 2. Silently set the animation playback rate of animation to −animation playback rate.
894     //    This must be done silently or else we may end up resolving the current ready promise when
895     //    we do the compensatory seek despite the fact that we will most likely still have a pending
896     //    task queued at the end of the operation.
897     setPlaybackRate(-m_playbackRate, Silently::Yes);
898
899     // 3. Run the steps to play an animation for animation with the auto-rewind flag set to true.
900     auto playResult = play(AutoRewind::Yes);
901
902     // If the steps to play an animation throw an exception, restore the original animation playback rate
903     // by re-running the procedure to silently set the animation playback rate with the original animation
904     // playback rate.
905     if (playResult.hasException()) {
906         setPlaybackRate(-m_playbackRate, Silently::Yes);
907         return playResult.releaseException();
908     }
909
910     return { };
911 }
912
913 void WebAnimation::setTimeToRunPendingPauseTask(TimeToRunPendingTask timeToRunPendingTask)
914 {
915     if (m_timeToRunPendingPauseTask == timeToRunPendingTask)
916         return;
917
918     m_timeToRunPendingPauseTask = timeToRunPendingTask;
919     updatePendingTasks();
920 }
921
922 void WebAnimation::runPendingPauseTask()
923 {
924     // 3.4.11. Pausing an animation, step 7.
925     // https://drafts.csswg.org/web-animations-1/#pause-an-animation
926
927     m_timeToRunPendingPauseTask = TimeToRunPendingTask::NotScheduled;
928
929     // 1. Let ready time be the time value of the timeline associated with animation at the moment when the user agent
930     //    completed processing necessary to suspend playback of animation's target effect.
931     auto readyTime = m_timeline->currentTime();
932     auto animationStartTime = m_startTime;
933
934     // 2. If animation's start time is resolved and its hold time is not resolved, let animation's hold time be the result of
935     //    evaluating (ready time - start time) × playback rate.
936     //    Note: The hold time might be already set if the animation is finished, or if the animation is pending, waiting to begin
937     //    playback. In either case we want to preserve the hold time as we enter the paused state.
938     if (animationStartTime && !m_holdTime)
939         setHoldTime((readyTime.value() - animationStartTime.value()) * m_playbackRate);
940
941     // 3. Make animation's start time unresolved.
942     setStartTime(std::nullopt);
943
944     // 4. Resolve animation's current ready promise with animation.
945     if (!m_readyPromise->isFulfilled())
946         m_readyPromise->resolve(*this);
947
948     // 5. Run the procedure to update an animation's finished state for animation with the did seek flag set to false, and the
949     //    synchronously notify flag set to false.
950     updateFinishedState(DidSeek::No, SynchronouslyNotify::No);
951 }
952
953 void WebAnimation::updatePendingTasks()
954 {
955     if (hasPendingPauseTask() && is<DocumentTimeline>(m_timeline)) {
956         if (auto document = downcast<DocumentTimeline>(*m_timeline).document()) {
957             document->postTask([this, protectedThis = makeRef(*this)] (auto&) {
958                 if (this->hasPendingPauseTask() && m_timeline)
959                     this->runPendingPauseTask();
960             });
961         }
962     }
963
964     // FIXME: This should only happen if we're ready, at the moment we think we're ready if we have a timeline.
965     if (hasPendingPlayTask() && is<DocumentTimeline>(m_timeline)) {
966         if (auto document = downcast<DocumentTimeline>(*m_timeline).document()) {
967             document->postTask([this, protectedThis = makeRef(*this)] (auto&) {
968                 if (this->hasPendingPlayTask() && m_timeline)
969                     this->runPendingPlayTask();
970             });
971         }
972     }
973
974     timingModelDidChange();
975 }
976
977 Seconds WebAnimation::timeToNextRequiredTick() const
978 {
979     // If we don't have a timeline, an effect, a start time or a playback rate other than 0,
980     // there is no value to apply so we don't need to schedule invalidation.
981     if (!m_timeline || !m_effect || !m_startTime || !m_playbackRate)
982         return Seconds::infinity();
983
984     // If we're in the running state, we need to schedule invalidation as soon as possible.
985     if (playState() == PlayState::Running)
986         return 0_s;
987
988     // If our current time is negative, we need to be scheduled to be resolved at the inverse
989     // of our current time, unless we fill backwards, in which case we want to invalidate as
990     // soon as possible.
991     auto localTime = currentTime().value();
992     if (localTime < 0_s)
993         return -localTime;
994
995     // If our current time is just at the acthive duration threshold we want to invalidate as
996     // soon as possible to restore a non-animated value.
997     if (std::abs(localTime.microseconds() - m_effect->timing()->activeDuration().microseconds()) < timeEpsilon.microseconds())
998         return 0_s;
999
1000     // In any other case, we're idle or already outside our active duration and have no need
1001     // to schedule an invalidation.
1002     return Seconds::infinity();
1003 }
1004
1005 void WebAnimation::resolve(RenderStyle& targetStyle)
1006 {
1007     if (m_effect)
1008         m_effect->apply(targetStyle);
1009 }
1010
1011 void WebAnimation::acceleratedRunningStateDidChange()
1012 {
1013     if (is<DocumentTimeline>(m_timeline))
1014         downcast<DocumentTimeline>(*m_timeline).animationAcceleratedRunningStateDidChange(*this);
1015 }
1016
1017 void WebAnimation::startOrStopAccelerated()
1018 {
1019     if (is<KeyframeEffectReadOnly>(m_effect))
1020         downcast<KeyframeEffectReadOnly>(*m_effect).startOrStopAccelerated();
1021 }
1022
1023 WebAnimation& WebAnimation::readyPromiseResolve()
1024 {
1025     return *this;
1026 }
1027
1028 WebAnimation& WebAnimation::finishedPromiseResolve()
1029 {
1030     return *this;
1031 }
1032
1033 String WebAnimation::description()
1034 {
1035     return "Animation";
1036 }
1037
1038 const char* WebAnimation::activeDOMObjectName() const
1039 {
1040     return "Animation";
1041 }
1042
1043 bool WebAnimation::canSuspendForDocumentSuspension() const
1044 {
1045     return !hasPendingActivity();
1046 }
1047
1048 void WebAnimation::stop()
1049 {
1050     m_isStopped = true;
1051     removeAllEventListeners();
1052 }
1053
1054 bool WebAnimation::canBeListed() const
1055 {
1056     // To be listed in getAnimations() an animation needs a target effect which is current or in effect.
1057     if (!m_effect)
1058         return false;
1059
1060     // An animation effect is in effect if its active time is not unresolved.
1061     if (m_effect->activeTime())
1062         return true;
1063
1064     // An animation effect is current if either of the following conditions is true:
1065     // - the animation effect is in the before phase, or
1066     // - the animation effect is in play.
1067
1068     // An animation effect is in play if all of the following conditions are met:
1069     // - the animation effect is in the active phase, and
1070     // - the animation effect is associated with an animation that is not finished.
1071     auto phase = m_effect->phase();
1072     return phase == AnimationEffectReadOnly::Phase::Before || (phase == AnimationEffectReadOnly::Phase::Active && playState() != PlayState::Finished);
1073 }
1074
1075 } // namespace WebCore