[Web Animations] Refactor cancelDeclarativeAnimationsForElement and willDestroyRender...
[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 "AnimationEffect.h"
30 #include "AnimationPlaybackEvent.h"
31 #include "AnimationTimeline.h"
32 #include "CSSComputedStyleDeclaration.h"
33 #include "DOMPromiseProxy.h"
34 #include "DeclarativeAnimation.h"
35 #include "Document.h"
36 #include "DocumentTimeline.h"
37 #include "EventLoop.h"
38 #include "EventNames.h"
39 #include "InspectorInstrumentation.h"
40 #include "JSWebAnimation.h"
41 #include "KeyframeEffect.h"
42 #include "KeyframeEffectStack.h"
43 #include "Logging.h"
44 #include "RenderElement.h"
45 #include "StyledElement.h"
46 #include "WebAnimationUtilities.h"
47 #include <wtf/IsoMallocInlines.h>
48 #include <wtf/NeverDestroyed.h>
49 #include <wtf/Optional.h>
50 #include <wtf/text/TextStream.h>
51 #include <wtf/text/WTFString.h>
52
53 namespace WebCore {
54
55 WTF_MAKE_ISO_ALLOCATED_IMPL(WebAnimation);
56
57 HashSet<WebAnimation*>& WebAnimation::instances()
58 {
59     static NeverDestroyed<HashSet<WebAnimation*>> instances;
60     return instances;
61 }
62
63 Ref<WebAnimation> WebAnimation::create(Document& document, AnimationEffect* effect)
64 {
65     auto result = adoptRef(*new WebAnimation(document));
66     result->setEffect(effect);
67     result->setTimeline(&document.timeline());
68
69     InspectorInstrumentation::didCreateWebAnimation(result.get());
70
71     return result;
72 }
73
74 Ref<WebAnimation> WebAnimation::create(Document& document, AnimationEffect* effect, AnimationTimeline* timeline)
75 {
76     auto result = adoptRef(*new WebAnimation(document));
77     result->setEffect(effect);
78     if (timeline)
79         result->setTimeline(timeline);
80
81     InspectorInstrumentation::didCreateWebAnimation(result.get());
82
83     return result;
84 }
85
86 WebAnimation::WebAnimation(Document& document)
87     : ActiveDOMObject(document)
88     , m_readyPromise(makeUniqueRef<ReadyPromise>(*this, &WebAnimation::readyPromiseResolve))
89     , m_finishedPromise(makeUniqueRef<FinishedPromise>(*this, &WebAnimation::finishedPromiseResolve))
90 {
91     m_readyPromise->resolve(*this);
92     suspendIfNeeded();
93
94     instances().add(this);
95 }
96
97 WebAnimation::~WebAnimation()
98 {
99     InspectorInstrumentation::willDestroyWebAnimation(*this);
100
101     if (m_timeline)
102         m_timeline->forgetAnimation(this);
103
104     ASSERT(instances().contains(this));
105     instances().remove(this);
106 }
107
108 void WebAnimation::contextDestroyed()
109 {
110     InspectorInstrumentation::willDestroyWebAnimation(*this);
111
112     ActiveDOMObject::contextDestroyed();
113 }
114
115 void WebAnimation::remove()
116 {
117     // This object could be deleted after either clearing the effect or timeline relationship.
118     auto protectedThis = makeRef(*this);
119     setEffectInternal(nullptr);
120     setTimelineInternal(nullptr);
121 }
122
123 void WebAnimation::suspendEffectInvalidation()
124 {
125     ++m_suspendCount;
126 }
127
128 void WebAnimation::unsuspendEffectInvalidation()
129 {
130     ASSERT(m_suspendCount > 0);
131     --m_suspendCount;
132 }
133
134 void WebAnimation::effectTimingDidChange(Optional<ComputedEffectTiming> previousTiming)
135 {
136     timingDidChange(DidSeek::No, SynchronouslyNotify::Yes);
137
138     InspectorInstrumentation::didChangeWebAnimationEffectTiming(*this);
139
140     if (!previousTiming)
141         return;
142
143     auto* effect = this->effect();
144     ASSERT(effect);
145     if (previousTiming->progress != effect->getComputedTiming().progress)
146         effect->animationDidSeek();
147 }
148
149 void WebAnimation::setEffect(RefPtr<AnimationEffect>&& newEffect)
150 {
151     // 3.4.3. Setting the target effect of an animation
152     // https://drafts.csswg.org/web-animations-1/#setting-the-target-effect
153
154     // 1. Let old effect be the current target effect of animation, if any.
155     auto oldEffect = m_effect;
156
157     // 2. If new effect is the same object as old effect, abort this procedure.
158     if (newEffect == oldEffect)
159         return;
160
161     // 3. If animation has a pending pause task, reschedule that task to run as soon as animation is ready.
162     if (hasPendingPauseTask())
163         m_timeToRunPendingPauseTask = TimeToRunPendingTask::WhenReady;
164
165     // 4. If animation has a pending play task, reschedule that task to run as soon as animation is ready to play new effect.
166     if (hasPendingPlayTask())
167         m_timeToRunPendingPlayTask = TimeToRunPendingTask::WhenReady;
168
169     // 5. If new effect is not null and if new effect is the target effect of another animation, previous animation, run the
170     // procedure to set the target effect of an animation (this procedure) on previous animation passing null as new effect.
171     if (newEffect && newEffect->animation())
172         newEffect->animation()->setEffect(nullptr);
173
174     // 6. Let the target effect of animation be new effect.
175     // In the case of a declarative animation, we don't want to remove the animation from the relevant maps because
176     // while the effect was set via the API, the element still has a transition or animation set up and we must
177     // not break the timeline-to-animation relationship.
178
179     invalidateEffect();
180
181     // This object could be deleted after clearing the effect relationship.
182     auto protectedThis = makeRef(*this);
183     setEffectInternal(WTFMove(newEffect), isDeclarativeAnimation());
184
185     // 7. Run the procedure to update an animation's finished state for animation with the did seek flag set to false,
186     // and the synchronously notify flag set to false.
187     timingDidChange(DidSeek::No, SynchronouslyNotify::No);
188
189     invalidateEffect();
190 }
191
192 void WebAnimation::setEffectInternal(RefPtr<AnimationEffect>&& newEffect, bool doNotRemoveFromTimeline)
193 {
194     if (m_effect == newEffect)
195         return;
196
197     auto oldEffect = std::exchange(m_effect, WTFMove(newEffect));
198
199     Element* previousTarget = nullptr;
200     if (is<KeyframeEffect>(oldEffect))
201         previousTarget = downcast<KeyframeEffect>(oldEffect.get())->target();
202
203     Element* newTarget = nullptr;
204     if (is<KeyframeEffect>(m_effect))
205         newTarget = downcast<KeyframeEffect>(m_effect.get())->target();
206
207     // Update the effect-to-animation relationships and the timeline's animation map.
208     if (oldEffect) {
209         oldEffect->setAnimation(nullptr);
210         if (!doNotRemoveFromTimeline && m_timeline && previousTarget && previousTarget != newTarget)
211             m_timeline->animationWasRemovedFromElement(*this, *previousTarget);
212         updateRelevance();
213     }
214
215     if (m_effect) {
216         m_effect->setAnimation(this);
217         if (m_timeline && newTarget && previousTarget != newTarget)
218             m_timeline->animationWasAddedToElement(*this, *newTarget);
219     }
220
221     InspectorInstrumentation::didSetWebAnimationEffect(*this);
222 }
223
224 void WebAnimation::setTimeline(RefPtr<AnimationTimeline>&& timeline)
225 {
226     // 3.4.1. Setting the timeline of an animation
227     // https://drafts.csswg.org/web-animations-1/#setting-the-timeline
228
229     // 2. If new timeline is the same object as old timeline, abort this procedure.
230     if (timeline == m_timeline)
231         return;
232
233     // 4. If the animation start time of animation is resolved, make animation's hold time unresolved.
234     if (m_startTime)
235         m_holdTime = WTF::nullopt;
236
237     if (is<KeyframeEffect>(m_effect)) {
238         auto* keyframeEffect = downcast<KeyframeEffect>(m_effect.get());
239         auto* target = keyframeEffect->target();
240         if (target) {
241             // In the case of a declarative animation, we don't want to remove the animation from the relevant maps because
242             // while the timeline was set via the API, the element still has a transition or animation set up and we must
243             // not break the relationship.
244             if (m_timeline && !isDeclarativeAnimation())
245                 m_timeline->animationWasRemovedFromElement(*this, *target);
246             if (timeline)
247                 timeline->animationWasAddedToElement(*this, *target);
248         }
249     }
250
251     // This object could be deleted after clearing the timeline relationship.
252     auto protectedThis = makeRef(*this);
253     setTimelineInternal(WTFMove(timeline));
254
255     setSuspended(is<DocumentTimeline>(m_timeline) && downcast<DocumentTimeline>(*m_timeline).animationsAreSuspended());
256
257     // 5. Run the procedure to update an animation's finished state for animation with the did seek flag set to false,
258     // and the synchronously notify flag set to false.
259     timingDidChange(DidSeek::No, SynchronouslyNotify::No);
260
261     invalidateEffect();
262 }
263
264 void WebAnimation::setTimelineInternal(RefPtr<AnimationTimeline>&& timeline)
265 {
266     if (m_timeline == timeline)
267         return;
268
269     if (m_timeline)
270         m_timeline->removeAnimation(*this);
271
272     m_timeline = WTFMove(timeline);
273
274     if (m_effect)
275         m_effect->animationTimelineDidChange(m_timeline.get());
276 }
277
278 void WebAnimation::effectTargetDidChange(Element* previousTarget, Element* newTarget)
279 {
280     if (m_timeline) {
281         if (previousTarget)
282             m_timeline->animationWasRemovedFromElement(*this, *previousTarget);
283
284         if (newTarget)
285             m_timeline->animationWasAddedToElement(*this, *newTarget);
286
287         // This could have changed whether we have replaced animations, so we may need to schedule an update.
288         m_timeline->animationTimingDidChange(*this);
289     }
290
291     InspectorInstrumentation::didChangeWebAnimationEffectTarget(*this);
292 }
293
294 Optional<double> WebAnimation::startTime() const
295 {
296     if (!m_startTime)
297         return WTF::nullopt;
298     return secondsToWebAnimationsAPITime(m_startTime.value());
299 }
300
301 void WebAnimation::setStartTime(Optional<double> startTime)
302 {
303     // 3.4.6 The procedure to set the start time of animation, animation, to new start time, is as follows:
304     // https://drafts.csswg.org/web-animations/#setting-the-start-time-of-an-animation
305
306     Optional<Seconds> newStartTime;
307     if (!startTime)
308         newStartTime = WTF::nullopt;
309     else
310         newStartTime = Seconds::fromMilliseconds(startTime.value());
311
312     // 1. Let timeline time be the current time value of the timeline that animation is associated with. If
313     //    there is no timeline associated with animation or the associated timeline is inactive, let the timeline
314     //    time be unresolved.
315     auto timelineTime = m_timeline ? m_timeline->currentTime() : WTF::nullopt;
316
317     // 2. If timeline time is unresolved and new start time is resolved, make animation's hold time unresolved.
318     if (!timelineTime && newStartTime)
319         m_holdTime = WTF::nullopt;
320
321     // 3. Let previous current time be animation's current time.
322     auto previousCurrentTime = currentTime();
323
324     // 4. Apply any pending playback rate on animation.
325     applyPendingPlaybackRate();
326
327     // 5. Set animation's start time to new start time.
328     m_startTime = newStartTime;
329
330     // 6. Update animation's hold time based on the first matching condition from the following,
331     if (newStartTime) {
332         // If new start time is resolved,
333         // If animation's playback rate is not zero, make animation's hold time unresolved.
334         if (m_playbackRate)
335             m_holdTime = WTF::nullopt;
336     } else {
337         // Otherwise (new start time is unresolved),
338         // Set animation's hold time to previous current time even if previous current time is unresolved.
339         m_holdTime = previousCurrentTime;
340     }
341
342     // 7. If animation has a pending play task or a pending pause task, cancel that task and resolve animation's current ready promise with animation.
343     if (pending()) {
344         m_timeToRunPendingPauseTask = TimeToRunPendingTask::NotScheduled;
345         m_timeToRunPendingPlayTask = TimeToRunPendingTask::NotScheduled;
346         m_readyPromise->resolve(*this);
347     }
348
349     // 8. 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.
350     timingDidChange(DidSeek::Yes, SynchronouslyNotify::No);
351
352     invalidateEffect();
353 }
354
355 Optional<double> WebAnimation::bindingsCurrentTime() const
356 {
357     auto time = currentTime();
358     if (!time)
359         return WTF::nullopt;
360     return secondsToWebAnimationsAPITime(time.value());
361 }
362
363 ExceptionOr<void> WebAnimation::setBindingsCurrentTime(Optional<double> currentTime)
364 {
365     if (!currentTime)
366         return setCurrentTime(WTF::nullopt);
367     return setCurrentTime(Seconds::fromMilliseconds(currentTime.value()));
368 }
369
370 Optional<Seconds> WebAnimation::currentTime() const
371 {
372     return currentTime(RespectHoldTime::Yes);
373 }
374
375 Optional<Seconds> WebAnimation::currentTime(RespectHoldTime respectHoldTime) const
376 {
377     // 3.4.4. The current time of an animation
378     // https://drafts.csswg.org/web-animations-1/#the-current-time-of-an-animation
379
380     // The current time is calculated from the first matching condition from below:
381
382     // If the animation's hold time is resolved, the current time is the animation's hold time.
383     if (respectHoldTime == RespectHoldTime::Yes && m_holdTime)
384         return m_holdTime;
385
386     // If any of the following are true:
387     //     1. the animation has no associated timeline, or
388     //     2. the associated timeline is inactive, or
389     //     3. the animation's start time is unresolved.
390     // The current time is an unresolved time value.
391     if (!m_timeline || !m_timeline->currentTime() || !m_startTime)
392         return WTF::nullopt;
393
394     // Otherwise, current time = (timeline time - start time) * playback rate
395     return (m_timeline->currentTime().value() - m_startTime.value()) * m_playbackRate;
396 }
397
398 ExceptionOr<void> WebAnimation::silentlySetCurrentTime(Optional<Seconds> seekTime)
399 {
400     LOG_WITH_STREAM(Animations, stream << "WebAnimation " << this << " silentlySetCurrentTime " << seekTime);
401
402     // 3.4.5. Setting the current time of an animation
403     // https://drafts.csswg.org/web-animations-1/#setting-the-current-time-of-an-animation
404
405     // 1. If seek time is an unresolved time value, then perform the following steps.
406     if (!seekTime) {
407         // 1. If the current time is resolved, then throw a TypeError.
408         if (currentTime())
409             return Exception { TypeError };
410         // 2. Abort these steps.
411         return { };
412     }
413
414     // 2. Update either animation's hold time or start time as follows:
415     // If any of the following conditions are true:
416     //     - animation's hold time is resolved, or
417     //     - animation's start time is unresolved, or
418     //     - animation has no associated timeline or the associated timeline is inactive, or
419     //     - animation's playback rate is 0,
420     // Set animation's hold time to seek time.
421     // Otherwise, set animation's start time to the result of evaluating timeline time - (seek time / playback rate)
422     // where timeline time is the current time value of timeline associated with animation.
423     if (m_holdTime || !m_startTime || !m_timeline || !m_timeline->currentTime() || !m_playbackRate)
424         m_holdTime = seekTime;
425     else
426         m_startTime = m_timeline->currentTime().value() - (seekTime.value() / m_playbackRate);
427
428     // 3. If animation has no associated timeline or the associated timeline is inactive, make animation's start time unresolved.
429     if (!m_timeline || !m_timeline->currentTime())
430         m_startTime = WTF::nullopt;
431
432     // 4. Make animation's previous current time unresolved.
433     m_previousCurrentTime = WTF::nullopt;
434
435     return { };
436 }
437
438 ExceptionOr<void> WebAnimation::setCurrentTime(Optional<Seconds> seekTime)
439 {
440     LOG_WITH_STREAM(Animations, stream << "WebAnimation " << this << " setCurrentTime " << seekTime);
441
442     // 3.4.5. Setting the current time of an animation
443     // https://drafts.csswg.org/web-animations-1/#setting-the-current-time-of-an-animation
444
445     // 1. Run the steps to silently set the current time of animation to seek time.
446     auto silentResult = silentlySetCurrentTime(seekTime);
447     if (silentResult.hasException())
448         return silentResult.releaseException();
449
450     // 2. If animation has a pending pause task, synchronously complete the pause operation by performing the following steps:
451     if (hasPendingPauseTask()) {
452         // 1. Set animation's hold time to seek time.
453         m_holdTime = seekTime;
454         // 2. Apply any pending playback rate to animation.
455         applyPendingPlaybackRate();
456         // 3. Make animation's start time unresolved.
457         m_startTime = WTF::nullopt;
458         // 4. Cancel the pending pause task.
459         m_timeToRunPendingPauseTask = TimeToRunPendingTask::NotScheduled;
460         // 5. Resolve animation's current ready promise with animation.
461         m_readyPromise->resolve(*this);
462     }
463
464     // 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.
465     timingDidChange(DidSeek::Yes, SynchronouslyNotify::No);
466
467     if (m_effect)
468         m_effect->animationDidSeek();
469
470     invalidateEffect();
471
472     return { };
473 }
474
475 double WebAnimation::effectivePlaybackRate() const
476 {
477     // https://drafts.csswg.org/web-animations/#effective-playback-rate
478     // The effective playback rate of an animation is its pending playback rate, if set, otherwise it is the animation's playback rate.
479     return (m_pendingPlaybackRate ? m_pendingPlaybackRate.value() : m_playbackRate);
480 }
481
482 void WebAnimation::setPlaybackRate(double newPlaybackRate)
483 {
484     // 3.4.17.1. Updating the playback rate of an animation
485     // https://drafts.csswg.org/web-animations-1/#updating-the-playback-rate-of-an-animation
486
487     // 1. Clear any pending playback rate on animation.
488     m_pendingPlaybackRate = WTF::nullopt;
489     
490     // 2. Let previous time be the value of the current time of animation before changing the playback rate.
491     auto previousTime = currentTime();
492
493     // 3. Set the playback rate to new playback rate.
494     m_playbackRate = newPlaybackRate;
495
496     // 4. If previous time is resolved, set the current time of animation to previous time.
497     if (previousTime)
498         setCurrentTime(previousTime);
499 }
500
501 void WebAnimation::updatePlaybackRate(double newPlaybackRate)
502 {
503     // https://drafts.csswg.org/web-animations/#seamlessly-update-the-playback-rate
504
505     // The procedure to seamlessly update the playback rate an animation, animation, to new playback rate preserving its current time is as follows:
506
507     // 1. Let previous play state be animation's play state.
508     //    Note: It is necessary to record the play state before updating animation's effective playback rate since, in the following logic,
509     //    we want to immediately apply the pending playback rate of animation if it is currently finished regardless of whether or not it will
510     //    still be finished after we apply the pending playback rate.
511     auto previousPlayState = playState();
512
513     // 2. Let animation's pending playback rate be new playback rate.
514     m_pendingPlaybackRate = newPlaybackRate;
515
516     // 3. Perform the steps corresponding to the first matching condition from below:
517     if (pending()) {
518         // If animation has a pending play task or a pending pause task,
519         // Abort these steps.
520         // Note: The different types of pending tasks will apply the pending playback rate when they run so there is no further action required in this case.
521         return;
522     }
523
524     if (previousPlayState == PlayState::Idle || previousPlayState == PlayState::Paused) {
525         // If previous play state is idle or paused,
526         // Apply any pending playback rate on animation.
527         applyPendingPlaybackRate();
528     } else if (previousPlayState == PlayState::Finished) {
529         // If previous play state is finished,
530         // 1. Let the unconstrained current time be the result of calculating the current time of animation substituting an unresolved time value for the hold time.
531         auto unconstrainedCurrentTime = currentTime(RespectHoldTime::No);
532         // 2. Let animation's start time be the result of evaluating the following expression:
533         // timeline time - (unconstrained current time / pending playback rate)
534         // Where timeline time is the current time value of the timeline associated with animation.
535         // If pending playback rate is zero, let animation's start time be timeline time.
536         auto newStartTime = m_timeline->currentTime().value();
537         if (m_pendingPlaybackRate)
538             newStartTime -= (unconstrainedCurrentTime.value() / m_pendingPlaybackRate.value());
539         m_startTime = newStartTime;
540         // 3. Apply any pending playback rate on animation.
541         applyPendingPlaybackRate();
542         // 4. Run the procedure to update an animation's finished state for animation with the did seek flag set to false, and the synchronously notify flag set to false.
543         timingDidChange(DidSeek::No, SynchronouslyNotify::No);
544
545         invalidateEffect();
546     } else {
547         // Otherwise,
548         // Run the procedure to play an animation for animation with the auto-rewind flag set to false.
549         play(AutoRewind::No);
550     }
551 }
552
553 void WebAnimation::applyPendingPlaybackRate()
554 {
555     // https://drafts.csswg.org/web-animations/#apply-any-pending-playback-rate
556
557     // 1. If animation does not have a pending playback rate, abort these steps.
558     if (!m_pendingPlaybackRate)
559         return;
560
561     // 2. Set animation's playback rate to its pending playback rate.
562     m_playbackRate = m_pendingPlaybackRate.value();
563
564     // 3. Clear animation's pending playback rate.
565     m_pendingPlaybackRate = WTF::nullopt;
566 }
567
568 auto WebAnimation::playState() const -> PlayState
569 {
570     // 3.5.19 Play states
571     // https://drafts.csswg.org/web-animations/#play-states
572
573     // The play state of animation, animation, at a given moment is the state corresponding to the
574     // first matching condition from the following:
575
576     // The current time of animation is unresolved, and animation does not have either a pending
577     // play task or a pending pause task,
578     // → idle
579     auto animationCurrentTime = currentTime();
580     if (!animationCurrentTime && !pending())
581         return PlayState::Idle;
582
583     // Animation has a pending pause task, or both the start time of animation is unresolved and it does not
584     // have a pending play task,
585     // → paused
586     if (hasPendingPauseTask() || (!m_startTime && !hasPendingPlayTask()))
587         return PlayState::Paused;
588
589     // For animation, current time is resolved and either of the following conditions are true:
590     // animation's effective playback rate > 0 and current time ≥ target effect end; or
591     // animation's effective playback rate < 0 and current time ≤ 0,
592     // → finished
593     if (animationCurrentTime && ((effectivePlaybackRate() > 0 && (*animationCurrentTime + timeEpsilon) >= effectEndTime()) || (effectivePlaybackRate() < 0 && (*animationCurrentTime - timeEpsilon) <= 0_s)))
594         return PlayState::Finished;
595
596     // Otherwise → running
597     return PlayState::Running;
598 }
599
600 Seconds WebAnimation::effectEndTime() const
601 {
602     // The target effect end of an animation is equal to the end time of the animation's target effect.
603     // If the animation has no target effect, the target effect end is zero.
604     return m_effect ? m_effect->endTime() : 0_s;
605 }
606
607 void WebAnimation::cancel(Silently silently)
608 {
609     LOG_WITH_STREAM(Animations, stream << "WebAnimation " << this << " cancel(silently " << (silently == Silently::Yes) << ") (current time is " << currentTime() << ")");
610
611     // 3.4.16. Canceling an animation
612     // https://drafts.csswg.org/web-animations-1/#canceling-an-animation-section
613     //
614     // An animation can be canceled which causes the current time to become unresolved hence removing any effects caused by the target effect.
615     //
616     // The procedure to cancel an animation for animation is as follows:
617     //
618     // 1. If animation's play state is not idle, perform the following steps:
619     if (playState() != PlayState::Idle) {
620         // 1. Run the procedure to reset an animation's pending tasks on animation.
621         resetPendingTasks(silently);
622
623         // 2. Reject the current finished promise with a DOMException named "AbortError".
624         // 3. Set the [[PromiseIsHandled]] internal slot of the current finished promise to true.
625         if (silently == Silently::No && !m_finishedPromise->isFulfilled())
626             m_finishedPromise->reject(Exception { AbortError }, RejectAsHandled::Yes);
627
628         // 4. Let current finished promise be a new (pending) Promise object.
629         m_finishedPromise = makeUniqueRef<FinishedPromise>(*this, &WebAnimation::finishedPromiseResolve);
630
631         // 5. Create an AnimationPlaybackEvent, cancelEvent.
632         // 6. Set cancelEvent's type attribute to cancel.
633         // 7. Set cancelEvent's currentTime to null.
634         // 8. Let timeline time be the current time of the timeline with which animation is associated. If animation is not associated with an
635         //    active timeline, let timeline time be n unresolved time value.
636         // 9. Set cancelEvent's timelineTime to timeline time. If timeline time is unresolved, set it to null.
637         // 10. If animation has a document for timing, then append cancelEvent to its document for timing's pending animation event queue along
638         //    with its target, animation. If animation is associated with an active timeline that defines a procedure to convert timeline times
639         //    to origin-relative time, let the scheduled event time be the result of applying that procedure to timeline time. Otherwise, the
640         //    scheduled event time is an unresolved time value.
641         // Otherwise, queue a task to dispatch cancelEvent at animation. The task source for this task is the DOM manipulation task source.
642         if (silently == Silently::No)
643             enqueueAnimationPlaybackEvent(eventNames().cancelEvent, WTF::nullopt, m_timeline ? m_timeline->currentTime() : WTF::nullopt);
644     }
645
646     // 2. Make animation's hold time unresolved.
647     m_holdTime = WTF::nullopt;
648
649     // 3. Make animation's start time unresolved.
650     m_startTime = WTF::nullopt;
651
652     timingDidChange(DidSeek::No, SynchronouslyNotify::No);
653
654     invalidateEffect();
655
656     if (m_effect)
657         m_effect->animationWasCanceled();
658 }
659
660 void WebAnimation::willChangeRenderer()
661 {
662     if (is<KeyframeEffect>(m_effect))
663         downcast<KeyframeEffect>(*m_effect).willChangeRenderer();
664 }
665
666 void WebAnimation::enqueueAnimationPlaybackEvent(const AtomString& type, Optional<Seconds> currentTime, Optional<Seconds> timelineTime)
667 {
668     auto event = AnimationPlaybackEvent::create(type, currentTime, timelineTime, this);
669     event->setTarget(this);
670     enqueueAnimationEvent(WTFMove(event));
671 }
672
673 void WebAnimation::enqueueAnimationEvent(Ref<AnimationEventBase>&& event)
674 {
675     if (is<DocumentTimeline>(m_timeline)) {
676         // If animation has a document for timing, then append event to its document for timing's pending animation event queue along
677         // with its target, animation. If animation is associated with an active timeline that defines a procedure to convert timeline times
678         // to origin-relative time, let the scheduled event time be the result of applying that procedure to timeline time. Otherwise, the
679         // scheduled event time is an unresolved time value.
680         m_hasScheduledEventsDuringTick = true;
681         downcast<DocumentTimeline>(*m_timeline).enqueueAnimationEvent(WTFMove(event));
682     } else {
683         // Otherwise, queue a task to dispatch event at animation. The task source for this task is the DOM manipulation task source.
684         queueTaskToDispatchEvent(*this, TaskSource::DOMManipulation, WTFMove(event));
685     }
686 }
687
688 void WebAnimation::resetPendingTasks(Silently silently)
689 {
690     // The procedure to reset an animation's pending tasks for animation is as follows:
691     // https://drafts.csswg.org/web-animations-1/#reset-an-animations-pending-tasks
692     //
693     // 1. If animation does not have a pending play task or a pending pause task, abort this procedure.
694     if (!pending())
695         return;
696
697     // 2. If animation has a pending play task, cancel that task.
698     if (hasPendingPlayTask())
699         m_timeToRunPendingPlayTask = TimeToRunPendingTask::NotScheduled;
700
701     // 3. If animation has a pending pause task, cancel that task.
702     if (hasPendingPauseTask())
703         m_timeToRunPendingPauseTask = TimeToRunPendingTask::NotScheduled;
704
705     // 4. Apply any pending playback rate on animation.
706     applyPendingPlaybackRate();
707
708     // 5. Reject animation's current ready promise with a DOMException named "AbortError".
709     // 6. Set the [[PromiseIsHandled]] internal slot of animation’s current ready promise to true.
710     if (silently == Silently::No)
711         m_readyPromise->reject(Exception { AbortError }, RejectAsHandled::Yes);
712
713     // 7. Let animation's current ready promise be the result of creating a new resolved Promise object.
714     m_readyPromise = makeUniqueRef<ReadyPromise>(*this, &WebAnimation::readyPromiseResolve);
715     m_readyPromise->resolve(*this);
716 }
717
718 ExceptionOr<void> WebAnimation::finish()
719 {
720     LOG_WITH_STREAM(Animations, stream << "WebAnimation " << this << " finish (current time is " << currentTime() << ")");
721
722     // 3.4.15. Finishing an animation
723     // https://drafts.csswg.org/web-animations-1/#finishing-an-animation-section
724
725     // 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:
726     //
727     // 1. If animation's effective playback rate is zero, or if animation's effective playback rate > 0 and target effect end is infinity, throw an InvalidStateError and abort these steps.
728     if (!effectivePlaybackRate() || (effectivePlaybackRate() > 0 && effectEndTime() == Seconds::infinity()))
729         return Exception { InvalidStateError };
730
731     // 2. Apply any pending playback rate to animation.
732     applyPendingPlaybackRate();
733
734     // 3. Set limit as follows:
735     // If animation playback rate > 0, let limit be target effect end.
736     // Otherwise, let limit be zero.
737     auto limit = m_playbackRate > 0 ? effectEndTime() : 0_s;
738
739     // 4. Silently set the current time to limit.
740     silentlySetCurrentTime(limit);
741
742     // 5. If animation's start time is unresolved and animation has an associated active timeline, let the start time be the result of
743     //    evaluating timeline time - (limit / playback rate) where timeline time is the current time value of the associated timeline.
744     if (!m_startTime && m_timeline && m_timeline->currentTime())
745         m_startTime = m_timeline->currentTime().value() - (limit / m_playbackRate);
746
747     // 6. If there is a pending pause task and start time is resolved,
748     if (hasPendingPauseTask() && m_startTime) {
749         // 1. Let the hold time be unresolved.
750         m_holdTime = WTF::nullopt;
751         // 2. Cancel the pending pause task.
752         m_timeToRunPendingPauseTask = TimeToRunPendingTask::NotScheduled;
753         // 3. Resolve the current ready promise of animation with animation.
754         m_readyPromise->resolve(*this);
755     }
756
757     // 7. 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.
758     if (hasPendingPlayTask() && m_startTime) {
759         m_timeToRunPendingPlayTask = TimeToRunPendingTask::NotScheduled;
760         m_readyPromise->resolve(*this);
761     }
762
763     // 8. 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.
764     timingDidChange(DidSeek::Yes, SynchronouslyNotify::Yes);
765
766     invalidateEffect();
767
768     return { };
769 }
770
771 void WebAnimation::timingDidChange(DidSeek didSeek, SynchronouslyNotify synchronouslyNotify, Silently silently)
772 {
773     m_shouldSkipUpdatingFinishedStateWhenResolving = false;
774     updateFinishedState(didSeek, synchronouslyNotify);
775
776     if (is<KeyframeEffect>(m_effect)) {
777         updateRelevance();
778         downcast<KeyframeEffect>(*m_effect).animationTimingDidChange();
779     }
780
781     if (silently == Silently::No && m_timeline)
782         m_timeline->animationTimingDidChange(*this);
783 };
784
785 void WebAnimation::invalidateEffect()
786 {
787     if (!isEffectInvalidationSuspended() && m_effect)
788         m_effect->invalidate();
789 }
790
791 void WebAnimation::updateFinishedState(DidSeek didSeek, SynchronouslyNotify synchronouslyNotify)
792 {
793     // 3.4.14. Updating the finished state
794     // https://drafts.csswg.org/web-animations-1/#updating-the-finished-state
795
796     // 1. Let the unconstrained current time be the result of calculating the current time substituting an unresolved time value
797     // for the hold time if did seek is false. If did seek is true, the unconstrained current time is equal to the current time.
798     auto unconstrainedCurrentTime = currentTime(didSeek == DidSeek::Yes ? RespectHoldTime::Yes : RespectHoldTime::No);
799     auto endTime = effectEndTime();
800
801     // 2. If all three of the following conditions are true,
802     //    - the unconstrained current time is resolved, and
803     //    - animation's start time is resolved, and
804     //    - animation does not have a pending play task or a pending pause task,
805     if (unconstrainedCurrentTime && m_startTime && !pending()) {
806         // then update animation's hold time based on the first matching condition for animation from below, if any:
807         if (m_playbackRate > 0 && unconstrainedCurrentTime >= endTime) {
808             // If animation playback rate > 0 and unconstrained current time is greater than or equal to target effect end,
809             // If did seek is true, let the hold time be the value of unconstrained current time.
810             if (didSeek == DidSeek::Yes)
811                 m_holdTime = unconstrainedCurrentTime;
812             // 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.
813             else if (!m_previousCurrentTime)
814                 m_holdTime = endTime;
815             else
816                 m_holdTime = std::max(m_previousCurrentTime.value(), endTime);
817         } else if (m_playbackRate < 0 && unconstrainedCurrentTime <= 0_s) {
818             // If animation playback rate < 0 and unconstrained current time is less than or equal to 0,
819             // If did seek is true, let the hold time be the value of unconstrained current time.
820             if (didSeek == DidSeek::Yes)
821                 m_holdTime = unconstrainedCurrentTime;
822             // 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.
823             else if (!m_previousCurrentTime)
824                 m_holdTime = 0_s;
825             else
826                 m_holdTime = std::min(m_previousCurrentTime.value(), 0_s);
827         } else if (m_playbackRate && m_timeline && m_timeline->currentTime()) {
828             // If animation playback rate ≠ 0, and animation is associated with an active timeline,
829             // Perform the following steps:
830             // 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)
831             //    where timeline time is the current time value of timeline associated with animation.
832             if (didSeek == DidSeek::Yes && m_holdTime)
833                 m_startTime = m_timeline->currentTime().value() - (m_holdTime.value() / m_playbackRate);
834             // 2. Let the hold time be unresolved.
835             m_holdTime = WTF::nullopt;
836         }
837     }
838
839     // 3. Set the previous current time of animation be the result of calculating its current time.
840     m_previousCurrentTime = currentTime();
841
842     // 4. Let current finished state be true if the play state of animation is finished. Otherwise, let it be false.
843     auto currentFinishedState = playState() == PlayState::Finished;
844
845     // 5. If current finished state is true and the current finished promise is not yet resolved, perform the following steps:
846     if (currentFinishedState && !m_finishedPromise->isFulfilled()) {
847         if (synchronouslyNotify == SynchronouslyNotify::Yes) {
848             // If synchronously notify is true, cancel any queued microtask to run the finish notification steps for this animation,
849             // and run the finish notification steps immediately.
850             m_finishNotificationStepsMicrotaskPending = false;
851             finishNotificationSteps();
852         } else if (!m_finishNotificationStepsMicrotaskPending) {
853             // Otherwise, if synchronously notify is false, queue a microtask to run finish notification steps for animation unless there
854             // is already a microtask queued to run those steps for animation.
855             m_finishNotificationStepsMicrotaskPending = true;
856             if (auto* context = scriptExecutionContext()) {
857                 context->eventLoop().queueMicrotask([this, protectedThis = makeRef(*this)] {
858                     if (m_finishNotificationStepsMicrotaskPending) {
859                         m_finishNotificationStepsMicrotaskPending = false;
860                         finishNotificationSteps();
861                     }
862                 });
863             }
864         }
865     }
866
867     // 6. If current finished state is false and animation's current finished promise is already resolved, set animation's current
868     // finished promise to a new (pending) Promise object.
869     if (!currentFinishedState && m_finishedPromise->isFulfilled())
870         m_finishedPromise = makeUniqueRef<FinishedPromise>(*this, &WebAnimation::finishedPromiseResolve);
871
872     updateRelevance();
873 }
874
875 void WebAnimation::finishNotificationSteps()
876 {
877     // 3.4.14. Updating the finished state
878     // https://drafts.csswg.org/web-animations-1/#finish-notification-steps
879
880     // Let finish notification steps refer to the following procedure:
881     // 1. If animation's play state is not equal to finished, abort these steps.
882     if (playState() != PlayState::Finished)
883         return;
884
885     // 2. Resolve animation's current finished promise object with animation.
886     m_finishedPromise->resolve(*this);
887
888     // 3. Create an AnimationPlaybackEvent, finishEvent.
889     // 4. Set finishEvent's type attribute to finish.
890     // 5. Set finishEvent's currentTime attribute to the current time of animation.
891     // 6. Set finishEvent's timelineTime attribute to the current time of the timeline with which animation is associated.
892     //    If animation is not associated with a timeline, or the timeline is inactive, let timelineTime be null.
893     // 7. If animation has a document for timing, then append finishEvent to its document for timing's pending animation event
894     //    queue along with its target, animation. For the scheduled event time, use the result of converting animation's target
895     //    effect end to an origin-relative time.
896     //    Otherwise, queue a task to dispatch finishEvent at animation. The task source for this task is the DOM manipulation task source.
897     enqueueAnimationPlaybackEvent(eventNames().finishEvent, currentTime(), m_timeline ? m_timeline->currentTime() : WTF::nullopt);
898 }
899
900 ExceptionOr<void> WebAnimation::play()
901 {
902     return play(AutoRewind::Yes);
903 }
904
905 ExceptionOr<void> WebAnimation::play(AutoRewind autoRewind)
906 {
907     LOG_WITH_STREAM(Animations, stream << "WebAnimation " << this << " play(autoRewind " << (autoRewind == AutoRewind::Yes) << ") (current time is " << currentTime() << ")");
908
909     // 3.4.10. Playing an animation
910     // https://drafts.csswg.org/web-animations-1/#play-an-animation
911
912     auto localTime = currentTime();
913     auto endTime = effectEndTime();
914
915     // 1. Let aborted pause be a boolean flag that is true if animation has a pending pause task, and false otherwise.
916     bool abortedPause = hasPendingPauseTask();
917
918     // 2. Let has pending ready promise be a boolean flag that is initially false.
919     bool hasPendingReadyPromise = false;
920
921     // 3. Perform the steps corresponding to the first matching condition from the following, if any:
922     if (effectivePlaybackRate() > 0 && autoRewind == AutoRewind::Yes && (!localTime || localTime.value() < 0_s || (localTime.value() + timeEpsilon) >= endTime)) {
923         // If animation's effective playback rate > 0, the auto-rewind flag is true and either animation's:
924         //     - current time is unresolved, or
925         //     - current time < zero, or
926         //     - current time ≥ target effect end,
927         // Set animation's hold time to zero.
928         m_holdTime = 0_s;
929     } else if (effectivePlaybackRate() < 0 && autoRewind == AutoRewind::Yes && (!localTime || localTime.value() <= 0_s || localTime.value() > endTime)) {
930         // If animation's effective playback rate < 0, the auto-rewind flag is true and either animation's:
931         //     - current time is unresolved, or
932         //     - current time ≤ zero, or
933         //     - current time > target effect end
934         // If target effect end is positive infinity, throw an InvalidStateError and abort these steps.
935         if (endTime == Seconds::infinity())
936             return Exception { InvalidStateError };
937         m_holdTime = endTime;
938     } else if (!effectivePlaybackRate() && !localTime) {
939         // If animation's effective playback rate = 0 and animation's current time is unresolved,
940         // Set animation's hold time to zero.
941         m_holdTime = 0_s;
942     }
943
944     // 4. If animation has a pending play task or a pending pause task,
945     if (pending()) {
946         // 1. Cancel that task.
947         m_timeToRunPendingPauseTask = TimeToRunPendingTask::NotScheduled;
948         m_timeToRunPendingPlayTask = TimeToRunPendingTask::NotScheduled;
949         // 2. Set has pending ready promise to true.
950         hasPendingReadyPromise = true;
951     }
952
953     // 5. If the following three conditions are all satisfied:
954     //    - animation's hold time is unresolved, and
955     //    - aborted pause is false, and
956     //    - animation does not have a pending playback rate,
957     // abort this procedure.
958     if (!m_holdTime && !abortedPause && !m_pendingPlaybackRate)
959         return { };
960
961     // 6. If animation's hold time is resolved, let its start time be unresolved.
962     if (m_holdTime)
963         m_startTime = WTF::nullopt;
964
965     // 7. If has pending ready promise is false, let animation's current ready promise be a new (pending) Promise object.
966     if (!hasPendingReadyPromise)
967         m_readyPromise = makeUniqueRef<ReadyPromise>(*this, &WebAnimation::readyPromiseResolve);
968
969     // 8. Schedule a task to run as soon as animation is ready.
970     m_timeToRunPendingPlayTask = TimeToRunPendingTask::WhenReady;
971
972     // 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.
973     timingDidChange(DidSeek::No, SynchronouslyNotify::No);
974
975     invalidateEffect();
976
977     if (m_effect)
978         m_effect->animationDidPlay();
979
980     return { };
981 }
982
983 void WebAnimation::runPendingPlayTask()
984 {
985     LOG_WITH_STREAM(Animations, stream << "WebAnimation " << this << " runPendingPlayTask (current time is " << currentTime() << ")");
986
987     // 3.4.10. Playing an animation, step 8.
988     // https://drafts.csswg.org/web-animations-1/#play-an-animation
989
990     m_timeToRunPendingPlayTask = TimeToRunPendingTask::NotScheduled;
991
992     // 1. Assert that at least one of animation's start time or hold time is resolved.
993     ASSERT(m_startTime || m_holdTime);
994
995     // 2. Let ready time be the time value of the timeline associated with animation at the moment when animation became ready.
996     auto readyTime = m_timeline->currentTime();
997
998     // 3. Perform the steps corresponding to the first matching condition below, if any:
999     if (m_holdTime) {
1000         // If animation's hold time is resolved,
1001         // 1. Apply any pending playback rate on animation.
1002         applyPendingPlaybackRate();
1003         // 2. Let new start time be the result of evaluating ready time - hold time / animation playback rate for animation.
1004         // If the animation playback rate is zero, let new start time be simply ready time.
1005         // FIXME: Implementation cannot guarantee an active timeline at the point of this async dispatch.
1006         // Subsequently, the resulting readyTime value can be null. Unify behavior between C++17 and
1007         // C++14 builds (the latter using WTF's Optional) and avoid null Optional dereferencing
1008         // by defaulting to a Seconds(0) value. See https://bugs.webkit.org/show_bug.cgi?id=186189.
1009         auto newStartTime = readyTime.valueOr(0_s);
1010         if (m_playbackRate)
1011             newStartTime -= m_holdTime.value() / m_playbackRate;
1012         // 3. Set the start time of animation to new start time.
1013         m_startTime = newStartTime;
1014         // 4. If animation's playback rate is not 0, make animation's hold time unresolved.
1015         if (m_playbackRate)
1016             m_holdTime = WTF::nullopt;
1017     } else if (m_startTime && m_pendingPlaybackRate) {
1018         // If animation's start time is resolved and animation has a pending playback rate,
1019         // 1. Let current time to match be the result of evaluating (ready time - start time) × playback rate for animation.
1020         auto currentTimeToMatch = (readyTime.valueOr(0_s) - m_startTime.value()) * m_playbackRate;
1021         // 2. Apply any pending playback rate on animation.
1022         applyPendingPlaybackRate();
1023         // 3. If animation's playback rate is zero, let animation's hold time be current time to match.
1024         if (m_playbackRate)
1025             m_holdTime = currentTimeToMatch;
1026         // 4. Let new start time be the result of evaluating ready time - current time to match / playback rate for animation.
1027         // If the playback rate is zero, let new start time be simply ready time.
1028         auto newStartTime = readyTime.valueOr(0_s);
1029         if (m_playbackRate)
1030             newStartTime -= currentTimeToMatch / m_playbackRate;
1031         // 5. Set the start time of animation to new start time.
1032         m_startTime = newStartTime;
1033     }
1034
1035     // 4. Resolve animation's current ready promise with animation.
1036     if (!m_readyPromise->isFulfilled())
1037         m_readyPromise->resolve(*this);
1038
1039     // 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.
1040     timingDidChange(DidSeek::No, SynchronouslyNotify::No, Silently::Yes);
1041
1042     invalidateEffect();
1043 }
1044
1045 ExceptionOr<void> WebAnimation::pause()
1046 {
1047     LOG_WITH_STREAM(Animations, stream << "WebAnimation " << this << " pause (current time is " << currentTime() << ")");
1048
1049     // 3.4.11. Pausing an animation
1050     // https://drafts.csswg.org/web-animations-1/#pause-an-animation
1051
1052     // 1. If animation has a pending pause task, abort these steps.
1053     if (hasPendingPauseTask())
1054         return { };
1055
1056     // 2. If the play state of animation is paused, abort these steps.
1057     if (playState() == PlayState::Paused)
1058         return { };
1059
1060     auto localTime = currentTime();
1061
1062     // 3. If the animation's current time is unresolved, perform the steps according to the first matching condition from below:
1063     if (!localTime) {
1064         if (m_playbackRate >= 0) {
1065             // If animation's playback rate is ≥ 0, let animation's hold time be zero.
1066             m_holdTime = 0_s;
1067         } else if (effectEndTime() == Seconds::infinity()) {
1068             // Otherwise, if target effect end for animation is positive infinity, throw an InvalidStateError and abort these steps.
1069             return Exception { InvalidStateError };
1070         } else {
1071             // Otherwise, let animation's hold time be target effect end.
1072             m_holdTime = effectEndTime();
1073         }
1074     }
1075
1076     // 4. Let has pending ready promise be a boolean flag that is initially false.
1077     bool hasPendingReadyPromise = false;
1078
1079     // 5. If animation has a pending play task, cancel that task and let has pending ready promise be true.
1080     if (hasPendingPlayTask()) {
1081         m_timeToRunPendingPlayTask = TimeToRunPendingTask::NotScheduled;
1082         hasPendingReadyPromise = true;
1083     }
1084
1085     // 6. If has pending ready promise is false, set animation's current ready promise to a new (pending) Promise object.
1086     if (!hasPendingReadyPromise)
1087         m_readyPromise = makeUniqueRef<ReadyPromise>(*this, &WebAnimation::readyPromiseResolve);
1088
1089     // 7. Schedule a task to be executed at the first possible moment after the user agent has performed any processing necessary
1090     //    to suspend the playback of animation's target effect, if any.
1091     m_timeToRunPendingPauseTask = TimeToRunPendingTask::ASAP;
1092
1093     // 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.
1094     timingDidChange(DidSeek::No, SynchronouslyNotify::No);
1095
1096     invalidateEffect();
1097
1098     return { };
1099 }
1100
1101 ExceptionOr<void> WebAnimation::reverse()
1102 {
1103     LOG_WITH_STREAM(Animations, stream << "WebAnimation " << this << " reverse (current time is " << currentTime() << ")");
1104
1105     // 3.4.18. Reversing an animation
1106     // https://drafts.csswg.org/web-animations-1/#reverse-an-animation
1107
1108     // The procedure to reverse an animation of animation animation is as follows:
1109
1110     // 1. If there is no timeline associated with animation, or the associated timeline is inactive
1111     //    throw an InvalidStateError and abort these steps.
1112     if (!m_timeline || !m_timeline->currentTime())
1113         return Exception { InvalidStateError };
1114
1115     // 2. Let original pending playback rate be animation's pending playback rate.
1116     auto originalPendingPlaybackRate = m_pendingPlaybackRate;
1117
1118     // 3. Let animation's pending playback rate be the additive inverse of its effective playback rate (i.e. -effective playback rate).
1119     m_pendingPlaybackRate = -effectivePlaybackRate();
1120
1121     // 4. Run the steps to play an animation for animation with the auto-rewind flag set to true.
1122     auto playResult = play(AutoRewind::Yes);
1123
1124     // If the steps to play an animation throw an exception, set animation's pending playback rate to original
1125     // pending playback rate and propagate the exception.
1126     if (playResult.hasException()) {
1127         m_pendingPlaybackRate = originalPendingPlaybackRate;
1128         return playResult.releaseException();
1129     }
1130
1131     return { };
1132 }
1133
1134 void WebAnimation::runPendingPauseTask()
1135 {
1136     LOG_WITH_STREAM(Animations, stream << "WebAnimation " << this << " runPendingPauseTask (current time is " << currentTime() << ")");
1137
1138     // 3.4.11. Pausing an animation, step 7.
1139     // https://drafts.csswg.org/web-animations-1/#pause-an-animation
1140
1141     m_timeToRunPendingPauseTask = TimeToRunPendingTask::NotScheduled;
1142
1143     // 1. Let ready time be the time value of the timeline associated with animation at the moment when the user agent
1144     //    completed processing necessary to suspend playback of animation's target effect.
1145     auto readyTime = m_timeline->currentTime();
1146     auto animationStartTime = m_startTime;
1147
1148     // 2. If animation's start time is resolved and its hold time is not resolved, let animation's hold time be the result of
1149     //    evaluating (ready time - start time) × playback rate.
1150     //    Note: The hold time might be already set if the animation is finished, or if the animation is pending, waiting to begin
1151     //    playback. In either case we want to preserve the hold time as we enter the paused state.
1152     if (animationStartTime && !m_holdTime) {
1153         // FIXME: Implementation cannot guarantee an active timeline at the point of this async dispatch.
1154         // Subsequently, the resulting readyTime value can be null. Unify behavior between C++17 and
1155         // C++14 builds (the latter using WTF's Optional) and avoid null Optional dereferencing
1156         // by defaulting to a Seconds(0) value. See https://bugs.webkit.org/show_bug.cgi?id=186189.
1157         m_holdTime = (readyTime.valueOr(0_s) - animationStartTime.value()) * m_playbackRate;
1158     }
1159
1160     // 3. Apply any pending playback rate on animation.
1161     applyPendingPlaybackRate();
1162
1163     // 4. Make animation's start time unresolved.
1164     m_startTime = WTF::nullopt;
1165
1166     // 5. Resolve animation's current ready promise with animation.
1167     if (!m_readyPromise->isFulfilled())
1168         m_readyPromise->resolve(*this);
1169
1170     // 6. Run the procedure to update an animation's finished state for animation with the did seek flag set to false, and the
1171     //    synchronously notify flag set to false.
1172     timingDidChange(DidSeek::No, SynchronouslyNotify::No, Silently::Yes);
1173
1174     invalidateEffect();
1175 }
1176
1177 bool WebAnimation::isRunningAccelerated() const
1178 {
1179     return is<KeyframeEffect>(m_effect) && downcast<KeyframeEffect>(*m_effect).isRunningAccelerated();
1180 }
1181
1182 bool WebAnimation::isCompletelyAccelerated() const
1183 {
1184     return is<KeyframeEffect>(m_effect) && downcast<KeyframeEffect>(*m_effect).isCompletelyAccelerated();
1185 }
1186
1187 bool WebAnimation::needsTick() const
1188 {
1189     return pending() || playState() == PlayState::Running || m_hasScheduledEventsDuringTick;
1190 }
1191
1192 void WebAnimation::tick()
1193 {
1194     m_hasScheduledEventsDuringTick = false;
1195     updateFinishedState(DidSeek::No, SynchronouslyNotify::Yes);
1196     m_shouldSkipUpdatingFinishedStateWhenResolving = true;
1197
1198     // Run pending tasks, if any.
1199     if (hasPendingPauseTask())
1200         runPendingPauseTask();
1201     if (hasPendingPlayTask())
1202         runPendingPlayTask();
1203
1204     if (!isEffectInvalidationSuspended() && m_effect)
1205         m_effect->animationDidTick();
1206 }
1207
1208 void WebAnimation::resolve(RenderStyle& targetStyle)
1209 {
1210     if (!m_shouldSkipUpdatingFinishedStateWhenResolving)
1211         updateFinishedState(DidSeek::No, SynchronouslyNotify::Yes);
1212     m_shouldSkipUpdatingFinishedStateWhenResolving = false;
1213
1214     if (m_effect)
1215         m_effect->apply(targetStyle);
1216 }
1217
1218 void WebAnimation::setSuspended(bool isSuspended)
1219 {
1220     if (m_isSuspended == isSuspended)
1221         return;
1222
1223     m_isSuspended = isSuspended;
1224
1225     if (m_effect && playState() == PlayState::Running)
1226         m_effect->animationSuspensionStateDidChange(isSuspended);
1227 }
1228
1229 void WebAnimation::acceleratedStateDidChange()
1230 {
1231     if (is<DocumentTimeline>(m_timeline))
1232         downcast<DocumentTimeline>(*m_timeline).animationAcceleratedRunningStateDidChange(*this);
1233 }
1234
1235 void WebAnimation::applyPendingAcceleratedActions()
1236 {
1237     if (is<KeyframeEffect>(m_effect))
1238         downcast<KeyframeEffect>(*m_effect).applyPendingAcceleratedActions();
1239 }
1240
1241 WebAnimation& WebAnimation::readyPromiseResolve()
1242 {
1243     return *this;
1244 }
1245
1246 WebAnimation& WebAnimation::finishedPromiseResolve()
1247 {
1248     return *this;
1249 }
1250
1251 const char* WebAnimation::activeDOMObjectName() const
1252 {
1253     return "Animation";
1254 }
1255
1256 void WebAnimation::suspend(ReasonForSuspension)
1257 {
1258     setSuspended(true);
1259 }
1260
1261 void WebAnimation::resume()
1262 {
1263     setSuspended(false);
1264 }
1265
1266 void WebAnimation::stop()
1267 {
1268     ActiveDOMObject::stop();
1269     removeAllEventListeners();
1270 }
1271
1272 bool WebAnimation::hasPendingActivity() const
1273 {
1274     // Keep the JS wrapper alive if the animation is considered relevant or could become relevant again by virtue of having a timeline.
1275     return m_timeline || m_isRelevant || ActiveDOMObject::hasPendingActivity();
1276 }
1277
1278 void WebAnimation::updateRelevance()
1279 {
1280     m_isRelevant = computeRelevance();
1281 }
1282
1283 bool WebAnimation::computeRelevance()
1284 {
1285     // To be listed in getAnimations() an animation needs a target effect which is current or in effect.
1286     if (!m_effect)
1287         return false;
1288
1289     if (m_replaceState == ReplaceState::Removed)
1290         return false;
1291
1292     auto timing = m_effect->getBasicTiming();
1293
1294     // An animation effect is in effect if its active time is not unresolved.
1295     if (timing.activeTime)
1296         return true;
1297
1298     // An animation effect is current if either of the following conditions is true:
1299     // - the animation effect is in the before phase, or
1300     // - the animation effect is in play.
1301
1302     // An animation effect is in play if all of the following conditions are met:
1303     // - the animation effect is in the active phase, and
1304     // - the animation effect is associated with an animation that is not finished.
1305     return timing.phase == AnimationEffectPhase::Before || (timing.phase == AnimationEffectPhase::Active && playState() != PlayState::Finished);
1306 }
1307
1308 bool WebAnimation::isReplaceable() const
1309 {
1310     // An animation is replaceable if all of the following conditions are true:
1311     // https://drafts.csswg.org/web-animations/#removing-replaced-animations
1312
1313     // The existence of the animation is not prescribed by markup. That is, it is not a CSS animation with an owning element,
1314     // nor a CSS transition with an owning element.
1315     if (isDeclarativeAnimation() && downcast<DeclarativeAnimation>(this)->owningElement())
1316         return false;
1317
1318     // The animation's play state is finished.
1319     if (playState() != PlayState::Finished)
1320         return false;
1321
1322     // The animation's replace state is not removed.
1323     if (m_replaceState == ReplaceState::Removed)
1324         return false;
1325
1326     // The animation is associated with a monotonically increasing timeline.
1327     if (!m_timeline)
1328         return false;
1329
1330     // The animation has an associated target effect.
1331     if (!m_effect)
1332         return false;
1333
1334     // The target effect associated with the animation is in effect.
1335     if (!m_effect->getBasicTiming().activeTime)
1336         return false;
1337
1338     // The target effect has an associated target element.
1339     if (!is<KeyframeEffect>(m_effect) || !downcast<KeyframeEffect>(m_effect.get())->target())
1340         return false;
1341
1342     return true;
1343 }
1344
1345 void WebAnimation::persist()
1346 {
1347     auto previousReplaceState = std::exchange(m_replaceState, ReplaceState::Persisted);
1348
1349     if (previousReplaceState == ReplaceState::Removed && m_timeline) {
1350         if (is<KeyframeEffect>(m_effect)) {
1351             auto& keyframeEffect = downcast<KeyframeEffect>(*m_effect);
1352             auto& target = *keyframeEffect.target();
1353             m_timeline->animationWasAddedToElement(*this, target);
1354             target.ensureKeyframeEffectStack().addEffect(keyframeEffect);
1355         }
1356     }
1357 }
1358
1359 ExceptionOr<void> WebAnimation::commitStyles()
1360 {
1361     // https://drafts.csswg.org/web-animations-1/#commit-computed-styles
1362
1363     // 1. Let targets be the set of all effect targets for animation effects associated with animation.
1364     auto* effect = is<KeyframeEffect>(m_effect) ? downcast<KeyframeEffect>(m_effect.get()) : nullptr;
1365     auto* target = effect ? effect->target() : nullptr;
1366
1367     // 2. For each target in targets:
1368     //
1369     // 2.1 If target is not an element capable of having a style attribute (for example, it is a pseudo-element or is an element in a
1370     // document format for which style attributes are not defined) throw a "NoModificationAllowedError" DOMException and abort these steps.
1371     if (!is<StyledElement>(target))
1372         return Exception { NoModificationAllowedError };
1373
1374     auto& styledElement = downcast<StyledElement>(*target);
1375
1376     // 2.2 If, after applying any pending style changes, target is not being rendered, throw an "InvalidStateError" DOMException and abort these steps.
1377     styledElement.document().updateStyleIfNeeded();
1378     auto* renderer = styledElement.renderer();
1379     if (!renderer)
1380         return Exception { InvalidStateError };
1381
1382     // 2.3 Let inline style be the result of getting the CSS declaration block corresponding to target’s style attribute. If target does not have a style
1383     // attribute, let inline style be a new empty CSS declaration block with the readonly flag unset and owner node set to target.
1384
1385     // 2.4 Let targeted properties be the set of physical longhand properties that are a target property for at least one animation effect associated with
1386     // animation whose effect target is target.
1387
1388     auto& style = renderer->style();
1389     auto computedStyleExtractor = ComputedStyleExtractor(&styledElement);
1390     auto inlineStyle = styledElement.document().createCSSStyleDeclaration();
1391     inlineStyle->setCssText(styledElement.getAttribute("style"));
1392
1393     auto& keyframeStack = styledElement.ensureKeyframeEffectStack();
1394     auto* cssAnimationList = keyframeStack.cssAnimationList();
1395
1396     // 2.5 For each property, property, in targeted properties:
1397     for (auto property : effect->animatedProperties()) {
1398         // 1. Let partialEffectStack be a copy of the effect stack for property on target.
1399         // 2. If animation's replace state is removed, add all animation effects associated with animation whose effect target is target and which include
1400         // property as a target property to partialEffectStack.
1401         // 3. Remove from partialEffectStack any animation effects whose associated animation has a higher composite order than animation.
1402         // 4. Let effect value be the result of calculating the result of partialEffectStack for property using target's computed style (see § 5.4.3 Calculating
1403         // the result of an effect stack).
1404         // 5. Set a CSS declaration property for effect value in inline style.
1405         // 6. Update style attribute for inline style.
1406
1407         // We actually perform those steps in a different way: instead of building a copy of the effect stack and then removing stuff, we iterate through the
1408         // effect stack and stop when we've found this animation's effect or when we've found an effect associated with an animation with a higher composite order.
1409         auto animatedStyle = RenderStyle::clonePtr(style);
1410         for (const auto& effectInStack : keyframeStack.sortedEffects()) {
1411             if (effectInStack->animation() != this && !compareAnimationsByCompositeOrder(*effectInStack->animation(), *this, cssAnimationList))
1412                 break;
1413             if (effectInStack->animatedProperties().contains(property))
1414                 effectInStack->animation()->resolve(*animatedStyle);
1415             if (effectInStack->animation() == this)
1416                 break;
1417         }
1418         if (m_replaceState == ReplaceState::Removed)
1419             effect->animation()->resolve(*animatedStyle);
1420         if (auto cssValue = computedStyleExtractor.valueForPropertyInStyle(*animatedStyle, property, renderer))
1421             inlineStyle->setPropertyInternal(property, cssValue->cssText(), false);
1422     }
1423
1424     styledElement.setAttribute("style", inlineStyle->cssText());
1425
1426     return { };
1427 }
1428
1429 Seconds WebAnimation::timeToNextTick() const
1430 {
1431     ASSERT(effect());
1432
1433     if (pending())
1434         return 0_s;
1435
1436     // If we're not running, or time is not advancing for this animation, there's no telling when we'll end.
1437     auto playbackRate = effectivePlaybackRate();
1438     if (playState() != PlayState::Running || !playbackRate)
1439         return Seconds::infinity();
1440
1441     auto& effect = *this->effect();
1442     auto timing = effect.getBasicTiming();
1443     switch (timing.phase) {
1444     case AnimationEffectPhase::Before:
1445         // The animation is in its "before" phase, in this case we can wait until it enters its "active" phase.
1446         return (effect.delay() - timing.localTime.value()) / playbackRate;
1447     case AnimationEffectPhase::Active:
1448         if (isCompletelyAccelerated() && isRunningAccelerated()) {
1449             // Fully-accelerated running CSS Animations need to trigger "animationiteration" events, in this case we must wait until the next iteration.
1450             if (isCSSAnimation()) {
1451                 if (auto iterationProgress = effect.getComputedTiming().simpleIterationProgress)
1452                     return effect.iterationDuration() * (1 - *iterationProgress);
1453             }
1454             // Fully-accelerated running animations in the "active" phase can wait until they ended.
1455             return (effect.endTime() - timing.localTime.value()) / playbackRate;
1456         }
1457         // Other animations in the "active" phase will need to update their animated value at the immediate next opportunity.
1458         return 0_s;
1459     case AnimationEffectPhase::After:
1460         // The animation is in its after phase, which means it will no longer update its value, so it doens't need a tick.
1461         return Seconds::infinity();
1462     case AnimationEffectPhase::Idle:
1463         ASSERT_NOT_REACHED();
1464         return Seconds::infinity();
1465     }
1466
1467     ASSERT_NOT_REACHED();
1468     return Seconds::infinity();
1469 }
1470
1471 } // namespace WebCore