Bug 1850713: remove duplicated setting of early hint preloader id in `ScriptLoader...
[gecko.git] / dom / animation / Animation.cpp
blobe75473d5bbf0fcea7bf50164c7cee4258e036994
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "Animation.h"
9 #include "AnimationUtils.h"
10 #include "mozAutoDocUpdate.h"
11 #include "mozilla/dom/AnimationBinding.h"
12 #include "mozilla/dom/AnimationPlaybackEvent.h"
13 #include "mozilla/dom/Document.h"
14 #include "mozilla/dom/DocumentInlines.h"
15 #include "mozilla/dom/DocumentTimeline.h"
16 #include "mozilla/dom/MutationObservers.h"
17 #include "mozilla/dom/Promise.h"
18 #include "mozilla/AnimationEventDispatcher.h"
19 #include "mozilla/AnimationTarget.h"
20 #include "mozilla/AutoRestore.h"
21 #include "mozilla/CycleCollectedJSContext.h"
22 #include "mozilla/DeclarationBlock.h"
23 #include "mozilla/Maybe.h" // For Maybe
24 #include "mozilla/StaticPrefs_dom.h"
25 #include "nsAnimationManager.h" // For CSSAnimation
26 #include "nsComputedDOMStyle.h"
27 #include "nsDOMMutationObserver.h" // For nsAutoAnimationMutationBatch
28 #include "nsDOMCSSAttrDeclaration.h" // For nsDOMCSSAttributeDeclaration
29 #include "nsThreadUtils.h" // For nsRunnableMethod and nsRevocableEventPtr
30 #include "nsTransitionManager.h" // For CSSTransition
31 #include "PendingAnimationTracker.h" // For PendingAnimationTracker
32 #include "ScrollTimelineAnimationTracker.h"
34 namespace mozilla::dom {
36 // Static members
37 uint64_t Animation::sNextAnimationIndex = 0;
39 NS_IMPL_CYCLE_COLLECTION_INHERITED(Animation, DOMEventTargetHelper, mTimeline,
40 mEffect, mReady, mFinished)
42 NS_IMPL_ADDREF_INHERITED(Animation, DOMEventTargetHelper)
43 NS_IMPL_RELEASE_INHERITED(Animation, DOMEventTargetHelper)
45 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Animation)
46 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
48 JSObject* Animation::WrapObject(JSContext* aCx,
49 JS::Handle<JSObject*> aGivenProto) {
50 return dom::Animation_Binding::Wrap(aCx, this, aGivenProto);
53 // ---------------------------------------------------------------------------
55 // Utility methods
57 // ---------------------------------------------------------------------------
59 namespace {
60 // A wrapper around nsAutoAnimationMutationBatch that looks up the
61 // appropriate document from the supplied animation.
62 class MOZ_RAII AutoMutationBatchForAnimation {
63 public:
64 explicit AutoMutationBatchForAnimation(const Animation& aAnimation) {
65 NonOwningAnimationTarget target = aAnimation.GetTargetForAnimation();
66 if (!target) {
67 return;
70 // For mutation observers, we use the OwnerDoc.
71 mAutoBatch.emplace(target.mElement->OwnerDoc());
74 private:
75 Maybe<nsAutoAnimationMutationBatch> mAutoBatch;
77 } // namespace
79 // ---------------------------------------------------------------------------
81 // Animation interface:
83 // ---------------------------------------------------------------------------
85 Animation::Animation(nsIGlobalObject* aGlobal)
86 : DOMEventTargetHelper(aGlobal),
87 mAnimationIndex(sNextAnimationIndex++),
88 mRTPCallerType(aGlobal->GetRTPCallerType()) {}
90 Animation::~Animation() = default;
92 /* static */
93 already_AddRefed<Animation> Animation::ClonePausedAnimation(
94 nsIGlobalObject* aGlobal, const Animation& aOther, AnimationEffect& aEffect,
95 AnimationTimeline& aTimeline) {
96 // FIXME: Bug 1805950: Support printing for scroll-timeline once we resolve
97 // the spec issue.
98 if (aOther.UsingScrollTimeline()) {
99 return nullptr;
102 RefPtr<Animation> animation = new Animation(aGlobal);
104 // Setup the timeline. We always use document-timeline of the new document,
105 // even if the timeline of |aOther| is null.
106 animation->mTimeline = &aTimeline;
108 // Setup the playback rate.
109 animation->mPlaybackRate = aOther.mPlaybackRate;
111 // Setup the timing.
112 const Nullable<TimeDuration> currentTime = aOther.GetCurrentTimeAsDuration();
113 if (!aOther.GetTimeline()) {
114 // This simulates what we do in SetTimelineNoUpdate(). It's possible to
115 // preserve the progress if the previous timeline is a scroll-timeline.
116 // So for null timeline, it may have a progress and the non-null current
117 // time.
118 if (!currentTime.IsNull()) {
119 animation->SilentlySetCurrentTime(currentTime.Value());
121 animation->mPreviousCurrentTime = animation->GetCurrentTimeAsDuration();
122 } else {
123 animation->mHoldTime = currentTime;
124 if (!currentTime.IsNull()) {
125 // FIXME: Should we use |timelineTime| as previous current time here? It
126 // seems we should use animation->GetCurrentTimeAsDuration(), per
127 // UpdateFinishedState().
128 const Nullable<TimeDuration> timelineTime =
129 aTimeline.GetCurrentTimeAsDuration();
130 MOZ_ASSERT(!timelineTime.IsNull(), "Timeline not yet set");
131 animation->mPreviousCurrentTime = timelineTime;
135 // Setup the effect's link to this.
136 animation->mEffect = &aEffect;
137 animation->mEffect->SetAnimation(animation);
139 animation->mPendingState = PendingState::PausePending;
141 Document* doc = animation->GetRenderedDocument();
142 MOZ_ASSERT(doc,
143 "Cloning animation should already have the rendered document");
144 PendingAnimationTracker* tracker = doc->GetOrCreatePendingAnimationTracker();
145 tracker->AddPausePending(*animation);
147 // We expect our relevance to be the same as the orginal.
148 animation->mIsRelevant = aOther.mIsRelevant;
150 animation->PostUpdate();
151 animation->mTimeline->NotifyAnimationUpdated(*animation);
152 return animation.forget();
155 NonOwningAnimationTarget Animation::GetTargetForAnimation() const {
156 AnimationEffect* effect = GetEffect();
157 NonOwningAnimationTarget target;
158 if (!effect || !effect->AsKeyframeEffect()) {
159 return target;
161 return effect->AsKeyframeEffect()->GetAnimationTarget();
164 /* static */
165 already_AddRefed<Animation> Animation::Constructor(
166 const GlobalObject& aGlobal, AnimationEffect* aEffect,
167 const Optional<AnimationTimeline*>& aTimeline, ErrorResult& aRv) {
168 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
170 AnimationTimeline* timeline;
171 Document* document =
172 AnimationUtils::GetCurrentRealmDocument(aGlobal.Context());
174 if (aTimeline.WasPassed()) {
175 timeline = aTimeline.Value();
176 } else {
177 if (!document) {
178 aRv.Throw(NS_ERROR_FAILURE);
179 return nullptr;
181 timeline = document->Timeline();
184 RefPtr<Animation> animation = new Animation(global);
185 animation->SetTimelineNoUpdate(timeline);
186 animation->SetEffectNoUpdate(aEffect);
188 return animation.forget();
191 void Animation::SetId(const nsAString& aId) {
192 if (mId == aId) {
193 return;
195 mId = aId;
196 MutationObservers::NotifyAnimationChanged(this);
199 void Animation::SetEffect(AnimationEffect* aEffect) {
200 SetEffectNoUpdate(aEffect);
201 PostUpdate();
204 // https://drafts.csswg.org/web-animations/#setting-the-target-effect
205 void Animation::SetEffectNoUpdate(AnimationEffect* aEffect) {
206 RefPtr<Animation> kungFuDeathGrip(this);
208 if (mEffect == aEffect) {
209 return;
212 AutoMutationBatchForAnimation mb(*this);
213 bool wasRelevant = mIsRelevant;
215 if (mEffect) {
216 // We need to notify observers now because once we set mEffect to null
217 // we won't be able to find the target element to notify.
218 if (mIsRelevant) {
219 MutationObservers::NotifyAnimationRemoved(this);
222 // Break links with the old effect and then drop it.
223 RefPtr<AnimationEffect> oldEffect = mEffect;
224 mEffect = nullptr;
225 if (IsPartialPrerendered()) {
226 if (KeyframeEffect* oldKeyframeEffect = oldEffect->AsKeyframeEffect()) {
227 oldKeyframeEffect->ResetPartialPrerendered();
230 oldEffect->SetAnimation(nullptr);
232 // The following will not do any notification because mEffect is null.
233 UpdateRelevance();
236 if (aEffect) {
237 // Break links from the new effect to its previous animation, if any.
238 RefPtr<AnimationEffect> newEffect = aEffect;
239 Animation* prevAnim = aEffect->GetAnimation();
240 if (prevAnim) {
241 prevAnim->SetEffect(nullptr);
244 // Create links with the new effect. SetAnimation(this) will also update
245 // mIsRelevant of this animation, and then notify mutation observer if
246 // needed by calling Animation::UpdateRelevance(), so we don't need to
247 // call it again.
248 mEffect = newEffect;
249 mEffect->SetAnimation(this);
251 // Notify possible add or change.
252 // If the target is different, the change notification will be ignored by
253 // AutoMutationBatchForAnimation.
254 if (wasRelevant && mIsRelevant) {
255 MutationObservers::NotifyAnimationChanged(this);
258 ReschedulePendingTasks();
261 MaybeScheduleReplacementCheck();
263 UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
266 void Animation::SetTimeline(AnimationTimeline* aTimeline) {
267 SetTimelineNoUpdate(aTimeline);
268 PostUpdate();
271 // https://drafts.csswg.org/web-animations/#setting-the-timeline
272 void Animation::SetTimelineNoUpdate(AnimationTimeline* aTimeline) {
273 if (mTimeline == aTimeline) {
274 return;
277 StickyTimeDuration activeTime =
278 mEffect ? mEffect->GetComputedTiming().mActiveTime : StickyTimeDuration();
280 const AnimationPlayState previousPlayState = PlayState();
281 const Nullable<TimeDuration> previousCurrentTime = GetCurrentTimeAsDuration();
282 // FIXME: The definition of end time in web-animation-1 is different from that
283 // in web-animation-2, which includes the start time. We are still using the
284 // definition in web-animation-1 here for now.
285 const TimeDuration endTime = TimeDuration(EffectEnd());
286 double previousProgress = 0.0;
287 if (!previousCurrentTime.IsNull() && !endTime.IsZero()) {
288 previousProgress =
289 previousCurrentTime.Value().ToSeconds() / endTime.ToSeconds();
292 RefPtr<AnimationTimeline> oldTimeline = mTimeline;
293 if (oldTimeline) {
294 oldTimeline->RemoveAnimation(this);
297 mTimeline = aTimeline;
299 mResetCurrentTimeOnResume = false;
301 if (mEffect) {
302 mEffect->UpdateNormalizedTiming();
305 if (mTimeline && !mTimeline->IsMonotonicallyIncreasing()) {
306 // If "to finite timeline" is true.
308 ApplyPendingPlaybackRate();
309 Nullable<TimeDuration> seekTime;
310 if (mPlaybackRate >= 0.0) {
311 seekTime.SetValue(TimeDuration());
312 } else {
313 seekTime.SetValue(TimeDuration(EffectEnd()));
316 switch (previousPlayState) {
317 case AnimationPlayState::Running:
318 case AnimationPlayState::Finished:
319 mStartTime = seekTime;
320 break;
321 case AnimationPlayState::Paused:
322 if (!previousCurrentTime.IsNull()) {
323 mResetCurrentTimeOnResume = true;
324 mStartTime.SetNull();
325 mHoldTime.SetValue(
326 TimeDuration(EffectEnd().MultDouble(previousProgress)));
327 } else {
328 mStartTime = seekTime;
330 break;
331 case AnimationPlayState::Idle:
332 default:
333 break;
335 } else if (oldTimeline && !oldTimeline->IsMonotonicallyIncreasing() &&
336 !previousCurrentTime.IsNull()) {
337 // If "from finite timeline" and previous progress is resolved.
338 SetCurrentTimeNoUpdate(
339 TimeDuration(EffectEnd().MultDouble(previousProgress)));
342 if (!mStartTime.IsNull()) {
343 mHoldTime.SetNull();
346 if (!aTimeline) {
347 MaybeQueueCancelEvent(activeTime);
350 UpdatePendingAnimationTracker(oldTimeline, aTimeline);
352 UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
354 // FIXME: Bug 1799071: Check if we need to add
355 // MutationObservers::NotifyAnimationChanged(this) here.
358 // https://drafts.csswg.org/web-animations/#set-the-animation-start-time
359 void Animation::SetStartTime(const Nullable<TimeDuration>& aNewStartTime) {
360 // Return early if the start time will not change. However, if we
361 // are pending, then setting the start time to any value
362 // including the current value has the effect of aborting
363 // pending tasks so we should not return early in that case.
364 if (!Pending() && aNewStartTime == mStartTime) {
365 return;
368 AutoMutationBatchForAnimation mb(*this);
370 Nullable<TimeDuration> timelineTime;
371 if (mTimeline) {
372 // The spec says to check if the timeline is active (has a resolved time)
373 // before using it here, but we don't need to since it's harmless to set
374 // the already null time to null.
375 timelineTime = mTimeline->GetCurrentTimeAsDuration();
377 if (timelineTime.IsNull() && !aNewStartTime.IsNull()) {
378 mHoldTime.SetNull();
381 Nullable<TimeDuration> previousCurrentTime = GetCurrentTimeAsDuration();
383 ApplyPendingPlaybackRate();
384 mStartTime = aNewStartTime;
386 mResetCurrentTimeOnResume = false;
388 if (!aNewStartTime.IsNull()) {
389 if (mPlaybackRate != 0.0) {
390 mHoldTime.SetNull();
392 } else {
393 mHoldTime = previousCurrentTime;
396 CancelPendingTasks();
397 if (mReady) {
398 // We may have already resolved mReady, but in that case calling
399 // MaybeResolve is a no-op, so that's okay.
400 mReady->MaybeResolve(this);
403 UpdateTiming(SeekFlag::DidSeek, SyncNotifyFlag::Async);
404 if (IsRelevant()) {
405 MutationObservers::NotifyAnimationChanged(this);
407 PostUpdate();
410 // https://drafts.csswg.org/web-animations/#current-time
411 Nullable<TimeDuration> Animation::GetCurrentTimeForHoldTime(
412 const Nullable<TimeDuration>& aHoldTime) const {
413 Nullable<TimeDuration> result;
414 if (!aHoldTime.IsNull()) {
415 result = aHoldTime;
416 return result;
419 if (mTimeline && !mStartTime.IsNull()) {
420 Nullable<TimeDuration> timelineTime = mTimeline->GetCurrentTimeAsDuration();
421 if (!timelineTime.IsNull()) {
422 result = CurrentTimeFromTimelineTime(timelineTime.Value(),
423 mStartTime.Value(), mPlaybackRate);
426 return result;
429 // https://drafts.csswg.org/web-animations/#set-the-current-time
430 void Animation::SetCurrentTime(const TimeDuration& aSeekTime) {
431 // Return early if the current time has not changed. However, if we
432 // are pause-pending, then setting the current time to any value
433 // including the current value has the effect of aborting the
434 // pause so we should not return early in that case.
435 if (mPendingState != PendingState::PausePending &&
436 Nullable<TimeDuration>(aSeekTime) == GetCurrentTimeAsDuration()) {
437 return;
440 AutoMutationBatchForAnimation mb(*this);
442 SetCurrentTimeNoUpdate(aSeekTime);
444 if (IsRelevant()) {
445 MutationObservers::NotifyAnimationChanged(this);
447 PostUpdate();
450 void Animation::SetCurrentTimeNoUpdate(const TimeDuration& aSeekTime) {
451 SilentlySetCurrentTime(aSeekTime);
453 if (mPendingState == PendingState::PausePending) {
454 // Finish the pause operation
455 mHoldTime.SetValue(aSeekTime);
457 ApplyPendingPlaybackRate();
458 mStartTime.SetNull();
460 if (mReady) {
461 mReady->MaybeResolve(this);
463 CancelPendingTasks();
466 UpdateTiming(SeekFlag::DidSeek, SyncNotifyFlag::Async);
469 // https://drafts.csswg.org/web-animations/#set-the-playback-rate
470 void Animation::SetPlaybackRate(double aPlaybackRate) {
471 mPendingPlaybackRate.reset();
473 if (aPlaybackRate == mPlaybackRate) {
474 return;
477 AutoMutationBatchForAnimation mb(*this);
479 Nullable<TimeDuration> previousTime = GetCurrentTimeAsDuration();
480 mPlaybackRate = aPlaybackRate;
481 if (!previousTime.IsNull()) {
482 SetCurrentTime(previousTime.Value());
485 // In the case where GetCurrentTimeAsDuration() returns the same result before
486 // and after updating mPlaybackRate, SetCurrentTime will return early since,
487 // as far as it can tell, nothing has changed.
488 // As a result, we need to perform the following updates here:
489 // - update timing (since, if the sign of the playback rate has changed, our
490 // finished state may have changed),
491 // - dispatch a change notification for the changed playback rate, and
492 // - update the playback rate on animations on layers.
493 UpdateTiming(SeekFlag::DidSeek, SyncNotifyFlag::Async);
494 if (IsRelevant()) {
495 MutationObservers::NotifyAnimationChanged(this);
497 PostUpdate();
500 // https://drafts.csswg.org/web-animations/#seamlessly-update-the-playback-rate
501 void Animation::UpdatePlaybackRate(double aPlaybackRate) {
502 if (mPendingPlaybackRate && mPendingPlaybackRate.value() == aPlaybackRate) {
503 return;
506 // Calculate the play state using the existing playback rate since below we
507 // want to know if the animation is _currently_ finished or not, not whether
508 // it _will_ be finished.
509 AnimationPlayState playState = PlayState();
511 mPendingPlaybackRate = Some(aPlaybackRate);
513 if (Pending()) {
514 // If we already have a pending task, there is nothing more to do since the
515 // playback rate will be applied then.
517 // However, as with the idle/paused case below, we still need to update the
518 // relevance (and effect set to make sure it only contains relevant
519 // animations) since the relevance is based on the Animation play state
520 // which incorporates the _pending_ playback rate.
521 UpdateEffect(PostRestyleMode::Never);
522 return;
525 AutoMutationBatchForAnimation mb(*this);
527 if (playState == AnimationPlayState::Idle ||
528 playState == AnimationPlayState::Paused ||
529 GetCurrentTimeAsDuration().IsNull()) {
530 // If |previous play state| is idle or paused, or the current time is
531 // unresolved, we apply any pending playback rate on animation immediately.
532 ApplyPendingPlaybackRate();
534 // We don't need to update timing or post an update here because:
536 // * the current time hasn't changed -- it's either unresolved or fixed
537 // with a hold time -- so the output won't have changed
538 // * the finished state won't have changed even if the sign of the
539 // playback rate changed since we're not finished (we're paused or idle)
540 // * the playback rate on layers doesn't need to be updated since we're not
541 // moving. Once we get a start time etc. we'll update the playback rate
542 // then.
544 // However we still need to update the relevance and effect set as well as
545 // notifying observers.
546 UpdateEffect(PostRestyleMode::Never);
547 if (IsRelevant()) {
548 MutationObservers::NotifyAnimationChanged(this);
550 } else if (playState == AnimationPlayState::Finished) {
551 MOZ_ASSERT(mTimeline && !mTimeline->GetCurrentTimeAsDuration().IsNull(),
552 "If we have no active timeline, we should be idle or paused");
553 if (aPlaybackRate != 0) {
554 // The unconstrained current time can only be unresolved if either we
555 // don't have an active timeline (and we already asserted that is not
556 // true) or we have an unresolved start time (in which case we should be
557 // paused).
558 MOZ_ASSERT(!GetUnconstrainedCurrentTime().IsNull(),
559 "Unconstrained current time should be resolved");
560 TimeDuration unconstrainedCurrentTime =
561 GetUnconstrainedCurrentTime().Value();
562 TimeDuration timelineTime = mTimeline->GetCurrentTimeAsDuration().Value();
563 mStartTime = StartTimeFromTimelineTime(
564 timelineTime, unconstrainedCurrentTime, aPlaybackRate);
565 } else {
566 mStartTime = mTimeline->GetCurrentTimeAsDuration();
569 ApplyPendingPlaybackRate();
571 // Even though we preserve the current time, we might now leave the finished
572 // state (e.g. if the playback rate changes sign) so we need to update
573 // timing.
574 UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
575 if (IsRelevant()) {
576 MutationObservers::NotifyAnimationChanged(this);
578 PostUpdate();
579 } else {
580 ErrorResult rv;
581 Play(rv, LimitBehavior::Continue);
582 MOZ_ASSERT(!rv.Failed(),
583 "We should only fail to play when using auto-rewind behavior");
587 // https://drafts.csswg.org/web-animations/#play-state
588 AnimationPlayState Animation::PlayState() const {
589 Nullable<TimeDuration> currentTime = GetCurrentTimeAsDuration();
590 if (currentTime.IsNull() && mStartTime.IsNull() && !Pending()) {
591 return AnimationPlayState::Idle;
594 if (mPendingState == PendingState::PausePending ||
595 (mStartTime.IsNull() && !Pending())) {
596 return AnimationPlayState::Paused;
599 double playbackRate = CurrentOrPendingPlaybackRate();
600 if (!currentTime.IsNull() &&
601 ((playbackRate > 0.0 && currentTime.Value() >= EffectEnd()) ||
602 (playbackRate < 0.0 && currentTime.Value() <= TimeDuration()))) {
603 return AnimationPlayState::Finished;
606 return AnimationPlayState::Running;
609 Promise* Animation::GetReady(ErrorResult& aRv) {
610 nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal();
611 if (!mReady && global) {
612 mReady = Promise::Create(global, aRv); // Lazily create on demand
614 if (!mReady) {
615 aRv.Throw(NS_ERROR_FAILURE);
616 return nullptr;
618 if (!Pending()) {
619 mReady->MaybeResolve(this);
621 return mReady;
624 Promise* Animation::GetFinished(ErrorResult& aRv) {
625 nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal();
626 if (!mFinished && global) {
627 mFinished = Promise::Create(global, aRv); // Lazily create on demand
629 if (!mFinished) {
630 aRv.Throw(NS_ERROR_FAILURE);
631 return nullptr;
633 if (mFinishedIsResolved) {
634 MaybeResolveFinishedPromise();
636 return mFinished;
639 // https://drafts.csswg.org/web-animations/#cancel-an-animation
640 void Animation::Cancel(PostRestyleMode aPostRestyle) {
641 bool newlyIdle = false;
643 if (PlayState() != AnimationPlayState::Idle) {
644 newlyIdle = true;
646 ResetPendingTasks();
648 if (mFinished) {
649 mFinished->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
650 // mFinished can already be resolved.
651 MOZ_ALWAYS_TRUE(mFinished->SetAnyPromiseIsHandled());
653 ResetFinishedPromise();
655 QueuePlaybackEvent(u"cancel"_ns, GetTimelineCurrentTimeAsTimeStamp());
658 StickyTimeDuration activeTime =
659 mEffect ? mEffect->GetComputedTiming().mActiveTime : StickyTimeDuration();
661 mHoldTime.SetNull();
662 mStartTime.SetNull();
664 // Allow our effect to remove itself from the its target element's EffectSet.
665 UpdateEffect(aPostRestyle);
667 if (mTimeline) {
668 mTimeline->RemoveAnimation(this);
670 MaybeQueueCancelEvent(activeTime);
672 if (newlyIdle && aPostRestyle == PostRestyleMode::IfNeeded) {
673 PostUpdate();
677 // https://drafts.csswg.org/web-animations/#finish-an-animation
678 void Animation::Finish(ErrorResult& aRv) {
679 double effectivePlaybackRate = CurrentOrPendingPlaybackRate();
681 if (effectivePlaybackRate == 0) {
682 return aRv.ThrowInvalidStateError(
683 "Can't finish animation with zero playback rate");
685 if (effectivePlaybackRate > 0 && EffectEnd() == TimeDuration::Forever()) {
686 return aRv.ThrowInvalidStateError("Can't finish infinite animation");
689 AutoMutationBatchForAnimation mb(*this);
691 ApplyPendingPlaybackRate();
693 // Seek to the end
694 TimeDuration limit =
695 mPlaybackRate > 0 ? TimeDuration(EffectEnd()) : TimeDuration(0);
696 bool didChange = GetCurrentTimeAsDuration() != Nullable<TimeDuration>(limit);
697 SilentlySetCurrentTime(limit);
699 // If we are paused or play-pending we need to fill in the start time in
700 // order to transition to the finished state.
702 // We only do this, however, if we have an active timeline. If we have an
703 // inactive timeline we can't transition into the finished state just like
704 // we can't transition to the running state (this finished state is really
705 // a substate of the running state).
706 if (mStartTime.IsNull() && mTimeline &&
707 !mTimeline->GetCurrentTimeAsDuration().IsNull()) {
708 mStartTime = StartTimeFromTimelineTime(
709 mTimeline->GetCurrentTimeAsDuration().Value(), limit, mPlaybackRate);
710 didChange = true;
713 // If we just resolved the start time for a pause or play-pending
714 // animation, we need to clear the task. We don't do this as a branch of
715 // the above however since we can have a play-pending animation with a
716 // resolved start time if we aborted a pause operation.
717 if (!mStartTime.IsNull() && (mPendingState == PendingState::PlayPending ||
718 mPendingState == PendingState::PausePending)) {
719 if (mPendingState == PendingState::PausePending) {
720 mHoldTime.SetNull();
722 CancelPendingTasks();
723 didChange = true;
724 if (mReady) {
725 mReady->MaybeResolve(this);
728 UpdateTiming(SeekFlag::DidSeek, SyncNotifyFlag::Sync);
729 if (didChange && IsRelevant()) {
730 MutationObservers::NotifyAnimationChanged(this);
732 PostUpdate();
735 void Animation::Play(ErrorResult& aRv, LimitBehavior aLimitBehavior) {
736 PlayNoUpdate(aRv, aLimitBehavior);
737 PostUpdate();
740 // https://drafts.csswg.org/web-animations/#reverse-an-animation
741 void Animation::Reverse(ErrorResult& aRv) {
742 if (!mTimeline) {
743 return aRv.ThrowInvalidStateError(
744 "Can't reverse an animation with no associated timeline");
746 if (mTimeline->GetCurrentTimeAsDuration().IsNull()) {
747 return aRv.ThrowInvalidStateError(
748 "Can't reverse an animation associated with an inactive timeline");
751 double effectivePlaybackRate = CurrentOrPendingPlaybackRate();
753 if (effectivePlaybackRate == 0.0) {
754 return;
757 Maybe<double> originalPendingPlaybackRate = mPendingPlaybackRate;
759 mPendingPlaybackRate = Some(-effectivePlaybackRate);
761 Play(aRv, LimitBehavior::AutoRewind);
763 // If Play() threw, restore state and don't report anything to mutation
764 // observers.
765 if (aRv.Failed()) {
766 mPendingPlaybackRate = originalPendingPlaybackRate;
769 // Play(), above, unconditionally calls PostUpdate so we don't need to do
770 // it here.
773 void Animation::Persist() {
774 if (mReplaceState == AnimationReplaceState::Persisted) {
775 return;
778 bool wasRemoved = mReplaceState == AnimationReplaceState::Removed;
780 mReplaceState = AnimationReplaceState::Persisted;
782 // If the animation is not (yet) removed, there should be no side effects of
783 // persisting it.
784 if (wasRemoved) {
785 UpdateEffect(PostRestyleMode::IfNeeded);
786 PostUpdate();
790 DocGroup* Animation::GetDocGroup() {
791 Document* doc = GetRenderedDocument();
792 return doc ? doc->GetDocGroup() : nullptr;
795 // https://drafts.csswg.org/web-animations/#dom-animation-commitstyles
796 void Animation::CommitStyles(ErrorResult& aRv) {
797 if (!mEffect) {
798 return;
801 // Take an owning reference to the keyframe effect. This will ensure that
802 // this Animation and the target element remain alive after flushing style.
803 RefPtr<KeyframeEffect> keyframeEffect = mEffect->AsKeyframeEffect();
804 if (!keyframeEffect) {
805 return;
808 NonOwningAnimationTarget target = keyframeEffect->GetAnimationTarget();
809 if (!target) {
810 return;
813 if (target.mPseudoType != PseudoStyleType::NotPseudo) {
814 return aRv.ThrowNoModificationAllowedError(
815 "Can't commit styles of a pseudo-element");
818 // Check it is an element with a style attribute
819 RefPtr<nsStyledElement> styledElement =
820 nsStyledElement::FromNodeOrNull(target.mElement);
821 if (!styledElement) {
822 return aRv.ThrowNoModificationAllowedError(
823 "Target is not capable of having a style attribute");
826 // Hold onto a strong reference to the doc in case the flush destroys it.
827 RefPtr<Document> doc = target.mElement->GetComposedDoc();
829 // Flush frames before checking if the target element is rendered since the
830 // result could depend on pending style changes, and IsRendered() looks at the
831 // primary frame.
832 if (doc) {
833 doc->FlushPendingNotifications(FlushType::Frames);
835 if (!target.mElement->IsRendered()) {
836 return aRv.ThrowInvalidStateError("Target is not rendered");
838 nsPresContext* presContext =
839 nsContentUtils::GetContextForContent(target.mElement);
840 if (!presContext) {
841 return aRv.ThrowInvalidStateError("Target is not rendered");
844 // Get the computed animation values
845 UniquePtr<StyleAnimationValueMap> animationValues(
846 Servo_AnimationValueMap_Create());
847 if (!presContext->EffectCompositor()->ComposeServoAnimationRuleForEffect(
848 *keyframeEffect, CascadeLevel(), animationValues.get())) {
849 NS_WARNING("Failed to compose animation style to commit");
850 return;
853 // Calling SetCSSDeclaration will trigger attribute setting code.
854 // Start the update now so that the old rule doesn't get used
855 // between when we mutate the declaration and when we set the new
856 // rule.
857 mozAutoDocUpdate autoUpdate(target.mElement->OwnerDoc(), true);
859 // Get the inline style to append to
860 RefPtr<DeclarationBlock> declarationBlock;
861 if (auto* existing = target.mElement->GetInlineStyleDeclaration()) {
862 declarationBlock = existing->EnsureMutable();
863 } else {
864 declarationBlock = new DeclarationBlock();
865 declarationBlock->SetDirty();
868 // Prepare the callback
869 MutationClosureData closureData;
870 closureData.mShouldBeCalled = true;
871 closureData.mElement = target.mElement;
872 DeclarationBlockMutationClosure beforeChangeClosure = {
873 nsDOMCSSAttributeDeclaration::MutationClosureFunction,
874 &closureData,
877 // Set the animated styles
878 bool changed = false;
879 nsCSSPropertyIDSet properties = keyframeEffect->GetPropertySet();
880 for (nsCSSPropertyID property : properties) {
881 RefPtr<StyleAnimationValue> computedValue =
882 Servo_AnimationValueMap_GetValue(animationValues.get(), property)
883 .Consume();
884 if (computedValue) {
885 changed |= Servo_DeclarationBlock_SetPropertyToAnimationValue(
886 declarationBlock->Raw(), computedValue, beforeChangeClosure);
890 if (!changed) {
891 MOZ_ASSERT(!closureData.mWasCalled);
892 return;
895 MOZ_ASSERT(closureData.mWasCalled);
896 // Update inline style declaration
897 target.mElement->SetInlineStyleDeclaration(*declarationBlock, closureData);
900 // ---------------------------------------------------------------------------
902 // JS wrappers for Animation interface:
904 // ---------------------------------------------------------------------------
906 Nullable<double> Animation::GetStartTimeAsDouble() const {
907 return AnimationUtils::TimeDurationToDouble(mStartTime, mRTPCallerType);
910 void Animation::SetStartTimeAsDouble(const Nullable<double>& aStartTime) {
911 return SetStartTime(AnimationUtils::DoubleToTimeDuration(aStartTime));
914 Nullable<double> Animation::GetCurrentTimeAsDouble() const {
915 return AnimationUtils::TimeDurationToDouble(GetCurrentTimeAsDuration(),
916 mRTPCallerType);
919 void Animation::SetCurrentTimeAsDouble(const Nullable<double>& aCurrentTime,
920 ErrorResult& aRv) {
921 if (aCurrentTime.IsNull()) {
922 if (!GetCurrentTimeAsDuration().IsNull()) {
923 aRv.ThrowTypeError(
924 "Current time is resolved but trying to set it to an unresolved "
925 "time");
927 return;
930 return SetCurrentTime(TimeDuration::FromMilliseconds(aCurrentTime.Value()));
933 // ---------------------------------------------------------------------------
935 void Animation::Tick() {
936 // Finish pending if we have a pending ready time, but only if we also
937 // have an active timeline.
938 if (mPendingState != PendingState::NotPending &&
939 !mPendingReadyTime.IsNull() && mTimeline &&
940 !mTimeline->GetCurrentTimeAsDuration().IsNull()) {
941 // Even though mPendingReadyTime is initialized using TimeStamp::Now()
942 // during the *previous* tick of the refresh driver, it can still be
943 // ahead of the *current* timeline time when we are using the
944 // vsync timer so we need to clamp it to the timeline time.
945 TimeDuration currentTime = mTimeline->GetCurrentTimeAsDuration().Value();
946 if (currentTime < mPendingReadyTime.Value()) {
947 mPendingReadyTime.SetValue(currentTime);
949 FinishPendingAt(mPendingReadyTime.Value());
950 mPendingReadyTime.SetNull();
953 if (IsPossiblyOrphanedPendingAnimation()) {
954 MOZ_ASSERT(mTimeline && !mTimeline->GetCurrentTimeAsDuration().IsNull(),
955 "Orphaned pending animations should have an active timeline");
956 FinishPendingAt(mTimeline->GetCurrentTimeAsDuration().Value());
959 UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Sync);
961 // Check for changes to whether or not this animation is replaceable.
962 bool isReplaceable = IsReplaceable();
963 if (isReplaceable && !mWasReplaceableAtLastTick) {
964 ScheduleReplacementCheck();
966 mWasReplaceableAtLastTick = isReplaceable;
968 if (!mEffect) {
969 return;
972 // Update layers if we are newly finished.
973 KeyframeEffect* keyframeEffect = mEffect->AsKeyframeEffect();
974 if (keyframeEffect && !keyframeEffect->Properties().IsEmpty() &&
975 !mFinishedAtLastComposeStyle &&
976 PlayState() == AnimationPlayState::Finished) {
977 PostUpdate();
981 void Animation::TriggerOnNextTick(const Nullable<TimeDuration>& aReadyTime) {
982 // Normally we expect the play state to be pending but it's possible that,
983 // due to the handling of possibly orphaned animations in Tick(), this
984 // animation got started whilst still being in another document's pending
985 // animation map.
986 if (!Pending()) {
987 return;
990 // If aReadyTime.IsNull() we'll detect this in Tick() where we check for
991 // orphaned animations and trigger this animation anyway
992 mPendingReadyTime = aReadyTime;
995 void Animation::TriggerNow() {
996 // Normally we expect the play state to be pending but when an animation
997 // is cancelled and its rendered document can't be reached, we can end up
998 // with the animation still in a pending player tracker even after it is
999 // no longer pending.
1000 if (!Pending()) {
1001 return;
1004 // If we don't have an active timeline we can't trigger the animation.
1005 // However, this is a test-only method that we don't expect to be used in
1006 // conjunction with animations without an active timeline so generate
1007 // a warning if we do find ourselves in that situation.
1008 if (!mTimeline || mTimeline->GetCurrentTimeAsDuration().IsNull()) {
1009 NS_WARNING("Failed to trigger an animation with an active timeline");
1010 return;
1013 FinishPendingAt(mTimeline->GetCurrentTimeAsDuration().Value());
1016 bool Animation::TryTriggerNowForFiniteTimeline() {
1017 // Normally we expect the play state to be pending but when an animation
1018 // is cancelled and its rendered document can't be reached, we can end up
1019 // with the animation still in a pending player tracker even after it is
1020 // no longer pending.
1021 if (!Pending()) {
1022 return true;
1025 MOZ_ASSERT(mTimeline && !mTimeline->IsMonotonicallyIncreasing());
1027 // It's possible that the primary frame or the scrollable frame is not ready
1028 // when setting up this animation. So we don't finish pending right now. In
1029 // this case, the timeline is inactive so it is still pending. The caller
1030 // should handle this case by trying this later once the scrollable frame is
1031 // ready.
1032 const auto currentTime = mTimeline->GetCurrentTimeAsDuration();
1033 if (currentTime.IsNull()) {
1034 return false;
1037 FinishPendingAt(currentTime.Value());
1038 return true;
1041 Nullable<TimeDuration> Animation::GetCurrentOrPendingStartTime() const {
1042 Nullable<TimeDuration> result;
1044 // If we have a pending playback rate, work out what start time we will use
1045 // when we come to updating that playback rate.
1047 // This logic roughly shadows that in ResumeAt but is just different enough
1048 // that it is difficult to extract out the common functionality (and
1049 // extracting that functionality out would make it harder to match ResumeAt up
1050 // against the spec).
1051 if (mPendingPlaybackRate && !mPendingReadyTime.IsNull() &&
1052 !mStartTime.IsNull()) {
1053 // If we have a hold time, use it as the current time to match.
1054 TimeDuration currentTimeToMatch =
1055 !mHoldTime.IsNull()
1056 ? mHoldTime.Value()
1057 : CurrentTimeFromTimelineTime(mPendingReadyTime.Value(),
1058 mStartTime.Value(), mPlaybackRate);
1060 result = StartTimeFromTimelineTime(
1061 mPendingReadyTime.Value(), currentTimeToMatch, *mPendingPlaybackRate);
1062 return result;
1065 if (!mStartTime.IsNull()) {
1066 result = mStartTime;
1067 return result;
1070 if (mPendingReadyTime.IsNull() || mHoldTime.IsNull()) {
1071 return result;
1074 // Calculate the equivalent start time from the pending ready time.
1075 result = StartTimeFromTimelineTime(mPendingReadyTime.Value(),
1076 mHoldTime.Value(), mPlaybackRate);
1078 return result;
1081 TimeStamp Animation::AnimationTimeToTimeStamp(
1082 const StickyTimeDuration& aTime) const {
1083 // Initializes to null. Return the same object every time to benefit from
1084 // return-value-optimization.
1085 TimeStamp result;
1087 // We *don't* check for mTimeline->TracksWallclockTime() here because that
1088 // method only tells us if the timeline times can be converted to
1089 // TimeStamps that can be compared to TimeStamp::Now() or not, *not*
1090 // whether the timelines can be converted to TimeStamp values at all.
1092 // Furthermore, we want to be able to use this method when the refresh driver
1093 // is under test control (in which case TracksWallclockTime() will return
1094 // false).
1096 // Once we introduce timelines that are not time-based we will need to
1097 // differentiate between them here and determine how to sort their events.
1098 if (!mTimeline) {
1099 return result;
1102 // Check the time is convertible to a timestamp
1103 if (aTime == TimeDuration::Forever() || mPlaybackRate == 0.0 ||
1104 mStartTime.IsNull()) {
1105 return result;
1108 // Invert the standard relation:
1109 // current time = (timeline time - start time) * playback rate
1110 TimeDuration timelineTime =
1111 TimeDuration(aTime).MultDouble(1.0 / mPlaybackRate) + mStartTime.Value();
1113 result = mTimeline->ToTimeStamp(timelineTime);
1114 return result;
1117 TimeStamp Animation::ElapsedTimeToTimeStamp(
1118 const StickyTimeDuration& aElapsedTime) const {
1119 TimeDuration delay =
1120 mEffect ? mEffect->NormalizedTiming().Delay() : TimeDuration();
1121 return AnimationTimeToTimeStamp(aElapsedTime + delay);
1124 // https://drafts.csswg.org/web-animations/#silently-set-the-current-time
1125 void Animation::SilentlySetCurrentTime(const TimeDuration& aSeekTime) {
1126 // TODO: Bug 1762238: Introduce "valid seek time" after introducing
1127 // CSSNumberish time values.
1128 // https://drafts.csswg.org/web-animations-2/#silently-set-the-current-time
1130 if (!mHoldTime.IsNull() || mStartTime.IsNull() || !mTimeline ||
1131 mTimeline->GetCurrentTimeAsDuration().IsNull() || mPlaybackRate == 0.0) {
1132 mHoldTime.SetValue(aSeekTime);
1133 } else {
1134 mStartTime =
1135 StartTimeFromTimelineTime(mTimeline->GetCurrentTimeAsDuration().Value(),
1136 aSeekTime, mPlaybackRate);
1139 if (!mTimeline || mTimeline->GetCurrentTimeAsDuration().IsNull()) {
1140 mStartTime.SetNull();
1143 mPreviousCurrentTime.SetNull();
1144 mResetCurrentTimeOnResume = false;
1147 bool Animation::ShouldBeSynchronizedWithMainThread(
1148 const nsCSSPropertyIDSet& aPropertySet, const nsIFrame* aFrame,
1149 AnimationPerformanceWarning::Type& aPerformanceWarning) const {
1150 // Only synchronize playing animations
1151 if (!IsPlaying()) {
1152 return false;
1155 // Currently only transform animations need to be synchronized
1156 if (!aPropertySet.Intersects(nsCSSPropertyIDSet::TransformLikeProperties())) {
1157 return false;
1160 KeyframeEffect* keyframeEffect =
1161 mEffect ? mEffect->AsKeyframeEffect() : nullptr;
1162 if (!keyframeEffect) {
1163 return false;
1166 // Are we starting at the same time as other geometric animations?
1167 // We check this before calling ShouldBlockAsyncTransformAnimations, partly
1168 // because it's cheaper, but also because it's often the most useful thing
1169 // to know when you're debugging performance.
1170 // Note: |mSyncWithGeometricAnimations| wouldn't be set if the geometric
1171 // animations use scroll-timeline.
1172 if (StaticPrefs::
1173 dom_animations_mainthread_synchronization_with_geometric_animations() &&
1174 mSyncWithGeometricAnimations &&
1175 keyframeEffect->HasAnimationOfPropertySet(
1176 nsCSSPropertyIDSet::TransformLikeProperties())) {
1177 aPerformanceWarning =
1178 AnimationPerformanceWarning::Type::TransformWithSyncGeometricAnimations;
1179 return true;
1182 return keyframeEffect->ShouldBlockAsyncTransformAnimations(
1183 aFrame, aPropertySet, aPerformanceWarning);
1186 void Animation::UpdateRelevance() {
1187 bool wasRelevant = mIsRelevant;
1188 mIsRelevant = mReplaceState != AnimationReplaceState::Removed &&
1189 (HasCurrentEffect() || IsInEffect());
1191 // Notify animation observers.
1192 if (wasRelevant && !mIsRelevant) {
1193 MutationObservers::NotifyAnimationRemoved(this);
1194 } else if (!wasRelevant && mIsRelevant) {
1195 MutationObservers::NotifyAnimationAdded(this);
1199 template <class T>
1200 bool IsMarkupAnimation(T* aAnimation) {
1201 return aAnimation && aAnimation->IsTiedToMarkup();
1204 // https://drafts.csswg.org/web-animations/#replaceable-animation
1205 bool Animation::IsReplaceable() const {
1206 // We never replace CSS animations or CSS transitions since they are managed
1207 // by CSS.
1208 if (IsMarkupAnimation(AsCSSAnimation()) ||
1209 IsMarkupAnimation(AsCSSTransition())) {
1210 return false;
1213 // Only finished animations can be replaced.
1214 if (PlayState() != AnimationPlayState::Finished) {
1215 return false;
1218 // Already removed animations cannot be replaced.
1219 if (ReplaceState() == AnimationReplaceState::Removed) {
1220 return false;
1223 // We can only replace an animation if we know that, uninterfered, it would
1224 // never start playing again. That excludes any animations on timelines that
1225 // aren't monotonically increasing.
1227 // If we don't have any timeline at all, then we can't be in the finished
1228 // state (since we need both a resolved start time and current time for that)
1229 // and will have already returned false above.
1231 // (However, if it ever does become possible to be finished without a timeline
1232 // then we will want to return false here since it probably suggests an
1233 // animation being driven directly by script, in which case we can't assume
1234 // anything about how they will behave.)
1235 if (!GetTimeline() || !GetTimeline()->TracksWallclockTime()) {
1236 return false;
1239 // If the animation doesn't have an effect then we can't determine if it is
1240 // filling or not so just leave it alone.
1241 if (!GetEffect()) {
1242 return false;
1245 // At the time of writing we only know about KeyframeEffects. If we introduce
1246 // other types of effects we will need to decide if they are replaceable or
1247 // not.
1248 MOZ_ASSERT(GetEffect()->AsKeyframeEffect(),
1249 "Effect should be a keyframe effect");
1251 // We only replace animations that are filling.
1252 if (GetEffect()->GetComputedTiming().mProgress.IsNull()) {
1253 return false;
1256 // We should only replace animations with a target element (since otherwise
1257 // what other effects would we consider when determining if they are covered
1258 // or not?).
1259 if (!GetEffect()->AsKeyframeEffect()->GetAnimationTarget()) {
1260 return false;
1263 return true;
1266 bool Animation::IsRemovable() const {
1267 return ReplaceState() == AnimationReplaceState::Active && IsReplaceable();
1270 void Animation::ScheduleReplacementCheck() {
1271 MOZ_ASSERT(
1272 IsReplaceable(),
1273 "Should only schedule a replacement check for a replaceable animation");
1275 // If IsReplaceable() is true, the following should also hold
1276 MOZ_ASSERT(GetEffect());
1277 MOZ_ASSERT(GetEffect()->AsKeyframeEffect());
1279 NonOwningAnimationTarget target =
1280 GetEffect()->AsKeyframeEffect()->GetAnimationTarget();
1282 MOZ_ASSERT(target);
1284 nsPresContext* presContext =
1285 nsContentUtils::GetContextForContent(target.mElement);
1286 if (presContext) {
1287 presContext->EffectCompositor()->NoteElementForReducing(target);
1291 void Animation::MaybeScheduleReplacementCheck() {
1292 if (!IsReplaceable()) {
1293 return;
1296 ScheduleReplacementCheck();
1299 void Animation::Remove() {
1300 MOZ_ASSERT(IsRemovable(),
1301 "Should not be trying to remove an effect that is not removable");
1303 mReplaceState = AnimationReplaceState::Removed;
1305 UpdateEffect(PostRestyleMode::IfNeeded);
1306 PostUpdate();
1308 QueuePlaybackEvent(u"remove"_ns, GetTimelineCurrentTimeAsTimeStamp());
1311 bool Animation::HasLowerCompositeOrderThan(const Animation& aOther) const {
1312 // 0. Object-equality case
1313 if (&aOther == this) {
1314 return false;
1317 // 1. CSS Transitions sort lowest
1319 auto asCSSTransitionForSorting =
1320 [](const Animation& anim) -> const CSSTransition* {
1321 const CSSTransition* transition = anim.AsCSSTransition();
1322 return transition && transition->IsTiedToMarkup() ? transition : nullptr;
1324 auto thisTransition = asCSSTransitionForSorting(*this);
1325 auto otherTransition = asCSSTransitionForSorting(aOther);
1326 if (thisTransition && otherTransition) {
1327 return thisTransition->HasLowerCompositeOrderThan(*otherTransition);
1329 if (thisTransition || otherTransition) {
1330 // Cancelled transitions no longer have an owning element. To be strictly
1331 // correct we should store a strong reference to the owning element
1332 // so that if we arrive here while sorting cancel events, we can sort
1333 // them in the correct order.
1335 // However, given that cancel events are almost always queued
1336 // synchronously in some deterministic manner, we can be fairly sure
1337 // that cancel events will be dispatched in a deterministic order
1338 // (which is our only hard requirement until specs say otherwise).
1339 // Furthermore, we only reach here when we have events with equal
1340 // timestamps so this is an edge case we can probably ignore for now.
1341 return thisTransition;
1345 // 2. CSS Animations sort next
1347 auto asCSSAnimationForSorting =
1348 [](const Animation& anim) -> const CSSAnimation* {
1349 const CSSAnimation* animation = anim.AsCSSAnimation();
1350 return animation && animation->IsTiedToMarkup() ? animation : nullptr;
1352 auto thisAnimation = asCSSAnimationForSorting(*this);
1353 auto otherAnimation = asCSSAnimationForSorting(aOther);
1354 if (thisAnimation && otherAnimation) {
1355 return thisAnimation->HasLowerCompositeOrderThan(*otherAnimation);
1357 if (thisAnimation || otherAnimation) {
1358 return thisAnimation;
1362 // Subclasses of Animation repurpose mAnimationIndex to implement their
1363 // own brand of composite ordering. However, by this point we should have
1364 // handled any such custom composite ordering so we should now have unique
1365 // animation indices.
1366 MOZ_ASSERT(mAnimationIndex != aOther.mAnimationIndex,
1367 "Animation indices should be unique");
1369 // 3. Finally, generic animations sort by their position in the global
1370 // animation array.
1371 return mAnimationIndex < aOther.mAnimationIndex;
1374 void Animation::WillComposeStyle() {
1375 mFinishedAtLastComposeStyle = (PlayState() == AnimationPlayState::Finished);
1377 MOZ_ASSERT(mEffect);
1379 KeyframeEffect* keyframeEffect = mEffect->AsKeyframeEffect();
1380 if (keyframeEffect) {
1381 keyframeEffect->WillComposeStyle();
1385 void Animation::ComposeStyle(StyleAnimationValueMap& aComposeResult,
1386 const nsCSSPropertyIDSet& aPropertiesToSkip) {
1387 if (!mEffect) {
1388 return;
1391 // In order to prevent flicker, there are a few cases where we want to use
1392 // a different time for rendering that would otherwise be returned by
1393 // GetCurrentTimeAsDuration. These are:
1395 // (a) For animations that are pausing but which are still running on the
1396 // compositor. In this case we send a layer transaction that removes the
1397 // animation but which also contains the animation values calculated on
1398 // the main thread. To prevent flicker when this occurs we want to ensure
1399 // the timeline time used to calculate the main thread animation values
1400 // does not lag far behind the time used on the compositor. Ideally we
1401 // would like to use the "animation ready time" calculated at the end of
1402 // the layer transaction as the timeline time but it will be too late to
1403 // update the style rule at that point so instead we just use the current
1404 // wallclock time.
1406 // (b) For animations that are pausing that we have already taken off the
1407 // compositor. In this case we record a pending ready time but we don't
1408 // apply it until the next tick. However, while waiting for the next tick,
1409 // we should still use the pending ready time as the timeline time. If we
1410 // use the regular timeline time the animation may appear jump backwards
1411 // if the main thread's timeline time lags behind the compositor.
1413 // (c) For animations that are play-pending due to an aborted pause operation
1414 // (i.e. a pause operation that was interrupted before we entered the
1415 // paused state). When we cancel a pending pause we might momentarily take
1416 // the animation off the compositor, only to re-add it moments later. In
1417 // that case the compositor might have been ahead of the main thread so we
1418 // should use the current wallclock time to ensure the animation doesn't
1419 // temporarily jump backwards.
1421 // To address each of these cases we temporarily tweak the hold time
1422 // immediately before updating the style rule and then restore it immediately
1423 // afterwards. This is purely to prevent visual flicker. Other behavior
1424 // such as dispatching events continues to rely on the regular timeline time.
1425 bool pending = Pending();
1427 AutoRestore<Nullable<TimeDuration>> restoreHoldTime(mHoldTime);
1429 if (pending && mHoldTime.IsNull() && !mStartTime.IsNull()) {
1430 Nullable<TimeDuration> timeToUse = mPendingReadyTime;
1431 if (timeToUse.IsNull() && mTimeline && mTimeline->TracksWallclockTime()) {
1432 timeToUse = mTimeline->ToTimelineTime(TimeStamp::Now());
1434 if (!timeToUse.IsNull()) {
1435 mHoldTime = CurrentTimeFromTimelineTime(
1436 timeToUse.Value(), mStartTime.Value(), mPlaybackRate);
1440 KeyframeEffect* keyframeEffect = mEffect->AsKeyframeEffect();
1441 if (keyframeEffect) {
1442 keyframeEffect->ComposeStyle(aComposeResult, aPropertiesToSkip);
1446 MOZ_ASSERT(
1447 pending == Pending(),
1448 "Pending state should not change during the course of compositing");
1451 void Animation::NotifyEffectTimingUpdated() {
1452 MOZ_ASSERT(mEffect,
1453 "We should only update effect timing when we have a target "
1454 "effect");
1455 UpdateTiming(Animation::SeekFlag::NoSeek, Animation::SyncNotifyFlag::Async);
1458 void Animation::NotifyEffectPropertiesUpdated() {
1459 MOZ_ASSERT(mEffect,
1460 "We should only update effect properties when we have a target "
1461 "effect");
1463 MaybeScheduleReplacementCheck();
1466 void Animation::NotifyEffectTargetUpdated() {
1467 MOZ_ASSERT(mEffect,
1468 "We should only update the effect target when we have a target "
1469 "effect");
1471 MaybeScheduleReplacementCheck();
1474 void Animation::NotifyGeometricAnimationsStartingThisFrame() {
1475 if (!IsNewlyStarted() || !mEffect) {
1476 return;
1479 mSyncWithGeometricAnimations = true;
1482 // https://drafts.csswg.org/web-animations/#play-an-animation
1483 void Animation::PlayNoUpdate(ErrorResult& aRv, LimitBehavior aLimitBehavior) {
1484 AutoMutationBatchForAnimation mb(*this);
1486 const bool isAutoRewind = aLimitBehavior == LimitBehavior::AutoRewind;
1487 const bool abortedPause = mPendingState == PendingState::PausePending;
1488 double effectivePlaybackRate = CurrentOrPendingPlaybackRate();
1490 Nullable<TimeDuration> currentTime = GetCurrentTimeAsDuration();
1491 if (mResetCurrentTimeOnResume) {
1492 currentTime.SetNull();
1493 mResetCurrentTimeOnResume = false;
1496 Nullable<TimeDuration> seekTime;
1497 if (isAutoRewind) {
1498 if (effectivePlaybackRate >= 0.0 &&
1499 (currentTime.IsNull() || currentTime.Value() < TimeDuration() ||
1500 currentTime.Value() >= EffectEnd())) {
1501 seekTime.SetValue(TimeDuration());
1502 } else if (effectivePlaybackRate < 0.0 &&
1503 (currentTime.IsNull() || currentTime.Value() <= TimeDuration() ||
1504 currentTime.Value() > EffectEnd())) {
1505 if (EffectEnd() == TimeDuration::Forever()) {
1506 return aRv.ThrowInvalidStateError(
1507 "Can't rewind animation with infinite effect end");
1509 seekTime.SetValue(TimeDuration(EffectEnd()));
1513 if (seekTime.IsNull() && mStartTime.IsNull() && currentTime.IsNull()) {
1514 seekTime.SetValue(TimeDuration());
1517 if (!seekTime.IsNull()) {
1518 if (HasFiniteTimeline()) {
1519 mStartTime = seekTime;
1520 mHoldTime.SetNull();
1521 ApplyPendingPlaybackRate();
1522 } else {
1523 mHoldTime = seekTime;
1527 bool reuseReadyPromise = false;
1528 if (mPendingState != PendingState::NotPending) {
1529 CancelPendingTasks();
1530 reuseReadyPromise = true;
1533 // If the hold time is null then we're already playing normally and,
1534 // typically, we can bail out here.
1536 // However, there are two cases where we can't do that:
1538 // (a) If we just aborted a pause. In this case, for consistency, we need to
1539 // go through the motions of doing an asynchronous start.
1541 // (b) If we have timing changes (specifically a change to the playbackRate)
1542 // that should be applied asynchronously.
1544 if (mHoldTime.IsNull() && seekTime.IsNull() && !abortedPause &&
1545 !mPendingPlaybackRate) {
1546 return;
1549 // Clear the start time until we resolve a new one. We do this except
1550 // for the case where we are aborting a pause and don't have a hold time.
1552 // If we're aborting a pause and *do* have a hold time (e.g. because
1553 // the animation is finished or we just applied the auto-rewind behavior
1554 // above) we should respect it by clearing the start time. If we *don't*
1555 // have a hold time we should keep the current start time so that the
1556 // the animation continues moving uninterrupted by the aborted pause.
1558 // (If we're not aborting a pause, mHoldTime must be resolved by now
1559 // or else we would have returned above.)
1560 if (!mHoldTime.IsNull()) {
1561 mStartTime.SetNull();
1564 if (!reuseReadyPromise) {
1565 // Clear ready promise. We'll create a new one lazily.
1566 mReady = nullptr;
1569 mPendingState = PendingState::PlayPending;
1571 // Clear flag that causes us to sync transform animations with the main
1572 // thread for now. We'll set this when we go to set up compositor
1573 // animations if it applies.
1574 mSyncWithGeometricAnimations = false;
1576 if (HasFiniteTimeline()) {
1577 // Always schedule a task even if we would like to let this animation
1578 // immedidately ready, per spec.
1579 // https://drafts.csswg.org/web-animations/#playing-an-animation-section
1580 if (Document* doc = GetRenderedDocument()) {
1581 doc->GetOrCreateScrollTimelineAnimationTracker()->AddPending(*this);
1582 } // else: we fail to track this animation, so let the scroll frame to
1583 // trigger it when ticking.
1584 } else {
1585 if (Document* doc = GetRenderedDocument()) {
1586 PendingAnimationTracker* tracker =
1587 doc->GetOrCreatePendingAnimationTracker();
1588 tracker->AddPlayPending(*this);
1589 } else {
1590 TriggerOnNextTick(Nullable<TimeDuration>());
1594 UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
1595 if (IsRelevant()) {
1596 MutationObservers::NotifyAnimationChanged(this);
1600 // https://drafts.csswg.org/web-animations/#pause-an-animation
1601 void Animation::Pause(ErrorResult& aRv) {
1602 if (IsPausedOrPausing()) {
1603 return;
1606 AutoMutationBatchForAnimation mb(*this);
1608 Nullable<TimeDuration> seekTime;
1609 // If we are transitioning from idle, fill in the current time
1610 if (GetCurrentTimeAsDuration().IsNull()) {
1611 if (mPlaybackRate >= 0.0) {
1612 seekTime.SetValue(TimeDuration(0));
1613 } else {
1614 if (EffectEnd() == TimeDuration::Forever()) {
1615 return aRv.ThrowInvalidStateError("Can't seek to infinite effect end");
1617 seekTime.SetValue(TimeDuration(EffectEnd()));
1621 if (!seekTime.IsNull()) {
1622 if (HasFiniteTimeline()) {
1623 mStartTime = seekTime;
1624 } else {
1625 mHoldTime = seekTime;
1629 bool reuseReadyPromise = false;
1630 if (mPendingState == PendingState::PlayPending) {
1631 CancelPendingTasks();
1632 reuseReadyPromise = true;
1635 if (!reuseReadyPromise) {
1636 // Clear ready promise. We'll create a new one lazily.
1637 mReady = nullptr;
1640 mPendingState = PendingState::PausePending;
1642 if (HasFiniteTimeline()) {
1643 // Always schedule a task even if we would like to let this animation
1644 // immedidately ready, per spec.
1645 // https://drafts.csswg.org/web-animations/#playing-an-animation-section
1646 if (Document* doc = GetRenderedDocument()) {
1647 doc->GetOrCreateScrollTimelineAnimationTracker()->AddPending(*this);
1648 } // else: we fail to track this animation, so let the scroll frame to
1649 // trigger it when ticking.
1650 } else {
1651 if (Document* doc = GetRenderedDocument()) {
1652 PendingAnimationTracker* tracker =
1653 doc->GetOrCreatePendingAnimationTracker();
1654 tracker->AddPausePending(*this);
1655 } else {
1656 TriggerOnNextTick(Nullable<TimeDuration>());
1660 UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
1661 if (IsRelevant()) {
1662 MutationObservers::NotifyAnimationChanged(this);
1665 PostUpdate();
1668 // https://drafts.csswg.org/web-animations/#play-an-animation
1669 void Animation::ResumeAt(const TimeDuration& aReadyTime) {
1670 // This method is only expected to be called for an animation that is
1671 // waiting to play. We can easily adapt it to handle other states
1672 // but it's currently not necessary.
1673 MOZ_ASSERT(mPendingState == PendingState::PlayPending,
1674 "Expected to resume a play-pending animation");
1675 MOZ_ASSERT(!mHoldTime.IsNull() || !mStartTime.IsNull(),
1676 "An animation in the play-pending state should have either a"
1677 " resolved hold time or resolved start time");
1679 AutoMutationBatchForAnimation mb(*this);
1680 bool hadPendingPlaybackRate = mPendingPlaybackRate.isSome();
1682 if (!mHoldTime.IsNull()) {
1683 // The hold time is set, so we don't need any special handling to preserve
1684 // the current time.
1685 ApplyPendingPlaybackRate();
1686 mStartTime =
1687 StartTimeFromTimelineTime(aReadyTime, mHoldTime.Value(), mPlaybackRate);
1688 if (mPlaybackRate != 0) {
1689 mHoldTime.SetNull();
1691 } else if (!mStartTime.IsNull() && mPendingPlaybackRate) {
1692 // Apply any pending playback rate, preserving the current time.
1693 TimeDuration currentTimeToMatch = CurrentTimeFromTimelineTime(
1694 aReadyTime, mStartTime.Value(), mPlaybackRate);
1695 ApplyPendingPlaybackRate();
1696 mStartTime = StartTimeFromTimelineTime(aReadyTime, currentTimeToMatch,
1697 mPlaybackRate);
1698 if (mPlaybackRate == 0) {
1699 mHoldTime.SetValue(currentTimeToMatch);
1703 mPendingState = PendingState::NotPending;
1705 UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Sync);
1707 // If we had a pending playback rate, we will have now applied it so we need
1708 // to notify observers.
1709 if (hadPendingPlaybackRate && IsRelevant()) {
1710 MutationObservers::NotifyAnimationChanged(this);
1713 if (mReady) {
1714 mReady->MaybeResolve(this);
1718 void Animation::PauseAt(const TimeDuration& aReadyTime) {
1719 MOZ_ASSERT(mPendingState == PendingState::PausePending,
1720 "Expected to pause a pause-pending animation");
1722 if (!mStartTime.IsNull() && mHoldTime.IsNull()) {
1723 mHoldTime = CurrentTimeFromTimelineTime(aReadyTime, mStartTime.Value(),
1724 mPlaybackRate);
1726 ApplyPendingPlaybackRate();
1727 mStartTime.SetNull();
1728 mPendingState = PendingState::NotPending;
1730 UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
1732 if (mReady) {
1733 mReady->MaybeResolve(this);
1737 void Animation::UpdateTiming(SeekFlag aSeekFlag,
1738 SyncNotifyFlag aSyncNotifyFlag) {
1739 // We call UpdateFinishedState before UpdateEffect because the former
1740 // can change the current time, which is used by the latter.
1741 UpdateFinishedState(aSeekFlag, aSyncNotifyFlag);
1742 UpdateEffect(PostRestyleMode::IfNeeded);
1744 if (mTimeline) {
1745 mTimeline->NotifyAnimationUpdated(*this);
1749 // https://drafts.csswg.org/web-animations/#update-an-animations-finished-state
1750 void Animation::UpdateFinishedState(SeekFlag aSeekFlag,
1751 SyncNotifyFlag aSyncNotifyFlag) {
1752 Nullable<TimeDuration> unconstrainedCurrentTime =
1753 aSeekFlag == SeekFlag::NoSeek ? GetUnconstrainedCurrentTime()
1754 : GetCurrentTimeAsDuration();
1755 TimeDuration effectEnd = TimeDuration(EffectEnd());
1757 if (!unconstrainedCurrentTime.IsNull() && !mStartTime.IsNull() &&
1758 mPendingState == PendingState::NotPending) {
1759 if (mPlaybackRate > 0.0 && unconstrainedCurrentTime.Value() >= effectEnd) {
1760 if (aSeekFlag == SeekFlag::DidSeek) {
1761 mHoldTime = unconstrainedCurrentTime;
1762 } else if (!mPreviousCurrentTime.IsNull()) {
1763 mHoldTime.SetValue(std::max(mPreviousCurrentTime.Value(), effectEnd));
1764 } else {
1765 mHoldTime.SetValue(effectEnd);
1767 } else if (mPlaybackRate < 0.0 &&
1768 unconstrainedCurrentTime.Value() <= TimeDuration()) {
1769 if (aSeekFlag == SeekFlag::DidSeek) {
1770 mHoldTime = unconstrainedCurrentTime;
1771 } else if (!mPreviousCurrentTime.IsNull()) {
1772 mHoldTime.SetValue(
1773 std::min(mPreviousCurrentTime.Value(), TimeDuration(0)));
1774 } else {
1775 mHoldTime.SetValue(0);
1777 } else if (mPlaybackRate != 0.0 && mTimeline &&
1778 !mTimeline->GetCurrentTimeAsDuration().IsNull()) {
1779 if (aSeekFlag == SeekFlag::DidSeek && !mHoldTime.IsNull()) {
1780 mStartTime = StartTimeFromTimelineTime(
1781 mTimeline->GetCurrentTimeAsDuration().Value(), mHoldTime.Value(),
1782 mPlaybackRate);
1784 mHoldTime.SetNull();
1788 // We must recalculate the current time to take account of any mHoldTime
1789 // changes the code above made.
1790 mPreviousCurrentTime = GetCurrentTimeAsDuration();
1792 bool currentFinishedState = PlayState() == AnimationPlayState::Finished;
1793 if (currentFinishedState && !mFinishedIsResolved) {
1794 DoFinishNotification(aSyncNotifyFlag);
1795 } else if (!currentFinishedState && mFinishedIsResolved) {
1796 ResetFinishedPromise();
1800 void Animation::UpdateEffect(PostRestyleMode aPostRestyle) {
1801 if (mEffect) {
1802 UpdateRelevance();
1804 KeyframeEffect* keyframeEffect = mEffect->AsKeyframeEffect();
1805 if (keyframeEffect) {
1806 keyframeEffect->NotifyAnimationTimingUpdated(aPostRestyle);
1811 void Animation::FlushUnanimatedStyle() const {
1812 if (Document* doc = GetRenderedDocument()) {
1813 doc->FlushPendingNotifications(
1814 ChangesToFlush(FlushType::Style, false /* flush animations */));
1818 void Animation::PostUpdate() {
1819 if (!mEffect) {
1820 return;
1823 KeyframeEffect* keyframeEffect = mEffect->AsKeyframeEffect();
1824 if (!keyframeEffect) {
1825 return;
1827 keyframeEffect->RequestRestyle(EffectCompositor::RestyleType::Layer);
1830 void Animation::CancelPendingTasks() {
1831 if (mPendingState == PendingState::NotPending) {
1832 return;
1835 if (Document* doc = GetRenderedDocument()) {
1836 PendingAnimationTracker* tracker = doc->GetPendingAnimationTracker();
1837 if (tracker) {
1838 if (mPendingState == PendingState::PlayPending) {
1839 tracker->RemovePlayPending(*this);
1840 } else {
1841 tracker->RemovePausePending(*this);
1846 mPendingState = PendingState::NotPending;
1847 mPendingReadyTime.SetNull();
1850 // https://drafts.csswg.org/web-animations/#reset-an-animations-pending-tasks
1851 void Animation::ResetPendingTasks() {
1852 if (mPendingState == PendingState::NotPending) {
1853 return;
1856 CancelPendingTasks();
1857 ApplyPendingPlaybackRate();
1859 if (mReady) {
1860 mReady->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
1861 MOZ_ALWAYS_TRUE(mReady->SetAnyPromiseIsHandled());
1862 mReady = nullptr;
1866 void Animation::ReschedulePendingTasks() {
1867 if (mPendingState == PendingState::NotPending) {
1868 return;
1871 mPendingReadyTime.SetNull();
1873 if (Document* doc = GetRenderedDocument()) {
1874 PendingAnimationTracker* tracker =
1875 doc->GetOrCreatePendingAnimationTracker();
1876 if (mPendingState == PendingState::PlayPending &&
1877 !tracker->IsWaitingToPlay(*this)) {
1878 tracker->AddPlayPending(*this);
1879 } else if (mPendingState == PendingState::PausePending &&
1880 !tracker->IsWaitingToPause(*this)) {
1881 tracker->AddPausePending(*this);
1886 // https://drafts.csswg.org/web-animations-2/#at-progress-timeline-boundary
1887 /* static*/ Animation::ProgressTimelinePosition
1888 Animation::AtProgressTimelineBoundary(
1889 const Nullable<TimeDuration>& aTimelineDuration,
1890 const Nullable<TimeDuration>& aCurrentTime,
1891 const TimeDuration& aEffectStartTime, const double aPlaybackRate) {
1892 // Based on changed defined in: https://github.com/w3c/csswg-drafts/pull/6702
1893 // 1. If any of the following conditions are true:
1894 // * the associated animation's timeline is not a progress-based timeline,
1895 // or
1896 // * the associated animation's timeline duration is unresolved or zero,
1897 // or
1898 // * the animation's playback rate is zero
1899 // return false
1900 // Note: We can detect a progress-based timeline by relying on the fact that
1901 // monotonic timelines (i.e. non-progress-based timelines) have an unresolved
1902 // timeline duration.
1903 if (aTimelineDuration.IsNull() || aTimelineDuration.Value().IsZero() ||
1904 aPlaybackRate == 0.0) {
1905 return ProgressTimelinePosition::NotBoundary;
1908 // 2. Let effective start time be the animation's start time if resolved, or
1909 // zero otherwise.
1910 const TimeDuration& effectiveStartTime = aEffectStartTime;
1912 // 3. Let effective timeline time be (animation's current time / animation's
1913 // playback rate) + effective start time.
1914 // Note: we use zero if the current time is unresolved. See the spec issue:
1915 // https://github.com/w3c/csswg-drafts/issues/7458
1916 const TimeDuration effectiveTimelineTime =
1917 (aCurrentTime.IsNull()
1918 ? TimeDuration()
1919 : aCurrentTime.Value().MultDouble(1.0 / aPlaybackRate)) +
1920 effectiveStartTime;
1922 // 4. Let effective timeline progress be (effective timeline time / timeline
1923 // duration)
1924 // 5. If effective timeline progress is 0 or 1, return true,
1925 // We avoid the division here but it is effectively the same as 4 & 5 above.
1926 return effectiveTimelineTime.IsZero() ||
1927 (AnimationUtils::IsWithinAnimationTimeTolerance(
1928 effectiveTimelineTime, aTimelineDuration.Value()))
1929 ? ProgressTimelinePosition::Boundary
1930 : ProgressTimelinePosition::NotBoundary;
1933 bool Animation::IsPossiblyOrphanedPendingAnimation() const {
1934 // Check if we are pending but might never start because we are not being
1935 // tracked.
1937 // This covers the following cases:
1939 // * We started playing but our effect's target element was orphaned
1940 // or bound to a different document.
1941 // (note that for the case of our effect changing we should handle
1942 // that in SetEffect)
1943 // * We started playing but our timeline became inactive.
1944 // In this case the pending animation tracker will drop us from its hashmap
1945 // when we have been painted.
1946 // * When we started playing we couldn't find a
1947 // PendingAnimationTracker/ScrollTimelineAnimationTracker to register with
1948 // (perhaps the effect had no document) so we may
1949 // 1. simply set mPendingState in PlayNoUpdate and relied on this method to
1950 // catch us on the next tick, or
1951 // 2. rely on the scroll frame to tick this animation and catch us in this
1952 // method.
1954 // If we're not pending we're ok.
1955 if (mPendingState == PendingState::NotPending) {
1956 return false;
1959 // If we have a pending ready time then we will be started on the next
1960 // tick.
1961 if (!mPendingReadyTime.IsNull()) {
1962 return false;
1965 // If we don't have an active timeline then we shouldn't start until
1966 // we do.
1967 if (!mTimeline || mTimeline->GetCurrentTimeAsDuration().IsNull()) {
1968 return false;
1971 // If we have no rendered document, or we're not in our rendered document's
1972 // PendingAnimationTracker then there's a good chance no one is tracking us.
1974 // If we're wrong and another document is tracking us then, at worst, we'll
1975 // simply start/pause the animation one tick too soon. That's better than
1976 // never starting/pausing the animation and is unlikely.
1977 Document* doc = GetRenderedDocument();
1978 if (!doc) {
1979 return true;
1982 PendingAnimationTracker* tracker = doc->GetPendingAnimationTracker();
1983 return !tracker || (!tracker->IsWaitingToPlay(*this) &&
1984 !tracker->IsWaitingToPause(*this));
1987 StickyTimeDuration Animation::EffectEnd() const {
1988 if (!mEffect) {
1989 return StickyTimeDuration(0);
1992 return mEffect->NormalizedTiming().EndTime();
1995 Document* Animation::GetRenderedDocument() const {
1996 if (!mEffect || !mEffect->AsKeyframeEffect()) {
1997 return nullptr;
2000 return mEffect->AsKeyframeEffect()->GetRenderedDocument();
2003 Document* Animation::GetTimelineDocument() const {
2004 return mTimeline ? mTimeline->GetDocument() : nullptr;
2007 void Animation::UpdatePendingAnimationTracker(AnimationTimeline* aOldTimeline,
2008 AnimationTimeline* aNewTimeline) {
2009 // If we are still in pending, we may have to move this animation into the
2010 // correct animation tracker.
2011 Document* doc = GetRenderedDocument();
2012 if (!doc || !Pending()) {
2013 return;
2016 const bool fromFiniteTimeline =
2017 aOldTimeline && !aOldTimeline->IsMonotonicallyIncreasing();
2018 const bool toFiniteTimeline =
2019 aNewTimeline && !aNewTimeline->IsMonotonicallyIncreasing();
2020 if (fromFiniteTimeline == toFiniteTimeline) {
2021 return;
2024 const bool isPlayPending = mPendingState == PendingState::PlayPending;
2025 if (toFiniteTimeline) {
2026 // From null/document-timeline to scroll-timeline
2027 if (auto* tracker = doc->GetPendingAnimationTracker()) {
2028 if (isPlayPending) {
2029 tracker->RemovePlayPending(*this);
2030 } else {
2031 tracker->RemovePausePending(*this);
2035 doc->GetOrCreateScrollTimelineAnimationTracker()->AddPending(*this);
2036 } else {
2037 // From scroll-timeline to null/document-timeline
2038 if (auto* tracker = doc->GetScrollTimelineAnimationTracker()) {
2039 tracker->RemovePending(*this);
2042 auto* tracker = doc->GetOrCreatePendingAnimationTracker();
2043 if (isPlayPending) {
2044 tracker->AddPlayPending(*this);
2045 } else {
2046 tracker->AddPausePending(*this);
2051 class AsyncFinishNotification : public MicroTaskRunnable {
2052 public:
2053 explicit AsyncFinishNotification(Animation* aAnimation)
2054 : MicroTaskRunnable(), mAnimation(aAnimation) {}
2056 virtual void Run(AutoSlowOperation& aAso) override {
2057 mAnimation->DoFinishNotificationImmediately(this);
2058 mAnimation = nullptr;
2061 virtual bool Suppressed() override {
2062 nsIGlobalObject* global = mAnimation->GetOwnerGlobal();
2063 return global && global->IsInSyncOperation();
2066 private:
2067 RefPtr<Animation> mAnimation;
2070 void Animation::DoFinishNotification(SyncNotifyFlag aSyncNotifyFlag) {
2071 CycleCollectedJSContext* context = CycleCollectedJSContext::Get();
2073 if (aSyncNotifyFlag == SyncNotifyFlag::Sync) {
2074 DoFinishNotificationImmediately();
2075 } else if (!mFinishNotificationTask) {
2076 RefPtr<MicroTaskRunnable> runnable = new AsyncFinishNotification(this);
2077 context->DispatchToMicroTask(do_AddRef(runnable));
2078 mFinishNotificationTask = std::move(runnable);
2082 void Animation::ResetFinishedPromise() {
2083 mFinishedIsResolved = false;
2084 mFinished = nullptr;
2087 void Animation::MaybeResolveFinishedPromise() {
2088 if (mFinished) {
2089 mFinished->MaybeResolve(this);
2091 mFinishedIsResolved = true;
2094 void Animation::DoFinishNotificationImmediately(MicroTaskRunnable* aAsync) {
2095 if (aAsync && aAsync != mFinishNotificationTask) {
2096 return;
2099 mFinishNotificationTask = nullptr;
2101 if (PlayState() != AnimationPlayState::Finished) {
2102 return;
2105 MaybeResolveFinishedPromise();
2107 QueuePlaybackEvent(u"finish"_ns, AnimationTimeToTimeStamp(EffectEnd()));
2110 void Animation::QueuePlaybackEvent(const nsAString& aName,
2111 TimeStamp&& aScheduledEventTime) {
2112 // Use document for timing.
2113 // https://drafts.csswg.org/web-animations-1/#document-for-timing
2114 Document* doc = GetTimelineDocument();
2115 if (!doc) {
2116 return;
2119 nsPresContext* presContext = doc->GetPresContext();
2120 if (!presContext) {
2121 return;
2124 AnimationPlaybackEventInit init;
2125 if (aName.EqualsLiteral("finish") || aName.EqualsLiteral("remove")) {
2126 init.mCurrentTime = GetCurrentTimeAsDouble();
2128 if (mTimeline) {
2129 init.mTimelineTime = mTimeline->GetCurrentTimeAsDouble();
2132 RefPtr<AnimationPlaybackEvent> event =
2133 AnimationPlaybackEvent::Constructor(this, aName, init);
2134 event->SetTrusted(true);
2136 presContext->AnimationEventDispatcher()->QueueEvent(AnimationEventInfo(
2137 std::move(event), std::move(aScheduledEventTime), this));
2140 bool Animation::IsRunningOnCompositor() const {
2141 return mEffect && mEffect->AsKeyframeEffect() &&
2142 mEffect->AsKeyframeEffect()->IsRunningOnCompositor();
2145 bool Animation::HasCurrentEffect() const {
2146 return GetEffect() && GetEffect()->IsCurrent();
2149 bool Animation::IsInEffect() const {
2150 return GetEffect() && GetEffect()->IsInEffect();
2153 void Animation::SetHiddenByContentVisibility(bool hidden) {
2154 if (mHiddenByContentVisibility == hidden) {
2155 return;
2158 mHiddenByContentVisibility = hidden;
2160 if (!GetTimeline()) {
2161 return;
2164 GetTimeline()->NotifyAnimationContentVisibilityChanged(this, !hidden);
2167 StickyTimeDuration Animation::IntervalStartTime(
2168 const StickyTimeDuration& aActiveDuration) const {
2169 MOZ_ASSERT(AsCSSTransition() || AsCSSAnimation(),
2170 "Should be called for CSS animations or transitions");
2171 static constexpr StickyTimeDuration zeroDuration = StickyTimeDuration();
2172 return std::max(
2173 std::min(StickyTimeDuration(-mEffect->NormalizedTiming().Delay()),
2174 aActiveDuration),
2175 zeroDuration);
2178 // Later side of the elapsed time range reported in CSS Animations and CSS
2179 // Transitions events.
2181 // https://drafts.csswg.org/css-animations-2/#interval-end
2182 // https://drafts.csswg.org/css-transitions-2/#interval-end
2183 StickyTimeDuration Animation::IntervalEndTime(
2184 const StickyTimeDuration& aActiveDuration) const {
2185 MOZ_ASSERT(AsCSSTransition() || AsCSSAnimation(),
2186 "Should be called for CSS animations or transitions");
2188 static constexpr StickyTimeDuration zeroDuration = StickyTimeDuration();
2189 const StickyTimeDuration& effectEnd = EffectEnd();
2191 // If both "associated effect end" and "start delay" are Infinity, we skip it
2192 // because we will get NaN when computing "Infinity - Infinity", and
2193 // using NaN in std::min or std::max is undefined.
2194 if (MOZ_UNLIKELY(effectEnd == TimeDuration::Forever() &&
2195 effectEnd == mEffect->NormalizedTiming().Delay())) {
2196 // Note: If we use TimeDuration::Forever(), within our animation event
2197 // handling, we'd end up turning that into a null TimeStamp which can causes
2198 // errors if we try to do any arithmetic with it. Given that we should never
2199 // end up _using_ the interval end time. So returning zeroDuration here is
2200 // probably fine.
2201 return zeroDuration;
2204 return std::max(std::min(effectEnd - mEffect->NormalizedTiming().Delay(),
2205 aActiveDuration),
2206 zeroDuration);
2209 } // namespace mozilla::dom