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