Bug 1916749 - Add set as default prompt settings helper and use it only where needed in UI tests r=aaronmt, a=dsmith
With the changes done in [[ https://bugzilla.mozilla.org/show_bug.cgi?id=1911100 | 1911100 ]] Gela wanted to display the "Set as default browser" native prompt in home fragment.
The changes were backed out because addPrivateBrowsingShortcutFromHomeScreenCFRTest started to fail all of a sudden.
The UI test failed because the "Set as default browser" native prompt was displayed when performing the second switch to private browsing while trying to trigger the "Add private browsing shortcut" prompt.
To overcome this problem I've added a settings helper to disable it by default and use it where needed.
All UI tests successfully passed 3x on Firebase ✅
And I've also did a [[ https://firefox-ci-tc.services.mozilla.com/tasks/eMjCXxH1S2Kvmd3xdKYTNQ/runs/0/logs/public/logs/live.log | try run ]] with Gela's changes and addPrivateBrowsingShortcutFromHomeScreenCFRTest successfully passed 50x on Firebase ✅ (after landing these changes she can try to land her changes again)
Original Revision: https://phabricator.services.mozilla.com/D221038
Differential Revision: https://phabricator.services.mozilla.com/D223604
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- *//* vim: set ts=8 sts=2 et sw=2 tw=80: *//* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */#include"Animation.h"#include"mozilla/Likely.h"#include"nsIFrame.h"#include"AnimationUtils.h"#include"mozAutoDocUpdate.h"#include"mozilla/dom/AnimationBinding.h"#include"mozilla/dom/Document.h"#include"mozilla/dom/DocumentInlines.h"#include"mozilla/dom/DocumentTimeline.h"#include"mozilla/dom/MutationObservers.h"#include"mozilla/dom/Promise.h"#include"mozilla/AnimationEventDispatcher.h"#include"mozilla/AnimationTarget.h"#include"mozilla/AutoRestore.h"#include"mozilla/CycleCollectedJSContext.h"#include"mozilla/DeclarationBlock.h"#include"mozilla/Maybe.h" // For Maybe#include"mozilla/StaticPrefs_dom.h"#include"nsAnimationManager.h" // For CSSAnimation#include"nsComputedDOMStyle.h"#include"nsDOMMutationObserver.h" // For nsAutoAnimationMutationBatch#include"nsDOMCSSAttrDeclaration.h" // For nsDOMCSSAttributeDeclaration#include"nsThreadUtils.h" // For nsRunnableMethod and nsRevocableEventPtr#include"nsTransitionManager.h" // For CSSTransition#include"ScrollTimelineAnimationTracker.h"namespacemozilla::dom{// Static membersuint64_tAnimation::sNextAnimationIndex=0;NS_IMPL_CYCLE_COLLECTION_INHERITED(Animation,DOMEventTargetHelper,mTimeline,mEffect,mReady,mFinished)NS_IMPL_ADDREF_INHERITED(Animation,DOMEventTargetHelper)NS_IMPL_RELEASE_INHERITED(Animation,DOMEventTargetHelper)NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Animation)NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)JSObject*Animation::WrapObject(JSContext*aCx,JS::Handle<JSObject*>aGivenProto){returndom::Animation_Binding::Wrap(aCx,this,aGivenProto);}// ---------------------------------------------------------------------------//// Utility methods//// ---------------------------------------------------------------------------namespace{// A wrapper around nsAutoAnimationMutationBatch that looks up the// appropriate document from the supplied animation.classMOZ_RAIIAutoMutationBatchForAnimation{public:explicitAutoMutationBatchForAnimation(constAnimation&aAnimation){NonOwningAnimationTargettarget=aAnimation.GetTargetForAnimation();if(!target){return;}// For mutation observers, we use the OwnerDoc.mAutoBatch.emplace(target.mElement->OwnerDoc());}private:Maybe<nsAutoAnimationMutationBatch>mAutoBatch;};}// namespace// ---------------------------------------------------------------------------//// Animation interface://// ---------------------------------------------------------------------------Animation::Animation(nsIGlobalObject*aGlobal):DOMEventTargetHelper(aGlobal),mAnimationIndex(sNextAnimationIndex++),mRTPCallerType(aGlobal->GetRTPCallerType()){}Animation::~Animation()=default;/* static */already_AddRefed<Animation>Animation::ClonePausedAnimation(nsIGlobalObject*aGlobal,constAnimation&aOther,AnimationEffect&aEffect,AnimationTimeline&aTimeline){// FIXME: Bug 1805950: Support printing for scroll-timeline once we resolve// the spec issue.if(aOther.UsingScrollTimeline()){returnnullptr;}RefPtr<Animation>animation=newAnimation(aGlobal);// Setup the timeline. We always use document-timeline of the new document,// even if the timeline of |aOther| is null.animation->mTimeline=&aTimeline;// Setup the playback rate.animation->mPlaybackRate=aOther.mPlaybackRate;// Setup the timing.constNullable<TimeDuration>currentTime=aOther.GetCurrentTimeAsDuration();if(!aOther.GetTimeline()){// This simulates what we do in SetTimelineNoUpdate(). It's possible to// preserve the progress if the previous timeline is a scroll-timeline.// So for null timeline, it may have a progress and the non-null current// time.if(!currentTime.IsNull()){animation->SilentlySetCurrentTime(currentTime.Value());}animation->mPreviousCurrentTime=animation->GetCurrentTimeAsDuration();}else{animation->mHoldTime=currentTime;if(!currentTime.IsNull()){// FIXME: Should we use |timelineTime| as previous current time here? It// seems we should use animation->GetCurrentTimeAsDuration(), per// UpdateFinishedState().constNullable<TimeDuration>timelineTime=aTimeline.GetCurrentTimeAsDuration();MOZ_ASSERT(!timelineTime.IsNull(),"Timeline not yet set");animation->mPreviousCurrentTime=timelineTime;}}// Setup the effect's link to this.animation->mEffect=&aEffect;animation->mEffect->SetAnimation(animation);animation->mPendingState=PendingState::PausePending;// We expect our relevance to be the same as the orginal.animation->mIsRelevant=aOther.mIsRelevant;animation->PostUpdate();animation->mTimeline->NotifyAnimationUpdated(*animation);returnanimation.forget();}NonOwningAnimationTargetAnimation::GetTargetForAnimation()const{AnimationEffect*effect=GetEffect();NonOwningAnimationTargettarget;if(!effect||!effect->AsKeyframeEffect()){returntarget;}returneffect->AsKeyframeEffect()->GetAnimationTarget();}/* static */already_AddRefed<Animation>Animation::Constructor(constGlobalObject&aGlobal,AnimationEffect*aEffect,constOptional<AnimationTimeline*>&aTimeline,ErrorResult&aRv){nsCOMPtr<nsIGlobalObject>global=do_QueryInterface(aGlobal.GetAsSupports());AnimationTimeline*timeline;Document*document=AnimationUtils::GetCurrentRealmDocument(aGlobal.Context());if(aTimeline.WasPassed()){timeline=aTimeline.Value();}else{if(!document){aRv.Throw(NS_ERROR_FAILURE);returnnullptr;}timeline=document->Timeline();}RefPtr<Animation>animation=newAnimation(global);animation->SetTimelineNoUpdate(timeline);animation->SetEffectNoUpdate(aEffect);returnanimation.forget();}voidAnimation::SetId(constnsAString&aId){if(mId==aId){return;}mId=aId;MutationObservers::NotifyAnimationChanged(this);}voidAnimation::SetEffect(AnimationEffect*aEffect){SetEffectNoUpdate(aEffect);PostUpdate();}// https://drafts.csswg.org/web-animations/#setting-the-target-effectvoidAnimation::SetEffectNoUpdate(AnimationEffect*aEffect){RefPtr<Animation>kungFuDeathGrip(this);if(mEffect==aEffect){return;}AutoMutationBatchForAnimationmb(*this);boolwasRelevant=mIsRelevant;if(mEffect){// We need to notify observers now because once we set mEffect to null// we won't be able to find the target element to notify.if(mIsRelevant){MutationObservers::NotifyAnimationRemoved(this);}// Break links with the old effect and then drop it.RefPtr<AnimationEffect>oldEffect=mEffect;mEffect=nullptr;if(IsPartialPrerendered()){if(KeyframeEffect*oldKeyframeEffect=oldEffect->AsKeyframeEffect()){oldKeyframeEffect->ResetPartialPrerendered();}}oldEffect->SetAnimation(nullptr);// The following will not do any notification because mEffect is null.UpdateRelevance();}if(aEffect){// Break links from the new effect to its previous animation, if any.RefPtr<AnimationEffect>newEffect=aEffect;Animation*prevAnim=aEffect->GetAnimation();if(prevAnim){prevAnim->SetEffect(nullptr);}// Create links with the new effect. SetAnimation(this) will also update// mIsRelevant of this animation, and then notify mutation observer if// needed by calling Animation::UpdateRelevance(), so we don't need to// call it again.mEffect=newEffect;mEffect->SetAnimation(this);// Notify possible add or change.// If the target is different, the change notification will be ignored by// AutoMutationBatchForAnimation.if(wasRelevant&&mIsRelevant){MutationObservers::NotifyAnimationChanged(this);}}MaybeScheduleReplacementCheck();UpdateTiming(SeekFlag::NoSeek,SyncNotifyFlag::Async);}voidAnimation::SetTimeline(AnimationTimeline*aTimeline){SetTimelineNoUpdate(aTimeline);PostUpdate();}// https://drafts.csswg.org/web-animations/#setting-the-timelinevoidAnimation::SetTimelineNoUpdate(AnimationTimeline*aTimeline){if(mTimeline==aTimeline){return;}StickyTimeDurationactiveTime=mEffect?mEffect->GetComputedTiming().mActiveTime:StickyTimeDuration();constAnimationPlayStatepreviousPlayState=PlayState();constNullable<TimeDuration>previousCurrentTime=GetCurrentTimeAsDuration();// FIXME: The definition of end time in web-animation-1 is different from that// in web-animation-2, which includes the start time. We are still using the// definition in web-animation-1 here for now.constTimeDurationendTime=TimeDuration(EffectEnd());doublepreviousProgress=0.0;if(!previousCurrentTime.IsNull()&&!endTime.IsZero()){previousProgress=previousCurrentTime.Value().ToSeconds()/endTime.ToSeconds();}RefPtr<AnimationTimeline>oldTimeline=mTimeline;if(oldTimeline){oldTimeline->RemoveAnimation(this);}mTimeline=aTimeline;mResetCurrentTimeOnResume=false;if(mEffect){mEffect->UpdateNormalizedTiming();}if(mTimeline&&!mTimeline->IsMonotonicallyIncreasing()){// If "to finite timeline" is true.ApplyPendingPlaybackRate();Nullable<TimeDuration>seekTime;if(mPlaybackRate>=0.0){seekTime.SetValue(TimeDuration());}else{seekTime.SetValue(TimeDuration(EffectEnd()));}switch(previousPlayState){caseAnimationPlayState::Running:caseAnimationPlayState::Finished:mStartTime=seekTime;break;caseAnimationPlayState::Paused:if(!previousCurrentTime.IsNull()){mResetCurrentTimeOnResume=true;mStartTime.SetNull();mHoldTime.SetValue(TimeDuration(EffectEnd().MultDouble(previousProgress)));}else{mStartTime=seekTime;}break;caseAnimationPlayState::Idle:default:break;}}elseif(oldTimeline&&!oldTimeline->IsMonotonicallyIncreasing()&&!previousCurrentTime.IsNull()){// If "from finite timeline" and previous progress is resolved.SetCurrentTimeNoUpdate(TimeDuration(EffectEnd().MultDouble(previousProgress)));}if(!mStartTime.IsNull()){mHoldTime.SetNull();}if(!aTimeline){MaybeQueueCancelEvent(activeTime);}UpdateScrollTimelineAnimationTracker(oldTimeline,aTimeline);UpdateTiming(SeekFlag::NoSeek,SyncNotifyFlag::Async);// FIXME: Bug 1799071: Check if we need to add// MutationObservers::NotifyAnimationChanged(this) here.}// https://drafts.csswg.org/web-animations/#set-the-animation-start-timevoidAnimation::SetStartTime(constNullable<TimeDuration>&aNewStartTime){// Return early if the start time will not change. However, if we// are pending, then setting the start time to any value// including the current value has the effect of aborting// pending tasks so we should not return early in that case.if(!Pending()&&aNewStartTime==mStartTime){return;}AutoMutationBatchForAnimationmb(*this);Nullable<TimeDuration>timelineTime;if(mTimeline){// The spec says to check if the timeline is active (has a resolved time)// before using it here, but we don't need to since it's harmless to set// the already null time to null.timelineTime=mTimeline->GetCurrentTimeAsDuration();}if(timelineTime.IsNull()&&!aNewStartTime.IsNull()){mHoldTime.SetNull();}Nullable<TimeDuration>previousCurrentTime=GetCurrentTimeAsDuration();ApplyPendingPlaybackRate();mStartTime=aNewStartTime;mResetCurrentTimeOnResume=false;if(!aNewStartTime.IsNull()){if(mPlaybackRate!=0.0){mHoldTime.SetNull();}}else{mHoldTime=previousCurrentTime;}CancelPendingTasks();if(mReady){// We may have already resolved mReady, but in that case calling// MaybeResolve is a no-op, so that's okay.mReady->MaybeResolve(this);}UpdateTiming(SeekFlag::DidSeek,SyncNotifyFlag::Async);if(IsRelevant()){MutationObservers::NotifyAnimationChanged(this);}PostUpdate();}// https://drafts.csswg.org/web-animations/#current-timeNullable<TimeDuration>Animation::GetCurrentTimeForHoldTime(constNullable<TimeDuration>&aHoldTime)const{Nullable<TimeDuration>result;if(!aHoldTime.IsNull()){result=aHoldTime;returnresult;}if(mTimeline&&!mStartTime.IsNull()){Nullable<TimeDuration>timelineTime=mTimeline->GetCurrentTimeAsDuration();if(!timelineTime.IsNull()){result=CurrentTimeFromTimelineTime(timelineTime.Value(),mStartTime.Value(),mPlaybackRate);}}returnresult;}// https://drafts.csswg.org/web-animations/#set-the-current-timevoidAnimation::SetCurrentTime(constTimeDuration&aSeekTime){// Return early if the current time has not changed. However, if we// are pause-pending, then setting the current time to any value// including the current value has the effect of aborting the// pause so we should not return early in that case.if(mPendingState!=PendingState::PausePending&&Nullable<TimeDuration>(aSeekTime)==GetCurrentTimeAsDuration()){return;}AutoMutationBatchForAnimationmb(*this);SetCurrentTimeNoUpdate(aSeekTime);if(IsRelevant()){MutationObservers::NotifyAnimationChanged(this);}PostUpdate();}voidAnimation::SetCurrentTimeNoUpdate(constTimeDuration&aSeekTime){SilentlySetCurrentTime(aSeekTime);if(mPendingState==PendingState::PausePending){// Finish the pause operationmHoldTime.SetValue(aSeekTime);ApplyPendingPlaybackRate();mStartTime.SetNull();if(mReady){mReady->MaybeResolve(this);}CancelPendingTasks();}UpdateTiming(SeekFlag::DidSeek,SyncNotifyFlag::Async);}// https://drafts.csswg.org/web-animations/#set-the-playback-ratevoidAnimation::SetPlaybackRate(doubleaPlaybackRate){mPendingPlaybackRate.reset();if(aPlaybackRate==mPlaybackRate){return;}AutoMutationBatchForAnimationmb(*this);Nullable<TimeDuration>previousTime=GetCurrentTimeAsDuration();mPlaybackRate=aPlaybackRate;if(!previousTime.IsNull()){SetCurrentTime(previousTime.Value());}// In the case where GetCurrentTimeAsDuration() returns the same result before// and after updating mPlaybackRate, SetCurrentTime will return early since,// as far as it can tell, nothing has changed.// As a result, we need to perform the following updates here:// - update timing (since, if the sign of the playback rate has changed, our// finished state may have changed),// - dispatch a change notification for the changed playback rate, and// - update the playback rate on animations on layers.UpdateTiming(SeekFlag::DidSeek,SyncNotifyFlag::Async);if(IsRelevant()){MutationObservers::NotifyAnimationChanged(this);}PostUpdate();}// https://drafts.csswg.org/web-animations/#seamlessly-update-the-playback-ratevoidAnimation::UpdatePlaybackRate(doubleaPlaybackRate){if(mPendingPlaybackRate&&mPendingPlaybackRate.value()==aPlaybackRate){return;}// Calculate the play state using the existing playback rate since below we// want to know if the animation is _currently_ finished or not, not whether// it _will_ be finished.AnimationPlayStateplayState=PlayState();mPendingPlaybackRate=Some(aPlaybackRate);if(Pending()){// If we already have a pending task, there is nothing more to do since the// playback rate will be applied then.//// However, as with the idle/paused case below, we still need to update the// relevance (and effect set to make sure it only contains relevant// animations) since the relevance is based on the Animation play state// which incorporates the _pending_ playback rate.UpdateEffect(PostRestyleMode::Never);return;}AutoMutationBatchForAnimationmb(*this);if(playState==AnimationPlayState::Idle||playState==AnimationPlayState::Paused||GetCurrentTimeAsDuration().IsNull()){// If |previous play state| is idle or paused, or the current time is// unresolved, we apply any pending playback rate on animation immediately.ApplyPendingPlaybackRate();// We don't need to update timing or post an update here because://// * the current time hasn't changed -- it's either unresolved or fixed// with a hold time -- so the output won't have changed// * the finished state won't have changed even if the sign of the// playback rate changed since we're not finished (we're paused or idle)// * the playback rate on layers doesn't need to be updated since we're not// moving. Once we get a start time etc. we'll update the playback rate// then.//// However we still need to update the relevance and effect set as well as// notifying observers.UpdateEffect(PostRestyleMode::Never);if(IsRelevant()){MutationObservers::NotifyAnimationChanged(this);}}elseif(playState==AnimationPlayState::Finished){MOZ_ASSERT(mTimeline&&!mTimeline->GetCurrentTimeAsDuration().IsNull(),"If we have no active timeline, we should be idle or paused");if(aPlaybackRate!=0){// The unconstrained current time can only be unresolved if either we// don't have an active timeline (and we already asserted that is not// true) or we have an unresolved start time (in which case we should be// paused).MOZ_ASSERT(!GetUnconstrainedCurrentTime().IsNull(),"Unconstrained current time should be resolved");TimeDurationunconstrainedCurrentTime=GetUnconstrainedCurrentTime().Value();TimeDurationtimelineTime=mTimeline->GetCurrentTimeAsDuration().Value();mStartTime=StartTimeFromTimelineTime(timelineTime,unconstrainedCurrentTime,aPlaybackRate);}else{mStartTime=mTimeline->GetCurrentTimeAsDuration();}ApplyPendingPlaybackRate();// Even though we preserve the current time, we might now leave the finished// state (e.g. if the playback rate changes sign) so we need to update// timing.UpdateTiming(SeekFlag::NoSeek,SyncNotifyFlag::Async);if(IsRelevant()){MutationObservers::NotifyAnimationChanged(this);}PostUpdate();}else{ErrorResultrv;Play(rv,LimitBehavior::Continue);MOZ_ASSERT(!rv.Failed(),"We should only fail to play when using auto-rewind behavior");}}// https://drafts.csswg.org/web-animations/#play-stateAnimationPlayStateAnimation::PlayState()const{Nullable<TimeDuration>currentTime=GetCurrentTimeAsDuration();if(currentTime.IsNull()&&mStartTime.IsNull()&&!Pending()){returnAnimationPlayState::Idle;}if(mPendingState==PendingState::PausePending||(mStartTime.IsNull()&&!Pending())){returnAnimationPlayState::Paused;}doubleplaybackRate=CurrentOrPendingPlaybackRate();if(!currentTime.IsNull()&&((playbackRate>0.0&¤tTime.Value()>=EffectEnd())||(playbackRate<0.0&¤tTime.Value()<=TimeDuration()))){returnAnimationPlayState::Finished;}returnAnimationPlayState::Running;}Promise*Animation::GetReady(ErrorResult&aRv){nsCOMPtr<nsIGlobalObject>global=GetOwnerGlobal();if(!mReady&&global){mReady=Promise::Create(global,aRv);// Lazily create on demand}if(!mReady){aRv.Throw(NS_ERROR_FAILURE);returnnullptr;}if(!Pending()){mReady->MaybeResolve(this);}returnmReady;}Promise*Animation::GetFinished(ErrorResult&aRv){nsCOMPtr<nsIGlobalObject>global=GetOwnerGlobal();if(!mFinished&&global){mFinished=Promise::Create(global,aRv);// Lazily create on demand}if(!mFinished){aRv.Throw(NS_ERROR_FAILURE);returnnullptr;}if(mFinishedIsResolved){MaybeResolveFinishedPromise();}returnmFinished;}// https://drafts.csswg.org/web-animations/#cancel-an-animationvoidAnimation::Cancel(PostRestyleModeaPostRestyle){boolnewlyIdle=false;if(PlayState()!=AnimationPlayState::Idle){newlyIdle=true;ResetPendingTasks();if(mFinished){mFinished->MaybeReject(NS_ERROR_DOM_ABORT_ERR);// mFinished can already be resolved.MOZ_ALWAYS_TRUE(mFinished->SetAnyPromiseIsHandled());}ResetFinishedPromise();QueuePlaybackEvent(nsGkAtoms::oncancel,GetTimelineCurrentTimeAsTimeStamp());}StickyTimeDurationactiveTime=mEffect?mEffect->GetComputedTiming().mActiveTime:StickyTimeDuration();mHoldTime.SetNull();mStartTime.SetNull();// Allow our effect to remove itself from the its target element's EffectSet.UpdateEffect(aPostRestyle);if(mTimeline){mTimeline->RemoveAnimation(this);}MaybeQueueCancelEvent(activeTime);if(newlyIdle&&aPostRestyle==PostRestyleMode::IfNeeded){PostUpdate();}}// https://drafts.csswg.org/web-animations/#finish-an-animationvoidAnimation::Finish(ErrorResult&aRv){doubleeffectivePlaybackRate=CurrentOrPendingPlaybackRate();if(effectivePlaybackRate==0){returnaRv.ThrowInvalidStateError("Can't finish animation with zero playback rate");}if(effectivePlaybackRate>0&&EffectEnd()==TimeDuration::Forever()){returnaRv.ThrowInvalidStateError("Can't finish infinite animation");}AutoMutationBatchForAnimationmb(*this);ApplyPendingPlaybackRate();// Seek to the endTimeDurationlimit=mPlaybackRate>0?TimeDuration(EffectEnd()):TimeDuration(0);booldidChange=GetCurrentTimeAsDuration()!=Nullable<TimeDuration>(limit);SilentlySetCurrentTime(limit);// If we are paused or play-pending we need to fill in the start time in// order to transition to the finished state.//// We only do this, however, if we have an active timeline. If we have an// inactive timeline we can't transition into the finished state just like// we can't transition to the running state (this finished state is really// a substate of the running state).if(mStartTime.IsNull()&&mTimeline&&!mTimeline->GetCurrentTimeAsDuration().IsNull()){mStartTime=StartTimeFromTimelineTime(mTimeline->GetCurrentTimeAsDuration().Value(),limit,mPlaybackRate);didChange=true;}// If we just resolved the start time for a pause or play-pending// animation, we need to clear the task. We don't do this as a branch of// the above however since we can have a play-pending animation with a// resolved start time if we aborted a pause operation.if(!mStartTime.IsNull()&&(mPendingState==PendingState::PlayPending||mPendingState==PendingState::PausePending)){if(mPendingState==PendingState::PausePending){mHoldTime.SetNull();}CancelPendingTasks();didChange=true;if(mReady){mReady->MaybeResolve(this);}}UpdateTiming(SeekFlag::DidSeek,SyncNotifyFlag::Sync);if(didChange&&IsRelevant()){MutationObservers::NotifyAnimationChanged(this);}PostUpdate();}voidAnimation::Play(ErrorResult&aRv,LimitBehavioraLimitBehavior){PlayNoUpdate(aRv,aLimitBehavior);PostUpdate();}// https://drafts.csswg.org/web-animations/#reverse-an-animationvoidAnimation::Reverse(ErrorResult&aRv){if(!mTimeline){returnaRv.ThrowInvalidStateError("Can't reverse an animation with no associated timeline");}if(mTimeline->GetCurrentTimeAsDuration().IsNull()){returnaRv.ThrowInvalidStateError("Can't reverse an animation associated with an inactive timeline");}doubleeffectivePlaybackRate=CurrentOrPendingPlaybackRate();if(effectivePlaybackRate==0.0){return;}Maybe<double>originalPendingPlaybackRate=mPendingPlaybackRate;mPendingPlaybackRate=Some(-effectivePlaybackRate);Play(aRv,LimitBehavior::AutoRewind);// If Play() threw, restore state and don't report anything to mutation// observers.if(aRv.Failed()){mPendingPlaybackRate=originalPendingPlaybackRate;}// Play(), above, unconditionally calls PostUpdate so we don't need to do// it here.}voidAnimation::Persist(){if(mReplaceState==AnimationReplaceState::Persisted){return;}boolwasRemoved=mReplaceState==AnimationReplaceState::Removed;mReplaceState=AnimationReplaceState::Persisted;// If the animation is not (yet) removed, there should be no side effects of// persisting it.if(wasRemoved){UpdateEffect(PostRestyleMode::IfNeeded);PostUpdate();}}DocGroup*Animation::GetDocGroup(){Document*doc=GetRenderedDocument();returndoc?doc->GetDocGroup():nullptr;}// https://drafts.csswg.org/web-animations/#dom-animation-commitstylesvoidAnimation::CommitStyles(ErrorResult&aRv){if(!mEffect){return;}// Take an owning reference to the keyframe effect. This will ensure that// this Animation and the target element remain alive after flushing style.RefPtr<KeyframeEffect>keyframeEffect=mEffect->AsKeyframeEffect();if(!keyframeEffect){return;}NonOwningAnimationTargettarget=keyframeEffect->GetAnimationTarget();if(!target){return;}if(target.mPseudoType!=PseudoStyleType::NotPseudo){returnaRv.ThrowNoModificationAllowedError("Can't commit styles of a pseudo-element");}// Check it is an element with a style attributeRefPtr<nsStyledElement>styledElement=nsStyledElement::FromNodeOrNull(target.mElement);if(!styledElement){returnaRv.ThrowNoModificationAllowedError("Target is not capable of having a style attribute");}// Hold onto a strong reference to the doc in case the flush destroys it.RefPtr<Document>doc=target.mElement->GetComposedDoc();// Flush frames before checking if the target element is rendered since the// result could depend on pending style changes, and IsRendered() looks at the// primary frame.if(doc){doc->FlushPendingNotifications(FlushType::Frames);}if(!target.mElement->IsRendered()){returnaRv.ThrowInvalidStateError("Target is not rendered");}nsPresContext*presContext=nsContentUtils::GetContextForContent(target.mElement);if(!presContext){returnaRv.ThrowInvalidStateError("Target is not rendered");}// Get the computed animation valuesUniquePtr<StyleAnimationValueMap>animationValues(Servo_AnimationValueMap_Create());if(!presContext->EffectCompositor()->ComposeServoAnimationRuleForEffect(*keyframeEffect,CascadeLevel(),animationValues.get())){NS_WARNING("Failed to compose animation style to commit");return;}// Calling SetCSSDeclaration will trigger attribute setting code.// Start the update now so that the old rule doesn't get used// between when we mutate the declaration and when we set the new// rule.mozAutoDocUpdateautoUpdate(target.mElement->OwnerDoc(),true);// Get the inline style to append toRefPtr<DeclarationBlock>declarationBlock;if(auto*existing=target.mElement->GetInlineStyleDeclaration()){declarationBlock=existing->EnsureMutable();}else{declarationBlock=newDeclarationBlock();declarationBlock->SetDirty();}// Prepare the callbackMutationClosureDataclosureData;closureData.mShouldBeCalled=true;closureData.mElement=target.mElement;DeclarationBlockMutationClosurebeforeChangeClosure={nsDOMCSSAttributeDeclaration::MutationClosureFunction,&closureData,};// Set the animated stylesboolchanged=false;constAnimatedPropertyIDSet&properties=keyframeEffect->GetPropertySet();for(constAnimatedPropertyID&property:properties){RefPtr<StyleAnimationValue>computedValue=Servo_AnimationValueMap_GetValue(animationValues.get(),&property).Consume();if(computedValue){changed|=Servo_DeclarationBlock_SetPropertyToAnimationValue(declarationBlock->Raw(),computedValue,beforeChangeClosure);}}if(!changed){MOZ_ASSERT(!closureData.mWasCalled);return;}MOZ_ASSERT(closureData.mWasCalled);// Update inline style declarationtarget.mElement->SetInlineStyleDeclaration(*declarationBlock,closureData);}// ---------------------------------------------------------------------------//// JS wrappers for Animation interface://// ---------------------------------------------------------------------------Nullable<double>Animation::GetStartTimeAsDouble()const{returnAnimationUtils::TimeDurationToDouble(mStartTime,mRTPCallerType);}voidAnimation::SetStartTimeAsDouble(constNullable<double>&aStartTime){returnSetStartTime(AnimationUtils::DoubleToTimeDuration(aStartTime));}Nullable<double>Animation::GetCurrentTimeAsDouble()const{returnAnimationUtils::TimeDurationToDouble(GetCurrentTimeAsDuration(),mRTPCallerType);}voidAnimation::SetCurrentTimeAsDouble(constNullable<double>&aCurrentTime,ErrorResult&aRv){if(aCurrentTime.IsNull()){if(!GetCurrentTimeAsDuration().IsNull()){aRv.ThrowTypeError("Current time is resolved but trying to set it to an unresolved ""time");}return;}returnSetCurrentTime(TimeDuration::FromMilliseconds(aCurrentTime.Value()));}// ---------------------------------------------------------------------------voidAnimation::Tick(AnimationTimeline::TickState&aTickState){if(Pending()){if(!mPendingReadyTime.IsNull()){TryTriggerNow();}elseif(MOZ_LIKELY(mTimeline)){// Makes sure that we trigger the animation on the next tick but,// importantly, with this tick's timestamp.mPendingReadyTime=mTimeline->GetCurrentTimeAsTimeStamp();}}UpdateTiming(SeekFlag::NoSeek,SyncNotifyFlag::Sync);// Check for changes to whether or not this animation is replaceable.boolisReplaceable=IsReplaceable();if(isReplaceable&&!mWasReplaceableAtLastTick){ScheduleReplacementCheck();}mWasReplaceableAtLastTick=isReplaceable;if(!mEffect){return;}// Update layers if we are newly finished.KeyframeEffect*keyframeEffect=mEffect->AsKeyframeEffect();if(keyframeEffect&&!keyframeEffect->Properties().IsEmpty()&&!mFinishedAtLastComposeStyle&&PlayState()==AnimationPlayState::Finished){PostUpdate();}}boolAnimation::TryTriggerNow(){if(!Pending()){returntrue;}// If we don't have an active timeline we can't trigger the animation.if(NS_WARN_IF(!mTimeline)){returnfalse;}autocurrentTime=mPendingReadyTime.IsNull()?mTimeline->GetCurrentTimeAsDuration():mTimeline->ToTimelineTime(mPendingReadyTime);mPendingReadyTime={};if(NS_WARN_IF(currentTime.IsNull())){returnfalse;}FinishPendingAt(currentTime.Value());returntrue;}TimeStampAnimation::AnimationTimeToTimeStamp(constStickyTimeDuration&aTime)const{// Initializes to null. Return the same object every time to benefit from// return-value-optimization.TimeStampresult;// We *don't* check for mTimeline->TracksWallclockTime() here because that// method only tells us if the timeline times can be converted to// TimeStamps that can be compared to TimeStamp::Now() or not, *not*// whether the timelines can be converted to TimeStamp values at all.//// Furthermore, we want to be able to use this method when the refresh driver// is under test control (in which case TracksWallclockTime() will return// false).//// Once we introduce timelines that are not time-based we will need to// differentiate between them here and determine how to sort their events.if(!mTimeline){returnresult;}// Check the time is convertible to a timestampif(aTime==TimeDuration::Forever()||mPlaybackRate==0.0||mStartTime.IsNull()){returnresult;}// Invert the standard relation:// current time = (timeline time - start time) * playback rateTimeDurationtimelineTime=TimeDuration(aTime).MultDouble(1.0/mPlaybackRate)+mStartTime.Value();result=mTimeline->ToTimeStamp(timelineTime);returnresult;}TimeStampAnimation::ElapsedTimeToTimeStamp(constStickyTimeDuration&aElapsedTime)const{TimeDurationdelay=mEffect?mEffect->NormalizedTiming().Delay():TimeDuration();returnAnimationTimeToTimeStamp(aElapsedTime+delay);}// https://drafts.csswg.org/web-animations/#silently-set-the-current-timevoidAnimation::SilentlySetCurrentTime(constTimeDuration&aSeekTime){// TODO: Bug 1762238: Introduce "valid seek time" after introducing// CSSNumberish time values.// https://drafts.csswg.org/web-animations-2/#silently-set-the-current-timeif(!mHoldTime.IsNull()||mStartTime.IsNull()||!mTimeline||mTimeline->GetCurrentTimeAsDuration().IsNull()||mPlaybackRate==0.0){mHoldTime.SetValue(aSeekTime);}else{mStartTime=StartTimeFromTimelineTime(mTimeline->GetCurrentTimeAsDuration().Value(),aSeekTime,mPlaybackRate);}if(!mTimeline||mTimeline->GetCurrentTimeAsDuration().IsNull()){mStartTime.SetNull();}mPreviousCurrentTime.SetNull();mResetCurrentTimeOnResume=false;}boolAnimation::ShouldBeSynchronizedWithMainThread(constnsCSSPropertyIDSet&aPropertySet,constnsIFrame*aFrame,AnimationPerformanceWarning::Type&aPerformanceWarning)const{// Only synchronize playing animationsif(!IsPlaying()){returnfalse;}// Currently only transform animations need to be synchronizedif(!aPropertySet.Intersects(nsCSSPropertyIDSet::TransformLikeProperties())){returnfalse;}KeyframeEffect*keyframeEffect=mEffect?mEffect->AsKeyframeEffect():nullptr;if(!keyframeEffect){returnfalse;}returnkeyframeEffect->ShouldBlockAsyncTransformAnimations(aFrame,aPropertySet,aPerformanceWarning);}voidAnimation::UpdateRelevance(){boolwasRelevant=mIsRelevant;mIsRelevant=mReplaceState!=AnimationReplaceState::Removed&&(HasCurrentEffect()||IsInEffect());// Notify animation observers.if(wasRelevant&&!mIsRelevant){MutationObservers::NotifyAnimationRemoved(this);}elseif(!wasRelevant&&mIsRelevant){UpdateHiddenByContentVisibility();MutationObservers::NotifyAnimationAdded(this);}}template<classT>boolIsMarkupAnimation(T*aAnimation){returnaAnimation&&aAnimation->IsTiedToMarkup();}// https://drafts.csswg.org/web-animations/#replaceable-animationboolAnimation::IsReplaceable()const{// We never replace CSS animations or CSS transitions since they are managed// by CSS.if(IsMarkupAnimation(AsCSSAnimation())||IsMarkupAnimation(AsCSSTransition())){returnfalse;}// Only finished animations can be replaced.if(PlayState()!=AnimationPlayState::Finished){returnfalse;}// Already removed animations cannot be replaced.if(ReplaceState()==AnimationReplaceState::Removed){returnfalse;}// We can only replace an animation if we know that, uninterfered, it would// never start playing again. That excludes any animations on timelines that// aren't monotonically increasing.//// If we don't have any timeline at all, then we can't be in the finished// state (since we need both a resolved start time and current time for that)// and will have already returned false above.//// (However, if it ever does become possible to be finished without a timeline// then we will want to return false here since it probably suggests an// animation being driven directly by script, in which case we can't assume// anything about how they will behave.)if(!GetTimeline()||!GetTimeline()->TracksWallclockTime()){returnfalse;}// If the animation doesn't have an effect then we can't determine if it is// filling or not so just leave it alone.if(!GetEffect()){returnfalse;}// At the time of writing we only know about KeyframeEffects. If we introduce// other types of effects we will need to decide if they are replaceable or// not.MOZ_ASSERT(GetEffect()->AsKeyframeEffect(),"Effect should be a keyframe effect");// We only replace animations that are filling.if(GetEffect()->GetComputedTiming().mProgress.IsNull()){returnfalse;}// We should only replace animations with a target element (since otherwise// what other effects would we consider when determining if they are covered// or not?).if(!GetEffect()->AsKeyframeEffect()->GetAnimationTarget()){returnfalse;}returntrue;}boolAnimation::IsRemovable()const{returnReplaceState()==AnimationReplaceState::Active&&IsReplaceable();}voidAnimation::ScheduleReplacementCheck(){MOZ_ASSERT(IsReplaceable(),"Should only schedule a replacement check for a replaceable animation");// If IsReplaceable() is true, the following should also holdMOZ_ASSERT(GetEffect());MOZ_ASSERT(GetEffect()->AsKeyframeEffect());NonOwningAnimationTargettarget=GetEffect()->AsKeyframeEffect()->GetAnimationTarget();MOZ_ASSERT(target);nsPresContext*presContext=nsContentUtils::GetContextForContent(target.mElement);if(presContext){presContext->EffectCompositor()->NoteElementForReducing(target);}}voidAnimation::MaybeScheduleReplacementCheck(){if(!IsReplaceable()){return;}ScheduleReplacementCheck();}voidAnimation::Remove(){MOZ_ASSERT(IsRemovable(),"Should not be trying to remove an effect that is not removable");mReplaceState=AnimationReplaceState::Removed;UpdateEffect(PostRestyleMode::IfNeeded);PostUpdate();QueuePlaybackEvent(nsGkAtoms::onremove,GetTimelineCurrentTimeAsTimeStamp());}boolAnimation::HasLowerCompositeOrderThan(constAnimation&aOther)const{// 0. Object-equality caseif(&aOther==this){returnfalse;}// 1. CSS Transitions sort lowest{autoasCSSTransitionForSorting=[](constAnimation&anim)->constCSSTransition*{constCSSTransition*transition=anim.AsCSSTransition();returntransition&&transition->IsTiedToMarkup()?transition:nullptr;};autothisTransition=asCSSTransitionForSorting(*this);autootherTransition=asCSSTransitionForSorting(aOther);if(thisTransition&&otherTransition){returnthisTransition->HasLowerCompositeOrderThan(*otherTransition);}if(thisTransition||otherTransition){// Cancelled transitions no longer have an owning element. To be strictly// correct we should store a strong reference to the owning element// so that if we arrive here while sorting cancel events, we can sort// them in the correct order.//// However, given that cancel events are almost always queued// synchronously in some deterministic manner, we can be fairly sure// that cancel events will be dispatched in a deterministic order// (which is our only hard requirement until specs say otherwise).// Furthermore, we only reach here when we have events with equal// timestamps so this is an edge case we can probably ignore for now.returnthisTransition;}}// 2. CSS Animations sort next{autoasCSSAnimationForSorting=[](constAnimation&anim)->constCSSAnimation*{constCSSAnimation*animation=anim.AsCSSAnimation();returnanimation&&animation->IsTiedToMarkup()?animation:nullptr;};autothisAnimation=asCSSAnimationForSorting(*this);autootherAnimation=asCSSAnimationForSorting(aOther);if(thisAnimation&&otherAnimation){returnthisAnimation->HasLowerCompositeOrderThan(*otherAnimation);}if(thisAnimation||otherAnimation){returnthisAnimation;}}// Subclasses of Animation repurpose mAnimationIndex to implement their// own brand of composite ordering. However, by this point we should have// handled any such custom composite ordering so we should now have unique// animation indices.MOZ_ASSERT(mAnimationIndex!=aOther.mAnimationIndex,"Animation indices should be unique");// 3. Finally, generic animations sort by their position in the global// animation array.returnmAnimationIndex<aOther.mAnimationIndex;}voidAnimation::WillComposeStyle(){mFinishedAtLastComposeStyle=(PlayState()==AnimationPlayState::Finished);MOZ_ASSERT(mEffect);KeyframeEffect*keyframeEffect=mEffect->AsKeyframeEffect();if(keyframeEffect){keyframeEffect->WillComposeStyle();}}voidAnimation::ComposeStyle(StyleAnimationValueMap&aComposeResult,constInvertibleAnimatedPropertyIDSet&aPropertiesToSkip){if(!mEffect){return;}// In order to prevent flicker, there are a few cases where we want to use// a different time for rendering that would otherwise be returned by// GetCurrentTimeAsDuration. These are://// (a) For animations that are pausing but which are still running on the// compositor. In this case we send a layer transaction that removes the// animation but which also contains the animation values calculated on// the main thread. To prevent flicker when this occurs we want to ensure// the timeline time used to calculate the main thread animation values// does not lag far behind the time used on the compositor. Ideally we// would like to use the "animation ready time", but for now we just use// the current wallclock time. TODO(emilio): After bug 1864425 it seems we// could just use the ready time, or maybe we can remove this?//// (b) For animations that are pausing that we have already taken off the// compositor. In this case we record a pending ready time but we don't// apply it until the next tick. However, while waiting for the next tick,// we should still use the pending ready time as the timeline time. If we// use the regular timeline time the animation may appear jump backwards// if the main thread's timeline time lags behind the compositor.//// (c) For animations that are play-pending due to an aborted pause operation// (i.e. a pause operation that was interrupted before we entered the// paused state). When we cancel a pending pause we might momentarily take// the animation off the compositor, only to re-add it moments later. In// that case the compositor might have been ahead of the main thread so we// should use the current wallclock time to ensure the animation doesn't// temporarily jump backwards.//// To address each of these cases we temporarily tweak the hold time// immediately before updating the style rule and then restore it immediately// afterwards. This is purely to prevent visual flicker. Other behavior// such as dispatching events continues to rely on the regular timeline time.constboolpending=Pending();{AutoRestore<Nullable<TimeDuration>>restoreHoldTime(mHoldTime);if(pending&&mHoldTime.IsNull()&&!mStartTime.IsNull()){Nullable<TimeDuration>timeToUse;if(mTimeline&&mTimeline->TracksWallclockTime()){timeToUse=mTimeline->ToTimelineTime(TimeStamp::Now());}if(!timeToUse.IsNull()){mHoldTime=CurrentTimeFromTimelineTime(timeToUse.Value(),mStartTime.Value(),mPlaybackRate);}}KeyframeEffect*keyframeEffect=mEffect->AsKeyframeEffect();if(keyframeEffect){keyframeEffect->ComposeStyle(aComposeResult,aPropertiesToSkip);}}MOZ_ASSERT(pending==Pending(),"Pending state should not change during the course of compositing");}voidAnimation::NotifyEffectTimingUpdated(){MOZ_ASSERT(mEffect,"We should only update effect timing when we have a target ""effect");UpdateTiming(Animation::SeekFlag::NoSeek,Animation::SyncNotifyFlag::Async);}voidAnimation::NotifyEffectPropertiesUpdated(){MOZ_ASSERT(mEffect,"We should only update effect properties when we have a target ""effect");MaybeScheduleReplacementCheck();}voidAnimation::NotifyEffectTargetUpdated(){MOZ_ASSERT(mEffect,"We should only update the effect target when we have a target ""effect");MaybeScheduleReplacementCheck();}staticTimeStampEnsurePaintIsScheduled(Document&aDoc){PresShell*presShell=aDoc.GetPresShell();if(!presShell){return{};}nsIFrame*rootFrame=presShell->GetRootFrame();if(!rootFrame){return{};}rootFrame->SchedulePaintWithoutInvalidatingObservers();auto*rd=rootFrame->PresContext()->RefreshDriver();if(!rd->IsInRefresh()){return{};}returnrd->MostRecentRefresh(/* aEnsureTimerStarted = */false);}// https://drafts.csswg.org/web-animations/#play-an-animationvoidAnimation::PlayNoUpdate(ErrorResult&aRv,LimitBehavioraLimitBehavior){AutoMutationBatchForAnimationmb(*this);constboolisAutoRewind=aLimitBehavior==LimitBehavior::AutoRewind;constboolabortedPause=mPendingState==PendingState::PausePending;doubleeffectivePlaybackRate=CurrentOrPendingPlaybackRate();Nullable<TimeDuration>currentTime=GetCurrentTimeAsDuration();if(mResetCurrentTimeOnResume){currentTime.SetNull();mResetCurrentTimeOnResume=false;}Nullable<TimeDuration>seekTime;if(isAutoRewind){if(effectivePlaybackRate>=0.0&&(currentTime.IsNull()||currentTime.Value()<TimeDuration()||currentTime.Value()>=EffectEnd())){seekTime.SetValue(TimeDuration());}elseif(effectivePlaybackRate<0.0&&(currentTime.IsNull()||currentTime.Value()<=TimeDuration()||currentTime.Value()>EffectEnd())){if(EffectEnd()==TimeDuration::Forever()){returnaRv.ThrowInvalidStateError("Can't rewind animation with infinite effect end");}seekTime.SetValue(TimeDuration(EffectEnd()));}}if(seekTime.IsNull()&&mStartTime.IsNull()&¤tTime.IsNull()){seekTime.SetValue(TimeDuration());}if(!seekTime.IsNull()){if(HasFiniteTimeline()){mStartTime=seekTime;mHoldTime.SetNull();ApplyPendingPlaybackRate();}else{mHoldTime=seekTime;}}boolreuseReadyPromise=false;if(mPendingState!=PendingState::NotPending){CancelPendingTasks();reuseReadyPromise=true;}// If the hold time is null then we're already playing normally and,// typically, we can bail out here.//// However, there are two cases where we can't do that://// (a) If we just aborted a pause. In this case, for consistency, we need to// go through the motions of doing an asynchronous start.//// (b) If we have timing changes (specifically a change to the playbackRate)// that should be applied asynchronously.//if(mHoldTime.IsNull()&&seekTime.IsNull()&&!abortedPause&&!mPendingPlaybackRate){return;}// Clear the start time until we resolve a new one. We do this except// for the case where we are aborting a pause and don't have a hold time.//// If we're aborting a pause and *do* have a hold time (e.g. because// the animation is finished or we just applied the auto-rewind behavior// above) we should respect it by clearing the start time. If we *don't*// have a hold time we should keep the current start time so that the// the animation continues moving uninterrupted by the aborted pause.//// (If we're not aborting a pause, mHoldTime must be resolved by now// or else we would have returned above.)if(!mHoldTime.IsNull()){mStartTime.SetNull();}if(!reuseReadyPromise){// Clear ready promise. We'll create a new one lazily.mReady=nullptr;}mPendingState=PendingState::PlayPending;mPendingReadyTime={};if(Document*doc=GetRenderedDocument()){if(HasFiniteTimeline()){// Always schedule a task even if we would like to let this animation// immediately ready, per spec.// https://drafts.csswg.org/web-animations/#playing-an-animation-section// If there's no rendered document, we fail to track this animation, so// let the scroll frame to trigger it when ticking.doc->GetOrCreateScrollTimelineAnimationTracker()->AddPending(*this);}// Make sure to try to schedule a tick.mPendingReadyTime=EnsurePaintIsScheduled(*doc);}UpdateTiming(SeekFlag::NoSeek,SyncNotifyFlag::Async);if(IsRelevant()){MutationObservers::NotifyAnimationChanged(this);}}// https://drafts.csswg.org/web-animations/#pause-an-animationvoidAnimation::Pause(ErrorResult&aRv){if(IsPausedOrPausing()){return;}AutoMutationBatchForAnimationmb(*this);Nullable<TimeDuration>seekTime;// If we are transitioning from idle, fill in the current timeif(GetCurrentTimeAsDuration().IsNull()){if(mPlaybackRate>=0.0){seekTime.SetValue(TimeDuration(0));}else{if(EffectEnd()==TimeDuration::Forever()){returnaRv.ThrowInvalidStateError("Can't seek to infinite effect end");}seekTime.SetValue(TimeDuration(EffectEnd()));}}if(!seekTime.IsNull()){if(HasFiniteTimeline()){mStartTime=seekTime;}else{mHoldTime=seekTime;}}boolreuseReadyPromise=false;if(mPendingState==PendingState::PlayPending){CancelPendingTasks();reuseReadyPromise=true;}if(!reuseReadyPromise){// Clear ready promise. We'll create a new one lazily.mReady=nullptr;}mPendingState=PendingState::PausePending;mPendingReadyTime={};// See the relevant PlayPending code for comments.if(Document*doc=GetRenderedDocument()){if(HasFiniteTimeline()){doc->GetOrCreateScrollTimelineAnimationTracker()->AddPending(*this);}mPendingReadyTime=EnsurePaintIsScheduled(*doc);}UpdateTiming(SeekFlag::NoSeek,SyncNotifyFlag::Async);if(IsRelevant()){MutationObservers::NotifyAnimationChanged(this);}PostUpdate();}// https://drafts.csswg.org/web-animations/#play-an-animationvoidAnimation::ResumeAt(constTimeDuration&aReadyTime){// This method is only expected to be called for an animation that is// waiting to play. We can easily adapt it to handle other states// but it's currently not necessary.MOZ_ASSERT(mPendingState==PendingState::PlayPending,"Expected to resume a play-pending animation");MOZ_ASSERT(!mHoldTime.IsNull()||!mStartTime.IsNull(),"An animation in the play-pending state should have either a"" resolved hold time or resolved start time");AutoMutationBatchForAnimationmb(*this);boolhadPendingPlaybackRate=mPendingPlaybackRate.isSome();if(!mHoldTime.IsNull()){// The hold time is set, so we don't need any special handling to preserve// the current time.ApplyPendingPlaybackRate();mStartTime=StartTimeFromTimelineTime(aReadyTime,mHoldTime.Value(),mPlaybackRate);if(mPlaybackRate!=0){mHoldTime.SetNull();}}elseif(!mStartTime.IsNull()&&mPendingPlaybackRate){// Apply any pending playback rate, preserving the current time.TimeDurationcurrentTimeToMatch=CurrentTimeFromTimelineTime(aReadyTime,mStartTime.Value(),mPlaybackRate);ApplyPendingPlaybackRate();mStartTime=StartTimeFromTimelineTime(aReadyTime,currentTimeToMatch,mPlaybackRate);if(mPlaybackRate==0){mHoldTime.SetValue(currentTimeToMatch);}}mPendingState=PendingState::NotPending;UpdateTiming(SeekFlag::NoSeek,SyncNotifyFlag::Sync);// If we had a pending playback rate, we will have now applied it so we need// to notify observers.if(hadPendingPlaybackRate&&IsRelevant()){MutationObservers::NotifyAnimationChanged(this);}if(mReady){mReady->MaybeResolve(this);}}voidAnimation::PauseAt(constTimeDuration&aReadyTime){MOZ_ASSERT(mPendingState==PendingState::PausePending,"Expected to pause a pause-pending animation");if(!mStartTime.IsNull()&&mHoldTime.IsNull()){mHoldTime=CurrentTimeFromTimelineTime(aReadyTime,mStartTime.Value(),mPlaybackRate);}ApplyPendingPlaybackRate();mStartTime.SetNull();mPendingState=PendingState::NotPending;UpdateTiming(SeekFlag::NoSeek,SyncNotifyFlag::Async);if(mReady){mReady->MaybeResolve(this);}}voidAnimation::UpdateTiming(SeekFlagaSeekFlag,SyncNotifyFlagaSyncNotifyFlag){// We call UpdateFinishedState before UpdateEffect because the former// can change the current time, which is used by the latter.UpdateFinishedState(aSeekFlag,aSyncNotifyFlag);UpdateEffect(PostRestyleMode::IfNeeded);if(mTimeline){mTimeline->NotifyAnimationUpdated(*this);}}// https://drafts.csswg.org/web-animations/#update-an-animations-finished-statevoidAnimation::UpdateFinishedState(SeekFlagaSeekFlag,SyncNotifyFlagaSyncNotifyFlag){Nullable<TimeDuration>unconstrainedCurrentTime=aSeekFlag==SeekFlag::NoSeek?GetUnconstrainedCurrentTime():GetCurrentTimeAsDuration();TimeDurationeffectEnd=TimeDuration(EffectEnd());if(!unconstrainedCurrentTime.IsNull()&&!mStartTime.IsNull()&&mPendingState==PendingState::NotPending){if(mPlaybackRate>0.0&&unconstrainedCurrentTime.Value()>=effectEnd){if(aSeekFlag==SeekFlag::DidSeek){mHoldTime=unconstrainedCurrentTime;}elseif(!mPreviousCurrentTime.IsNull()){mHoldTime.SetValue(std::max(mPreviousCurrentTime.Value(),effectEnd));}else{mHoldTime.SetValue(effectEnd);}}elseif(mPlaybackRate<0.0&&unconstrainedCurrentTime.Value()<=TimeDuration()){if(aSeekFlag==SeekFlag::DidSeek){mHoldTime=unconstrainedCurrentTime;}elseif(!mPreviousCurrentTime.IsNull()){mHoldTime.SetValue(std::min(mPreviousCurrentTime.Value(),TimeDuration(0)));}else{mHoldTime.SetValue(0);}}elseif(mPlaybackRate!=0.0&&mTimeline&&!mTimeline->GetCurrentTimeAsDuration().IsNull()){if(aSeekFlag==SeekFlag::DidSeek&&!mHoldTime.IsNull()){mStartTime=StartTimeFromTimelineTime(mTimeline->GetCurrentTimeAsDuration().Value(),mHoldTime.Value(),mPlaybackRate);}mHoldTime.SetNull();}}// We must recalculate the current time to take account of any mHoldTime// changes the code above made.mPreviousCurrentTime=GetCurrentTimeAsDuration();boolcurrentFinishedState=PlayState()==AnimationPlayState::Finished;if(currentFinishedState&&!mFinishedIsResolved){DoFinishNotification(aSyncNotifyFlag);}elseif(!currentFinishedState&&mFinishedIsResolved){ResetFinishedPromise();}}voidAnimation::UpdateEffect(PostRestyleModeaPostRestyle){if(mEffect){UpdateRelevance();KeyframeEffect*keyframeEffect=mEffect->AsKeyframeEffect();if(keyframeEffect){keyframeEffect->NotifyAnimationTimingUpdated(aPostRestyle);}}}voidAnimation::FlushUnanimatedStyle()const{if(Document*doc=GetRenderedDocument()){doc->FlushPendingNotifications(ChangesToFlush(FlushType::Style,false/* flush animations */));}}voidAnimation::PostUpdate(){if(!mEffect){return;}KeyframeEffect*keyframeEffect=mEffect->AsKeyframeEffect();if(!keyframeEffect){return;}keyframeEffect->RequestRestyle(EffectCompositor::RestyleType::Layer);}voidAnimation::CancelPendingTasks(){mPendingState=PendingState::NotPending;}// https://drafts.csswg.org/web-animations/#reset-an-animations-pending-tasksvoidAnimation::ResetPendingTasks(){if(!Pending()){return;}CancelPendingTasks();ApplyPendingPlaybackRate();if(mReady){mReady->MaybeReject(NS_ERROR_DOM_ABORT_ERR);MOZ_ALWAYS_TRUE(mReady->SetAnyPromiseIsHandled());mReady=nullptr;}}// https://drafts.csswg.org/web-animations-2/#at-progress-timeline-boundary/* static*/Animation::ProgressTimelinePositionAnimation::AtProgressTimelineBoundary(constNullable<TimeDuration>&aTimelineDuration,constNullable<TimeDuration>&aCurrentTime,constTimeDuration&aEffectStartTime,constdoubleaPlaybackRate){// Based on changed defined in: https://github.com/w3c/csswg-drafts/pull/6702// 1. If any of the following conditions are true:// * the associated animation's timeline is not a progress-based timeline,// or// * the associated animation's timeline duration is unresolved or zero,// or// * the animation's playback rate is zero// return false// Note: We can detect a progress-based timeline by relying on the fact that// monotonic timelines (i.e. non-progress-based timelines) have an unresolved// timeline duration.if(aTimelineDuration.IsNull()||aTimelineDuration.Value().IsZero()||aPlaybackRate==0.0){returnProgressTimelinePosition::NotBoundary;}// 2. Let effective start time be the animation's start time if resolved, or// zero otherwise.constTimeDuration&effectiveStartTime=aEffectStartTime;// 3. Let effective timeline time be (animation's current time / animation's// playback rate) + effective start time.// Note: we use zero if the current time is unresolved. See the spec issue:// https://github.com/w3c/csswg-drafts/issues/7458constTimeDurationeffectiveTimelineTime=(aCurrentTime.IsNull()?TimeDuration():aCurrentTime.Value().MultDouble(1.0/aPlaybackRate))+effectiveStartTime;// 4. Let effective timeline progress be (effective timeline time / timeline// duration)// 5. If effective timeline progress is 0 or 1, return true,// We avoid the division here but it is effectively the same as 4 & 5 above.returneffectiveTimelineTime.IsZero()||(AnimationUtils::IsWithinAnimationTimeTolerance(effectiveTimelineTime,aTimelineDuration.Value()))?ProgressTimelinePosition::Boundary:ProgressTimelinePosition::NotBoundary;}StickyTimeDurationAnimation::EffectEnd()const{if(!mEffect){returnStickyTimeDuration(0);}returnmEffect->NormalizedTiming().EndTime();}Document*Animation::GetRenderedDocument()const{if(!mEffect||!mEffect->AsKeyframeEffect()){returnnullptr;}returnmEffect->AsKeyframeEffect()->GetRenderedDocument();}Document*Animation::GetTimelineDocument()const{returnmTimeline?mTimeline->GetDocument():nullptr;}voidAnimation::UpdateScrollTimelineAnimationTracker(AnimationTimeline*aOldTimeline,AnimationTimeline*aNewTimeline){// If we are still in pending, we may have to move this animation into the// correct animation tracker.Document*doc=GetRenderedDocument();if(!doc||!Pending()){return;}constboolfromFiniteTimeline=aOldTimeline&&!aOldTimeline->IsMonotonicallyIncreasing();constbooltoFiniteTimeline=aNewTimeline&&!aNewTimeline->IsMonotonicallyIncreasing();if(fromFiniteTimeline==toFiniteTimeline){return;}if(toFiniteTimeline){doc->GetOrCreateScrollTimelineAnimationTracker()->AddPending(*this);}else{// From scroll-timeline to null/document-timelineif(auto*tracker=doc->GetScrollTimelineAnimationTracker()){tracker->RemovePending(*this);}EnsurePaintIsScheduled(*doc);}}classAsyncFinishNotification:publicMicroTaskRunnable{public:explicitAsyncFinishNotification(Animation*aAnimation):mAnimation(aAnimation){}virtualvoidRun(AutoSlowOperation&aAso)override{mAnimation->DoFinishNotificationImmediately(this);mAnimation=nullptr;}virtualboolSuppressed()override{nsIGlobalObject*global=mAnimation->GetOwnerGlobal();returnglobal&&global->IsInSyncOperation();}private:RefPtr<Animation>mAnimation;};voidAnimation::DoFinishNotification(SyncNotifyFlagaSyncNotifyFlag){CycleCollectedJSContext*context=CycleCollectedJSContext::Get();if(aSyncNotifyFlag==SyncNotifyFlag::Sync){DoFinishNotificationImmediately();}elseif(!mFinishNotificationTask){RefPtr<MicroTaskRunnable>runnable=newAsyncFinishNotification(this);context->DispatchToMicroTask(do_AddRef(runnable));mFinishNotificationTask=std::move(runnable);}}voidAnimation::ResetFinishedPromise(){mFinishedIsResolved=false;mFinished=nullptr;}voidAnimation::MaybeResolveFinishedPromise(){if(mFinished){mFinished->MaybeResolve(this);}mFinishedIsResolved=true;}voidAnimation::DoFinishNotificationImmediately(MicroTaskRunnable*aAsync){if(aAsync&&aAsync!=mFinishNotificationTask){return;}mFinishNotificationTask=nullptr;if(PlayState()!=AnimationPlayState::Finished){return;}MaybeResolveFinishedPromise();QueuePlaybackEvent(nsGkAtoms::onfinish,AnimationTimeToTimeStamp(EffectEnd()));}voidAnimation::QueuePlaybackEvent(nsAtom*aOnEvent,TimeStamp&&aScheduledEventTime){// Use document for timing.// https://drafts.csswg.org/web-animations-1/#document-for-timingDocument*doc=GetTimelineDocument();if(!doc){return;}nsPresContext*presContext=doc->GetPresContext();if(!presContext){return;}Nullable<double>currentTime;if(aOnEvent==nsGkAtoms::onfinish||aOnEvent==nsGkAtoms::onremove){currentTime=GetCurrentTimeAsDouble();}Nullable<double>timelineTime;if(mTimeline){timelineTime=mTimeline->GetCurrentTimeAsDouble();}presContext->AnimationEventDispatcher()->QueueEvent(AnimationEventInfo(aOnEvent,currentTime,timelineTime,std::move(aScheduledEventTime),this));}boolAnimation::IsRunningOnCompositor()const{returnmEffect&&mEffect->AsKeyframeEffect()&&mEffect->AsKeyframeEffect()->IsRunningOnCompositor();}boolAnimation::HasCurrentEffect()const{returnGetEffect()&&GetEffect()->IsCurrent();}boolAnimation::IsInEffect()const{returnGetEffect()&&GetEffect()->IsInEffect();}voidAnimation::SetHiddenByContentVisibility(boolhidden){if(mHiddenByContentVisibility==hidden){return;}mHiddenByContentVisibility=hidden;if(!GetTimeline()){return;}GetTimeline()->NotifyAnimationContentVisibilityChanged(this,!hidden);}voidAnimation::UpdateHiddenByContentVisibility(){// To be consistent with nsIFrame::UpdateAnimationVisibility, here we only// deal with CSSAnimation and CSSTransition.if(!AsCSSAnimation()&&!AsCSSTransition()){return;}NonOwningAnimationTargettarget=GetTargetForAnimation();if(!target){return;}// If a CSS animation or CSS transition is no longer associated with an owning// element, it behaves like a programmatic web animation, c-v shouldn't hide// it.boolhasOwningElement=IsMarkupAnimation(AsCSSAnimation())||IsMarkupAnimation(AsCSSTransition());if(auto*frame=target.mElement->GetPrimaryFrame()){SetHiddenByContentVisibility(hasOwningElement&&frame->IsHiddenByContentVisibilityOnAnyAncestor());}}StickyTimeDurationAnimation::IntervalStartTime(constStickyTimeDuration&aActiveDuration)const{MOZ_ASSERT(AsCSSTransition()||AsCSSAnimation(),"Should be called for CSS animations or transitions");staticconstexprStickyTimeDurationzeroDuration=StickyTimeDuration();returnstd::max(std::min(StickyTimeDuration(-mEffect->NormalizedTiming().Delay()),aActiveDuration),zeroDuration);}// Later side of the elapsed time range reported in CSS Animations and CSS// Transitions events.//// https://drafts.csswg.org/css-animations-2/#interval-end// https://drafts.csswg.org/css-transitions-2/#interval-endStickyTimeDurationAnimation::IntervalEndTime(constStickyTimeDuration&aActiveDuration)const{MOZ_ASSERT(AsCSSTransition()||AsCSSAnimation(),"Should be called for CSS animations or transitions");staticconstexprStickyTimeDurationzeroDuration=StickyTimeDuration();constStickyTimeDuration&effectEnd=EffectEnd();// If both "associated effect end" and "start delay" are Infinity, we skip it// because we will get NaN when computing "Infinity - Infinity", and// using NaN in std::min or std::max is undefined.if(MOZ_UNLIKELY(effectEnd==TimeDuration::Forever()&&effectEnd==mEffect->NormalizedTiming().Delay())){// Note: If we use TimeDuration::Forever(), within our animation event// handling, we'd end up turning that into a null TimeStamp which can causes// errors if we try to do any arithmetic with it. Given that we should never// end up _using_ the interval end time. So returning zeroDuration here is// probably fine.returnzeroDuration;}returnstd::max(std::min(effectEnd-mEffect->NormalizedTiming().Delay(),aActiveDuration),zeroDuration);}}// namespace mozilla::dom