LCOV - code coverage report
Current view: top level - dom/animation - Animation.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 264 659 40.1 %
Date: 2017-07-14 16:53:18 Functions: 33 70 47.1 %
Legend: Lines: hit not hit

          Line data    Source code
       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/. */
       6             : 
       7             : #include "Animation.h"
       8             : #include "AnimationUtils.h"
       9             : #include "mozilla/dom/AnimationBinding.h"
      10             : #include "mozilla/dom/AnimationPlaybackEvent.h"
      11             : #include "mozilla/dom/DocumentTimeline.h"
      12             : #include "mozilla/AnimationTarget.h"
      13             : #include "mozilla/AutoRestore.h"
      14             : #include "mozilla/AsyncEventDispatcher.h" // For AsyncEventDispatcher
      15             : #include "mozilla/Maybe.h" // For Maybe
      16             : #include "mozilla/TypeTraits.h" // For Forward<>
      17             : #include "nsAnimationManager.h" // For CSSAnimation
      18             : #include "nsDOMMutationObserver.h" // For nsAutoAnimationMutationBatch
      19             : #include "nsIDocument.h" // For nsIDocument
      20             : #include "nsIPresShell.h" // For nsIPresShell
      21             : #include "nsThreadUtils.h" // For nsRunnableMethod and nsRevocableEventPtr
      22             : #include "nsTransitionManager.h" // For CSSTransition
      23             : #include "PendingAnimationTracker.h" // For PendingAnimationTracker
      24             : 
      25             : namespace mozilla {
      26             : namespace dom {
      27             : 
      28             : // Static members
      29             : uint64_t Animation::sNextAnimationIndex = 0;
      30             : 
      31           0 : NS_IMPL_CYCLE_COLLECTION_INHERITED(Animation, DOMEventTargetHelper,
      32             :                                    mTimeline,
      33             :                                    mEffect,
      34             :                                    mReady,
      35             :                                    mFinished)
      36             : 
      37          73 : NS_IMPL_ADDREF_INHERITED(Animation, DOMEventTargetHelper)
      38          69 : NS_IMPL_RELEASE_INHERITED(Animation, DOMEventTargetHelper)
      39             : 
      40          26 : NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(Animation)
      41          24 : NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
      42             : 
      43             : JSObject*
      44           0 : Animation::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
      45             : {
      46           0 :   return dom::AnimationBinding::Wrap(aCx, this, aGivenProto);
      47             : }
      48             : 
      49             : // ---------------------------------------------------------------------------
      50             : //
      51             : // Utility methods
      52             : //
      53             : // ---------------------------------------------------------------------------
      54             : 
      55             : namespace {
      56             :   // A wrapper around nsAutoAnimationMutationBatch that looks up the
      57             :   // appropriate document from the supplied animation.
      58           4 :   class MOZ_RAII AutoMutationBatchForAnimation {
      59             :   public:
      60           4 :     explicit AutoMutationBatchForAnimation(const Animation& aAnimation
      61           4 :                                            MOZ_GUARD_OBJECT_NOTIFIER_PARAM) {
      62           4 :       MOZ_GUARD_OBJECT_NOTIFIER_INIT;
      63             :       Maybe<NonOwningAnimationTarget> target =
      64           6 :         nsNodeUtils::GetTargetForAnimation(&aAnimation);
      65           4 :       if (!target) {
      66           2 :         return;
      67             :       }
      68             : 
      69             :       // For mutation observers, we use the OwnerDoc.
      70           2 :       nsIDocument* doc = target->mElement->OwnerDoc();
      71           2 :       if (!doc) {
      72           0 :         return;
      73             :       }
      74             : 
      75           2 :       mAutoBatch.emplace(doc);
      76             :     }
      77             : 
      78             :   private:
      79             :     MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
      80             :     Maybe<nsAutoAnimationMutationBatch> mAutoBatch;
      81             :   };
      82             : }
      83             : 
      84             : // ---------------------------------------------------------------------------
      85             : //
      86             : // Animation interface:
      87             : //
      88             : // ---------------------------------------------------------------------------
      89             : /* static */ already_AddRefed<Animation>
      90           0 : Animation::Constructor(const GlobalObject& aGlobal,
      91             :                        AnimationEffectReadOnly* aEffect,
      92             :                        const Optional<AnimationTimeline*>& aTimeline,
      93             :                        ErrorResult& aRv)
      94             : {
      95           0 :   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
      96           0 :   RefPtr<Animation> animation = new Animation(global);
      97             : 
      98             :   AnimationTimeline* timeline;
      99           0 :   if (aTimeline.WasPassed()) {
     100           0 :     timeline = aTimeline.Value();
     101             :   } else {
     102             :     nsIDocument* document =
     103           0 :       AnimationUtils::GetCurrentRealmDocument(aGlobal.Context());
     104           0 :     if (!document) {
     105           0 :       aRv.Throw(NS_ERROR_FAILURE);
     106           0 :       return nullptr;
     107             :     }
     108           0 :     timeline = document->Timeline();
     109             :   }
     110             : 
     111           0 :   animation->SetTimelineNoUpdate(timeline);
     112           0 :   animation->SetEffectNoUpdate(aEffect);
     113             : 
     114           0 :   return animation.forget();
     115             : }
     116             : 
     117             : void
     118           0 : Animation::SetId(const nsAString& aId)
     119             : {
     120           0 :   if (mId == aId) {
     121           0 :     return;
     122             :   }
     123           0 :   mId = aId;
     124           0 :   nsNodeUtils::AnimationChanged(this);
     125             : }
     126             : 
     127             : void
     128           0 : Animation::SetEffect(AnimationEffectReadOnly* aEffect)
     129             : {
     130           0 :   SetEffectNoUpdate(aEffect);
     131           0 :   PostUpdate();
     132           0 : }
     133             : 
     134             : // https://w3c.github.io/web-animations/#setting-the-target-effect
     135             : void
     136           2 : Animation::SetEffectNoUpdate(AnimationEffectReadOnly* aEffect)
     137             : {
     138           4 :   RefPtr<Animation> kungFuDeathGrip(this);
     139             : 
     140           2 :   if (mEffect == aEffect) {
     141           0 :     return;
     142             :   }
     143             : 
     144           4 :   AutoMutationBatchForAnimation mb(*this);
     145           2 :   bool wasRelevant = mIsRelevant;
     146             : 
     147           2 :   if (mEffect) {
     148           0 :     if (!aEffect) {
     149             :       // If the new effect is null, call ResetPendingTasks before clearing
     150             :       // mEffect since ResetPendingTasks needs it to get the appropriate
     151             :       // PendingAnimationTracker.
     152           0 :       ResetPendingTasks();
     153             :     }
     154             : 
     155             :     // We need to notify observers now because once we set mEffect to null
     156             :     // we won't be able to find the target element to notify.
     157           0 :     if (mIsRelevant) {
     158           0 :       nsNodeUtils::AnimationRemoved(this);
     159             :     }
     160             : 
     161             :     // Break links with the old effect and then drop it.
     162           0 :     RefPtr<AnimationEffectReadOnly> oldEffect = mEffect;
     163           0 :     mEffect = nullptr;
     164           0 :     oldEffect->SetAnimation(nullptr);
     165             : 
     166             :     // The following will not do any notification because mEffect is null.
     167           0 :     UpdateRelevance();
     168             :   }
     169             : 
     170           2 :   if (aEffect) {
     171             :     // Break links from the new effect to its previous animation, if any.
     172           4 :     RefPtr<AnimationEffectReadOnly> newEffect = aEffect;
     173           2 :     Animation* prevAnim = aEffect->GetAnimation();
     174           2 :     if (prevAnim) {
     175           0 :       prevAnim->SetEffect(nullptr);
     176             :     }
     177             : 
     178             :     // Create links with the new effect. SetAnimation(this) will also update
     179             :     // mIsRelevant of this animation, and then notify mutation observer if
     180             :     // needed by calling Animation::UpdateRelevance(), so we don't need to
     181             :     // call it again.
     182           2 :     mEffect = newEffect;
     183           2 :     mEffect->SetAnimation(this);
     184             : 
     185             :     // Notify possible add or change.
     186             :     // If the target is different, the change notification will be ignored by
     187             :     // AutoMutationBatchForAnimation.
     188           2 :     if (wasRelevant && mIsRelevant) {
     189           0 :       nsNodeUtils::AnimationChanged(this);
     190             :     }
     191             : 
     192             :     // Reschedule pending pause or pending play tasks.
     193             :     // If we have a pending animation, it will either be registered
     194             :     // in the pending animation tracker and have a null pending ready time,
     195             :     // or, after it has been painted, it will be removed from the tracker
     196             :     // and assigned a pending ready time.
     197             :     // After updating the effect we'll typically need to repaint so if we've
     198             :     // already been assigned a pending ready time, we should clear it and put
     199             :     // the animation back in the tracker.
     200           2 :     if (!mPendingReadyTime.IsNull()) {
     201           0 :       mPendingReadyTime.SetNull();
     202             : 
     203           0 :       nsIDocument* doc = GetRenderedDocument();
     204           0 :       if (doc) {
     205             :         PendingAnimationTracker* tracker =
     206           0 :           doc->GetOrCreatePendingAnimationTracker();
     207           0 :         if (mPendingState == PendingState::PlayPending) {
     208           0 :           tracker->AddPlayPending(*this);
     209             :         } else {
     210           0 :           tracker->AddPausePending(*this);
     211             :         }
     212             :       }
     213             :     }
     214             :   }
     215             : 
     216           2 :   UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
     217             : }
     218             : 
     219             : void
     220           0 : Animation::SetTimeline(AnimationTimeline* aTimeline)
     221             : {
     222           0 :   SetTimelineNoUpdate(aTimeline);
     223           0 :   PostUpdate();
     224           0 : }
     225             : 
     226             : // https://w3c.github.io/web-animations/#setting-the-timeline
     227             : void
     228           2 : Animation::SetTimelineNoUpdate(AnimationTimeline* aTimeline)
     229             : {
     230           2 :   if (mTimeline == aTimeline) {
     231           0 :     return;
     232             :   }
     233             : 
     234             :   StickyTimeDuration activeTime = mEffect
     235           2 :                                   ? mEffect->GetComputedTiming().mActiveTime
     236           4 :                                   : StickyTimeDuration();
     237             : 
     238           4 :   RefPtr<AnimationTimeline> oldTimeline = mTimeline;
     239           2 :   if (oldTimeline) {
     240           0 :     oldTimeline->RemoveAnimation(this);
     241             :   }
     242             : 
     243           2 :   mTimeline = aTimeline;
     244           2 :   if (!mStartTime.IsNull()) {
     245           0 :     mHoldTime.SetNull();
     246             :   }
     247             : 
     248           2 :   if (!aTimeline) {
     249           0 :     MaybeQueueCancelEvent(activeTime);
     250             :   }
     251           2 :   UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
     252             : }
     253             : 
     254             : // https://w3c.github.io/web-animations/#set-the-animation-start-time
     255             : void
     256           0 : Animation::SetStartTime(const Nullable<TimeDuration>& aNewStartTime)
     257             : {
     258           0 :   if (aNewStartTime == mStartTime) {
     259           0 :     return;
     260             :   }
     261             : 
     262           0 :   AutoMutationBatchForAnimation mb(*this);
     263             : 
     264           0 :   Nullable<TimeDuration> timelineTime;
     265           0 :   if (mTimeline) {
     266             :     // The spec says to check if the timeline is active (has a resolved time)
     267             :     // before using it here, but we don't need to since it's harmless to set
     268             :     // the already null time to null.
     269           0 :     timelineTime = mTimeline->GetCurrentTime();
     270             :   }
     271           0 :   if (timelineTime.IsNull() && !aNewStartTime.IsNull()) {
     272           0 :     mHoldTime.SetNull();
     273             :   }
     274             : 
     275           0 :   Nullable<TimeDuration> previousCurrentTime = GetCurrentTime();
     276           0 :   mStartTime = aNewStartTime;
     277           0 :   if (!aNewStartTime.IsNull()) {
     278           0 :     if (mPlaybackRate != 0.0) {
     279           0 :       mHoldTime.SetNull();
     280             :     }
     281             :   } else {
     282           0 :     mHoldTime = previousCurrentTime;
     283             :   }
     284             : 
     285           0 :   CancelPendingTasks();
     286           0 :   if (mReady) {
     287             :     // We may have already resolved mReady, but in that case calling
     288             :     // MaybeResolve is a no-op, so that's okay.
     289           0 :     mReady->MaybeResolve(this);
     290             :   }
     291             : 
     292           0 :   UpdateTiming(SeekFlag::DidSeek, SyncNotifyFlag::Async);
     293           0 :   if (IsRelevant()) {
     294           0 :     nsNodeUtils::AnimationChanged(this);
     295             :   }
     296           0 :   PostUpdate();
     297             : }
     298             : 
     299             : // https://w3c.github.io/web-animations/#current-time
     300             : Nullable<TimeDuration>
     301         312 : Animation::GetCurrentTime() const
     302             : {
     303         312 :   Nullable<TimeDuration> result;
     304         312 :   if (!mHoldTime.IsNull()) {
     305         184 :     result = mHoldTime;
     306         184 :     return result;
     307             :   }
     308             : 
     309         128 :   if (mTimeline && !mStartTime.IsNull()) {
     310         172 :     Nullable<TimeDuration> timelineTime = mTimeline->GetCurrentTime();
     311          86 :     if (!timelineTime.IsNull()) {
     312         172 :       result.SetValue((timelineTime.Value() - mStartTime.Value())
     313         172 :                         .MultDouble(mPlaybackRate));
     314             :     }
     315             :   }
     316         128 :   return result;
     317             : }
     318             : 
     319             : // https://w3c.github.io/web-animations/#set-the-current-time
     320             : void
     321           0 : Animation::SetCurrentTime(const TimeDuration& aSeekTime)
     322             : {
     323             :   // Return early if the current time has not changed. However, if we
     324             :   // are pause-pending, then setting the current time to any value
     325             :   // including the current value has the effect of aborting the
     326             :   // pause so we should not return early in that case.
     327           0 :   if (mPendingState != PendingState::PausePending &&
     328           0 :       Nullable<TimeDuration>(aSeekTime) == GetCurrentTime()) {
     329           0 :     return;
     330             :   }
     331             : 
     332           0 :   AutoMutationBatchForAnimation mb(*this);
     333             : 
     334           0 :   SilentlySetCurrentTime(aSeekTime);
     335             : 
     336           0 :   if (mPendingState == PendingState::PausePending) {
     337             :     // Finish the pause operation
     338           0 :     mHoldTime.SetValue(aSeekTime);
     339           0 :     mStartTime.SetNull();
     340             : 
     341           0 :     if (mReady) {
     342           0 :       mReady->MaybeResolve(this);
     343             :     }
     344           0 :     CancelPendingTasks();
     345             :   }
     346             : 
     347           0 :   UpdateTiming(SeekFlag::DidSeek, SyncNotifyFlag::Async);
     348           0 :   if (IsRelevant()) {
     349           0 :     nsNodeUtils::AnimationChanged(this);
     350             :   }
     351           0 :   PostUpdate();
     352             : }
     353             : 
     354             : // https://w3c.github.io/web-animations/#set-the-animation-playback-rate
     355             : void
     356           0 : Animation::SetPlaybackRate(double aPlaybackRate)
     357             : {
     358           0 :   if (aPlaybackRate == mPlaybackRate) {
     359           0 :     return;
     360             :   }
     361             : 
     362           0 :   AutoMutationBatchForAnimation mb(*this);
     363             : 
     364           0 :   Nullable<TimeDuration> previousTime = GetCurrentTime();
     365           0 :   mPlaybackRate = aPlaybackRate;
     366           0 :   if (!previousTime.IsNull()) {
     367           0 :     SetCurrentTime(previousTime.Value());
     368             :   }
     369             : 
     370             :   // In the case where GetCurrentTime() returns the same result before and
     371             :   // after updating mPlaybackRate, SetCurrentTime will return early since,
     372             :   // as far as it can tell, nothing has changed.
     373             :   // As a result, we need to perform the following updates here:
     374             :   // - update timing (since, if the sign of the playback rate has changed, our
     375             :   //   finished state may have changed),
     376             :   // - dispatch a change notification for the changed playback rate, and
     377             :   // - update the playback rate on animations on layers.
     378           0 :   UpdateTiming(SeekFlag::DidSeek, SyncNotifyFlag::Async);
     379           0 :   if (IsRelevant()) {
     380           0 :     nsNodeUtils::AnimationChanged(this);
     381             :   }
     382           0 :   PostUpdate();
     383             : }
     384             : 
     385             : // https://w3c.github.io/web-animations/#play-state
     386             : AnimationPlayState
     387         166 : Animation::PlayState() const
     388             : {
     389         166 :   if (mPendingState != PendingState::NotPending) {
     390         108 :     return AnimationPlayState::Pending;
     391             :   }
     392             : 
     393         116 :   Nullable<TimeDuration> currentTime = GetCurrentTime();
     394          58 :   if (currentTime.IsNull()) {
     395          12 :     return AnimationPlayState::Idle;
     396             :   }
     397             : 
     398          46 :   if (mStartTime.IsNull()) {
     399           0 :     return AnimationPlayState::Paused;
     400             :   }
     401             : 
     402         138 :   if ((mPlaybackRate > 0.0 && currentTime.Value() >= EffectEnd()) ||
     403          78 :       (mPlaybackRate < 0.0 && currentTime.Value() <= TimeDuration()))  {
     404          14 :     return AnimationPlayState::Finished;
     405             :   }
     406             : 
     407          32 :   return AnimationPlayState::Running;
     408             : }
     409             : 
     410             : Promise*
     411           0 : Animation::GetReady(ErrorResult& aRv)
     412             : {
     413           0 :   nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal();
     414           0 :   if (!mReady && global) {
     415           0 :     mReady = Promise::Create(global, aRv); // Lazily create on demand
     416             :   }
     417           0 :   if (!mReady) {
     418           0 :     aRv.Throw(NS_ERROR_FAILURE);
     419           0 :     return nullptr;
     420             :   }
     421           0 :   if (PlayState() != AnimationPlayState::Pending) {
     422           0 :     mReady->MaybeResolve(this);
     423             :   }
     424           0 :   return mReady;
     425             : }
     426             : 
     427             : Promise*
     428           0 : Animation::GetFinished(ErrorResult& aRv)
     429             : {
     430           0 :   nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal();
     431           0 :   if (!mFinished && global) {
     432           0 :     mFinished = Promise::Create(global, aRv); // Lazily create on demand
     433             :   }
     434           0 :   if (!mFinished) {
     435           0 :     aRv.Throw(NS_ERROR_FAILURE);
     436           0 :     return nullptr;
     437             :   }
     438           0 :   if (mFinishedIsResolved) {
     439           0 :     MaybeResolveFinishedPromise();
     440             :   }
     441           0 :   return mFinished;
     442             : }
     443             : 
     444             : void
     445           0 : Animation::Cancel()
     446             : {
     447           0 :   CancelNoUpdate();
     448           0 :   PostUpdate();
     449           0 : }
     450             : 
     451             : // https://w3c.github.io/web-animations/#finish-an-animation
     452             : void
     453           0 : Animation::Finish(ErrorResult& aRv)
     454             : {
     455           0 :   if (mPlaybackRate == 0 ||
     456           0 :       (mPlaybackRate > 0 && EffectEnd() == TimeDuration::Forever())) {
     457           0 :     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     458           0 :     return;
     459             :   }
     460             : 
     461           0 :   AutoMutationBatchForAnimation mb(*this);
     462             : 
     463             :   // Seek to the end
     464             :   TimeDuration limit =
     465           0 :     mPlaybackRate > 0 ? TimeDuration(EffectEnd()) : TimeDuration(0);
     466           0 :   bool didChange = GetCurrentTime() != Nullable<TimeDuration>(limit);
     467           0 :   SilentlySetCurrentTime(limit);
     468             : 
     469             :   // If we are paused or play-pending we need to fill in the start time in
     470             :   // order to transition to the finished state.
     471             :   //
     472             :   // We only do this, however, if we have an active timeline. If we have an
     473             :   // inactive timeline we can't transition into the finished state just like
     474             :   // we can't transition to the running state (this finished state is really
     475             :   // a substate of the running state).
     476           0 :   if (mStartTime.IsNull() &&
     477           0 :       mTimeline &&
     478           0 :       !mTimeline->GetCurrentTime().IsNull()) {
     479           0 :     mStartTime.SetValue(mTimeline->GetCurrentTime().Value() -
     480           0 :                         limit.MultDouble(1.0 / mPlaybackRate));
     481           0 :     didChange = true;
     482             :   }
     483             : 
     484             :   // If we just resolved the start time for a pause or play-pending
     485             :   // animation, we need to clear the task. We don't do this as a branch of
     486             :   // the above however since we can have a play-pending animation with a
     487             :   // resolved start time if we aborted a pause operation.
     488           0 :   if (!mStartTime.IsNull() &&
     489           0 :       (mPendingState == PendingState::PlayPending ||
     490           0 :        mPendingState == PendingState::PausePending)) {
     491           0 :     if (mPendingState == PendingState::PausePending) {
     492           0 :       mHoldTime.SetNull();
     493             :     }
     494           0 :     CancelPendingTasks();
     495           0 :     didChange = true;
     496           0 :     if (mReady) {
     497           0 :       mReady->MaybeResolve(this);
     498             :     }
     499             :   }
     500           0 :   UpdateTiming(SeekFlag::DidSeek, SyncNotifyFlag::Sync);
     501           0 :   if (didChange && IsRelevant()) {
     502           0 :     nsNodeUtils::AnimationChanged(this);
     503             :   }
     504           0 :   PostUpdate();
     505             : }
     506             : 
     507             : void
     508           0 : Animation::Play(ErrorResult& aRv, LimitBehavior aLimitBehavior)
     509             : {
     510           0 :   PlayNoUpdate(aRv, aLimitBehavior);
     511           0 :   PostUpdate();
     512           0 : }
     513             : 
     514             : void
     515           0 : Animation::Pause(ErrorResult& aRv)
     516             : {
     517           0 :   PauseNoUpdate(aRv);
     518           0 :   PostUpdate();
     519           0 : }
     520             : 
     521             : // https://w3c.github.io/web-animations/#reverse-an-animation
     522             : void
     523           0 : Animation::Reverse(ErrorResult& aRv)
     524             : {
     525           0 :   if (!mTimeline || mTimeline->GetCurrentTime().IsNull()) {
     526           0 :     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     527           0 :     return;
     528             :   }
     529             : 
     530           0 :   if (mPlaybackRate == 0.0) {
     531           0 :     return;
     532             :   }
     533             : 
     534           0 :   AutoMutationBatchForAnimation mb(*this);
     535             : 
     536           0 :   SilentlySetPlaybackRate(-mPlaybackRate);
     537           0 :   Play(aRv, LimitBehavior::AutoRewind);
     538             : 
     539             :   // If Play() threw, restore state and don't report anything to mutation
     540             :   // observers.
     541           0 :   if (aRv.Failed()) {
     542           0 :     SilentlySetPlaybackRate(-mPlaybackRate);
     543           0 :     return;
     544             :   }
     545             : 
     546           0 :   if (IsRelevant()) {
     547           0 :     nsNodeUtils::AnimationChanged(this);
     548             :   }
     549             :   // Play(), above, unconditionally calls PostUpdate so we don't need to do
     550             :   // it here.
     551             : }
     552             : 
     553             : // ---------------------------------------------------------------------------
     554             : //
     555             : // JS wrappers for Animation interface:
     556             : //
     557             : // ---------------------------------------------------------------------------
     558             : 
     559             : Nullable<double>
     560           0 : Animation::GetStartTimeAsDouble() const
     561             : {
     562           0 :   return AnimationUtils::TimeDurationToDouble(mStartTime);
     563             : }
     564             : 
     565             : void
     566           0 : Animation::SetStartTimeAsDouble(const Nullable<double>& aStartTime)
     567             : {
     568           0 :   return SetStartTime(AnimationUtils::DoubleToTimeDuration(aStartTime));
     569             : }
     570             : 
     571             : Nullable<double>
     572           2 : Animation::GetCurrentTimeAsDouble() const
     573             : {
     574           2 :   return AnimationUtils::TimeDurationToDouble(GetCurrentTime());
     575             : }
     576             : 
     577             : void
     578           0 : Animation::SetCurrentTimeAsDouble(const Nullable<double>& aCurrentTime,
     579             :                                         ErrorResult& aRv)
     580             : {
     581           0 :   if (aCurrentTime.IsNull()) {
     582           0 :     if (!GetCurrentTime().IsNull()) {
     583           0 :       aRv.Throw(NS_ERROR_DOM_TYPE_ERR);
     584             :     }
     585           0 :     return;
     586             :   }
     587             : 
     588           0 :   return SetCurrentTime(TimeDuration::FromMilliseconds(aCurrentTime.Value()));
     589             : }
     590             : 
     591             : // ---------------------------------------------------------------------------
     592             : 
     593             : void
     594          24 : Animation::Tick()
     595             : {
     596             :   // Finish pending if we have a pending ready time, but only if we also
     597             :   // have an active timeline.
     598         114 :   if (mPendingState != PendingState::NotPending &&
     599          22 :       !mPendingReadyTime.IsNull() &&
     600          54 :       mTimeline &&
     601          30 :       !mTimeline->GetCurrentTime().IsNull()) {
     602             :     // Even though mPendingReadyTime is initialized using TimeStamp::Now()
     603             :     // during the *previous* tick of the refresh driver, it can still be
     604             :     // ahead of the *current* timeline time when we are using the
     605             :     // vsync timer so we need to clamp it to the timeline time.
     606           4 :     mPendingReadyTime.SetValue(std::min(mTimeline->GetCurrentTime().Value(),
     607           4 :                                         mPendingReadyTime.Value()));
     608           2 :     FinishPendingAt(mPendingReadyTime.Value());
     609           2 :     mPendingReadyTime.SetNull();
     610             :   }
     611             : 
     612          24 :   if (IsPossiblyOrphanedPendingAnimation()) {
     613           0 :     MOZ_ASSERT(mTimeline && !mTimeline->GetCurrentTime().IsNull(),
     614             :                "Orphaned pending animations should have an active timeline");
     615           0 :     FinishPendingAt(mTimeline->GetCurrentTime().Value());
     616             :   }
     617             : 
     618          24 :   UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
     619             : 
     620          24 :   if (!mEffect) {
     621           0 :     return;
     622             :   }
     623             : 
     624             :   // Update layers if we are newly finished.
     625          24 :   KeyframeEffectReadOnly* keyframeEffect = mEffect->AsKeyframeEffect();
     626          48 :   if (keyframeEffect &&
     627          48 :       !keyframeEffect->Properties().IsEmpty() &&
     628          72 :       !mFinishedAtLastComposeStyle &&
     629          24 :       PlayState() == AnimationPlayState::Finished) {
     630           2 :     PostUpdate();
     631             :   }
     632             : }
     633             : 
     634             : void
     635           2 : Animation::TriggerOnNextTick(const Nullable<TimeDuration>& aReadyTime)
     636             : {
     637             :   // Normally we expect the play state to be pending but it's possible that,
     638             :   // due to the handling of possibly orphaned animations in Tick(), this
     639             :   // animation got started whilst still being in another document's pending
     640             :   // animation map.
     641           2 :   if (PlayState() != AnimationPlayState::Pending) {
     642           0 :     return;
     643             :   }
     644             : 
     645             :   // If aReadyTime.IsNull() we'll detect this in Tick() where we check for
     646             :   // orphaned animations and trigger this animation anyway
     647           2 :   mPendingReadyTime = aReadyTime;
     648             : }
     649             : 
     650             : void
     651           0 : Animation::TriggerNow()
     652             : {
     653             :   // Normally we expect the play state to be pending but when an animation
     654             :   // is cancelled and its rendered document can't be reached, we can end up
     655             :   // with the animation still in a pending player tracker even after it is
     656             :   // no longer pending.
     657           0 :   if (PlayState() != AnimationPlayState::Pending) {
     658           0 :     return;
     659             :   }
     660             : 
     661             :   // If we don't have an active timeline we can't trigger the animation.
     662             :   // However, this is a test-only method that we don't expect to be used in
     663             :   // conjunction with animations without an active timeline so generate
     664             :   // a warning if we do find ourselves in that situation.
     665           0 :   if (!mTimeline || mTimeline->GetCurrentTime().IsNull()) {
     666           0 :     NS_WARNING("Failed to trigger an animation with an active timeline");
     667           0 :     return;
     668             :   }
     669             : 
     670           0 :   FinishPendingAt(mTimeline->GetCurrentTime().Value());
     671             : }
     672             : 
     673             : Nullable<TimeDuration>
     674           0 : Animation::GetCurrentOrPendingStartTime() const
     675             : {
     676           0 :   Nullable<TimeDuration> result;
     677             : 
     678           0 :   if (!mStartTime.IsNull()) {
     679           0 :     result = mStartTime;
     680           0 :     return result;
     681             :   }
     682             : 
     683           0 :   if (mPendingReadyTime.IsNull() || mHoldTime.IsNull()) {
     684           0 :     return result;
     685             :   }
     686             : 
     687             :   // Calculate the equivalent start time from the pending ready time.
     688           0 :   result = StartTimeFromReadyTime(mPendingReadyTime.Value());
     689             : 
     690           0 :   return result;
     691             : }
     692             : 
     693             : TimeDuration
     694           2 : Animation::StartTimeFromReadyTime(const TimeDuration& aReadyTime) const
     695             : {
     696           2 :   MOZ_ASSERT(!mHoldTime.IsNull(), "Hold time should be set in order to"
     697             :                                   " convert a ready time to a start time");
     698           2 :   if (mPlaybackRate == 0) {
     699           0 :     return aReadyTime;
     700             :   }
     701           2 :   return aReadyTime - mHoldTime.Value().MultDouble(1 / mPlaybackRate);
     702             : }
     703             : 
     704             : TimeStamp
     705          72 : Animation::AnimationTimeToTimeStamp(const StickyTimeDuration& aTime) const
     706             : {
     707             :   // Initializes to null. Return the same object every time to benefit from
     708             :   // return-value-optimization.
     709          72 :   TimeStamp result;
     710             : 
     711             :   // We *don't* check for mTimeline->TracksWallclockTime() here because that
     712             :   // method only tells us if the timeline times can be converted to
     713             :   // TimeStamps that can be compared to TimeStamp::Now() or not, *not*
     714             :   // whether the timelines can be converted to TimeStamp values at all.
     715             :   //
     716             :   // Furthermore, we want to be able to use this method when the refresh driver
     717             :   // is under test control (in which case TracksWallclockTime() will return
     718             :   // false).
     719             :   //
     720             :   // Once we introduce timelines that are not time-based we will need to
     721             :   // differentiate between them here and determine how to sort their events.
     722          72 :   if (!mTimeline) {
     723           0 :     return result;
     724             :   }
     725             : 
     726             :   // Check the time is convertible to a timestamp
     727         288 :   if (aTime == TimeDuration::Forever() ||
     728         288 :       mPlaybackRate == 0.0 ||
     729          72 :       mStartTime.IsNull()) {
     730          54 :     return result;
     731             :   }
     732             : 
     733             :   // Invert the standard relation:
     734             :   //   animation time = (timeline time - start time) * playback rate
     735             :   TimeDuration timelineTime =
     736          18 :     TimeDuration(aTime).MultDouble(1.0 / mPlaybackRate) + mStartTime.Value();
     737             : 
     738          18 :   result = mTimeline->ToTimeStamp(timelineTime);
     739          18 :   return result;
     740             : }
     741             : 
     742             : TimeStamp
     743          48 : Animation::ElapsedTimeToTimeStamp(
     744             :   const StickyTimeDuration& aElapsedTime) const
     745             : {
     746             :   TimeDuration delay = mEffect
     747          48 :                        ? mEffect->SpecifiedTiming().Delay()
     748          96 :                        : TimeDuration();
     749          48 :   return AnimationTimeToTimeStamp(aElapsedTime + delay);
     750             : }
     751             : 
     752             : // https://w3c.github.io/web-animations/#silently-set-the-current-time
     753             : void
     754           0 : Animation::SilentlySetCurrentTime(const TimeDuration& aSeekTime)
     755             : {
     756           0 :   if (!mHoldTime.IsNull() ||
     757           0 :       mStartTime.IsNull() ||
     758           0 :       !mTimeline ||
     759           0 :       mTimeline->GetCurrentTime().IsNull() ||
     760           0 :       mPlaybackRate == 0.0) {
     761           0 :     mHoldTime.SetValue(aSeekTime);
     762           0 :     if (!mTimeline || mTimeline->GetCurrentTime().IsNull()) {
     763           0 :       mStartTime.SetNull();
     764             :     }
     765             :   } else {
     766           0 :     mStartTime.SetValue(mTimeline->GetCurrentTime().Value() -
     767           0 :                           (aSeekTime.MultDouble(1 / mPlaybackRate)));
     768             :   }
     769             : 
     770           0 :   mPreviousCurrentTime.SetNull();
     771           0 : }
     772             : 
     773             : void
     774           0 : Animation::SilentlySetPlaybackRate(double aPlaybackRate)
     775             : {
     776           0 :   Nullable<TimeDuration> previousTime = GetCurrentTime();
     777           0 :   mPlaybackRate = aPlaybackRate;
     778           0 :   if (!previousTime.IsNull()) {
     779           0 :     SilentlySetCurrentTime(previousTime.Value());
     780             :   }
     781           0 : }
     782             : 
     783             : // https://w3c.github.io/web-animations/#cancel-an-animation
     784             : void
     785           0 : Animation::CancelNoUpdate()
     786             : {
     787           0 :   ResetPendingTasks();
     788             : 
     789           0 :   if (mFinished) {
     790           0 :     mFinished->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
     791             :   }
     792           0 :   ResetFinishedPromise();
     793             : 
     794           0 :   DispatchPlaybackEvent(NS_LITERAL_STRING("cancel"));
     795             : 
     796             :   StickyTimeDuration activeTime = mEffect
     797           0 :                                   ? mEffect->GetComputedTiming().mActiveTime
     798           0 :                                   : StickyTimeDuration();
     799             : 
     800           0 :   mHoldTime.SetNull();
     801           0 :   mStartTime.SetNull();
     802             : 
     803           0 :   if (mTimeline) {
     804           0 :     mTimeline->RemoveAnimation(this);
     805             :   }
     806           0 :   MaybeQueueCancelEvent(activeTime);
     807             : 
     808             :   // When an animation is cancelled it no longer needs further ticks from the
     809             :   // timeline. However, if we queued a cancel event and this was the last
     810             :   // animation attached to the timeline, the timeline will stop observing the
     811             :   // refresh driver and there may be no subsequent refresh driver tick for
     812             :   // dispatching the queued event.
     813             :   //
     814             :   // By calling UpdateTiming *after* removing ourselves from our timeline, we
     815             :   // ensure the timeline will register with the refresh driver for at least one
     816             :   // more tick.
     817           0 :   UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
     818           0 : }
     819             : 
     820             : bool
     821           0 : Animation::ShouldBeSynchronizedWithMainThread(
     822             :   nsCSSPropertyID aProperty,
     823             :   const nsIFrame* aFrame,
     824             :   AnimationPerformanceWarning::Type& aPerformanceWarning) const
     825             : {
     826             :   // Only synchronize playing animations
     827           0 :   if (!IsPlaying()) {
     828           0 :     return false;
     829             :   }
     830             : 
     831             :   // Currently only transform animations need to be synchronized
     832           0 :   if (aProperty != eCSSProperty_transform) {
     833           0 :     return false;
     834             :   }
     835             : 
     836             :   KeyframeEffectReadOnly* keyframeEffect = mEffect
     837           0 :                                            ? mEffect->AsKeyframeEffect()
     838           0 :                                            : nullptr;
     839           0 :   if (!keyframeEffect) {
     840           0 :     return false;
     841             :   }
     842             : 
     843             :   // Are we starting at the same time as other geometric animations?
     844             :   // We check this before calling ShouldBlockAsyncTransformAnimations, partly
     845             :   // because it's cheaper, but also because it's often the most useful thing
     846             :   // to know when you're debugging performance.
     847           0 :   if (mSyncWithGeometricAnimations &&
     848           0 :       keyframeEffect->HasAnimationOfProperty(eCSSProperty_transform)) {
     849           0 :     aPerformanceWarning = AnimationPerformanceWarning::Type::
     850             :                           TransformWithSyncGeometricAnimations;
     851           0 :     return true;
     852             :   }
     853             : 
     854             :   return keyframeEffect->
     855           0 :            ShouldBlockAsyncTransformAnimations(aFrame, aPerformanceWarning);
     856             : }
     857             : 
     858             : void
     859          32 : Animation::UpdateRelevance()
     860             : {
     861          32 :   bool wasRelevant = mIsRelevant;
     862          32 :   mIsRelevant = HasCurrentEffect() || IsInEffect();
     863             : 
     864             :   // Notify animation observers.
     865          32 :   if (wasRelevant && !mIsRelevant) {
     866           2 :     nsNodeUtils::AnimationRemoved(this);
     867          30 :   } else if (!wasRelevant && mIsRelevant) {
     868           2 :     nsNodeUtils::AnimationAdded(this);
     869             :   }
     870          32 : }
     871             : 
     872             : bool
     873           3 : Animation::HasLowerCompositeOrderThan(const Animation& aOther) const
     874             : {
     875             :   // 0. Object-equality case
     876           3 :   if (&aOther == this) {
     877           0 :     return false;
     878             :   }
     879             : 
     880             :   // 1. CSS Transitions sort lowest
     881             :   {
     882             :     auto asCSSTransitionForSorting =
     883           6 :       [] (const Animation& anim) -> const CSSTransition*
     884             :       {
     885           6 :         const CSSTransition* transition = anim.AsCSSTransition();
     886           6 :         return transition && transition->IsTiedToMarkup() ?
     887             :                transition :
     888           6 :                nullptr;
     889             :       };
     890           3 :     auto thisTransition  = asCSSTransitionForSorting(*this);
     891           3 :     auto otherTransition = asCSSTransitionForSorting(aOther);
     892           3 :     if (thisTransition && otherTransition) {
     893           6 :       return thisTransition->HasLowerCompositeOrderThan(*otherTransition);
     894             :     }
     895           0 :     if (thisTransition || otherTransition) {
     896             :       // Cancelled transitions no longer have an owning element. To be strictly
     897             :       // correct we should store a strong reference to the owning element
     898             :       // so that if we arrive here while sorting cancel events, we can sort
     899             :       // them in the correct order.
     900             :       //
     901             :       // However, given that cancel events are almost always queued
     902             :       // synchronously in some deterministic manner, we can be fairly sure
     903             :       // that cancel events will be dispatched in a deterministic order
     904             :       // (which is our only hard requirement until specs say otherwise).
     905             :       // Furthermore, we only reach here when we have events with equal
     906             :       // timestamps so this is an edge case we can probably ignore for now.
     907           0 :       return thisTransition;
     908             :     }
     909             :   }
     910             : 
     911             :   // 2. CSS Animations sort next
     912             :   {
     913             :     auto asCSSAnimationForSorting =
     914           0 :       [] (const Animation& anim) -> const CSSAnimation*
     915             :       {
     916           0 :         const CSSAnimation* animation = anim.AsCSSAnimation();
     917           0 :         return animation && animation->IsTiedToMarkup() ? animation : nullptr;
     918             :       };
     919           0 :     auto thisAnimation  = asCSSAnimationForSorting(*this);
     920           0 :     auto otherAnimation = asCSSAnimationForSorting(aOther);
     921           0 :     if (thisAnimation && otherAnimation) {
     922           0 :       return thisAnimation->HasLowerCompositeOrderThan(*otherAnimation);
     923             :     }
     924           0 :     if (thisAnimation || otherAnimation) {
     925           0 :       return thisAnimation;
     926             :     }
     927             :   }
     928             : 
     929             :   // Subclasses of Animation repurpose mAnimationIndex to implement their
     930             :   // own brand of composite ordering. However, by this point we should have
     931             :   // handled any such custom composite ordering so we should now have unique
     932             :   // animation indices.
     933           0 :   MOZ_ASSERT(mAnimationIndex != aOther.mAnimationIndex,
     934             :              "Animation indices should be unique");
     935             : 
     936             :   // 3. Finally, generic animations sort by their position in the global
     937             :   // animation array.
     938           0 :   return mAnimationIndex < aOther.mAnimationIndex;
     939             : }
     940             : 
     941             : void
     942           4 : Animation::WillComposeStyle()
     943             : {
     944           4 :   mFinishedAtLastComposeStyle = (PlayState() == AnimationPlayState::Finished);
     945             : 
     946           4 :   MOZ_ASSERT(mEffect);
     947             : 
     948           4 :   KeyframeEffectReadOnly* keyframeEffect = mEffect->AsKeyframeEffect();
     949           4 :   if (keyframeEffect) {
     950           4 :     keyframeEffect->WillComposeStyle();
     951             :   }
     952           4 : }
     953             : 
     954             : template<typename ComposeAnimationResult>
     955             : void
     956           4 : Animation::ComposeStyle(ComposeAnimationResult&& aComposeResult,
     957             :                         const nsCSSPropertyIDSet& aPropertiesToSkip)
     958             : {
     959           4 :   if (!mEffect) {
     960           0 :     return;
     961             :   }
     962             : 
     963             :   // In order to prevent flicker, there are a few cases where we want to use
     964             :   // a different time for rendering that would otherwise be returned by
     965             :   // GetCurrentTime. These are:
     966             :   //
     967             :   // (a) For animations that are pausing but which are still running on the
     968             :   //     compositor. In this case we send a layer transaction that removes the
     969             :   //     animation but which also contains the animation values calculated on
     970             :   //     the main thread. To prevent flicker when this occurs we want to ensure
     971             :   //     the timeline time used to calculate the main thread animation values
     972             :   //     does not lag far behind the time used on the compositor. Ideally we
     973             :   //     would like to use the "animation ready time" calculated at the end of
     974             :   //     the layer transaction as the timeline time but it will be too late to
     975             :   //     update the style rule at that point so instead we just use the current
     976             :   //     wallclock time.
     977             :   //
     978             :   // (b) For animations that are pausing that we have already taken off the
     979             :   //     compositor. In this case we record a pending ready time but we don't
     980             :   //     apply it until the next tick. However, while waiting for the next tick,
     981             :   //     we should still use the pending ready time as the timeline time. If we
     982             :   //     use the regular timeline time the animation may appear jump backwards
     983             :   //     if the main thread's timeline time lags behind the compositor.
     984             :   //
     985             :   // (c) For animations that are play-pending due to an aborted pause operation
     986             :   //     (i.e. a pause operation that was interrupted before we entered the
     987             :   //     paused state). When we cancel a pending pause we might momentarily take
     988             :   //     the animation off the compositor, only to re-add it moments later. In
     989             :   //     that case the compositor might have been ahead of the main thread so we
     990             :   //     should use the current wallclock time to ensure the animation doesn't
     991             :   //     temporarily jump backwards.
     992             :   //
     993             :   // To address each of these cases we temporarily tweak the hold time
     994             :   // immediately before updating the style rule and then restore it immediately
     995             :   // afterwards. This is purely to prevent visual flicker. Other behavior
     996             :   // such as dispatching events continues to rely on the regular timeline time.
     997           4 :   AnimationPlayState playState = PlayState();
     998             :   {
     999           8 :     AutoRestore<Nullable<TimeDuration>> restoreHoldTime(mHoldTime);
    1000             : 
    1001           6 :     if (playState == AnimationPlayState::Pending &&
    1002           4 :         mHoldTime.IsNull() &&
    1003           0 :         !mStartTime.IsNull()) {
    1004           0 :       Nullable<TimeDuration> timeToUse = mPendingReadyTime;
    1005           0 :       if (timeToUse.IsNull() &&
    1006           0 :           mTimeline &&
    1007           0 :           mTimeline->TracksWallclockTime()) {
    1008           0 :         timeToUse = mTimeline->ToTimelineTime(TimeStamp::Now());
    1009             :       }
    1010           0 :       if (!timeToUse.IsNull()) {
    1011           0 :         mHoldTime.SetValue((timeToUse.Value() - mStartTime.Value())
    1012           0 :                             .MultDouble(mPlaybackRate));
    1013             :       }
    1014             :     }
    1015             : 
    1016           4 :     KeyframeEffectReadOnly* keyframeEffect = mEffect->AsKeyframeEffect();
    1017           4 :     if (keyframeEffect) {
    1018           4 :       keyframeEffect->ComposeStyle(Forward<ComposeAnimationResult>(aComposeResult),
    1019             :                                    aPropertiesToSkip);
    1020             :     }
    1021             :   }
    1022             : 
    1023           4 :   MOZ_ASSERT(playState == PlayState(),
    1024             :              "Play state should not change during the course of compositing");
    1025             : }
    1026             : 
    1027             : void
    1028           0 : Animation::NotifyEffectTimingUpdated()
    1029             : {
    1030           0 :   MOZ_ASSERT(mEffect,
    1031             :              "We should only update timing effect when we have a target "
    1032             :              "effect");
    1033             :   UpdateTiming(Animation::SeekFlag::NoSeek,
    1034           0 :                Animation::SyncNotifyFlag::Async);
    1035           0 : }
    1036             : 
    1037             : void
    1038           0 : Animation::NotifyGeometricAnimationsStartingThisFrame()
    1039             : {
    1040           0 :   if (!IsNewlyStarted() || !mEffect) {
    1041           0 :     return;
    1042             :   }
    1043             : 
    1044           0 :   mSyncWithGeometricAnimations = true;
    1045             : }
    1046             : 
    1047             : // https://w3c.github.io/web-animations/#play-an-animation
    1048             : void
    1049           2 : Animation::PlayNoUpdate(ErrorResult& aRv, LimitBehavior aLimitBehavior)
    1050             : {
    1051           4 :   AutoMutationBatchForAnimation mb(*this);
    1052             : 
    1053           2 :   bool abortedPause = mPendingState == PendingState::PausePending;
    1054             : 
    1055           4 :   Nullable<TimeDuration> currentTime = GetCurrentTime();
    1056          10 :   if (mPlaybackRate > 0.0 &&
    1057           2 :       (currentTime.IsNull() ||
    1058           0 :        (aLimitBehavior == LimitBehavior::AutoRewind &&
    1059           2 :         (currentTime.Value() < TimeDuration() ||
    1060           2 :          currentTime.Value() >= EffectEnd())))) {
    1061           2 :     mHoldTime.SetValue(TimeDuration(0));
    1062           0 :   } else if (mPlaybackRate < 0.0 &&
    1063           0 :              (currentTime.IsNull() ||
    1064           0 :               (aLimitBehavior == LimitBehavior::AutoRewind &&
    1065           0 :                (currentTime.Value() <= TimeDuration() ||
    1066           0 :                 currentTime.Value() > EffectEnd())))) {
    1067           0 :     if (EffectEnd() == TimeDuration::Forever()) {
    1068           0 :       aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
    1069           0 :       return;
    1070             :     }
    1071           0 :     mHoldTime.SetValue(TimeDuration(EffectEnd()));
    1072           0 :   } else if (mPlaybackRate == 0.0 && currentTime.IsNull()) {
    1073           0 :     mHoldTime.SetValue(TimeDuration(0));
    1074             :   }
    1075             : 
    1076           2 :   bool reuseReadyPromise = false;
    1077           2 :   if (mPendingState != PendingState::NotPending) {
    1078           0 :     CancelPendingTasks();
    1079           0 :     reuseReadyPromise = true;
    1080             :   }
    1081             : 
    1082             :   // If the hold time is null then we're either already playing normally (and
    1083             :   // we can ignore this call) or we aborted a pending pause operation (in which
    1084             :   // case, for consistency, we need to go through the motions of doing an
    1085             :   // asynchronous start even though we already have a resolved start time).
    1086           2 :   if (mHoldTime.IsNull() && !abortedPause) {
    1087           0 :     return;
    1088             :   }
    1089             : 
    1090             :   // Clear the start time until we resolve a new one. We do this except
    1091             :   // for the case where we are aborting a pause and don't have a hold time.
    1092             :   //
    1093             :   // If we're aborting a pause and *do* have a hold time (e.g. because
    1094             :   // the animation is finished or we just applied the auto-rewind behavior
    1095             :   // above) we should respect it by clearing the start time. If we *don't*
    1096             :   // have a hold time we should keep the current start time so that the
    1097             :   // the animation continues moving uninterrupted by the aborted pause.
    1098             :   //
    1099             :   // (If we're not aborting a pause, mHoldTime must be resolved by now
    1100             :   //  or else we would have returned above.)
    1101           2 :   if (!mHoldTime.IsNull()) {
    1102           2 :     mStartTime.SetNull();
    1103             :   }
    1104             : 
    1105           2 :   if (!reuseReadyPromise) {
    1106             :     // Clear ready promise. We'll create a new one lazily.
    1107           2 :     mReady = nullptr;
    1108             :   }
    1109             : 
    1110           2 :   mPendingState = PendingState::PlayPending;
    1111             : 
    1112             :   // Clear flag that causes us to sync transform animations with the main
    1113             :   // thread for now. We'll set this when we go to set up compositor
    1114             :   // animations if it applies.
    1115           2 :   mSyncWithGeometricAnimations = false;
    1116             : 
    1117           2 :   nsIDocument* doc = GetRenderedDocument();
    1118           2 :   if (doc) {
    1119             :     PendingAnimationTracker* tracker =
    1120           2 :       doc->GetOrCreatePendingAnimationTracker();
    1121           2 :     tracker->AddPlayPending(*this);
    1122             :   } else {
    1123           0 :     TriggerOnNextTick(Nullable<TimeDuration>());
    1124             :   }
    1125             : 
    1126           2 :   UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
    1127           2 :   if (IsRelevant()) {
    1128           2 :     nsNodeUtils::AnimationChanged(this);
    1129             :   }
    1130             : }
    1131             : 
    1132             : // https://w3c.github.io/web-animations/#pause-an-animation
    1133             : void
    1134           0 : Animation::PauseNoUpdate(ErrorResult& aRv)
    1135             : {
    1136           0 :   if (IsPausedOrPausing()) {
    1137           0 :     return;
    1138             :   }
    1139             : 
    1140           0 :   AutoMutationBatchForAnimation mb(*this);
    1141             : 
    1142             :   // If we are transitioning from idle, fill in the current time
    1143           0 :   if (GetCurrentTime().IsNull()) {
    1144           0 :     if (mPlaybackRate >= 0.0) {
    1145           0 :       mHoldTime.SetValue(TimeDuration(0));
    1146             :     } else {
    1147           0 :       if (EffectEnd() == TimeDuration::Forever()) {
    1148           0 :         aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
    1149           0 :         return;
    1150             :       }
    1151           0 :       mHoldTime.SetValue(TimeDuration(EffectEnd()));
    1152             :     }
    1153             :   }
    1154             : 
    1155           0 :   bool reuseReadyPromise = false;
    1156           0 :   if (mPendingState == PendingState::PlayPending) {
    1157           0 :     CancelPendingTasks();
    1158           0 :     reuseReadyPromise = true;
    1159             :   }
    1160             : 
    1161           0 :   if (!reuseReadyPromise) {
    1162             :     // Clear ready promise. We'll create a new one lazily.
    1163           0 :     mReady = nullptr;
    1164             :   }
    1165             : 
    1166           0 :   mPendingState = PendingState::PausePending;
    1167             : 
    1168           0 :   nsIDocument* doc = GetRenderedDocument();
    1169           0 :   if (doc) {
    1170             :     PendingAnimationTracker* tracker =
    1171           0 :       doc->GetOrCreatePendingAnimationTracker();
    1172           0 :     tracker->AddPausePending(*this);
    1173             :   } else {
    1174           0 :     TriggerOnNextTick(Nullable<TimeDuration>());
    1175             :   }
    1176             : 
    1177           0 :   UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
    1178           0 :   if (IsRelevant()) {
    1179           0 :     nsNodeUtils::AnimationChanged(this);
    1180             :   }
    1181             : }
    1182             : 
    1183             : void
    1184           2 : Animation::ResumeAt(const TimeDuration& aReadyTime)
    1185             : {
    1186             :   // This method is only expected to be called for an animation that is
    1187             :   // waiting to play. We can easily adapt it to handle other states
    1188             :   // but it's currently not necessary.
    1189           2 :   MOZ_ASSERT(mPendingState == PendingState::PlayPending,
    1190             :              "Expected to resume a play-pending animation");
    1191           2 :   MOZ_ASSERT(mHoldTime.IsNull() != mStartTime.IsNull(),
    1192             :              "An animation in the play-pending state should have either a"
    1193             :              " resolved hold time or resolved start time (but not both)");
    1194             : 
    1195             :   // If we aborted a pending pause operation we will already have a start time
    1196             :   // we should use. In all other cases, we resolve it from the ready time.
    1197           2 :   if (mStartTime.IsNull()) {
    1198           2 :     mStartTime = StartTimeFromReadyTime(aReadyTime);
    1199           2 :     if (mPlaybackRate != 0) {
    1200           2 :       mHoldTime.SetNull();
    1201             :     }
    1202             :   }
    1203           2 :   mPendingState = PendingState::NotPending;
    1204             : 
    1205           2 :   UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
    1206             : 
    1207           2 :   if (mReady) {
    1208           0 :     mReady->MaybeResolve(this);
    1209             :   }
    1210           2 : }
    1211             : 
    1212             : void
    1213           0 : Animation::PauseAt(const TimeDuration& aReadyTime)
    1214             : {
    1215           0 :   MOZ_ASSERT(mPendingState == PendingState::PausePending,
    1216             :              "Expected to pause a pause-pending animation");
    1217             : 
    1218           0 :   if (!mStartTime.IsNull() && mHoldTime.IsNull()) {
    1219           0 :     mHoldTime.SetValue((aReadyTime - mStartTime.Value())
    1220           0 :                         .MultDouble(mPlaybackRate));
    1221             :   }
    1222           0 :   mStartTime.SetNull();
    1223           0 :   mPendingState = PendingState::NotPending;
    1224             : 
    1225           0 :   UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
    1226             : 
    1227           0 :   if (mReady) {
    1228           0 :     mReady->MaybeResolve(this);
    1229             :   }
    1230           0 : }
    1231             : 
    1232             : void
    1233          32 : Animation::UpdateTiming(SeekFlag aSeekFlag, SyncNotifyFlag aSyncNotifyFlag)
    1234             : {
    1235             :   // We call UpdateFinishedState before UpdateEffect because the former
    1236             :   // can change the current time, which is used by the latter.
    1237          32 :   UpdateFinishedState(aSeekFlag, aSyncNotifyFlag);
    1238          32 :   UpdateEffect();
    1239             : 
    1240          32 :   if (mTimeline) {
    1241          32 :     mTimeline->NotifyAnimationUpdated(*this);
    1242             :   }
    1243          32 : }
    1244             : 
    1245             : // https://w3c.github.io/web-animations/#update-an-animations-finished-state
    1246             : void
    1247          32 : Animation::UpdateFinishedState(SeekFlag aSeekFlag,
    1248             :                                SyncNotifyFlag aSyncNotifyFlag)
    1249             : {
    1250          64 :   Nullable<TimeDuration> currentTime = GetCurrentTime();
    1251          32 :   TimeDuration effectEnd = TimeDuration(EffectEnd());
    1252             : 
    1253          40 :   if (!mStartTime.IsNull() &&
    1254           8 :       mPendingState == PendingState::NotPending) {
    1255          24 :     if (mPlaybackRate > 0.0 &&
    1256          16 :         !currentTime.IsNull() &&
    1257           8 :         currentTime.Value() >= effectEnd) {
    1258           2 :       if (aSeekFlag == SeekFlag::DidSeek) {
    1259           0 :         mHoldTime = currentTime;
    1260           2 :       } else if (!mPreviousCurrentTime.IsNull()) {
    1261           2 :         mHoldTime.SetValue(std::max(mPreviousCurrentTime.Value(), effectEnd));
    1262             :       } else {
    1263           0 :         mHoldTime.SetValue(effectEnd);
    1264             :       }
    1265          24 :     } else if (mPlaybackRate < 0.0 &&
    1266          12 :                !currentTime.IsNull() &&
    1267           6 :                currentTime.Value() <= TimeDuration()) {
    1268           0 :       if (aSeekFlag == SeekFlag::DidSeek) {
    1269           0 :         mHoldTime = currentTime;
    1270           0 :       } else if (!mPreviousCurrentTime.IsNull()) {
    1271           0 :         mHoldTime.SetValue(std::min(mPreviousCurrentTime.Value(),
    1272           0 :                                     TimeDuration(0)));
    1273             :       } else {
    1274           0 :         mHoldTime.SetValue(0);
    1275             :       }
    1276          24 :     } else if (mPlaybackRate != 0.0 &&
    1277          12 :                !currentTime.IsNull() &&
    1278          30 :                mTimeline &&
    1279          24 :                !mTimeline->GetCurrentTime().IsNull()) {
    1280           6 :       if (aSeekFlag == SeekFlag::DidSeek && !mHoldTime.IsNull()) {
    1281           0 :         mStartTime.SetValue(mTimeline->GetCurrentTime().Value() -
    1282           0 :                              (mHoldTime.Value().MultDouble(1 / mPlaybackRate)));
    1283             :       }
    1284           6 :       mHoldTime.SetNull();
    1285             :     }
    1286             :   }
    1287             : 
    1288          32 :   bool currentFinishedState = PlayState() == AnimationPlayState::Finished;
    1289          32 :   if (currentFinishedState && !mFinishedIsResolved) {
    1290           2 :     DoFinishNotification(aSyncNotifyFlag);
    1291          30 :   } else if (!currentFinishedState && mFinishedIsResolved) {
    1292           0 :     ResetFinishedPromise();
    1293             :   }
    1294             :   // We must recalculate the current time to take account of any mHoldTime
    1295             :   // changes the code above made.
    1296          32 :   mPreviousCurrentTime = GetCurrentTime();
    1297          32 : }
    1298             : 
    1299             : void
    1300          32 : Animation::UpdateEffect()
    1301             : {
    1302          32 :   if (mEffect) {
    1303          30 :     UpdateRelevance();
    1304             : 
    1305          30 :     KeyframeEffectReadOnly* keyframeEffect = mEffect->AsKeyframeEffect();
    1306          30 :     if (keyframeEffect) {
    1307          30 :       keyframeEffect->NotifyAnimationTimingUpdated();
    1308             :     }
    1309             :   }
    1310          32 : }
    1311             : 
    1312             : void
    1313           0 : Animation::FlushStyle() const
    1314             : {
    1315           0 :   nsIDocument* doc = GetRenderedDocument();
    1316           0 :   if (doc) {
    1317           0 :     doc->FlushPendingNotifications(FlushType::Style);
    1318             :   }
    1319           0 : }
    1320             : 
    1321             : void
    1322           2 : Animation::PostUpdate()
    1323             : {
    1324           2 :   if (!mEffect) {
    1325           0 :     return;
    1326             :   }
    1327             : 
    1328           2 :   KeyframeEffectReadOnly* keyframeEffect = mEffect->AsKeyframeEffect();
    1329           2 :   if (!keyframeEffect) {
    1330           0 :     return;
    1331             :   }
    1332           2 :   keyframeEffect->RequestRestyle(EffectCompositor::RestyleType::Layer);
    1333             : }
    1334             : 
    1335             : void
    1336           0 : Animation::CancelPendingTasks()
    1337             : {
    1338           0 :   if (mPendingState == PendingState::NotPending) {
    1339           0 :     return;
    1340             :   }
    1341             : 
    1342           0 :   nsIDocument* doc = GetRenderedDocument();
    1343           0 :   if (doc) {
    1344           0 :     PendingAnimationTracker* tracker = doc->GetPendingAnimationTracker();
    1345           0 :     if (tracker) {
    1346           0 :       if (mPendingState == PendingState::PlayPending) {
    1347           0 :         tracker->RemovePlayPending(*this);
    1348             :       } else {
    1349           0 :         tracker->RemovePausePending(*this);
    1350             :       }
    1351             :     }
    1352             :   }
    1353             : 
    1354           0 :   mPendingState = PendingState::NotPending;
    1355           0 :   mPendingReadyTime.SetNull();
    1356             : }
    1357             : 
    1358             : // https://w3c.github.io/web-animations/#reset-an-animations-pending-tasks
    1359             : void
    1360           0 : Animation::ResetPendingTasks()
    1361             : {
    1362           0 :   if (mPendingState == PendingState::NotPending) {
    1363           0 :     return;
    1364             :   }
    1365             : 
    1366           0 :   CancelPendingTasks();
    1367           0 :   if (mReady) {
    1368           0 :     mReady->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
    1369           0 :     mReady = nullptr;
    1370             :   }
    1371             : }
    1372             : 
    1373             : bool
    1374          24 : Animation::IsPossiblyOrphanedPendingAnimation() const
    1375             : {
    1376             :   // Check if we are pending but might never start because we are not being
    1377             :   // tracked.
    1378             :   //
    1379             :   // This covers the following cases:
    1380             :   //
    1381             :   // * We started playing but our effect's target element was orphaned
    1382             :   //   or bound to a different document.
    1383             :   //   (note that for the case of our effect changing we should handle
    1384             :   //   that in SetEffect)
    1385             :   // * We started playing but our timeline became inactive.
    1386             :   //   In this case the pending animation tracker will drop us from its hashmap
    1387             :   //   when we have been painted.
    1388             :   // * When we started playing we couldn't find a PendingAnimationTracker to
    1389             :   //   register with (perhaps the effect had no document) so we simply
    1390             :   //   set mPendingState in PlayNoUpdate and relied on this method to catch us
    1391             :   //   on the next tick.
    1392             : 
    1393             :   // If we're not pending we're ok.
    1394          24 :   if (mPendingState == PendingState::NotPending) {
    1395           6 :     return false;
    1396             :   }
    1397             : 
    1398             :   // If we have a pending ready time then we will be started on the next
    1399             :   // tick.
    1400          18 :   if (!mPendingReadyTime.IsNull()) {
    1401           0 :     return false;
    1402             :   }
    1403             : 
    1404             :   // If we don't have an active timeline then we shouldn't start until
    1405             :   // we do.
    1406          18 :   if (!mTimeline || mTimeline->GetCurrentTime().IsNull()) {
    1407           0 :     return false;
    1408             :   }
    1409             : 
    1410             :   // If we have no rendered document, or we're not in our rendered document's
    1411             :   // PendingAnimationTracker then there's a good chance no one is tracking us.
    1412             :   //
    1413             :   // If we're wrong and another document is tracking us then, at worst, we'll
    1414             :   // simply start/pause the animation one tick too soon. That's better than
    1415             :   // never starting/pausing the animation and is unlikely.
    1416          18 :   nsIDocument* doc = GetRenderedDocument();
    1417          18 :   if (!doc) {
    1418           0 :     return true;
    1419             :   }
    1420             : 
    1421          18 :   PendingAnimationTracker* tracker = doc->GetPendingAnimationTracker();
    1422          36 :   return !tracker ||
    1423          18 :          (!tracker->IsWaitingToPlay(*this) &&
    1424          18 :           !tracker->IsWaitingToPause(*this));
    1425             : }
    1426             : 
    1427             : StickyTimeDuration
    1428         102 : Animation::EffectEnd() const
    1429             : {
    1430         102 :   if (!mEffect) {
    1431           2 :     return StickyTimeDuration(0);
    1432             :   }
    1433             : 
    1434         100 :   return mEffect->SpecifiedTiming().EndTime();
    1435             : }
    1436             : 
    1437             : nsIDocument*
    1438          20 : Animation::GetRenderedDocument() const
    1439             : {
    1440          20 :   if (!mEffect || !mEffect->AsKeyframeEffect()) {
    1441           0 :     return nullptr;
    1442             :   }
    1443             : 
    1444          20 :   return mEffect->AsKeyframeEffect()->GetRenderedDocument();
    1445             : }
    1446             : 
    1447             : void
    1448           2 : Animation::DoFinishNotification(SyncNotifyFlag aSyncNotifyFlag)
    1449             : {
    1450           2 :   CycleCollectedJSContext* context = CycleCollectedJSContext::Get();
    1451             : 
    1452           2 :   if (aSyncNotifyFlag == SyncNotifyFlag::Sync) {
    1453           0 :     DoFinishNotificationImmediately();
    1454           2 :   } else if (!mFinishNotificationTask.IsPending()) {
    1455             :     RefPtr<nsRunnableMethod<Animation>> runnable =
    1456           4 :       NewRunnableMethod("dom::Animation::DoFinishNotificationImmediately",
    1457             :                         this,
    1458           4 :                         &Animation::DoFinishNotificationImmediately);
    1459           2 :     context->DispatchToMicroTask(do_AddRef(runnable));
    1460           2 :     mFinishNotificationTask = runnable.forget();
    1461             :   }
    1462           2 : }
    1463             : 
    1464             : void
    1465           0 : Animation::ResetFinishedPromise()
    1466             : {
    1467           0 :   mFinishedIsResolved = false;
    1468           0 :   mFinished = nullptr;
    1469           0 : }
    1470             : 
    1471             : void
    1472           2 : Animation::MaybeResolveFinishedPromise()
    1473             : {
    1474           2 :   if (mFinished) {
    1475           0 :     mFinished->MaybeResolve(this);
    1476             :   }
    1477           2 :   mFinishedIsResolved = true;
    1478           2 : }
    1479             : 
    1480             : void
    1481           2 : Animation::DoFinishNotificationImmediately()
    1482             : {
    1483           2 :   mFinishNotificationTask.Revoke();
    1484             : 
    1485           2 :   if (PlayState() != AnimationPlayState::Finished) {
    1486           0 :     return;
    1487             :   }
    1488             : 
    1489           2 :   MaybeResolveFinishedPromise();
    1490             : 
    1491           2 :   DispatchPlaybackEvent(NS_LITERAL_STRING("finish"));
    1492             : }
    1493             : 
    1494             : void
    1495           2 : Animation::DispatchPlaybackEvent(const nsAString& aName)
    1496             : {
    1497           4 :   AnimationPlaybackEventInit init;
    1498             : 
    1499           2 :   if (aName.EqualsLiteral("finish")) {
    1500           2 :     init.mCurrentTime = GetCurrentTimeAsDouble();
    1501             :   }
    1502           2 :   if (mTimeline) {
    1503           2 :     init.mTimelineTime = mTimeline->GetCurrentTimeAsDouble();
    1504             :   }
    1505             : 
    1506             :   RefPtr<AnimationPlaybackEvent> event =
    1507           4 :     AnimationPlaybackEvent::Constructor(this, aName, init);
    1508           2 :   event->SetTrusted(true);
    1509             : 
    1510             :   RefPtr<AsyncEventDispatcher> asyncDispatcher =
    1511           6 :     new AsyncEventDispatcher(this, event);
    1512           2 :   asyncDispatcher->PostDOMEvent();
    1513           2 : }
    1514             : 
    1515             : bool
    1516           0 : Animation::IsRunningOnCompositor() const
    1517             : {
    1518           0 :   return mEffect &&
    1519           0 :          mEffect->AsKeyframeEffect() &&
    1520           0 :          mEffect->AsKeyframeEffect()->IsRunningOnCompositor();
    1521             : }
    1522             : 
    1523             : template
    1524             : void
    1525             : Animation::ComposeStyle<RefPtr<AnimValuesStyleRule>&>(
    1526             :   RefPtr<AnimValuesStyleRule>& aAnimationRule,
    1527             :   const nsCSSPropertyIDSet& aPropertiesToSkip);
    1528             : 
    1529             : template
    1530             : void
    1531             : Animation::ComposeStyle<RawServoAnimationValueMap&>(
    1532             :   RawServoAnimationValueMap& aAnimationValues,
    1533             :   const nsCSSPropertyIDSet& aPropertiesToSkip);
    1534             : 
    1535             : } // namespace dom
    1536             : } // namespace mozilla

Generated by: LCOV version 1.13