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