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/. */
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
{
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 // ---------------------------------------------------------------------------
57 // ---------------------------------------------------------------------------
60 // A wrapper around nsAutoAnimationMutationBatch that looks up the
61 // appropriate document from the supplied animation.
62 class MOZ_RAII AutoMutationBatchForAnimation
{
64 explicit AutoMutationBatchForAnimation(const Animation
& aAnimation
) {
65 NonOwningAnimationTarget target
= aAnimation
.GetTargetForAnimation();
70 // For mutation observers, we use the OwnerDoc.
71 mAutoBatch
.emplace(target
.mElement
->OwnerDoc());
75 Maybe
<nsAutoAnimationMutationBatch
> mAutoBatch
;
79 // ---------------------------------------------------------------------------
81 // Animation interface:
83 // ---------------------------------------------------------------------------
85 Animation::Animation(nsIGlobalObject
* aGlobal
)
86 : DOMEventTargetHelper(aGlobal
),
87 mAnimationIndex(sNextAnimationIndex
++),
88 mRTPCallerType(aGlobal
->GetRTPCallerType()) {}
90 Animation::~Animation() = default;
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
98 if (aOther
.UsingScrollTimeline()) {
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
;
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
118 if (!currentTime
.IsNull()) {
119 animation
->SilentlySetCurrentTime(currentTime
.Value());
121 animation
->mPreviousCurrentTime
= animation
->GetCurrentTimeAsDuration();
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();
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()) {
161 return effect
->AsKeyframeEffect()->GetAnimationTarget();
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
;
172 AnimationUtils::GetCurrentRealmDocument(aGlobal
.Context());
174 if (aTimeline
.WasPassed()) {
175 timeline
= aTimeline
.Value();
178 aRv
.Throw(NS_ERROR_FAILURE
);
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
) {
196 MutationObservers::NotifyAnimationChanged(this);
199 void Animation::SetEffect(AnimationEffect
* aEffect
) {
200 SetEffectNoUpdate(aEffect
);
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
) {
212 AutoMutationBatchForAnimation
mb(*this);
213 bool wasRelevant
= mIsRelevant
;
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.
219 MutationObservers::NotifyAnimationRemoved(this);
222 // Break links with the old effect and then drop it.
223 RefPtr
<AnimationEffect
> oldEffect
= mEffect
;
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.
237 // Break links from the new effect to its previous animation, if any.
238 RefPtr
<AnimationEffect
> newEffect
= aEffect
;
239 Animation
* prevAnim
= aEffect
->GetAnimation();
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
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
);
271 // https://drafts.csswg.org/web-animations/#setting-the-timeline
272 void Animation::SetTimelineNoUpdate(AnimationTimeline
* aTimeline
) {
273 if (mTimeline
== aTimeline
) {
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()) {
289 previousCurrentTime
.Value().ToSeconds() / endTime
.ToSeconds();
292 RefPtr
<AnimationTimeline
> oldTimeline
= mTimeline
;
294 oldTimeline
->RemoveAnimation(this);
297 mTimeline
= aTimeline
;
299 mResetCurrentTimeOnResume
= false;
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());
313 seekTime
.SetValue(TimeDuration(EffectEnd()));
316 switch (previousPlayState
) {
317 case AnimationPlayState::Running
:
318 case AnimationPlayState::Finished
:
319 mStartTime
= seekTime
;
321 case AnimationPlayState::Paused
:
322 if (!previousCurrentTime
.IsNull()) {
323 mResetCurrentTimeOnResume
= true;
324 mStartTime
.SetNull();
326 TimeDuration(EffectEnd().MultDouble(previousProgress
)));
328 mStartTime
= seekTime
;
331 case AnimationPlayState::Idle
:
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()) {
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
) {
368 AutoMutationBatchForAnimation
mb(*this);
370 Nullable
<TimeDuration
> timelineTime
;
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()) {
381 Nullable
<TimeDuration
> previousCurrentTime
= GetCurrentTimeAsDuration();
383 ApplyPendingPlaybackRate();
384 mStartTime
= aNewStartTime
;
386 mResetCurrentTimeOnResume
= false;
388 if (!aNewStartTime
.IsNull()) {
389 if (mPlaybackRate
!= 0.0) {
393 mHoldTime
= previousCurrentTime
;
396 CancelPendingTasks();
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
);
405 MutationObservers::NotifyAnimationChanged(this);
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()) {
419 if (mTimeline
&& !mStartTime
.IsNull()) {
420 Nullable
<TimeDuration
> timelineTime
= mTimeline
->GetCurrentTimeAsDuration();
421 if (!timelineTime
.IsNull()) {
422 result
= CurrentTimeFromTimelineTime(timelineTime
.Value(),
423 mStartTime
.Value(), mPlaybackRate
);
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()) {
440 AutoMutationBatchForAnimation
mb(*this);
442 SetCurrentTimeNoUpdate(aSeekTime
);
445 MutationObservers::NotifyAnimationChanged(this);
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();
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
) {
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
);
495 MutationObservers::NotifyAnimationChanged(this);
500 // https://drafts.csswg.org/web-animations/#seamlessly-update-the-playback-rate
501 void Animation::UpdatePlaybackRate(double aPlaybackRate
) {
502 if (mPendingPlaybackRate
&& mPendingPlaybackRate
.value() == aPlaybackRate
) {
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
);
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
);
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
544 // However we still need to update the relevance and effect set as well as
545 // notifying observers.
546 UpdateEffect(PostRestyleMode::Never
);
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
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
);
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
574 UpdateTiming(SeekFlag::NoSeek
, SyncNotifyFlag::Async
);
576 MutationObservers::NotifyAnimationChanged(this);
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
615 aRv
.Throw(NS_ERROR_FAILURE
);
619 mReady
->MaybeResolve(this);
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
630 aRv
.Throw(NS_ERROR_FAILURE
);
633 if (mFinishedIsResolved
) {
634 MaybeResolveFinishedPromise();
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
) {
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();
662 mStartTime
.SetNull();
664 // Allow our effect to remove itself from the its target element's EffectSet.
665 UpdateEffect(aPostRestyle
);
668 mTimeline
->RemoveAnimation(this);
670 MaybeQueueCancelEvent(activeTime
);
672 if (newlyIdle
&& aPostRestyle
== PostRestyleMode::IfNeeded
) {
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();
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
);
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
) {
722 CancelPendingTasks();
725 mReady
->MaybeResolve(this);
728 UpdateTiming(SeekFlag::DidSeek
, SyncNotifyFlag::Sync
);
729 if (didChange
&& IsRelevant()) {
730 MutationObservers::NotifyAnimationChanged(this);
735 void Animation::Play(ErrorResult
& aRv
, LimitBehavior aLimitBehavior
) {
736 PlayNoUpdate(aRv
, aLimitBehavior
);
740 // https://drafts.csswg.org/web-animations/#reverse-an-animation
741 void Animation::Reverse(ErrorResult
& aRv
) {
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) {
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
766 mPendingPlaybackRate
= originalPendingPlaybackRate
;
769 // Play(), above, unconditionally calls PostUpdate so we don't need to do
773 void Animation::Persist() {
774 if (mReplaceState
== AnimationReplaceState::Persisted
) {
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
785 UpdateEffect(PostRestyleMode::IfNeeded
);
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
) {
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
) {
808 NonOwningAnimationTarget target
= keyframeEffect
->GetAnimationTarget();
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
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
);
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");
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
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();
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
,
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
)
885 changed
|= Servo_DeclarationBlock_SetPropertyToAnimationValue(
886 declarationBlock
->Raw(), computedValue
, beforeChangeClosure
);
891 MOZ_ASSERT(!closureData
.mWasCalled
);
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(),
919 void Animation::SetCurrentTimeAsDouble(const Nullable
<double>& aCurrentTime
,
921 if (aCurrentTime
.IsNull()) {
922 if (!GetCurrentTimeAsDuration().IsNull()) {
924 "Current time is resolved but trying to set it to an unresolved "
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
;
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
) {
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
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.
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");
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.
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
1032 const auto currentTime
= mTimeline
->GetCurrentTimeAsDuration();
1033 if (currentTime
.IsNull()) {
1037 FinishPendingAt(currentTime
.Value());
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
=
1057 : CurrentTimeFromTimelineTime(mPendingReadyTime
.Value(),
1058 mStartTime
.Value(), mPlaybackRate
);
1060 result
= StartTimeFromTimelineTime(
1061 mPendingReadyTime
.Value(), currentTimeToMatch
, *mPendingPlaybackRate
);
1065 if (!mStartTime
.IsNull()) {
1066 result
= mStartTime
;
1070 if (mPendingReadyTime
.IsNull() || mHoldTime
.IsNull()) {
1074 // Calculate the equivalent start time from the pending ready time.
1075 result
= StartTimeFromTimelineTime(mPendingReadyTime
.Value(),
1076 mHoldTime
.Value(), mPlaybackRate
);
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.
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
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.
1102 // Check the time is convertible to a timestamp
1103 if (aTime
== TimeDuration::Forever() || mPlaybackRate
== 0.0 ||
1104 mStartTime
.IsNull()) {
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
);
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
);
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
1155 // Currently only transform animations need to be synchronized
1156 if (!aPropertySet
.Intersects(nsCSSPropertyIDSet::TransformLikeProperties())) {
1160 KeyframeEffect
* keyframeEffect
=
1161 mEffect
? mEffect
->AsKeyframeEffect() : nullptr;
1162 if (!keyframeEffect
) {
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.
1173 dom_animations_mainthread_synchronization_with_geometric_animations() &&
1174 mSyncWithGeometricAnimations
&&
1175 keyframeEffect
->HasAnimationOfPropertySet(
1176 nsCSSPropertyIDSet::TransformLikeProperties())) {
1177 aPerformanceWarning
=
1178 AnimationPerformanceWarning::Type::TransformWithSyncGeometricAnimations
;
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);
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
1208 if (IsMarkupAnimation(AsCSSAnimation()) ||
1209 IsMarkupAnimation(AsCSSTransition())) {
1213 // Only finished animations can be replaced.
1214 if (PlayState() != AnimationPlayState::Finished
) {
1218 // Already removed animations cannot be replaced.
1219 if (ReplaceState() == AnimationReplaceState::Removed
) {
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()) {
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.
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
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()) {
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
1259 if (!GetEffect()->AsKeyframeEffect()->GetAnimationTarget()) {
1266 bool Animation::IsRemovable() const {
1267 return ReplaceState() == AnimationReplaceState::Active
&& IsReplaceable();
1270 void Animation::ScheduleReplacementCheck() {
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();
1284 nsPresContext
* presContext
=
1285 nsContentUtils::GetContextForContent(target
.mElement
);
1287 presContext
->EffectCompositor()->NoteElementForReducing(target
);
1291 void Animation::MaybeScheduleReplacementCheck() {
1292 if (!IsReplaceable()) {
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
);
1308 QueuePlaybackEvent(u
"remove"_ns
, GetTimelineCurrentTimeAsTimeStamp());
1311 bool Animation::HasLowerCompositeOrderThan(const Animation
& aOther
) const {
1312 // 0. Object-equality case
1313 if (&aOther
== this) {
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
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
) {
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
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
);
1447 pending
== Pending(),
1448 "Pending state should not change during the course of compositing");
1451 void Animation::NotifyEffectTimingUpdated() {
1453 "We should only update effect timing when we have a target "
1455 UpdateTiming(Animation::SeekFlag::NoSeek
, Animation::SyncNotifyFlag::Async
);
1458 void Animation::NotifyEffectPropertiesUpdated() {
1460 "We should only update effect properties when we have a target "
1463 MaybeScheduleReplacementCheck();
1466 void Animation::NotifyEffectTargetUpdated() {
1468 "We should only update the effect target when we have a target "
1471 MaybeScheduleReplacementCheck();
1474 void Animation::NotifyGeometricAnimationsStartingThisFrame() {
1475 if (!IsNewlyStarted() || !mEffect
) {
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
;
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();
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
) {
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.
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.
1585 if (Document
* doc
= GetRenderedDocument()) {
1586 PendingAnimationTracker
* tracker
=
1587 doc
->GetOrCreatePendingAnimationTracker();
1588 tracker
->AddPlayPending(*this);
1590 TriggerOnNextTick(Nullable
<TimeDuration
>());
1594 UpdateTiming(SeekFlag::NoSeek
, SyncNotifyFlag::Async
);
1596 MutationObservers::NotifyAnimationChanged(this);
1600 // https://drafts.csswg.org/web-animations/#pause-an-animation
1601 void Animation::Pause(ErrorResult
& aRv
) {
1602 if (IsPausedOrPausing()) {
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));
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
;
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.
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.
1651 if (Document
* doc
= GetRenderedDocument()) {
1652 PendingAnimationTracker
* tracker
=
1653 doc
->GetOrCreatePendingAnimationTracker();
1654 tracker
->AddPausePending(*this);
1656 TriggerOnNextTick(Nullable
<TimeDuration
>());
1660 UpdateTiming(SeekFlag::NoSeek
, SyncNotifyFlag::Async
);
1662 MutationObservers::NotifyAnimationChanged(this);
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();
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
,
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);
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(),
1726 ApplyPendingPlaybackRate();
1727 mStartTime
.SetNull();
1728 mPendingState
= PendingState::NotPending
;
1730 UpdateTiming(SeekFlag::NoSeek
, SyncNotifyFlag::Async
);
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
);
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
));
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()) {
1773 std::min(mPreviousCurrentTime
.Value(), TimeDuration(0)));
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(),
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
) {
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() {
1823 KeyframeEffect
* keyframeEffect
= mEffect
->AsKeyframeEffect();
1824 if (!keyframeEffect
) {
1827 keyframeEffect
->RequestRestyle(EffectCompositor::RestyleType::Layer
);
1830 void Animation::CancelPendingTasks() {
1831 if (mPendingState
== PendingState::NotPending
) {
1835 if (Document
* doc
= GetRenderedDocument()) {
1836 PendingAnimationTracker
* tracker
= doc
->GetPendingAnimationTracker();
1838 if (mPendingState
== PendingState::PlayPending
) {
1839 tracker
->RemovePlayPending(*this);
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
) {
1856 CancelPendingTasks();
1857 ApplyPendingPlaybackRate();
1860 mReady
->MaybeReject(NS_ERROR_DOM_ABORT_ERR
);
1861 MOZ_ALWAYS_TRUE(mReady
->SetAnyPromiseIsHandled());
1866 void Animation::ReschedulePendingTasks() {
1867 if (mPendingState
== PendingState::NotPending
) {
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,
1896 // * the associated animation's timeline duration is unresolved or zero,
1898 // * the animation's playback rate is zero
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
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()
1919 : aCurrentTime
.Value().MultDouble(1.0 / aPlaybackRate
)) +
1922 // 4. Let effective timeline progress be (effective timeline time / timeline
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
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
1954 // If we're not pending we're ok.
1955 if (mPendingState
== PendingState::NotPending
) {
1959 // If we have a pending ready time then we will be started on the next
1961 if (!mPendingReadyTime
.IsNull()) {
1965 // If we don't have an active timeline then we shouldn't start until
1967 if (!mTimeline
|| mTimeline
->GetCurrentTimeAsDuration().IsNull()) {
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();
1982 PendingAnimationTracker
* tracker
= doc
->GetPendingAnimationTracker();
1983 return !tracker
|| (!tracker
->IsWaitingToPlay(*this) &&
1984 !tracker
->IsWaitingToPause(*this));
1987 StickyTimeDuration
Animation::EffectEnd() const {
1989 return StickyTimeDuration(0);
1992 return mEffect
->NormalizedTiming().EndTime();
1995 Document
* Animation::GetRenderedDocument() const {
1996 if (!mEffect
|| !mEffect
->AsKeyframeEffect()) {
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()) {
2016 const bool fromFiniteTimeline
=
2017 aOldTimeline
&& !aOldTimeline
->IsMonotonicallyIncreasing();
2018 const bool toFiniteTimeline
=
2019 aNewTimeline
&& !aNewTimeline
->IsMonotonicallyIncreasing();
2020 if (fromFiniteTimeline
== toFiniteTimeline
) {
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);
2031 tracker
->RemovePausePending(*this);
2035 doc
->GetOrCreateScrollTimelineAnimationTracker()->AddPending(*this);
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);
2046 tracker
->AddPausePending(*this);
2051 class AsyncFinishNotification
: public MicroTaskRunnable
{
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();
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() {
2089 mFinished
->MaybeResolve(this);
2091 mFinishedIsResolved
= true;
2094 void Animation::DoFinishNotificationImmediately(MicroTaskRunnable
* aAsync
) {
2095 if (aAsync
&& aAsync
!= mFinishNotificationTask
) {
2099 mFinishNotificationTask
= nullptr;
2101 if (PlayState() != AnimationPlayState::Finished
) {
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();
2119 nsPresContext
* presContext
= doc
->GetPresContext();
2124 AnimationPlaybackEventInit init
;
2125 if (aName
.EqualsLiteral("finish") || aName
.EqualsLiteral("remove")) {
2126 init
.mCurrentTime
= GetCurrentTimeAsDouble();
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
) {
2158 mHiddenByContentVisibility
= hidden
;
2160 if (!GetTimeline()) {
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();
2173 std::min(StickyTimeDuration(-mEffect
->NormalizedTiming().Delay()),
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
2201 return zeroDuration
;
2204 return std::max(std::min(effectEnd
- mEffect
->NormalizedTiming().Delay(),
2209 } // namespace mozilla::dom