bool Animation::IsPossiblyOrphanedPendingAnimation() const { // Check if we are pending but might never start because we are not being // tracked. // // This covers the following cases: // // * We started playing but our effect's target element was orphaned // or bound to a different document. // (note that for the case of our effect changing we should handle // that in SetEffect) // * We started playing but our timeline became inactive. // In this case the pending animation tracker will drop us from its hashmap // when we have been painted. // * When we started playing we couldn't find a PendingAnimationTracker to // register with (perhaps the effect had no document) so we simply // set mPendingState in DoPlay and relied on this method to catch us on the // next tick. // If we're not pending we're ok. if (mPendingState == PendingState::NotPending) { return false; } // If we have a pending ready time then we will be started on the next // tick. if (!mPendingReadyTime.IsNull()) { return false; } // If we don't have an active timeline then we shouldn't start until // we do. if (!mTimeline || mTimeline->GetCurrentTime().IsNull()) { return false; } // If we have no rendered document, or we're not in our rendered document's // PendingAnimationTracker then there's a good chance no one is tracking us. // // If we're wrong and another document is tracking us then, at worst, we'll // simply start/pause the animation one tick too soon. That's better than // never starting/pausing the animation and is unlikely. nsIDocument* doc = GetRenderedDocument(); if (!doc) { return false; } PendingAnimationTracker* tracker = doc->GetPendingAnimationTracker(); return !tracker || (!tracker->IsWaitingToPlay(*this) && !tracker->IsWaitingToPause(*this)); }
// https://w3c.github.io/web-animations/#pause-an-animation void Animation::DoPause(ErrorResult& aRv) { if (IsPausedOrPausing()) { return; } AutoMutationBatchForAnimation mb(*this); // If we are transitioning from idle, fill in the current time if (GetCurrentTime().IsNull()) { if (mPlaybackRate >= 0.0) { mHoldTime.SetValue(TimeDuration(0)); } else { if (EffectEnd() == TimeDuration::Forever()) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return; } mHoldTime.SetValue(TimeDuration(EffectEnd())); } } bool reuseReadyPromise = false; if (mPendingState == PendingState::PlayPending) { CancelPendingTasks(); reuseReadyPromise = true; } if (!reuseReadyPromise) { // Clear ready promise. We'll create a new one lazily. mReady = nullptr; } mPendingState = PendingState::PausePending; nsIDocument* doc = GetRenderedDocument(); if (doc) { PendingAnimationTracker* tracker = doc->GetOrCreatePendingAnimationTracker(); tracker->AddPausePending(*this); } else { TriggerOnNextTick(Nullable<TimeDuration>()); } UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async); if (IsRelevant()) { nsNodeUtils::AnimationChanged(this); } }
// http://w3c.github.io/web-animations/#pause-an-animation void Animation::DoPause(ErrorResult& aRv) { if (IsPausedOrPausing()) { return; } // If we are transitioning from idle, fill in the current time if (GetCurrentTime().IsNull()) { if (mPlaybackRate >= 0.0) { mHoldTime.SetValue(TimeDuration(0)); } else { if (EffectEnd() == TimeDuration::Forever()) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return; } mHoldTime.SetValue(TimeDuration(EffectEnd())); } } bool reuseReadyPromise = false; if (mPendingState == PendingState::PlayPending) { CancelPendingTasks(); reuseReadyPromise = true; } // Mark this as no longer running on the compositor so that next time // we update animations we won't throttle them and will have a chance // to remove the animation from any layer it might be on. mIsRunningOnCompositor = false; if (!reuseReadyPromise) { // Clear ready promise. We'll create a new one lazily. mReady = nullptr; } mPendingState = PendingState::PausePending; nsIDocument* doc = GetRenderedDocument(); if (doc) { PendingAnimationTracker* tracker = doc->GetOrCreatePendingAnimationTracker(); tracker->AddPausePending(*this); } else { TriggerOnNextTick(Nullable<TimeDuration>()); } UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async); }
VectorImage::RequestRefresh(const TimeStamp& aTime) { if (HadRecentRefresh(aTime)) { return; } PendingAnimationTracker* tracker = mSVGDocumentWrapper->GetDocument()->GetPendingAnimationTracker(); if (tracker && ShouldAnimate()) { tracker->TriggerPendingAnimationsOnNextTick(aTime); } EvaluateAnimation(); mSVGDocumentWrapper->TickRefreshDriver(); if (mHasPendingInvalidation) { mHasPendingInvalidation = false; SendInvalidationNotifications(); } }
void Animation::CancelPendingTasks() { if (mPendingState == PendingState::NotPending) { return; } nsIDocument* doc = GetRenderedDocument(); if (doc) { PendingAnimationTracker* tracker = doc->GetPendingAnimationTracker(); if (tracker) { if (mPendingState == PendingState::PlayPending) { tracker->RemovePlayPending(*this); } else { tracker->RemovePausePending(*this); } } } mPendingState = PendingState::NotPending; mPendingReadyTime.SetNull(); }
// http://w3c.github.io/web-animations/#pause-an-animation void Animation::DoPause() { if (IsPausedOrPausing()) { return; } bool reuseReadyPromise = false; if (mPendingState == PendingState::PlayPending) { CancelPendingTasks(); reuseReadyPromise = true; } // Mark this as no longer running on the compositor so that next time // we update animations we won't throttle them and will have a chance // to remove the animation from any layer it might be on. mIsRunningOnCompositor = false; if (!reuseReadyPromise) { // Clear ready promise. We'll create a new one lazily. mReady = nullptr; } mPendingState = PendingState::PausePending; nsIDocument* doc = GetRenderedDocument(); if (!doc) { TriggerOnNextTick(Nullable<TimeDuration>()); return; } PendingAnimationTracker* tracker = doc->GetOrCreatePendingAnimationTracker(); tracker->AddPausePending(*this); UpdateFinishedState(); }
// http://w3c.github.io/web-animations/#play-an-animation void Animation::DoPlay(LimitBehavior aLimitBehavior) { bool abortedPause = mPendingState == PendingState::PausePending; bool reuseReadyPromise = false; if (mPendingState != PendingState::NotPending) { CancelPendingTasks(); reuseReadyPromise = true; } Nullable<TimeDuration> currentTime = GetCurrentTime(); if (mPlaybackRate > 0.0 && (currentTime.IsNull() || (aLimitBehavior == LimitBehavior::AutoRewind && (currentTime.Value().ToMilliseconds() < 0.0 || currentTime.Value() >= EffectEnd())))) { mHoldTime.SetValue(TimeDuration(0)); } else if (mPlaybackRate < 0.0 && (currentTime.IsNull() || (aLimitBehavior == LimitBehavior::AutoRewind && (currentTime.Value().ToMilliseconds() <= 0.0 || currentTime.Value() > EffectEnd())))) { mHoldTime.SetValue(TimeDuration(EffectEnd())); } else if (mPlaybackRate == 0.0 && currentTime.IsNull()) { mHoldTime.SetValue(TimeDuration(0)); } // If the hold time is null then we're either already playing normally (and // we can ignore this call) or we aborted a pending pause operation (in which // case, for consistency, we need to go through the motions of doing an // asynchronous start even though we already have a resolved start time). if (mHoldTime.IsNull() && !abortedPause) { return; } // Clear the start time until we resolve a new one. We do this except // for the case where we are aborting a pause and don't have a hold time. // // If we're aborting a pause and *do* have a hold time (e.g. because // the animation is finished or we just applied the auto-rewind behavior // above) we should respect it by clearing the start time. If we *don't* // have a hold time we should keep the current start time so that the // the animation continues moving uninterrupted by the aborted pause. // // (If we're not aborting a pause, mHoldTime must be resolved by now // or else we would have returned above.) if (!mHoldTime.IsNull()) { mStartTime.SetNull(); } if (!reuseReadyPromise) { // Clear ready promise. We'll create a new one lazily. mReady = nullptr; } mPendingState = PendingState::PlayPending; nsIDocument* doc = GetRenderedDocument(); if (!doc) { TriggerOnNextTick(Nullable<TimeDuration>()); return; } PendingAnimationTracker* tracker = doc->GetOrCreatePendingAnimationTracker(); tracker->AddPlayPending(*this); // We may have updated the current time when we set the hold time above. UpdateTiming(); }
// https://w3c.github.io/web-animations/#play-an-animation void Animation::PlayNoUpdate(ErrorResult& aRv, LimitBehavior aLimitBehavior) { AutoMutationBatchForAnimation mb(*this); bool abortedPause = mPendingState == PendingState::PausePending; Nullable<TimeDuration> currentTime = GetCurrentTime(); if (mPlaybackRate > 0.0 && (currentTime.IsNull() || (aLimitBehavior == LimitBehavior::AutoRewind && (currentTime.Value() < TimeDuration() || currentTime.Value() >= EffectEnd())))) { mHoldTime.SetValue(TimeDuration(0)); } else if (mPlaybackRate < 0.0 && (currentTime.IsNull() || (aLimitBehavior == LimitBehavior::AutoRewind && (currentTime.Value() <= TimeDuration() || currentTime.Value() > EffectEnd())))) { if (EffectEnd() == TimeDuration::Forever()) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return; } mHoldTime.SetValue(TimeDuration(EffectEnd())); } else if (mPlaybackRate == 0.0 && currentTime.IsNull()) { mHoldTime.SetValue(TimeDuration(0)); } bool reuseReadyPromise = false; if (mPendingState != PendingState::NotPending) { CancelPendingTasks(); reuseReadyPromise = true; } // If the hold time is null then we're either already playing normally (and // we can ignore this call) or we aborted a pending pause operation (in which // case, for consistency, we need to go through the motions of doing an // asynchronous start even though we already have a resolved start time). if (mHoldTime.IsNull() && !abortedPause) { return; } // Clear the start time until we resolve a new one. We do this except // for the case where we are aborting a pause and don't have a hold time. // // If we're aborting a pause and *do* have a hold time (e.g. because // the animation is finished or we just applied the auto-rewind behavior // above) we should respect it by clearing the start time. If we *don't* // have a hold time we should keep the current start time so that the // the animation continues moving uninterrupted by the aborted pause. // // (If we're not aborting a pause, mHoldTime must be resolved by now // or else we would have returned above.) if (!mHoldTime.IsNull()) { mStartTime.SetNull(); } if (!reuseReadyPromise) { // Clear ready promise. We'll create a new one lazily. mReady = nullptr; } mPendingState = PendingState::PlayPending; nsIDocument* doc = GetRenderedDocument(); if (doc) { PendingAnimationTracker* tracker = doc->GetOrCreatePendingAnimationTracker(); tracker->AddPlayPending(*this); } else { TriggerOnNextTick(Nullable<TimeDuration>()); } UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async); if (IsRelevant()) { nsNodeUtils::AnimationChanged(this); } }
// https://w3c.github.io/web-animations/#setting-the-target-effect void Animation::SetEffectNoUpdate(AnimationEffectReadOnly* aEffect) { RefPtr<Animation> kungFuDeathGrip(this); if (mEffect == aEffect) { return; } AutoMutationBatchForAnimation mb(*this); bool wasRelevant = mIsRelevant; if (mEffect) { if (!aEffect) { // If the new effect is null, call ResetPendingTasks before clearing // mEffect since ResetPendingTasks needs it to get the appropriate // PendingAnimationTracker. ResetPendingTasks(); } // We need to notify observers now because once we set mEffect to null // we won't be able to find the target element to notify. if (mIsRelevant) { nsNodeUtils::AnimationRemoved(this); } // Break links with the old effect and then drop it. RefPtr<AnimationEffectReadOnly> oldEffect = mEffect; mEffect = nullptr; oldEffect->SetAnimation(nullptr); // The following will not do any notification because mEffect is null. UpdateRelevance(); } if (aEffect) { // Break links from the new effect to its previous animation, if any. RefPtr<AnimationEffectReadOnly> newEffect = aEffect; Animation* prevAnim = aEffect->GetAnimation(); if (prevAnim) { prevAnim->SetEffect(nullptr); } // Create links with the new effect. SetAnimation(this) will also update // mIsRelevant of this animation, and then notify mutation observer if // needed by calling Animation::UpdateRelevance(), so we don't need to // call it again. mEffect = newEffect; mEffect->SetAnimation(this); // Notify possible add or change. // If the target is different, the change notification will be ignored by // AutoMutationBatchForAnimation. if (wasRelevant && mIsRelevant) { nsNodeUtils::AnimationChanged(this); } // Reschedule pending pause or pending play tasks. // If we have a pending animation, it will either be registered // in the pending animation tracker and have a null pending ready time, // or, after it has been painted, it will be removed from the tracker // and assigned a pending ready time. // After updating the effect we'll typically need to repaint so if we've // already been assigned a pending ready time, we should clear it and put // the animation back in the tracker. if (!mPendingReadyTime.IsNull()) { mPendingReadyTime.SetNull(); nsIDocument* doc = GetRenderedDocument(); if (doc) { PendingAnimationTracker* tracker = doc->GetOrCreatePendingAnimationTracker(); if (mPendingState == PendingState::PlayPending) { tracker->AddPlayPending(*this); } else { tracker->AddPausePending(*this); } } } } UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async); }