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