tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

Animation.cpp (68913B)


      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 
      9 #include "AnimationUtils.h"
     10 #include "ScrollTimelineAnimationTracker.h"
     11 #include "mozAutoDocUpdate.h"
     12 #include "mozilla/AnimationEventDispatcher.h"
     13 #include "mozilla/AnimationTarget.h"
     14 #include "mozilla/AutoRestore.h"
     15 #include "mozilla/CycleCollectedJSContext.h"
     16 #include "mozilla/DeclarationBlock.h"
     17 #include "mozilla/Likely.h"
     18 #include "mozilla/Maybe.h"  // For Maybe
     19 #include "mozilla/StaticPrefs_dom.h"
     20 #include "mozilla/dom/AnimationBinding.h"
     21 #include "mozilla/dom/Document.h"
     22 #include "mozilla/dom/DocumentInlines.h"
     23 #include "mozilla/dom/DocumentTimeline.h"
     24 #include "mozilla/dom/MutationObservers.h"
     25 #include "mozilla/dom/Promise.h"
     26 #include "nsAnimationManager.h"  // For CSSAnimation
     27 #include "nsComputedDOMStyle.h"
     28 #include "nsDOMCSSAttrDeclaration.h"  // For nsDOMCSSAttributeDeclaration
     29 #include "nsDOMMutationObserver.h"    // For nsAutoAnimationMutationBatch
     30 #include "nsIFrame.h"
     31 #include "nsThreadUtils.h"  // For nsRunnableMethod and nsRevocableEventPtr
     32 #include "nsTransitionManager.h"  // For CSSTransition
     33 
     34 namespace mozilla::dom {
     35 
     36 // Static members
     37 uint64_t Animation::sNextAnimationIndex = 0;
     38 
     39 NS_IMPL_CYCLE_COLLECTION_INHERITED(Animation, DOMEventTargetHelper, mTimeline,
     40                                   mEffect, mReady, mFinished)
     41 
     42 NS_IMPL_ADDREF_INHERITED(Animation, DOMEventTargetHelper)
     43 NS_IMPL_RELEASE_INHERITED(Animation, DOMEventTargetHelper)
     44 
     45 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Animation)
     46 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
     47 
     48 JSObject* Animation::WrapObject(JSContext* aCx,
     49                                JS::Handle<JSObject*> aGivenProto) {
     50  return dom::Animation_Binding::Wrap(aCx, this, aGivenProto);
     51 }
     52 
     53 // ---------------------------------------------------------------------------
     54 //
     55 // Utility methods
     56 //
     57 // ---------------------------------------------------------------------------
     58 
     59 namespace {
     60 // A wrapper around nsAutoAnimationMutationBatch that looks up the
     61 // appropriate document from the supplied animation.
     62 class MOZ_RAII AutoMutationBatchForAnimation {
     63 public:
     64  explicit AutoMutationBatchForAnimation(const Animation& aAnimation) {
     65    NonOwningAnimationTarget target = aAnimation.GetTargetForAnimation();
     66    if (!target) {
     67      return;
     68    }
     69 
     70    // For mutation observers, we use the OwnerDoc.
     71    mAutoBatch.emplace(target.mElement->OwnerDoc());
     72  }
     73 
     74 private:
     75  Maybe<nsAutoAnimationMutationBatch> mAutoBatch;
     76 };
     77 }  // namespace
     78 
     79 // ---------------------------------------------------------------------------
     80 //
     81 // Animation interface:
     82 //
     83 // ---------------------------------------------------------------------------
     84 
     85 Animation::Animation(nsIGlobalObject* aGlobal)
     86    : DOMEventTargetHelper(aGlobal),
     87      mAnimationIndex(sNextAnimationIndex++),
     88      mRTPCallerType(aGlobal->GetRTPCallerType()) {}
     89 
     90 Animation::~Animation() = default;
     91 
     92 /* static */
     93 already_AddRefed<Animation> Animation::ClonePausedAnimation(
     94    nsIGlobalObject* aGlobal, const Animation& aOther, AnimationEffect& aEffect,
     95    AnimationTimeline& aTimeline) {
     96  // FIXME: Bug 1805950: Support printing for scroll-timeline once we resolve
     97  // the spec issue.
     98  if (aOther.UsingScrollTimeline()) {
     99    return nullptr;
    100  }
    101 
    102  RefPtr<Animation> animation = new Animation(aGlobal);
    103 
    104  // Setup the timeline. We always use document-timeline of the new document,
    105  // even if the timeline of |aOther| is null.
    106  animation->mTimeline = &aTimeline;
    107 
    108  // Setup the playback rate.
    109  animation->mPlaybackRate = aOther.mPlaybackRate;
    110 
    111  // Setup the timing.
    112  const Nullable<TimeDuration> currentTime = aOther.GetCurrentTimeAsDuration();
    113  if (!aOther.GetTimeline()) {
    114    // This simulates what we do in SetTimelineNoUpdate(). It's possible to
    115    // preserve the progress if the previous timeline is a scroll-timeline.
    116    // So for null timeline, it may have a progress and the non-null current
    117    // time.
    118    if (!currentTime.IsNull()) {
    119      animation->SilentlySetCurrentTime(currentTime.Value());
    120    }
    121    animation->mPreviousCurrentTime = animation->GetCurrentTimeAsDuration();
    122  } else {
    123    animation->mHoldTime = currentTime;
    124    if (!currentTime.IsNull()) {
    125      // FIXME: Should we use |timelineTime| as previous current time here? It
    126      // seems we should use animation->GetCurrentTimeAsDuration(), per
    127      // UpdateFinishedState().
    128      const Nullable<TimeDuration> timelineTime =
    129          aTimeline.GetCurrentTimeAsDuration();
    130      MOZ_ASSERT(!timelineTime.IsNull(), "Timeline not yet set");
    131      animation->mPreviousCurrentTime = timelineTime;
    132    }
    133  }
    134 
    135  // Setup the effect's link to this.
    136  animation->mEffect = &aEffect;
    137  animation->mEffect->SetAnimation(animation);
    138 
    139  animation->mPendingState = PendingState::PausePending;
    140 
    141  // We expect our relevance to be the same as the orginal.
    142  animation->mIsRelevant = aOther.mIsRelevant;
    143 
    144  animation->PostUpdate();
    145  animation->mTimeline->NotifyAnimationUpdated(*animation);
    146  return animation.forget();
    147 }
    148 
    149 NonOwningAnimationTarget Animation::GetTargetForAnimation() const {
    150  AnimationEffect* effect = GetEffect();
    151  NonOwningAnimationTarget target;
    152  if (!effect || !effect->AsKeyframeEffect()) {
    153    return target;
    154  }
    155  return effect->AsKeyframeEffect()->GetAnimationTarget();
    156 }
    157 
    158 /* static */
    159 already_AddRefed<Animation> Animation::Constructor(
    160    const GlobalObject& aGlobal, AnimationEffect* aEffect,
    161    const Optional<AnimationTimeline*>& aTimeline, ErrorResult& aRv) {
    162  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
    163 
    164  AnimationTimeline* timeline;
    165  Document* document =
    166      AnimationUtils::GetCurrentRealmDocument(aGlobal.Context());
    167 
    168  if (aTimeline.WasPassed()) {
    169    timeline = aTimeline.Value();
    170  } else {
    171    if (!document) {
    172      aRv.Throw(NS_ERROR_FAILURE);
    173      return nullptr;
    174    }
    175    timeline = document->Timeline();
    176  }
    177 
    178  RefPtr<Animation> animation = new Animation(global);
    179  animation->SetTimelineNoUpdate(timeline);
    180  animation->SetEffectNoUpdate(aEffect);
    181 
    182  return animation.forget();
    183 }
    184 
    185 void Animation::SetId(const nsAString& aId) {
    186  if (mId == aId) {
    187    return;
    188  }
    189  mId = aId;
    190  MutationObservers::NotifyAnimationChanged(this);
    191 }
    192 
    193 void Animation::SetEffect(AnimationEffect* aEffect) {
    194  SetEffectNoUpdate(aEffect);
    195  PostUpdate();
    196 }
    197 
    198 // https://drafts.csswg.org/web-animations/#setting-the-target-effect
    199 void Animation::SetEffectNoUpdate(AnimationEffect* aEffect) {
    200  RefPtr<Animation> kungFuDeathGrip(this);
    201 
    202  if (mEffect == aEffect) {
    203    return;
    204  }
    205 
    206  AutoMutationBatchForAnimation mb(*this);
    207  bool wasRelevant = mIsRelevant;
    208 
    209  if (mEffect) {
    210    // We need to notify observers now because once we set mEffect to null
    211    // we won't be able to find the target element to notify.
    212    if (mIsRelevant) {
    213      MutationObservers::NotifyAnimationRemoved(this);
    214    }
    215 
    216    // Break links with the old effect and then drop it.
    217    RefPtr<AnimationEffect> oldEffect = mEffect;
    218    mEffect = nullptr;
    219    if (IsPartialPrerendered()) {
    220      if (KeyframeEffect* oldKeyframeEffect = oldEffect->AsKeyframeEffect()) {
    221        oldKeyframeEffect->ResetPartialPrerendered();
    222      }
    223    }
    224    oldEffect->SetAnimation(nullptr);
    225 
    226    // The following will not do any notification because mEffect is null.
    227    UpdateRelevance();
    228  }
    229 
    230  if (aEffect) {
    231    // Break links from the new effect to its previous animation, if any.
    232    RefPtr<AnimationEffect> newEffect = aEffect;
    233    Animation* prevAnim = aEffect->GetAnimation();
    234    if (prevAnim) {
    235      prevAnim->SetEffect(nullptr);
    236    }
    237 
    238    // Create links with the new effect. SetAnimation(this) will also update
    239    // mIsRelevant of this animation, and then notify mutation observer if
    240    // needed by calling Animation::UpdateRelevance(), so we don't need to
    241    // call it again.
    242    mEffect = newEffect;
    243    mEffect->SetAnimation(this);
    244 
    245    // Notify possible add or change.
    246    // If the target is different, the change notification will be ignored by
    247    // AutoMutationBatchForAnimation.
    248    if (wasRelevant && mIsRelevant) {
    249      MutationObservers::NotifyAnimationChanged(this);
    250    }
    251  }
    252 
    253  MaybeScheduleReplacementCheck();
    254 
    255  UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
    256 }
    257 
    258 void Animation::SetTimeline(AnimationTimeline* aTimeline) {
    259  SetTimelineNoUpdate(aTimeline);
    260  PostUpdate();
    261 }
    262 
    263 // https://drafts.csswg.org/web-animations/#setting-the-timeline
    264 void Animation::SetTimelineNoUpdate(AnimationTimeline* aTimeline) {
    265  if (mTimeline == aTimeline) {
    266    return;
    267  }
    268 
    269  StickyTimeDuration activeTime =
    270      mEffect ? mEffect->GetComputedTiming().mActiveTime : StickyTimeDuration();
    271 
    272  const AnimationPlayState previousPlayState = PlayState();
    273  const Nullable<TimeDuration> previousCurrentTime = GetCurrentTimeAsDuration();
    274  // FIXME: The definition of end time in web-animation-1 is different from that
    275  // in web-animation-2, which includes the start time. We are still using the
    276  // definition in web-animation-1 here for now.
    277  const TimeDuration endTime = TimeDuration(EffectEnd());
    278  double previousProgress = 0.0;
    279  if (!previousCurrentTime.IsNull() && !endTime.IsZero()) {
    280    previousProgress =
    281        previousCurrentTime.Value().ToSeconds() / endTime.ToSeconds();
    282  }
    283 
    284  RefPtr<AnimationTimeline> oldTimeline = mTimeline;
    285  if (oldTimeline) {
    286    oldTimeline->RemoveAnimation(this);
    287  }
    288 
    289  mTimeline = aTimeline;
    290 
    291  mResetCurrentTimeOnResume = false;
    292 
    293  if (mEffect) {
    294    mEffect->UpdateNormalizedTiming();
    295  }
    296 
    297  if (mTimeline && !mTimeline->IsMonotonicallyIncreasing()) {
    298    // If "to finite timeline" is true.
    299 
    300    ApplyPendingPlaybackRate();
    301    Nullable<TimeDuration> seekTime;
    302    if (mPlaybackRate >= 0.0) {
    303      seekTime.SetValue(TimeDuration());
    304    } else {
    305      seekTime.SetValue(TimeDuration(EffectEnd()));
    306    }
    307 
    308    switch (previousPlayState) {
    309      case AnimationPlayState::Running:
    310      case AnimationPlayState::Finished:
    311        mStartTime = seekTime;
    312        break;
    313      case AnimationPlayState::Paused:
    314        if (!previousCurrentTime.IsNull()) {
    315          mResetCurrentTimeOnResume = true;
    316          mStartTime.SetNull();
    317          mHoldTime.SetValue(
    318              TimeDuration(EffectEnd().MultDouble(previousProgress)));
    319        } else {
    320          mStartTime = seekTime;
    321        }
    322        break;
    323      case AnimationPlayState::Idle:
    324      default:
    325        break;
    326    }
    327  } else if (oldTimeline && !oldTimeline->IsMonotonicallyIncreasing() &&
    328             !previousCurrentTime.IsNull()) {
    329    // If "from finite timeline" and previous progress is resolved.
    330    SetCurrentTimeNoUpdate(
    331        TimeDuration(EffectEnd().MultDouble(previousProgress)));
    332  }
    333 
    334  if (!mStartTime.IsNull()) {
    335    mHoldTime.SetNull();
    336  }
    337 
    338  if (!aTimeline) {
    339    MaybeQueueCancelEvent(activeTime);
    340  }
    341 
    342  UpdateScrollTimelineAnimationTracker(oldTimeline, aTimeline);
    343 
    344  UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
    345 
    346  // FIXME: Bug 1799071: Check if we need to add
    347  // MutationObservers::NotifyAnimationChanged(this) here.
    348 }
    349 
    350 // https://drafts.csswg.org/web-animations/#set-the-animation-start-time
    351 void Animation::SetStartTime(const Nullable<TimeDuration>& aNewStartTime) {
    352  // Return early if the start time will not change. However, if we
    353  // are pending, then setting the start time to any value
    354  // including the current value has the effect of aborting
    355  // pending tasks so we should not return early in that case.
    356  if (!Pending() && aNewStartTime == mStartTime) {
    357    return;
    358  }
    359 
    360  AutoMutationBatchForAnimation mb(*this);
    361 
    362  Nullable<TimeDuration> timelineTime;
    363  if (mTimeline) {
    364    // The spec says to check if the timeline is active (has a resolved time)
    365    // before using it here, but we don't need to since it's harmless to set
    366    // the already null time to null.
    367    timelineTime = mTimeline->GetCurrentTimeAsDuration();
    368  }
    369  if (timelineTime.IsNull() && !aNewStartTime.IsNull()) {
    370    mHoldTime.SetNull();
    371  }
    372 
    373  Nullable<TimeDuration> previousCurrentTime = GetCurrentTimeAsDuration();
    374 
    375  ApplyPendingPlaybackRate();
    376  mStartTime = aNewStartTime;
    377 
    378  mResetCurrentTimeOnResume = false;
    379 
    380  if (!aNewStartTime.IsNull()) {
    381    if (mPlaybackRate != 0.0) {
    382      mHoldTime.SetNull();
    383    }
    384  } else {
    385    mHoldTime = previousCurrentTime;
    386  }
    387 
    388  CancelPendingTasks();
    389  if (mReady) {
    390    // We may have already resolved mReady, but in that case calling
    391    // MaybeResolve is a no-op, so that's okay.
    392    mReady->MaybeResolve(this);
    393  }
    394 
    395  UpdateTiming(SeekFlag::DidSeek, SyncNotifyFlag::Async);
    396  if (IsRelevant()) {
    397    MutationObservers::NotifyAnimationChanged(this);
    398  }
    399  PostUpdate();
    400 }
    401 
    402 // https://drafts.csswg.org/web-animations/#current-time
    403 Nullable<TimeDuration> Animation::GetCurrentTimeForHoldTime(
    404    const Nullable<TimeDuration>& aHoldTime) const {
    405  Nullable<TimeDuration> result;
    406  if (!aHoldTime.IsNull()) {
    407    result = aHoldTime;
    408    return result;
    409  }
    410 
    411  if (mTimeline && !mStartTime.IsNull()) {
    412    Nullable<TimeDuration> timelineTime = mTimeline->GetCurrentTimeAsDuration();
    413    if (!timelineTime.IsNull()) {
    414      result = CurrentTimeFromTimelineTime(timelineTime.Value(),
    415                                           mStartTime.Value(), mPlaybackRate);
    416    }
    417  }
    418  return result;
    419 }
    420 
    421 // https://drafts.csswg.org/web-animations/#set-the-current-time
    422 void Animation::SetCurrentTime(const TimeDuration& aSeekTime) {
    423  // Return early if the current time has not changed. However, if we
    424  // are pause-pending, then setting the current time to any value
    425  // including the current value has the effect of aborting the
    426  // pause so we should not return early in that case.
    427  if (mPendingState != PendingState::PausePending &&
    428      Nullable<TimeDuration>(aSeekTime) == GetCurrentTimeAsDuration()) {
    429    return;
    430  }
    431 
    432  AutoMutationBatchForAnimation mb(*this);
    433 
    434  SetCurrentTimeNoUpdate(aSeekTime);
    435 
    436  if (IsRelevant()) {
    437    MutationObservers::NotifyAnimationChanged(this);
    438  }
    439  PostUpdate();
    440 }
    441 
    442 void Animation::SetCurrentTimeNoUpdate(const TimeDuration& aSeekTime) {
    443  SilentlySetCurrentTime(aSeekTime);
    444 
    445  if (mPendingState == PendingState::PausePending) {
    446    // Finish the pause operation
    447    mHoldTime.SetValue(aSeekTime);
    448 
    449    ApplyPendingPlaybackRate();
    450    mStartTime.SetNull();
    451 
    452    if (mReady) {
    453      mReady->MaybeResolve(this);
    454    }
    455    CancelPendingTasks();
    456  }
    457 
    458  UpdateTiming(SeekFlag::DidSeek, SyncNotifyFlag::Async);
    459 }
    460 
    461 // https://drafts.csswg.org/web-animations-2/#the-overall-progress-of-an-animation
    462 Nullable<double> Animation::GetOverallProgress() const {
    463  Nullable<double> result;
    464  if (!mEffect) {
    465    return result;
    466  }
    467  const Nullable<TimeDuration> currentTime = GetCurrentTimeAsDuration();
    468  if (currentTime.IsNull()) {
    469    return result;
    470  }
    471 
    472  const StickyTimeDuration endTime = EffectEnd();
    473  if (endTime.IsZero()) {
    474    if (currentTime.Value() < TimeDuration(0)) {
    475      result.SetValue(0.0);
    476    } else {
    477      result.SetValue(1.0);
    478    }
    479    return result;
    480  }
    481 
    482  if (endTime == StickyTimeDuration::Forever()) {
    483    result.SetValue(0.0);
    484    return result;
    485  }
    486 
    487  auto overallProgress =
    488      std::min(std::max(currentTime.Value() / endTime, 0.0), 1.0);
    489  result.SetValue(overallProgress);
    490  return result;
    491 }
    492 
    493 // https://drafts.csswg.org/web-animations/#set-the-playback-rate
    494 void Animation::SetPlaybackRate(double aPlaybackRate) {
    495  mPendingPlaybackRate.reset();
    496 
    497  if (aPlaybackRate == mPlaybackRate) {
    498    return;
    499  }
    500 
    501  AutoMutationBatchForAnimation mb(*this);
    502 
    503  Nullable<TimeDuration> previousTime = GetCurrentTimeAsDuration();
    504  mPlaybackRate = aPlaybackRate;
    505  if (!previousTime.IsNull()) {
    506    SetCurrentTime(previousTime.Value());
    507  }
    508 
    509  // In the case where GetCurrentTimeAsDuration() returns the same result before
    510  // and after updating mPlaybackRate, SetCurrentTime will return early since,
    511  // as far as it can tell, nothing has changed.
    512  // As a result, we need to perform the following updates here:
    513  // - update timing (since, if the sign of the playback rate has changed, our
    514  //   finished state may have changed),
    515  // - dispatch a change notification for the changed playback rate, and
    516  // - update the playback rate on animations on layers.
    517  UpdateTiming(SeekFlag::DidSeek, SyncNotifyFlag::Async);
    518  if (IsRelevant()) {
    519    MutationObservers::NotifyAnimationChanged(this);
    520  }
    521  PostUpdate();
    522 }
    523 
    524 // https://drafts.csswg.org/web-animations/#seamlessly-update-the-playback-rate
    525 void Animation::UpdatePlaybackRate(double aPlaybackRate) {
    526  if (mPendingPlaybackRate && mPendingPlaybackRate.value() == aPlaybackRate) {
    527    return;
    528  }
    529 
    530  // Calculate the play state using the existing playback rate since below we
    531  // want to know if the animation is _currently_ finished or not, not whether
    532  // it _will_ be finished.
    533  AnimationPlayState playState = PlayState();
    534 
    535  mPendingPlaybackRate = Some(aPlaybackRate);
    536 
    537  if (Pending()) {
    538    // If we already have a pending task, there is nothing more to do since the
    539    // playback rate will be applied then.
    540    //
    541    // However, as with the idle/paused case below, we still need to update the
    542    // relevance (and effect set to make sure it only contains relevant
    543    // animations) since the relevance is based on the Animation play state
    544    // which incorporates the _pending_ playback rate.
    545    UpdateEffect(PostRestyleMode::Never);
    546    return;
    547  }
    548 
    549  AutoMutationBatchForAnimation mb(*this);
    550 
    551  if (playState == AnimationPlayState::Idle ||
    552      playState == AnimationPlayState::Paused ||
    553      GetCurrentTimeAsDuration().IsNull()) {
    554    // If |previous play state| is idle or paused, or the current time is
    555    // unresolved, we apply any pending playback rate on animation immediately.
    556    ApplyPendingPlaybackRate();
    557 
    558    // We don't need to update timing or post an update here because:
    559    //
    560    // * the current time hasn't changed -- it's either unresolved or fixed
    561    //   with a hold time -- so the output won't have changed
    562    // * the finished state won't have changed even if the sign of the
    563    //   playback rate changed since we're not finished (we're paused or idle)
    564    // * the playback rate on layers doesn't need to be updated since we're not
    565    //   moving. Once we get a start time etc. we'll update the playback rate
    566    //   then.
    567    //
    568    // However we still need to update the relevance and effect set as well as
    569    // notifying observers.
    570    UpdateEffect(PostRestyleMode::Never);
    571    if (IsRelevant()) {
    572      MutationObservers::NotifyAnimationChanged(this);
    573    }
    574  } else if (playState == AnimationPlayState::Finished) {
    575    MOZ_ASSERT(mTimeline && !mTimeline->GetCurrentTimeAsDuration().IsNull(),
    576               "If we have no active timeline, we should be idle or paused");
    577    if (aPlaybackRate != 0) {
    578      // The unconstrained current time can only be unresolved if either we
    579      // don't have an active timeline (and we already asserted that is not
    580      // true) or we have an unresolved start time (in which case we should be
    581      // paused).
    582      MOZ_ASSERT(!GetUnconstrainedCurrentTime().IsNull(),
    583                 "Unconstrained current time should be resolved");
    584      TimeDuration unconstrainedCurrentTime =
    585          GetUnconstrainedCurrentTime().Value();
    586      TimeDuration timelineTime = mTimeline->GetCurrentTimeAsDuration().Value();
    587      mStartTime = StartTimeFromTimelineTime(
    588          timelineTime, unconstrainedCurrentTime, aPlaybackRate);
    589    } else {
    590      mStartTime = mTimeline->GetCurrentTimeAsDuration();
    591    }
    592 
    593    ApplyPendingPlaybackRate();
    594 
    595    // Even though we preserve the current time, we might now leave the finished
    596    // state (e.g. if the playback rate changes sign) so we need to update
    597    // timing.
    598    UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
    599    if (IsRelevant()) {
    600      MutationObservers::NotifyAnimationChanged(this);
    601    }
    602    PostUpdate();
    603  } else {
    604    ErrorResult rv;
    605    Play(rv, LimitBehavior::Continue);
    606    MOZ_ASSERT(!rv.Failed(),
    607               "We should only fail to play when using auto-rewind behavior");
    608  }
    609 }
    610 
    611 // https://drafts.csswg.org/web-animations/#play-state
    612 AnimationPlayState Animation::PlayState() const {
    613  Nullable<TimeDuration> currentTime = GetCurrentTimeAsDuration();
    614  if (currentTime.IsNull() && mStartTime.IsNull() && !Pending()) {
    615    return AnimationPlayState::Idle;
    616  }
    617 
    618  if (mPendingState == PendingState::PausePending ||
    619      (mStartTime.IsNull() && !Pending())) {
    620    return AnimationPlayState::Paused;
    621  }
    622 
    623  double playbackRate = CurrentOrPendingPlaybackRate();
    624  if (!currentTime.IsNull() &&
    625      ((playbackRate > 0.0 && currentTime.Value() >= EffectEnd()) ||
    626       (playbackRate < 0.0 && currentTime.Value() <= TimeDuration()))) {
    627    return AnimationPlayState::Finished;
    628  }
    629 
    630  return AnimationPlayState::Running;
    631 }
    632 
    633 Promise* Animation::GetReady(ErrorResult& aRv) {
    634  nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal();
    635  if (!mReady && global) {
    636    mReady = Promise::Create(global, aRv);  // Lazily create on demand
    637  }
    638  if (!mReady) {
    639    aRv.Throw(NS_ERROR_FAILURE);
    640    return nullptr;
    641  }
    642  if (!Pending()) {
    643    mReady->MaybeResolve(this);
    644  }
    645  return mReady;
    646 }
    647 
    648 Promise* Animation::GetFinished(ErrorResult& aRv) {
    649  nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal();
    650  if (!mFinished && global) {
    651    mFinished = Promise::Create(global, aRv);  // Lazily create on demand
    652  }
    653  if (!mFinished) {
    654    aRv.Throw(NS_ERROR_FAILURE);
    655    return nullptr;
    656  }
    657  if (mFinishedIsResolved) {
    658    MaybeResolveFinishedPromise();
    659  }
    660  return mFinished;
    661 }
    662 
    663 // https://drafts.csswg.org/web-animations/#cancel-an-animation
    664 void Animation::Cancel(PostRestyleMode aPostRestyle) {
    665  bool newlyIdle = false;
    666 
    667  if (PlayState() != AnimationPlayState::Idle) {
    668    newlyIdle = true;
    669 
    670    ResetPendingTasks();
    671 
    672    if (mFinished) {
    673      mFinished->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
    674      // mFinished can already be resolved.
    675      MOZ_ALWAYS_TRUE(mFinished->SetAnyPromiseIsHandled());
    676    }
    677    ResetFinishedPromise();
    678 
    679    QueuePlaybackEvent(nsGkAtoms::oncancel,
    680                       GetTimelineCurrentTimeAsTimeStamp());
    681  }
    682 
    683  StickyTimeDuration activeTime =
    684      mEffect ? mEffect->GetComputedTiming().mActiveTime : StickyTimeDuration();
    685 
    686  mHoldTime.SetNull();
    687  mStartTime.SetNull();
    688 
    689  // Allow our effect to remove itself from the its target element's EffectSet.
    690  UpdateEffect(aPostRestyle);
    691 
    692  if (mTimeline) {
    693    mTimeline->RemoveAnimation(this);
    694  }
    695  MaybeQueueCancelEvent(activeTime);
    696 
    697  if (newlyIdle && aPostRestyle == PostRestyleMode::IfNeeded) {
    698    PostUpdate();
    699  }
    700 }
    701 
    702 // https://drafts.csswg.org/web-animations/#finish-an-animation
    703 void Animation::Finish(ErrorResult& aRv) {
    704  double effectivePlaybackRate = CurrentOrPendingPlaybackRate();
    705 
    706  if (effectivePlaybackRate == 0) {
    707    return aRv.ThrowInvalidStateError(
    708        "Can't finish animation with zero playback rate");
    709  }
    710  if (effectivePlaybackRate > 0 && EffectEnd() == TimeDuration::Forever()) {
    711    return aRv.ThrowInvalidStateError("Can't finish infinite animation");
    712  }
    713 
    714  AutoMutationBatchForAnimation mb(*this);
    715 
    716  ApplyPendingPlaybackRate();
    717 
    718  // Seek to the end
    719  TimeDuration limit =
    720      mPlaybackRate > 0 ? TimeDuration(EffectEnd()) : TimeDuration(0);
    721  bool didChange = GetCurrentTimeAsDuration() != Nullable<TimeDuration>(limit);
    722  SilentlySetCurrentTime(limit);
    723 
    724  // If we are paused or play-pending we need to fill in the start time in
    725  // order to transition to the finished state.
    726  //
    727  // We only do this, however, if we have an active timeline. If we have an
    728  // inactive timeline we can't transition into the finished state just like
    729  // we can't transition to the running state (this finished state is really
    730  // a substate of the running state).
    731  if (mStartTime.IsNull() && mTimeline &&
    732      !mTimeline->GetCurrentTimeAsDuration().IsNull()) {
    733    mStartTime = StartTimeFromTimelineTime(
    734        mTimeline->GetCurrentTimeAsDuration().Value(), limit, mPlaybackRate);
    735    didChange = true;
    736  }
    737 
    738  // If we just resolved the start time for a pause or play-pending
    739  // animation, we need to clear the task. We don't do this as a branch of
    740  // the above however since we can have a play-pending animation with a
    741  // resolved start time if we aborted a pause operation.
    742  if (!mStartTime.IsNull() && (mPendingState == PendingState::PlayPending ||
    743                               mPendingState == PendingState::PausePending)) {
    744    if (mPendingState == PendingState::PausePending) {
    745      mHoldTime.SetNull();
    746    }
    747    CancelPendingTasks();
    748    didChange = true;
    749    if (mReady) {
    750      mReady->MaybeResolve(this);
    751    }
    752  }
    753  UpdateTiming(SeekFlag::DidSeek, SyncNotifyFlag::Sync);
    754  if (didChange && IsRelevant()) {
    755    MutationObservers::NotifyAnimationChanged(this);
    756  }
    757  PostUpdate();
    758 }
    759 
    760 void Animation::Play(ErrorResult& aRv, LimitBehavior aLimitBehavior) {
    761  PlayNoUpdate(aRv, aLimitBehavior);
    762  PostUpdate();
    763 }
    764 
    765 // https://drafts.csswg.org/web-animations/#reverse-an-animation
    766 void Animation::Reverse(ErrorResult& aRv) {
    767  if (!mTimeline) {
    768    return aRv.ThrowInvalidStateError(
    769        "Can't reverse an animation with no associated timeline");
    770  }
    771  if (mTimeline->GetCurrentTimeAsDuration().IsNull()) {
    772    return aRv.ThrowInvalidStateError(
    773        "Can't reverse an animation associated with an inactive timeline");
    774  }
    775 
    776  double effectivePlaybackRate = CurrentOrPendingPlaybackRate();
    777 
    778  if (effectivePlaybackRate == 0.0) {
    779    return;
    780  }
    781 
    782  Maybe<double> originalPendingPlaybackRate = mPendingPlaybackRate;
    783 
    784  mPendingPlaybackRate = Some(-effectivePlaybackRate);
    785 
    786  Play(aRv, LimitBehavior::AutoRewind);
    787 
    788  // If Play() threw, restore state and don't report anything to mutation
    789  // observers.
    790  if (aRv.Failed()) {
    791    mPendingPlaybackRate = originalPendingPlaybackRate;
    792  }
    793 
    794  // Play(), above, unconditionally calls PostUpdate so we don't need to do
    795  // it here.
    796 }
    797 
    798 void Animation::Persist() {
    799  if (mReplaceState == AnimationReplaceState::Persisted) {
    800    return;
    801  }
    802 
    803  bool wasRemoved = mReplaceState == AnimationReplaceState::Removed;
    804 
    805  mReplaceState = AnimationReplaceState::Persisted;
    806 
    807  // If the animation is not (yet) removed, there should be no side effects of
    808  // persisting it.
    809  if (wasRemoved) {
    810    UpdateEffect(PostRestyleMode::IfNeeded);
    811    PostUpdate();
    812  }
    813 }
    814 
    815 DocGroup* Animation::GetDocGroup() {
    816  Document* doc = GetRenderedDocument();
    817  return doc ? doc->GetDocGroup() : nullptr;
    818 }
    819 
    820 // https://drafts.csswg.org/web-animations/#dom-animation-commitstyles
    821 void Animation::CommitStyles(ErrorResult& aRv) {
    822  if (!mEffect) {
    823    return;
    824  }
    825 
    826  // Take an owning reference to the keyframe effect. This will ensure that
    827  // this Animation and the target element remain alive after flushing style.
    828  RefPtr<KeyframeEffect> keyframeEffect = mEffect->AsKeyframeEffect();
    829  if (!keyframeEffect) {
    830    return;
    831  }
    832 
    833  NonOwningAnimationTarget target = keyframeEffect->GetAnimationTarget();
    834  if (!target) {
    835    return;
    836  }
    837 
    838  if (!target.mPseudoRequest.IsNotPseudo()) {
    839    return aRv.ThrowNoModificationAllowedError(
    840        "Can't commit styles of a pseudo-element");
    841  }
    842 
    843  // Check it is an element with a style attribute
    844  RefPtr<nsStyledElement> styledElement =
    845      nsStyledElement::FromNodeOrNull(target.mElement);
    846  if (!styledElement) {
    847    return aRv.ThrowNoModificationAllowedError(
    848        "Target is not capable of having a style attribute");
    849  }
    850 
    851  // Hold onto a strong reference to the doc in case the flush destroys it.
    852  RefPtr<Document> doc = target.mElement->GetComposedDoc();
    853 
    854  // Flush frames before checking if the target element is rendered since the
    855  // result could depend on pending style changes, and IsRendered() looks at the
    856  // primary frame.
    857  if (doc) {
    858    doc->FlushPendingNotifications(FlushType::Frames);
    859  }
    860  if (!target.mElement->IsRendered()) {
    861    return aRv.ThrowInvalidStateError("Target is not rendered");
    862  }
    863  nsPresContext* presContext =
    864      nsContentUtils::GetContextForContent(target.mElement);
    865  if (!presContext) {
    866    return aRv.ThrowInvalidStateError("Target is not rendered");
    867  }
    868 
    869  // Get the computed animation values
    870  UniquePtr<StyleAnimationValueMap> animationValues(
    871      Servo_AnimationValueMap_Create());
    872  if (!presContext->EffectCompositor()->ComposeServoAnimationRuleForEffect(
    873          *keyframeEffect, CascadeLevel(), animationValues.get(),
    874          StaticPrefs::dom_animations_commit_styles_endpoint_inclusive()
    875              ? EndpointBehavior::Inclusive
    876              : EndpointBehavior::Exclusive)) {
    877    NS_WARNING("Failed to compose animation style to commit");
    878    return;
    879  }
    880 
    881  // Calling SetCSSDeclaration will trigger attribute setting code.
    882  // Start the update now so that the old rule doesn't get used
    883  // between when we mutate the declaration and when we set the new
    884  // rule.
    885  mozAutoDocUpdate autoUpdate(target.mElement->OwnerDoc(), true);
    886 
    887  // Get the inline style to append to
    888  RefPtr<DeclarationBlock> declarationBlock;
    889  if (auto* existing = target.mElement->GetInlineStyleDeclaration()) {
    890    declarationBlock = existing->EnsureMutable();
    891  } else {
    892    declarationBlock = new DeclarationBlock();
    893    declarationBlock->SetDirty();
    894  }
    895 
    896  // Prepare the callback
    897  MutationClosureData closureData;
    898  closureData.mShouldBeCalled = true;
    899  closureData.mElement = target.mElement;
    900  DeclarationBlockMutationClosure beforeChangeClosure = {
    901      nsDOMCSSAttributeDeclaration::MutationClosureFunction,
    902      &closureData,
    903  };
    904 
    905  // Set the animated styles
    906  bool changed = false;
    907  const AnimatedPropertyIDSet& properties = keyframeEffect->GetPropertySet();
    908  for (const CSSPropertyId& property : properties) {
    909    RefPtr<StyleAnimationValue> computedValue =
    910        Servo_AnimationValueMap_GetValue(animationValues.get(), &property)
    911            .Consume();
    912    if (computedValue) {
    913      changed |= Servo_DeclarationBlock_SetPropertyToAnimationValue(
    914          declarationBlock->Raw(), computedValue, beforeChangeClosure);
    915    }
    916  }
    917 
    918  if (!changed) {
    919    MOZ_ASSERT(!closureData.mWasCalled);
    920    return;
    921  }
    922 
    923  MOZ_ASSERT(closureData.mWasCalled);
    924  // Update inline style declaration
    925  target.mElement->SetInlineStyleDeclaration(*declarationBlock, closureData);
    926 }
    927 
    928 // ---------------------------------------------------------------------------
    929 //
    930 // JS wrappers for Animation interface:
    931 //
    932 // ---------------------------------------------------------------------------
    933 
    934 Nullable<double> Animation::GetStartTimeAsDouble() const {
    935  return AnimationUtils::TimeDurationToDouble(mStartTime, mRTPCallerType);
    936 }
    937 
    938 void Animation::SetStartTimeAsDouble(const Nullable<double>& aStartTime) {
    939  return SetStartTime(AnimationUtils::DoubleToTimeDuration(aStartTime));
    940 }
    941 
    942 Nullable<double> Animation::GetCurrentTimeAsDouble() const {
    943  return AnimationUtils::TimeDurationToDouble(GetCurrentTimeAsDuration(),
    944                                              mRTPCallerType);
    945 }
    946 
    947 void Animation::SetCurrentTimeAsDouble(const Nullable<double>& aCurrentTime,
    948                                       ErrorResult& aRv) {
    949  if (aCurrentTime.IsNull()) {
    950    if (!GetCurrentTimeAsDuration().IsNull()) {
    951      aRv.ThrowTypeError(
    952          "Current time is resolved but trying to set it to an unresolved "
    953          "time");
    954    }
    955    return;
    956  }
    957 
    958  return SetCurrentTime(TimeDuration::FromMilliseconds(aCurrentTime.Value()));
    959 }
    960 
    961 // ---------------------------------------------------------------------------
    962 
    963 void Animation::Tick(AnimationTimeline::TickState& aTickState) {
    964  if (Pending()) {
    965    if (!mPendingReadyTime.IsNull()) {
    966      TryTriggerNow();
    967    } else if (MOZ_LIKELY(mTimeline)) {
    968      // Makes sure that we trigger the animation on the next tick but,
    969      // importantly, with this tick's timestamp.
    970      mPendingReadyTime = mTimeline->GetCurrentTimeAsTimeStamp();
    971    }
    972  }
    973 
    974  UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Sync);
    975 
    976  // Check for changes to whether or not this animation is replaceable.
    977  bool isReplaceable = IsReplaceable();
    978  if (isReplaceable && !mWasReplaceableAtLastTick) {
    979    ScheduleReplacementCheck();
    980  }
    981  mWasReplaceableAtLastTick = isReplaceable;
    982 
    983  if (!mEffect) {
    984    return;
    985  }
    986 
    987  // Update layers if we are newly finished.
    988  KeyframeEffect* keyframeEffect = mEffect->AsKeyframeEffect();
    989  if (keyframeEffect && !keyframeEffect->Properties().IsEmpty() &&
    990      !mFinishedAtLastComposeStyle &&
    991      PlayState() == AnimationPlayState::Finished) {
    992    PostUpdate();
    993  }
    994 }
    995 
    996 bool Animation::TryTriggerNow() {
    997  if (!Pending()) {
    998    return true;
    999  }
   1000  // If we don't have an active timeline we can't trigger the animation.
   1001  if (NS_WARN_IF(!mTimeline)) {
   1002    return false;
   1003  }
   1004  auto currentTime = mPendingReadyTime.IsNull()
   1005                         ? mTimeline->GetCurrentTimeAsDuration()
   1006                         : mTimeline->ToTimelineTime(mPendingReadyTime);
   1007  mPendingReadyTime = {};
   1008  if (NS_WARN_IF(currentTime.IsNull())) {
   1009    return false;
   1010  }
   1011  FinishPendingAt(currentTime.Value());
   1012  return true;
   1013 }
   1014 
   1015 TimeStamp Animation::AnimationTimeToTimeStamp(
   1016    const StickyTimeDuration& aTime) const {
   1017  // Initializes to null. Return the same object every time to benefit from
   1018  // return-value-optimization.
   1019  TimeStamp result;
   1020 
   1021  // We *don't* check for mTimeline->TracksWallclockTime() here because that
   1022  // method only tells us if the timeline times can be converted to
   1023  // TimeStamps that can be compared to TimeStamp::Now() or not, *not*
   1024  // whether the timelines can be converted to TimeStamp values at all.
   1025  //
   1026  // Furthermore, we want to be able to use this method when the refresh driver
   1027  // is under test control (in which case TracksWallclockTime() will return
   1028  // false).
   1029  //
   1030  // Once we introduce timelines that are not time-based we will need to
   1031  // differentiate between them here and determine how to sort their events.
   1032  if (!mTimeline) {
   1033    return result;
   1034  }
   1035 
   1036  // Check the time is convertible to a timestamp
   1037  if (aTime == TimeDuration::Forever() || mPlaybackRate == 0.0 ||
   1038      mStartTime.IsNull()) {
   1039    return result;
   1040  }
   1041 
   1042  // Invert the standard relation:
   1043  //   current time = (timeline time - start time) * playback rate
   1044  TimeDuration timelineTime =
   1045      TimeDuration(aTime).MultDouble(1.0 / mPlaybackRate) + mStartTime.Value();
   1046 
   1047  result = mTimeline->ToTimeStamp(timelineTime);
   1048  return result;
   1049 }
   1050 
   1051 TimeStamp Animation::ElapsedTimeToTimeStamp(
   1052    const StickyTimeDuration& aElapsedTime) const {
   1053  TimeDuration delay =
   1054      mEffect ? mEffect->NormalizedTiming().Delay() : TimeDuration();
   1055  return AnimationTimeToTimeStamp(aElapsedTime + delay);
   1056 }
   1057 
   1058 // https://drafts.csswg.org/web-animations/#silently-set-the-current-time
   1059 void Animation::SilentlySetCurrentTime(const TimeDuration& aSeekTime) {
   1060  // TODO: Bug 1762238: Introduce "valid seek time" after introducing
   1061  // CSSNumberish time values.
   1062  // https://drafts.csswg.org/web-animations-2/#silently-set-the-current-time
   1063 
   1064  if (!mHoldTime.IsNull() || mStartTime.IsNull() || !mTimeline ||
   1065      mTimeline->GetCurrentTimeAsDuration().IsNull() || mPlaybackRate == 0.0) {
   1066    mHoldTime.SetValue(aSeekTime);
   1067  } else {
   1068    mStartTime =
   1069        StartTimeFromTimelineTime(mTimeline->GetCurrentTimeAsDuration().Value(),
   1070                                  aSeekTime, mPlaybackRate);
   1071  }
   1072 
   1073  if (!mTimeline || mTimeline->GetCurrentTimeAsDuration().IsNull()) {
   1074    mStartTime.SetNull();
   1075  }
   1076 
   1077  mPreviousCurrentTime.SetNull();
   1078  mResetCurrentTimeOnResume = false;
   1079 }
   1080 
   1081 bool Animation::ShouldBeSynchronizedWithMainThread(
   1082    const nsCSSPropertyIDSet& aPropertySet, const nsIFrame* aFrame,
   1083    AnimationPerformanceWarning::Type& aPerformanceWarning) const {
   1084  // Only synchronize playing animations
   1085  if (!IsPlaying()) {
   1086    return false;
   1087  }
   1088 
   1089  // Currently only transform animations need to be synchronized
   1090  if (!aPropertySet.Intersects(nsCSSPropertyIDSet::TransformLikeProperties())) {
   1091    return false;
   1092  }
   1093 
   1094  KeyframeEffect* keyframeEffect =
   1095      mEffect ? mEffect->AsKeyframeEffect() : nullptr;
   1096  if (!keyframeEffect) {
   1097    return false;
   1098  }
   1099 
   1100  return keyframeEffect->ShouldBlockAsyncTransformAnimations(
   1101      aFrame, aPropertySet, aPerformanceWarning);
   1102 }
   1103 
   1104 void Animation::UpdateRelevance() {
   1105  bool wasRelevant = mIsRelevant;
   1106  mIsRelevant = mReplaceState != AnimationReplaceState::Removed &&
   1107                (HasCurrentEffect() || IsInEffect());
   1108 
   1109  // Notify animation observers.
   1110  if (wasRelevant && !mIsRelevant) {
   1111    MutationObservers::NotifyAnimationRemoved(this);
   1112  } else if (!wasRelevant && mIsRelevant) {
   1113    UpdateHiddenByContentVisibility();
   1114    MutationObservers::NotifyAnimationAdded(this);
   1115  }
   1116 }
   1117 
   1118 template <class T>
   1119 bool IsMarkupAnimation(T* aAnimation) {
   1120  return aAnimation && aAnimation->IsTiedToMarkup();
   1121 }
   1122 
   1123 // https://drafts.csswg.org/web-animations/#replaceable-animation
   1124 bool Animation::IsReplaceable() const {
   1125  // We never replace CSS animations or CSS transitions since they are managed
   1126  // by CSS.
   1127  if (IsMarkupAnimation(AsCSSAnimation()) ||
   1128      IsMarkupAnimation(AsCSSTransition())) {
   1129    return false;
   1130  }
   1131 
   1132  // Only finished animations can be replaced.
   1133  if (PlayState() != AnimationPlayState::Finished) {
   1134    return false;
   1135  }
   1136 
   1137  // Already removed animations cannot be replaced.
   1138  if (ReplaceState() == AnimationReplaceState::Removed) {
   1139    return false;
   1140  }
   1141 
   1142  // We can only replace an animation if we know that, uninterfered, it would
   1143  // never start playing again. That excludes any animations on timelines that
   1144  // aren't monotonically increasing.
   1145  //
   1146  // If we don't have any timeline at all, then we can't be in the finished
   1147  // state (since we need both a resolved start time and current time for that)
   1148  // and will have already returned false above.
   1149  //
   1150  // (However, if it ever does become possible to be finished without a timeline
   1151  // then we will want to return false here since it probably suggests an
   1152  // animation being driven directly by script, in which case we can't assume
   1153  // anything about how they will behave.)
   1154  if (!GetTimeline() || !GetTimeline()->TracksWallclockTime()) {
   1155    return false;
   1156  }
   1157 
   1158  // If the animation doesn't have an effect then we can't determine if it is
   1159  // filling or not so just leave it alone.
   1160  if (!GetEffect()) {
   1161    return false;
   1162  }
   1163 
   1164  // At the time of writing we only know about KeyframeEffects. If we introduce
   1165  // other types of effects we will need to decide if they are replaceable or
   1166  // not.
   1167  MOZ_ASSERT(GetEffect()->AsKeyframeEffect(),
   1168             "Effect should be a keyframe effect");
   1169 
   1170  // We only replace animations that are filling.
   1171  if (GetEffect()->GetComputedTiming().mProgress.IsNull()) {
   1172    return false;
   1173  }
   1174 
   1175  // We should only replace animations with a target element (since otherwise
   1176  // what other effects would we consider when determining if they are covered
   1177  // or not?).
   1178  if (!GetEffect()->AsKeyframeEffect()->GetAnimationTarget()) {
   1179    return false;
   1180  }
   1181 
   1182  return true;
   1183 }
   1184 
   1185 bool Animation::IsRemovable() const {
   1186  return ReplaceState() == AnimationReplaceState::Active && IsReplaceable();
   1187 }
   1188 
   1189 void Animation::ScheduleReplacementCheck() {
   1190  MOZ_ASSERT(
   1191      IsReplaceable(),
   1192      "Should only schedule a replacement check for a replaceable animation");
   1193 
   1194  // If IsReplaceable() is true, the following should also hold
   1195  MOZ_ASSERT(GetEffect());
   1196  MOZ_ASSERT(GetEffect()->AsKeyframeEffect());
   1197 
   1198  NonOwningAnimationTarget target =
   1199      GetEffect()->AsKeyframeEffect()->GetAnimationTarget();
   1200 
   1201  MOZ_ASSERT(target);
   1202 
   1203  nsPresContext* presContext =
   1204      nsContentUtils::GetContextForContent(target.mElement);
   1205  if (presContext) {
   1206    presContext->EffectCompositor()->NoteElementForReducing(target);
   1207  }
   1208 }
   1209 
   1210 void Animation::MaybeScheduleReplacementCheck() {
   1211  if (!IsReplaceable()) {
   1212    return;
   1213  }
   1214 
   1215  ScheduleReplacementCheck();
   1216 }
   1217 
   1218 void Animation::Remove() {
   1219  MOZ_ASSERT(IsRemovable(),
   1220             "Should not be trying to remove an effect that is not removable");
   1221 
   1222  mReplaceState = AnimationReplaceState::Removed;
   1223 
   1224  UpdateEffect(PostRestyleMode::IfNeeded);
   1225  PostUpdate();
   1226 
   1227  QueuePlaybackEvent(nsGkAtoms::onremove, GetTimelineCurrentTimeAsTimeStamp());
   1228 }
   1229 
   1230 int32_t Animation::CompareCompositeOrder(
   1231    const Maybe<EventContext>& aContext, const Animation& aOther,
   1232    const Maybe<EventContext>& aOtherContext,
   1233    nsContentUtils::NodeIndexCache& aCache) const {
   1234  // 0. Object-equality case
   1235  if (&aOther == this) {
   1236    return 0;
   1237  }
   1238 
   1239  // 1. CSS Transitions sort lowest
   1240  {
   1241    auto asCSSTransitionForSorting =
   1242        [](const Animation& anim,
   1243           const Maybe<EventContext>& aContext) -> const CSSTransition* {
   1244      const CSSTransition* transition = anim.AsCSSTransition();
   1245      return transition && (aContext || transition->IsTiedToMarkup())
   1246                 ? transition
   1247                 : nullptr;
   1248    };
   1249    const auto* const thisTransition =
   1250        asCSSTransitionForSorting(*this, aContext);
   1251    const auto* const otherTransition =
   1252        asCSSTransitionForSorting(aOther, aOtherContext);
   1253    if (thisTransition && otherTransition) {
   1254      return thisTransition->CompareCompositeOrder(aContext, *otherTransition,
   1255                                                   aOtherContext, aCache);
   1256    }
   1257    if (thisTransition || otherTransition) {
   1258      // Cancelled transitions no longer have an owning element. To be strictly
   1259      // correct we should store a strong reference to the owning element
   1260      // so that if we arrive here while sorting cancel events, we can sort
   1261      // them in the correct order.
   1262      //
   1263      // However, given that cancel events are almost always queued
   1264      // synchronously in some deterministic manner, we can be fairly sure
   1265      // that cancel events will be dispatched in a deterministic order
   1266      // (which is our only hard requirement until specs say otherwise).
   1267      // Furthermore, we only reach here when we have events with equal
   1268      // timestamps so this is an edge case we can probably ignore for now.
   1269      return thisTransition ? -1 : 1;
   1270    }
   1271  }
   1272 
   1273  // 2. CSS Animations sort next
   1274  {
   1275    auto asCSSAnimationForSorting =
   1276        [](const Animation& anim) -> const CSSAnimation* {
   1277      const CSSAnimation* animation = anim.AsCSSAnimation();
   1278      return animation && animation->IsTiedToMarkup() ? animation : nullptr;
   1279    };
   1280    auto thisAnimation = asCSSAnimationForSorting(*this);
   1281    auto otherAnimation = asCSSAnimationForSorting(aOther);
   1282    if (thisAnimation && otherAnimation) {
   1283      return thisAnimation->CompareCompositeOrder(*otherAnimation, aCache);
   1284    }
   1285    if (thisAnimation || otherAnimation) {
   1286      return thisAnimation ? -1 : 1;
   1287    }
   1288  }
   1289 
   1290  // Subclasses of Animation repurpose mAnimationIndex to implement their
   1291  // own brand of composite ordering. However, by this point we should have
   1292  // handled any such custom composite ordering so we should now have unique
   1293  // animation indices.
   1294  MOZ_ASSERT(mAnimationIndex != aOther.mAnimationIndex,
   1295             "Animation indices should be unique");
   1296 
   1297  // 3. Finally, generic animations sort by their position in the global
   1298  // animation array.
   1299  return mAnimationIndex > aOther.mAnimationIndex ? 1 : -1;
   1300 }
   1301 
   1302 void Animation::WillComposeStyle() {
   1303  mFinishedAtLastComposeStyle = (PlayState() == AnimationPlayState::Finished);
   1304 
   1305  MOZ_ASSERT(mEffect);
   1306 
   1307  KeyframeEffect* keyframeEffect = mEffect->AsKeyframeEffect();
   1308  if (keyframeEffect) {
   1309    keyframeEffect->WillComposeStyle();
   1310  }
   1311 }
   1312 
   1313 void Animation::ComposeStyle(
   1314    StyleAnimationValueMap& aComposeResult,
   1315    const InvertibleAnimatedPropertyIDSet& aPropertiesToSkip,
   1316    EndpointBehavior aEndpointBehavior) {
   1317  if (!mEffect) {
   1318    return;
   1319  }
   1320 
   1321  // In order to prevent flicker, there are a few cases where we want to use
   1322  // a different time for rendering that would otherwise be returned by
   1323  // GetCurrentTimeAsDuration. These are:
   1324  //
   1325  // (a) For animations that are pausing but which are still running on the
   1326  //     compositor. In this case we send a layer transaction that removes the
   1327  //     animation but which also contains the animation values calculated on
   1328  //     the main thread. To prevent flicker when this occurs we want to ensure
   1329  //     the timeline time used to calculate the main thread animation values
   1330  //     does not lag far behind the time used on the compositor. Ideally we
   1331  //     would like to use the "animation ready time", but for now we just use
   1332  //     the current wallclock time. TODO(emilio): After bug 1864425 it seems we
   1333  //     could just use the ready time, or maybe we can remove this?
   1334  //
   1335  // (b) For animations that are pausing that we have already taken off the
   1336  //     compositor. In this case we record a pending ready time but we don't
   1337  //     apply it until the next tick. However, while waiting for the next tick,
   1338  //     we should still use the pending ready time as the timeline time. If we
   1339  //     use the regular timeline time the animation may appear jump backwards
   1340  //     if the main thread's timeline time lags behind the compositor.
   1341  //
   1342  // (c) For animations that are play-pending due to an aborted pause operation
   1343  //     (i.e. a pause operation that was interrupted before we entered the
   1344  //     paused state). When we cancel a pending pause we might momentarily take
   1345  //     the animation off the compositor, only to re-add it moments later. In
   1346  //     that case the compositor might have been ahead of the main thread so we
   1347  //     should use the current wallclock time to ensure the animation doesn't
   1348  //     temporarily jump backwards.
   1349  //
   1350  // To address each of these cases we temporarily tweak the hold time
   1351  // immediately before updating the style rule and then restore it immediately
   1352  // afterwards. This is purely to prevent visual flicker. Other behavior
   1353  // such as dispatching events continues to rely on the regular timeline time.
   1354  const bool pending = Pending();
   1355  {
   1356    AutoRestore<Nullable<TimeDuration>> restoreHoldTime(mHoldTime);
   1357    if (pending && mHoldTime.IsNull() && !mStartTime.IsNull()) {
   1358      Nullable<TimeDuration> timeToUse;
   1359      if (mTimeline && mTimeline->TracksWallclockTime()) {
   1360        timeToUse = mTimeline->ToTimelineTime(TimeStamp::Now());
   1361      }
   1362      if (!timeToUse.IsNull()) {
   1363        mHoldTime = CurrentTimeFromTimelineTime(
   1364            timeToUse.Value(), mStartTime.Value(), mPlaybackRate);
   1365      }
   1366    }
   1367 
   1368    KeyframeEffect* keyframeEffect = mEffect->AsKeyframeEffect();
   1369    if (keyframeEffect) {
   1370      keyframeEffect->ComposeStyle(aComposeResult, aPropertiesToSkip,
   1371                                   aEndpointBehavior);
   1372    }
   1373  }
   1374 
   1375  MOZ_ASSERT(
   1376      pending == Pending(),
   1377      "Pending state should not change during the course of compositing");
   1378 }
   1379 
   1380 void Animation::NotifyEffectTimingUpdated() {
   1381  MOZ_ASSERT(mEffect,
   1382             "We should only update effect timing when we have a target "
   1383             "effect");
   1384  UpdateTiming(Animation::SeekFlag::NoSeek, Animation::SyncNotifyFlag::Async);
   1385 }
   1386 
   1387 void Animation::NotifyEffectPropertiesUpdated() {
   1388  MOZ_ASSERT(mEffect,
   1389             "We should only update effect properties when we have a target "
   1390             "effect");
   1391 
   1392  MaybeScheduleReplacementCheck();
   1393 }
   1394 
   1395 void Animation::NotifyEffectTargetUpdated() {
   1396  MOZ_ASSERT(mEffect,
   1397             "We should only update the effect target when we have a target "
   1398             "effect");
   1399 
   1400  MaybeScheduleReplacementCheck();
   1401 }
   1402 
   1403 static TimeStamp EnsurePaintIsScheduled(Document& aDoc) {
   1404  PresShell* presShell = aDoc.GetPresShell();
   1405  if (!presShell) {
   1406    return {};
   1407  }
   1408  nsIFrame* rootFrame = presShell->GetRootFrame();
   1409  if (!rootFrame) {
   1410    return {};
   1411  }
   1412  rootFrame->SchedulePaintWithoutInvalidatingObservers();
   1413  auto* rd = rootFrame->PresContext()->RefreshDriver();
   1414  if (!rd->IsInRefresh()) {
   1415    return {};
   1416  }
   1417  return rd->MostRecentRefresh();
   1418 }
   1419 
   1420 // https://drafts.csswg.org/web-animations/#play-an-animation
   1421 void Animation::PlayNoUpdate(ErrorResult& aRv, LimitBehavior aLimitBehavior) {
   1422  AutoMutationBatchForAnimation mb(*this);
   1423 
   1424  const bool isAutoRewind = aLimitBehavior == LimitBehavior::AutoRewind;
   1425  const bool abortedPause = mPendingState == PendingState::PausePending;
   1426  double effectivePlaybackRate = CurrentOrPendingPlaybackRate();
   1427 
   1428  Nullable<TimeDuration> currentTime = GetCurrentTimeAsDuration();
   1429  if (mResetCurrentTimeOnResume) {
   1430    currentTime.SetNull();
   1431    mResetCurrentTimeOnResume = false;
   1432  }
   1433 
   1434  Nullable<TimeDuration> seekTime;
   1435  if (isAutoRewind) {
   1436    if (effectivePlaybackRate >= 0.0 &&
   1437        (currentTime.IsNull() || currentTime.Value() < TimeDuration() ||
   1438         currentTime.Value() >= EffectEnd())) {
   1439      seekTime.SetValue(TimeDuration());
   1440    } else if (effectivePlaybackRate < 0.0 &&
   1441               (currentTime.IsNull() || currentTime.Value() <= TimeDuration() ||
   1442                currentTime.Value() > EffectEnd())) {
   1443      if (EffectEnd() == TimeDuration::Forever()) {
   1444        return aRv.ThrowInvalidStateError(
   1445            "Can't rewind animation with infinite effect end");
   1446      }
   1447      seekTime.SetValue(TimeDuration(EffectEnd()));
   1448    }
   1449  }
   1450 
   1451  if (seekTime.IsNull() && mStartTime.IsNull() && currentTime.IsNull()) {
   1452    seekTime.SetValue(TimeDuration());
   1453  }
   1454 
   1455  if (!seekTime.IsNull()) {
   1456    if (HasFiniteTimeline()) {
   1457      mStartTime = seekTime;
   1458      mHoldTime.SetNull();
   1459      ApplyPendingPlaybackRate();
   1460    } else {
   1461      mHoldTime = seekTime;
   1462    }
   1463  }
   1464 
   1465  bool reuseReadyPromise = false;
   1466  if (mPendingState != PendingState::NotPending) {
   1467    CancelPendingTasks();
   1468    reuseReadyPromise = true;
   1469  }
   1470 
   1471  // If the hold time is null then we're already playing normally and,
   1472  // typically, we can bail out here.
   1473  //
   1474  // However, there are two cases where we can't do that:
   1475  //
   1476  // (a) If we just aborted a pause. In this case, for consistency, we need to
   1477  //     go through the motions of doing an asynchronous start.
   1478  //
   1479  // (b) If we have timing changes (specifically a change to the playbackRate)
   1480  //     that should be applied asynchronously.
   1481  //
   1482  if (mHoldTime.IsNull() && seekTime.IsNull() && !abortedPause &&
   1483      !mPendingPlaybackRate) {
   1484    return;
   1485  }
   1486 
   1487  // Clear the start time until we resolve a new one. We do this except
   1488  // for the case where we are aborting a pause and don't have a hold time.
   1489  //
   1490  // If we're aborting a pause and *do* have a hold time (e.g. because
   1491  // the animation is finished or we just applied the auto-rewind behavior
   1492  // above) we should respect it by clearing the start time. If we *don't*
   1493  // have a hold time we should keep the current start time so that the
   1494  // the animation continues moving uninterrupted by the aborted pause.
   1495  //
   1496  // (If we're not aborting a pause, mHoldTime must be resolved by now
   1497  //  or else we would have returned above.)
   1498  if (!mHoldTime.IsNull()) {
   1499    mStartTime.SetNull();
   1500  }
   1501 
   1502  if (!reuseReadyPromise) {
   1503    // Clear ready promise. We'll create a new one lazily.
   1504    mReady = nullptr;
   1505  }
   1506 
   1507  mPendingState = PendingState::PlayPending;
   1508  mPendingReadyTime = {};
   1509  if (Document* doc = GetRenderedDocument()) {
   1510    if (HasFiniteTimeline()) {
   1511      // Always schedule a task even if we would like to let this animation
   1512      // immediately ready, per spec.
   1513      // https://drafts.csswg.org/web-animations/#playing-an-animation-section
   1514      // If there's no rendered document, we fail to track this animation, so
   1515      // let the scroll frame to trigger it when ticking.
   1516      doc->GetOrCreateScrollTimelineAnimationTracker()->AddPending(*this);
   1517    }
   1518    // Make sure to try to schedule a tick.
   1519    mPendingReadyTime = EnsurePaintIsScheduled(*doc);
   1520  }
   1521 
   1522  UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
   1523  if (IsRelevant()) {
   1524    MutationObservers::NotifyAnimationChanged(this);
   1525  }
   1526 }
   1527 
   1528 // https://drafts.csswg.org/web-animations/#pause-an-animation
   1529 void Animation::Pause(ErrorResult& aRv) {
   1530  if (IsPausedOrPausing()) {
   1531    return;
   1532  }
   1533 
   1534  AutoMutationBatchForAnimation mb(*this);
   1535 
   1536  Nullable<TimeDuration> seekTime;
   1537  // If we are transitioning from idle, fill in the current time
   1538  if (GetCurrentTimeAsDuration().IsNull()) {
   1539    if (mPlaybackRate >= 0.0) {
   1540      seekTime.SetValue(TimeDuration(0));
   1541    } else {
   1542      if (EffectEnd() == TimeDuration::Forever()) {
   1543        return aRv.ThrowInvalidStateError("Can't seek to infinite effect end");
   1544      }
   1545      seekTime.SetValue(TimeDuration(EffectEnd()));
   1546    }
   1547  }
   1548 
   1549  if (!seekTime.IsNull()) {
   1550    if (HasFiniteTimeline()) {
   1551      mStartTime = seekTime;
   1552    } else {
   1553      mHoldTime = seekTime;
   1554    }
   1555  }
   1556 
   1557  bool reuseReadyPromise = false;
   1558  if (mPendingState == PendingState::PlayPending) {
   1559    CancelPendingTasks();
   1560    reuseReadyPromise = true;
   1561  }
   1562 
   1563  if (!reuseReadyPromise) {
   1564    // Clear ready promise. We'll create a new one lazily.
   1565    mReady = nullptr;
   1566  }
   1567 
   1568  mPendingState = PendingState::PausePending;
   1569  mPendingReadyTime = {};
   1570  // See the relevant PlayPending code for comments.
   1571  if (Document* doc = GetRenderedDocument()) {
   1572    if (HasFiniteTimeline()) {
   1573      doc->GetOrCreateScrollTimelineAnimationTracker()->AddPending(*this);
   1574    }
   1575    mPendingReadyTime = EnsurePaintIsScheduled(*doc);
   1576  }
   1577 
   1578  UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
   1579  if (IsRelevant()) {
   1580    MutationObservers::NotifyAnimationChanged(this);
   1581  }
   1582 
   1583  PostUpdate();
   1584 }
   1585 
   1586 // https://drafts.csswg.org/web-animations/#play-an-animation
   1587 void Animation::ResumeAt(const TimeDuration& aReadyTime) {
   1588  // This method is only expected to be called for an animation that is
   1589  // waiting to play. We can easily adapt it to handle other states
   1590  // but it's currently not necessary.
   1591  MOZ_ASSERT(mPendingState == PendingState::PlayPending,
   1592             "Expected to resume a play-pending animation");
   1593  MOZ_ASSERT(!mHoldTime.IsNull() || !mStartTime.IsNull(),
   1594             "An animation in the play-pending state should have either a"
   1595             " resolved hold time or resolved start time");
   1596 
   1597  AutoMutationBatchForAnimation mb(*this);
   1598  bool hadPendingPlaybackRate = mPendingPlaybackRate.isSome();
   1599 
   1600  if (!mHoldTime.IsNull()) {
   1601    // The hold time is set, so we don't need any special handling to preserve
   1602    // the current time.
   1603    ApplyPendingPlaybackRate();
   1604    mStartTime =
   1605        StartTimeFromTimelineTime(aReadyTime, mHoldTime.Value(), mPlaybackRate);
   1606    if (mPlaybackRate != 0) {
   1607      mHoldTime.SetNull();
   1608    }
   1609  } else if (!mStartTime.IsNull() && mPendingPlaybackRate) {
   1610    // Apply any pending playback rate, preserving the current time.
   1611    TimeDuration currentTimeToMatch = CurrentTimeFromTimelineTime(
   1612        aReadyTime, mStartTime.Value(), mPlaybackRate);
   1613    ApplyPendingPlaybackRate();
   1614    mStartTime = StartTimeFromTimelineTime(aReadyTime, currentTimeToMatch,
   1615                                           mPlaybackRate);
   1616    if (mPlaybackRate == 0) {
   1617      mHoldTime.SetValue(currentTimeToMatch);
   1618    }
   1619  }
   1620 
   1621  mPendingState = PendingState::NotPending;
   1622 
   1623  UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Sync);
   1624 
   1625  // If we had a pending playback rate, we will have now applied it so we need
   1626  // to notify observers.
   1627  if (hadPendingPlaybackRate && IsRelevant()) {
   1628    MutationObservers::NotifyAnimationChanged(this);
   1629  }
   1630 
   1631  if (mReady) {
   1632    mReady->MaybeResolve(this);
   1633  }
   1634 }
   1635 
   1636 void Animation::PauseAt(const TimeDuration& aReadyTime) {
   1637  MOZ_ASSERT(mPendingState == PendingState::PausePending,
   1638             "Expected to pause a pause-pending animation");
   1639 
   1640  if (!mStartTime.IsNull() && mHoldTime.IsNull()) {
   1641    mHoldTime = CurrentTimeFromTimelineTime(aReadyTime, mStartTime.Value(),
   1642                                            mPlaybackRate);
   1643  }
   1644  ApplyPendingPlaybackRate();
   1645  mStartTime.SetNull();
   1646  mPendingState = PendingState::NotPending;
   1647 
   1648  UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
   1649 
   1650  if (mReady) {
   1651    mReady->MaybeResolve(this);
   1652  }
   1653 }
   1654 
   1655 void Animation::UpdateTiming(SeekFlag aSeekFlag,
   1656                             SyncNotifyFlag aSyncNotifyFlag) {
   1657  // We call UpdateFinishedState before UpdateEffect because the former
   1658  // can change the current time, which is used by the latter.
   1659  UpdateFinishedState(aSeekFlag, aSyncNotifyFlag);
   1660  UpdateEffect(PostRestyleMode::IfNeeded);
   1661 
   1662  if (mTimeline) {
   1663    mTimeline->NotifyAnimationUpdated(*this);
   1664  }
   1665 }
   1666 
   1667 // https://drafts.csswg.org/web-animations/#update-an-animations-finished-state
   1668 void Animation::UpdateFinishedState(SeekFlag aSeekFlag,
   1669                                    SyncNotifyFlag aSyncNotifyFlag) {
   1670  Nullable<TimeDuration> unconstrainedCurrentTime =
   1671      aSeekFlag == SeekFlag::NoSeek ? GetUnconstrainedCurrentTime()
   1672                                    : GetCurrentTimeAsDuration();
   1673  TimeDuration effectEnd = TimeDuration(EffectEnd());
   1674 
   1675  if (!unconstrainedCurrentTime.IsNull() && !mStartTime.IsNull() &&
   1676      mPendingState == PendingState::NotPending) {
   1677    if (mPlaybackRate > 0.0 && unconstrainedCurrentTime.Value() >= effectEnd) {
   1678      if (aSeekFlag == SeekFlag::DidSeek) {
   1679        mHoldTime = unconstrainedCurrentTime;
   1680      } else if (!mPreviousCurrentTime.IsNull()) {
   1681        mHoldTime.SetValue(std::max(mPreviousCurrentTime.Value(), effectEnd));
   1682      } else {
   1683        mHoldTime.SetValue(effectEnd);
   1684      }
   1685    } else if (mPlaybackRate < 0.0 &&
   1686               unconstrainedCurrentTime.Value() <= TimeDuration()) {
   1687      if (aSeekFlag == SeekFlag::DidSeek) {
   1688        mHoldTime = unconstrainedCurrentTime;
   1689      } else if (!mPreviousCurrentTime.IsNull()) {
   1690        mHoldTime.SetValue(
   1691            std::min(mPreviousCurrentTime.Value(), TimeDuration(0)));
   1692      } else {
   1693        mHoldTime.SetValue(0);
   1694      }
   1695    } else if (mPlaybackRate != 0.0 && mTimeline &&
   1696               !mTimeline->GetCurrentTimeAsDuration().IsNull()) {
   1697      if (aSeekFlag == SeekFlag::DidSeek && !mHoldTime.IsNull()) {
   1698        mStartTime = StartTimeFromTimelineTime(
   1699            mTimeline->GetCurrentTimeAsDuration().Value(), mHoldTime.Value(),
   1700            mPlaybackRate);
   1701      }
   1702      mHoldTime.SetNull();
   1703    }
   1704  }
   1705 
   1706  // We must recalculate the current time to take account of any mHoldTime
   1707  // changes the code above made.
   1708  mPreviousCurrentTime = GetCurrentTimeAsDuration();
   1709 
   1710  bool currentFinishedState = PlayState() == AnimationPlayState::Finished;
   1711  if (currentFinishedState && !mFinishedIsResolved) {
   1712    DoFinishNotification(aSyncNotifyFlag);
   1713  } else if (!currentFinishedState && mFinishedIsResolved) {
   1714    ResetFinishedPromise();
   1715  }
   1716 }
   1717 
   1718 void Animation::UpdateEffect(PostRestyleMode aPostRestyle) {
   1719  if (mEffect) {
   1720    UpdateRelevance();
   1721 
   1722    KeyframeEffect* keyframeEffect = mEffect->AsKeyframeEffect();
   1723    if (keyframeEffect) {
   1724      keyframeEffect->NotifyAnimationTimingUpdated(aPostRestyle);
   1725    }
   1726  }
   1727 }
   1728 
   1729 void Animation::FlushUnanimatedStyle() const {
   1730  if (Document* doc = GetRenderedDocument()) {
   1731    doc->FlushPendingNotifications(
   1732        ChangesToFlush(FlushType::Style, /* aFlushAnimations = */ false,
   1733                       /* aUpdateRelevancy = */ false));
   1734  }
   1735 }
   1736 
   1737 void Animation::PostUpdate() {
   1738  if (!mEffect) {
   1739    return;
   1740  }
   1741 
   1742  KeyframeEffect* keyframeEffect = mEffect->AsKeyframeEffect();
   1743  if (!keyframeEffect) {
   1744    return;
   1745  }
   1746  keyframeEffect->RequestRestyle(EffectCompositor::RestyleType::Layer);
   1747 }
   1748 
   1749 void Animation::CancelPendingTasks() {
   1750  mPendingState = PendingState::NotPending;
   1751 }
   1752 
   1753 // https://drafts.csswg.org/web-animations/#reset-an-animations-pending-tasks
   1754 void Animation::ResetPendingTasks() {
   1755  if (!Pending()) {
   1756    return;
   1757  }
   1758 
   1759  CancelPendingTasks();
   1760  ApplyPendingPlaybackRate();
   1761 
   1762  if (mReady) {
   1763    mReady->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
   1764    MOZ_ALWAYS_TRUE(mReady->SetAnyPromiseIsHandled());
   1765    mReady = nullptr;
   1766  }
   1767 }
   1768 
   1769 // https://drafts.csswg.org/web-animations-2/#at-progress-timeline-boundary
   1770 /* static*/ Animation::ProgressTimelinePosition
   1771 Animation::AtProgressTimelineBoundary(
   1772    const Nullable<TimeDuration>& aTimelineDuration,
   1773    const Nullable<TimeDuration>& aCurrentTime,
   1774    const TimeDuration& aEffectStartTime, const double aPlaybackRate) {
   1775  // Based on changed defined in: https://github.com/w3c/csswg-drafts/pull/6702
   1776  // 1.  If any of the following conditions are true:
   1777  //     * the associated animation's timeline is not a progress-based timeline,
   1778  //     or
   1779  //     * the associated animation's timeline duration is unresolved or zero,
   1780  //     or
   1781  //     * the animation's playback rate is zero
   1782  //     return false
   1783  // Note: We can detect a progress-based timeline by relying on the fact that
   1784  // monotonic timelines (i.e. non-progress-based timelines) have an unresolved
   1785  // timeline duration.
   1786  if (aTimelineDuration.IsNull() || aTimelineDuration.Value().IsZero() ||
   1787      aPlaybackRate == 0.0) {
   1788    return ProgressTimelinePosition::NotBoundary;
   1789  }
   1790 
   1791  // 2.  Let effective start time be the animation's start time if resolved, or
   1792  // zero otherwise.
   1793  const TimeDuration& effectiveStartTime = aEffectStartTime;
   1794 
   1795  // 3.  Let effective timeline time be (animation's current time / animation's
   1796  // playback rate) + effective start time.
   1797  // Note: we use zero if the current time is unresolved. See the spec issue:
   1798  // https://github.com/w3c/csswg-drafts/issues/7458
   1799  const TimeDuration effectiveTimelineTime =
   1800      (aCurrentTime.IsNull()
   1801           ? TimeDuration()
   1802           : aCurrentTime.Value().MultDouble(1.0 / aPlaybackRate)) +
   1803      effectiveStartTime;
   1804 
   1805  // 4.  Let effective timeline progress be (effective timeline time / timeline
   1806  // duration)
   1807  // 5.  If effective timeline progress is 0 or 1, return true,
   1808  // We avoid the division here but it is effectively the same as 4 & 5 above.
   1809  return effectiveTimelineTime.IsZero() ||
   1810                 (AnimationUtils::IsWithinAnimationTimeTolerance(
   1811                     effectiveTimelineTime, aTimelineDuration.Value()))
   1812             ? ProgressTimelinePosition::Boundary
   1813             : ProgressTimelinePosition::NotBoundary;
   1814 }
   1815 
   1816 StickyTimeDuration Animation::EffectEnd() const {
   1817  if (!mEffect) {
   1818    return StickyTimeDuration(0);
   1819  }
   1820 
   1821  return mEffect->NormalizedTiming().EndTime();
   1822 }
   1823 
   1824 Document* Animation::GetRenderedDocument() const {
   1825  if (!mEffect || !mEffect->AsKeyframeEffect()) {
   1826    return nullptr;
   1827  }
   1828 
   1829  return mEffect->AsKeyframeEffect()->GetRenderedDocument();
   1830 }
   1831 
   1832 Document* Animation::GetTimelineDocument() const {
   1833  return mTimeline ? mTimeline->GetDocument() : nullptr;
   1834 }
   1835 
   1836 void Animation::UpdateScrollTimelineAnimationTracker(
   1837    AnimationTimeline* aOldTimeline, AnimationTimeline* aNewTimeline) {
   1838  // If we are still in pending, we may have to move this animation into the
   1839  // correct animation tracker.
   1840  Document* doc = GetRenderedDocument();
   1841  if (!doc || !Pending()) {
   1842    return;
   1843  }
   1844 
   1845  const bool fromFiniteTimeline =
   1846      aOldTimeline && !aOldTimeline->IsMonotonicallyIncreasing();
   1847  const bool toFiniteTimeline =
   1848      aNewTimeline && !aNewTimeline->IsMonotonicallyIncreasing();
   1849  if (fromFiniteTimeline == toFiniteTimeline) {
   1850    return;
   1851  }
   1852 
   1853  if (toFiniteTimeline) {
   1854    doc->GetOrCreateScrollTimelineAnimationTracker()->AddPending(*this);
   1855  } else {
   1856    // From scroll-timeline to null/document-timeline
   1857    if (auto* tracker = doc->GetScrollTimelineAnimationTracker()) {
   1858      tracker->RemovePending(*this);
   1859    }
   1860    EnsurePaintIsScheduled(*doc);
   1861  }
   1862 }
   1863 
   1864 class AsyncFinishNotification : public MicroTaskRunnable {
   1865 public:
   1866  explicit AsyncFinishNotification(Animation* aAnimation)
   1867      : mAnimation(aAnimation) {}
   1868 
   1869  virtual void Run(AutoSlowOperation& aAso) override {
   1870    mAnimation->DoFinishNotificationImmediately(this);
   1871    mAnimation = nullptr;
   1872  }
   1873 
   1874  virtual bool Suppressed() override {
   1875    nsIGlobalObject* global = mAnimation->GetOwnerGlobal();
   1876    return global && global->IsInSyncOperation();
   1877  }
   1878 
   1879 private:
   1880  RefPtr<Animation> mAnimation;
   1881 };
   1882 
   1883 void Animation::DoFinishNotification(SyncNotifyFlag aSyncNotifyFlag) {
   1884  CycleCollectedJSContext* context = CycleCollectedJSContext::Get();
   1885 
   1886  if (aSyncNotifyFlag == SyncNotifyFlag::Sync) {
   1887    DoFinishNotificationImmediately();
   1888  } else if (!mFinishNotificationTask) {
   1889    RefPtr<MicroTaskRunnable> runnable = new AsyncFinishNotification(this);
   1890    context->DispatchToMicroTask(do_AddRef(runnable));
   1891    mFinishNotificationTask = std::move(runnable);
   1892  }
   1893 }
   1894 
   1895 void Animation::ResetFinishedPromise() {
   1896  mFinishedIsResolved = false;
   1897  mFinished = nullptr;
   1898 }
   1899 
   1900 void Animation::MaybeResolveFinishedPromise() {
   1901  if (mFinished) {
   1902    mFinished->MaybeResolve(this);
   1903  }
   1904  mFinishedIsResolved = true;
   1905 }
   1906 
   1907 void Animation::DoFinishNotificationImmediately(MicroTaskRunnable* aAsync) {
   1908  if (aAsync && aAsync != mFinishNotificationTask) {
   1909    return;
   1910  }
   1911 
   1912  mFinishNotificationTask = nullptr;
   1913 
   1914  if (PlayState() != AnimationPlayState::Finished) {
   1915    return;
   1916  }
   1917 
   1918  MaybeResolveFinishedPromise();
   1919 
   1920  QueuePlaybackEvent(nsGkAtoms::onfinish,
   1921                     AnimationTimeToTimeStamp(EffectEnd()));
   1922 }
   1923 
   1924 void Animation::QueuePlaybackEvent(nsAtom* aOnEvent,
   1925                                   TimeStamp&& aScheduledEventTime) {
   1926  // Use document for timing.
   1927  // https://drafts.csswg.org/web-animations-1/#document-for-timing
   1928  Document* doc = GetTimelineDocument();
   1929  if (!doc) {
   1930    return;
   1931  }
   1932 
   1933  nsPresContext* presContext = doc->GetPresContext();
   1934  if (!presContext) {
   1935    return;
   1936  }
   1937 
   1938  Nullable<double> currentTime;
   1939  if (aOnEvent == nsGkAtoms::onfinish || aOnEvent == nsGkAtoms::onremove) {
   1940    currentTime = GetCurrentTimeAsDouble();
   1941  }
   1942 
   1943  Nullable<double> timelineTime;
   1944  if (mTimeline) {
   1945    timelineTime = mTimeline->GetCurrentTimeAsDouble();
   1946  }
   1947 
   1948  presContext->AnimationEventDispatcher()->QueueEvent(
   1949      AnimationEventInfo(aOnEvent, currentTime, timelineTime,
   1950                         std::move(aScheduledEventTime), this));
   1951 }
   1952 
   1953 bool Animation::IsRunningOnCompositor() const {
   1954  return mEffect && mEffect->AsKeyframeEffect() &&
   1955         mEffect->AsKeyframeEffect()->IsRunningOnCompositor();
   1956 }
   1957 
   1958 bool Animation::HasCurrentEffect() const {
   1959  return GetEffect() && GetEffect()->IsCurrent();
   1960 }
   1961 
   1962 bool Animation::IsInEffect() const {
   1963  return GetEffect() && GetEffect()->IsInEffect();
   1964 }
   1965 
   1966 void Animation::SetHiddenByContentVisibility(bool hidden) {
   1967  if (mHiddenByContentVisibility == hidden) {
   1968    return;
   1969  }
   1970 
   1971  mHiddenByContentVisibility = hidden;
   1972 
   1973  if (!GetTimeline()) {
   1974    return;
   1975  }
   1976 
   1977  GetTimeline()->NotifyAnimationContentVisibilityChanged(this, !hidden);
   1978 }
   1979 
   1980 void Animation::UpdateHiddenByContentVisibility() {
   1981  // To be consistent with nsIFrame::UpdateAnimationVisibility, here we only
   1982  // deal with CSSAnimation and CSSTransition.
   1983  if (!AsCSSAnimation() && !AsCSSTransition()) {
   1984    return;
   1985  }
   1986  NonOwningAnimationTarget target = GetTargetForAnimation();
   1987  if (!target) {
   1988    return;
   1989  }
   1990  // If a CSS animation or CSS transition is no longer associated with an owning
   1991  // element, it behaves like a programmatic web animation, c-v shouldn't hide
   1992  // it.
   1993  bool hasOwningElement = IsMarkupAnimation(AsCSSAnimation()) ||
   1994                          IsMarkupAnimation(AsCSSTransition());
   1995  if (auto* frame = target.mElement->GetPrimaryFrame()) {
   1996    SetHiddenByContentVisibility(
   1997        hasOwningElement && frame->IsHiddenByContentVisibilityOnAnyAncestor());
   1998  }
   1999 }
   2000 
   2001 StickyTimeDuration Animation::IntervalStartTime(
   2002    const StickyTimeDuration& aActiveDuration) const {
   2003  MOZ_ASSERT(AsCSSTransition() || AsCSSAnimation(),
   2004             "Should be called for CSS animations or transitions");
   2005  static constexpr StickyTimeDuration zeroDuration = StickyTimeDuration();
   2006  return std::max(
   2007      std::min(StickyTimeDuration(-mEffect->NormalizedTiming().Delay()),
   2008               aActiveDuration),
   2009      zeroDuration);
   2010 }
   2011 
   2012 // Later side of the elapsed time range reported in CSS Animations and CSS
   2013 // Transitions events.
   2014 //
   2015 // https://drafts.csswg.org/css-animations-2/#interval-end
   2016 // https://drafts.csswg.org/css-transitions-2/#interval-end
   2017 StickyTimeDuration Animation::IntervalEndTime(
   2018    const StickyTimeDuration& aActiveDuration) const {
   2019  MOZ_ASSERT(AsCSSTransition() || AsCSSAnimation(),
   2020             "Should be called for CSS animations or transitions");
   2021 
   2022  static constexpr StickyTimeDuration zeroDuration = StickyTimeDuration();
   2023  const StickyTimeDuration& effectEnd = EffectEnd();
   2024 
   2025  // If both "associated effect end" and "start delay" are Infinity, we skip it
   2026  // because we will get NaN when computing "Infinity - Infinity", and
   2027  // using NaN in std::min or std::max is undefined.
   2028  if (MOZ_UNLIKELY(effectEnd == TimeDuration::Forever() &&
   2029                   effectEnd == mEffect->NormalizedTiming().Delay())) {
   2030    // Note: If we use TimeDuration::Forever(), within our animation event
   2031    // handling, we'd end up turning that into a null TimeStamp which can causes
   2032    // errors if we try to do any arithmetic with it. Given that we should never
   2033    // end up _using_ the interval end time. So returning zeroDuration here is
   2034    // probably fine.
   2035    return zeroDuration;
   2036  }
   2037 
   2038  return std::max(std::min(effectEnd - mEffect->NormalizedTiming().Delay(),
   2039                           aActiveDuration),
   2040                  zeroDuration);
   2041 }
   2042 
   2043 }  // namespace mozilla::dom