void Animation::PostUpdate() { if (!mEffect) { return; } KeyframeEffectReadOnly* keyframeEffect = mEffect->AsKeyframeEffect(); if (!keyframeEffect) { return; } Maybe<NonOwningAnimationTarget> target = keyframeEffect->GetTarget(); if (!target) { return; } nsPresContext* presContext = keyframeEffect->GetPresContext(); if (!presContext) { return; } presContext->EffectCompositor() ->RequestRestyle(target->mElement, target->mPseudoType, EffectCompositor::RestyleType::Layer, CascadeLevel()); }
// A helper function for IsScaleSubjectToAnimation which returns true if the // given AnimationCollection contains a current effect that animates scale. static bool ContainsAnimatedScale(AnimationCollection* aCollection, nsIFrame* aFrame) { if (!aCollection) { return false; } for (dom::Animation* anim : aCollection->mAnimations) { if (!anim->HasCurrentEffect()) { continue; } KeyframeEffectReadOnly* effect = anim->GetEffect(); for (const AnimationProperty& prop : effect->Properties()) { if (prop.mProperty != eCSSProperty_transform) { continue; } for (AnimationPropertySegment segment : prop.mSegments) { gfxSize from = segment.mFromValue.GetScaleValue(aFrame); if (from != gfxSize(1.0f, 1.0f)) { return true; } gfxSize to = segment.mToValue.GetScaleValue(aFrame); if (to != gfxSize(1.0f, 1.0f)) { return true; } } } } return false; }
void Animation::UpdateEffect() { if (mEffect) { UpdateRelevance(); KeyframeEffectReadOnly* keyframeEffect = mEffect->AsKeyframeEffect(); if (keyframeEffect) { keyframeEffect->NotifyAnimationTimingUpdated(); } } }
static void UpdateOldAnimationPropertiesWithNew( CSSAnimation& aOld, TimingParams& aNewTiming, nsTArray<Keyframe>& aNewKeyframes, bool aNewIsStylePaused, nsStyleContext* aStyleContext) { bool animationChanged = false; // Update the old from the new so we can keep the original object // identity (and any expando properties attached to it). if (aOld.GetEffect()) { AnimationEffectReadOnly* oldEffect = aOld.GetEffect(); animationChanged = oldEffect->SpecifiedTiming() != aNewTiming; oldEffect->SetSpecifiedTiming(aNewTiming); KeyframeEffectReadOnly* oldKeyframeEffect = oldEffect->AsKeyframeEffect(); if (oldKeyframeEffect) { oldKeyframeEffect->SetKeyframes(Move(aNewKeyframes), aStyleContext); } } // Handle changes in play state. If the animation is idle, however, // changes to animation-play-state should *not* restart it. if (aOld.PlayState() != AnimationPlayState::Idle) { // CSSAnimation takes care of override behavior so that, // for example, if the author has called pause(), that will // override the animation-play-state. // (We should check aNew->IsStylePaused() but that requires // downcasting to CSSAnimation and we happen to know that // aNew will only ever be paused by calling PauseFromStyle // making IsPausedOrPausing synonymous in this case.) if (!aOld.IsStylePaused() && aNewIsStylePaused) { aOld.PauseFromStyle(); animationChanged = true; } else if (aOld.IsStylePaused() && !aNewIsStylePaused) { aOld.PlayFromStyle(); animationChanged = true; } } // Updating the effect timing above might already have caused the // animation to become irrelevant so only add a changed record if // the animation is still relevant. if (animationChanged && aOld.IsRelevant()) { nsNodeUtils::AnimationChanged(&aOld); } }
/* static */ already_AddRefed<KeyframeEffectType> KeyframeEffectReadOnly::ConstructKeyframeEffect(const GlobalObject& aGlobal, KeyframeEffectReadOnly& aSource, ErrorResult& aRv) { nsIDocument* doc = AnimationUtils::GetCurrentRealmDocument(aGlobal.Context()); if (!doc) { aRv.Throw(NS_ERROR_FAILURE); return nullptr; } // Create a new KeyframeEffectReadOnly object with aSource's target, // iteration composite operation, composite operation, and spacing mode. // The constructor creates a new AnimationEffect(ReadOnly) object by // aSource's TimingParams. // Note: we don't need to re-throw exceptions since the value specified on // aSource's timing object can be assumed valid. RefPtr<KeyframeEffectType> effect = new KeyframeEffectType(doc, aSource.mTarget, aSource.SpecifiedTiming(), aSource.mEffectOptions); // Copy cumulative change hint. mCumulativeChangeHint should be the same as // the source one because both of targets are the same. effect->mCumulativeChangeHint = aSource.mCumulativeChangeHint; // Copy aSource's keyframes and animation properties. // Note: We don't call SetKeyframes directly, which might revise the // computed offsets and rebuild the animation properties. // FIXME: Bug 1314537: We have to make sure SharedKeyframeList is handled // properly. effect->mKeyframes = aSource.mKeyframes; effect->mProperties = aSource.mProperties; return effect.forget(); }
void nsAnimationManager::CopyIsRunningOnCompositor( KeyframeEffectReadOnly& aSourceEffect, KeyframeEffectReadOnly& aDestEffect) { nsCSSPropertySet sourceProperties; for (AnimationProperty& property : aSourceEffect.Properties()) { if (property.mIsRunningOnCompositor) { sourceProperties.AddProperty(property.mProperty); } } for (AnimationProperty& property : aDestEffect.Properties()) { if (sourceProperties.HasProperty(property.mProperty)) { property.mIsRunningOnCompositor = true; } } }
bool AnimationStack::getAnimatedBoundingBox(FloatBox& box, CSSPropertyID property) const { FloatBox originalBox(box); for (const auto& sampledEffect : m_sampledEffects) { if (sampledEffect->effect() && sampledEffect->effect()->affects(PropertyHandle(property))) { KeyframeEffectReadOnly* effect = sampledEffect->effect(); const Timing& timing = effect->specifiedTiming(); double startRange = 0; double endRange = 1; timing.timingFunction->range(&startRange, &endRange); FloatBox expandingBox(originalBox); if (!CompositorAnimations::getAnimatedBoundingBox( expandingBox, *effect->model(), startRange, endRange)) return false; box.expandTo(expandingBox); } } return true; }
static inline Element* GetTarget(Animation* aAnimation) { KeyframeEffectReadOnly* effect = aAnimation->GetEffect(); if (!effect) { return nullptr; } Element* target; nsCSSPseudoElements::Type pseudoType; effect->GetTarget(target, pseudoType); // If the animation targets a pseudo-element, we don't dispatch // notifications for it. (In the future we will have PseudoElement // objects we can use as the target of the notifications.) if (pseudoType != nsCSSPseudoElements::ePseudo_NotPseudoElement) { return nullptr; } return effect->GetTarget(); }
void Animation::Tick() { // Finish pending if we have a pending ready time, but only if we also // have an active timeline. if (mPendingState != PendingState::NotPending && !mPendingReadyTime.IsNull() && mTimeline && !mTimeline->GetCurrentTime().IsNull()) { // Even though mPendingReadyTime is initialized using TimeStamp::Now() // during the *previous* tick of the refresh driver, it can still be // ahead of the *current* timeline time when we are using the // vsync timer so we need to clamp it to the timeline time. mPendingReadyTime.SetValue(std::min(mTimeline->GetCurrentTime().Value(), mPendingReadyTime.Value())); FinishPendingAt(mPendingReadyTime.Value()); mPendingReadyTime.SetNull(); } if (IsPossiblyOrphanedPendingAnimation()) { MOZ_ASSERT(mTimeline && !mTimeline->GetCurrentTime().IsNull(), "Orphaned pending animations should have an active timeline"); FinishPendingAt(mTimeline->GetCurrentTime().Value()); } UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async); if (!mEffect) { return; } // Update layers if we are newly finished. KeyframeEffectReadOnly* keyframeEffect = mEffect->AsKeyframeEffect(); if (keyframeEffect && !keyframeEffect->Properties().IsEmpty() && !mFinishedAtLastComposeStyle && PlayState() == AnimationPlayState::Finished) { PostUpdate(); } }
/* static */ void nsAnimationManager::UpdateCascadeResults( nsStyleContext* aStyleContext, AnimationCollection* aElementAnimations) { /* * Figure out which properties we need to examine. */ // size of 2 since we only currently have 2 properties we animate on // the compositor nsAutoTArray<nsCSSProperty, 2> propertiesToTrack; { nsCSSPropertySet propertiesToTrackAsSet; for (size_t animIdx = aElementAnimations->mAnimations.Length(); animIdx-- != 0; ) { const Animation* anim = aElementAnimations->mAnimations[animIdx]; const KeyframeEffectReadOnly* effect = anim->GetEffect(); if (!effect) { continue; } for (size_t propIdx = 0, propEnd = effect->Properties().Length(); propIdx != propEnd; ++propIdx) { const AnimationProperty& prop = effect->Properties()[propIdx]; // We only bother setting mWinsInCascade for properties that we // can animate on the compositor. if (nsCSSProps::PropHasFlags(prop.mProperty, CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR)) { if (!propertiesToTrackAsSet.HasProperty(prop.mProperty)) { propertiesToTrack.AppendElement(prop.mProperty); propertiesToTrackAsSet.AddProperty(prop.mProperty); } } } } } /* * Determine whether those properties are set in things that * override animations. */ nsCSSPropertySet propertiesOverridden; nsRuleNode::ComputePropertiesOverridingAnimation(propertiesToTrack, aStyleContext, propertiesOverridden); /* * Set mWinsInCascade based both on what is overridden at levels * higher than animations and based on one animation overriding * another. * * We iterate from the last animation to the first, just like we do * when calling ComposeStyle from AnimationCollection::EnsureStyleRuleFor. * Later animations override earlier ones, so we add properties to the set * of overridden properties as we encounter them, if the animation is * currently in effect. */ bool changed = false; for (size_t animIdx = aElementAnimations->mAnimations.Length(); animIdx-- != 0; ) { CSSAnimation* anim = aElementAnimations->mAnimations[animIdx]->AsCSSAnimation(); KeyframeEffectReadOnly* effect = anim->GetEffect(); anim->mInEffectForCascadeResults = anim->IsInEffect(); if (!effect) { continue; } for (size_t propIdx = 0, propEnd = effect->Properties().Length(); propIdx != propEnd; ++propIdx) { AnimationProperty& prop = effect->Properties()[propIdx]; // We only bother setting mWinsInCascade for properties that we // can animate on the compositor. if (nsCSSProps::PropHasFlags(prop.mProperty, CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR)) { bool newWinsInCascade = !propertiesOverridden.HasProperty(prop.mProperty); if (newWinsInCascade != prop.mWinsInCascade) { changed = true; } prop.mWinsInCascade = newWinsInCascade; if (prop.mWinsInCascade && anim->mInEffectForCascadeResults) { // This animation is in effect right now, so it overrides // earlier animations. (For animations that aren't in effect, // we set mWinsInCascade as though they were, but they don't // suppress animations lower in the cascade.) propertiesOverridden.AddProperty(prop.mProperty); } } } } if (changed) { nsPresContext* presContext = aElementAnimations->mManager->PresContext(); presContext->RestyleManager()->IncrementAnimationGeneration(); aElementAnimations->UpdateAnimationGeneration(presContext); aElementAnimations->PostUpdateLayerAnimations(); // Invalidate our style rule. aElementAnimations->mNeedsRefreshes = true; aElementAnimations->mStyleRuleRefreshTime = TimeStamp(); } }
nsIStyleRule* nsAnimationManager::CheckAnimationRule(nsStyleContext* aStyleContext, mozilla::dom::Element* aElement) { if (!mPresContext->IsDynamic()) { // For print or print preview, ignore animations. return nullptr; } // Everything that causes our animation data to change triggers a // style change, which in turn triggers a non-animation restyle. // Likewise, when we initially construct frames, we're not in a // style change, but also not in an animation restyle. const nsStyleDisplay* disp = aStyleContext->StyleDisplay(); AnimationCollection* collection = GetAnimations(aElement, aStyleContext->GetPseudoType(), false); if (!collection && disp->mAnimationNameCount == 1 && disp->mAnimations[0].GetName().IsEmpty()) { return nullptr; } nsAutoAnimationMutationBatch mb(aElement); // build the animations list dom::DocumentTimeline* timeline = aElement->OwnerDoc()->Timeline(); AnimationPtrArray newAnimations; if (!aStyleContext->IsInDisplayNoneSubtree()) { BuildAnimations(aStyleContext, aElement, timeline, newAnimations); } if (newAnimations.IsEmpty()) { if (collection) { // There might be transitions that run now that animations don't // override them. mPresContext->TransitionManager()-> UpdateCascadeResultsWithAnimationsToBeDestroyed(collection); collection->Destroy(); } return nullptr; } if (collection) { collection->mStyleRule = nullptr; collection->mStyleRuleRefreshTime = TimeStamp(); collection->UpdateAnimationGeneration(mPresContext); // Copy over the start times and (if still paused) pause starts // for each animation (matching on name only) that was also in the // old list of animations. // This means that we honor dynamic changes, which isn't what the // spec says to do, but WebKit seems to honor at least some of // them. See // http://lists.w3.org/Archives/Public/www-style/2011Apr/0079.html // In order to honor what the spec said, we'd copy more data over // (or potentially optimize BuildAnimations to avoid rebuilding it // in the first place). if (!collection->mAnimations.IsEmpty()) { for (size_t newIdx = newAnimations.Length(); newIdx-- != 0;) { Animation* newAnim = newAnimations[newIdx]; // Find the matching animation with this name in the old list // of animations. We iterate through both lists in a backwards // direction which means that if there are more animations in // the new list of animations with a given name than in the old // list, it will be the animations towards the of the beginning of // the list that do not match and are treated as new animations. nsRefPtr<CSSAnimation> oldAnim; size_t oldIdx = collection->mAnimations.Length(); while (oldIdx-- != 0) { CSSAnimation* a = collection->mAnimations[oldIdx]->AsCSSAnimation(); MOZ_ASSERT(a, "All animations in the CSS Animation collection should" " be CSSAnimation objects"); if (a->Name() == newAnim->Name()) { oldAnim = a; break; } } if (!oldAnim) { continue; } bool animationChanged = false; // Update the old from the new so we can keep the original object // identity (and any expando properties attached to it). if (oldAnim->GetEffect() && newAnim->GetEffect()) { KeyframeEffectReadOnly* oldEffect = oldAnim->GetEffect(); KeyframeEffectReadOnly* newEffect = newAnim->GetEffect(); animationChanged = oldEffect->Timing() != newEffect->Timing() || oldEffect->Properties() != newEffect->Properties(); oldEffect->Timing() = newEffect->Timing(); oldEffect->Properties() = newEffect->Properties(); } // Reset compositor state so animation will be re-synchronized. oldAnim->ClearIsRunningOnCompositor(); // Handle changes in play state. If the animation is idle, however, // changes to animation-play-state should *not* restart it. if (oldAnim->PlayState() != AnimationPlayState::Idle) { // CSSAnimation takes care of override behavior so that, // for example, if the author has called pause(), that will // override the animation-play-state. // (We should check newAnim->IsStylePaused() but that requires // downcasting to CSSAnimation and we happen to know that // newAnim will only ever be paused by calling PauseFromStyle // making IsPausedOrPausing synonymous in this case.) if (!oldAnim->IsStylePaused() && newAnim->IsPausedOrPausing()) { oldAnim->PauseFromStyle(); animationChanged = true; } else if (oldAnim->IsStylePaused() && !newAnim->IsPausedOrPausing()) { oldAnim->PlayFromStyle(); animationChanged = true; } } if (animationChanged) { nsNodeUtils::AnimationChanged(oldAnim); } // Replace new animation with the (updated) old one and remove the // old one from the array so we don't try to match it any more. // // Although we're doing this while iterating this is safe because // we're not changing the length of newAnimations and we've finished // iterating over the list of old iterations. newAnim->CancelFromStyle(); newAnim = nullptr; newAnimations.ReplaceElementAt(newIdx, oldAnim); collection->mAnimations.RemoveElementAt(oldIdx); // We've touched the old animation's timing properties, so this // could update the old animation's relevance. oldAnim->UpdateRelevance(); } } } else { collection = GetAnimations(aElement, aStyleContext->GetPseudoType(), true); } collection->mAnimations.SwapElements(newAnimations); collection->mNeedsRefreshes = true; collection->Tick(); // Cancel removed animations for (size_t newAnimIdx = newAnimations.Length(); newAnimIdx-- != 0; ) { newAnimations[newAnimIdx]->CancelFromStyle(); } UpdateCascadeResults(aStyleContext, collection); TimeStamp refreshTime = mPresContext->RefreshDriver()->MostRecentRefresh(); UpdateStyleAndEvents(collection, refreshTime, EnsureStyleRule_IsNotThrottled); // We don't actually dispatch the mPendingEvents now. We'll either // dispatch them the next time we get a refresh driver notification // or the next time somebody calls // nsPresShell::FlushPendingNotifications. if (!mPendingEvents.IsEmpty()) { mPresContext->Document()->SetNeedStyleFlush(); } return GetAnimationRule(aElement, aStyleContext->GetPseudoType()); }
nsIStyleRule* nsAnimationManager::CheckAnimationRule(nsStyleContext* aStyleContext, mozilla::dom::Element* aElement) { // Ignore animations for print or print preview, and for elements // that are not attached to the document tree. if (!mPresContext->IsDynamic() || !aElement->IsInComposedDoc()) { return nullptr; } // Everything that causes our animation data to change triggers a // style change, which in turn triggers a non-animation restyle. // Likewise, when we initially construct frames, we're not in a // style change, but also not in an animation restyle. const nsStyleDisplay* disp = aStyleContext->StyleDisplay(); AnimationCollection* collection = GetAnimationCollection(aElement, aStyleContext->GetPseudoType(), false /* aCreateIfNeeded */); if (!collection && disp->mAnimationNameCount == 1 && disp->mAnimations[0].GetName().IsEmpty()) { return nullptr; } nsAutoAnimationMutationBatch mb(aElement->OwnerDoc()); // build the animations list dom::DocumentTimeline* timeline = aElement->OwnerDoc()->Timeline(); AnimationPtrArray newAnimations; if (!aStyleContext->IsInDisplayNoneSubtree()) { BuildAnimations(aStyleContext, aElement, timeline, newAnimations); } if (newAnimations.IsEmpty()) { if (collection) { collection->Destroy(); } return nullptr; } if (collection) { collection->mStyleRuleRefreshTime = TimeStamp(); EffectSet* effectSet = EffectSet::GetEffectSet(aElement, aStyleContext->GetPseudoType()); if (effectSet) { effectSet->UpdateAnimationGeneration(mPresContext); } // Copy over the start times and (if still paused) pause starts // for each animation (matching on name only) that was also in the // old list of animations. // This means that we honor dynamic changes, which isn't what the // spec says to do, but WebKit seems to honor at least some of // them. See // http://lists.w3.org/Archives/Public/www-style/2011Apr/0079.html // In order to honor what the spec said, we'd copy more data over // (or potentially optimize BuildAnimations to avoid rebuilding it // in the first place). if (!collection->mAnimations.IsEmpty()) { for (size_t newIdx = newAnimations.Length(); newIdx-- != 0;) { Animation* newAnim = newAnimations[newIdx]; // Find the matching animation with this name in the old list // of animations. We iterate through both lists in a backwards // direction which means that if there are more animations in // the new list of animations with a given name than in the old // list, it will be the animations towards the of the beginning of // the list that do not match and are treated as new animations. RefPtr<CSSAnimation> oldAnim; size_t oldIdx = collection->mAnimations.Length(); while (oldIdx-- != 0) { CSSAnimation* a = collection->mAnimations[oldIdx]->AsCSSAnimation(); MOZ_ASSERT(a, "All animations in the CSS Animation collection should" " be CSSAnimation objects"); if (a->AnimationName() == newAnim->AsCSSAnimation()->AnimationName()) { oldAnim = a; break; } } if (!oldAnim) { // FIXME: Bug 1134163 - We shouldn't queue animationstart events // until the animation is actually ready to run. However, we // currently have some tests that assume that these events are // dispatched within the same tick as the animation is added // so we need to queue up any animationstart events from newly-created // animations. newAnim->AsCSSAnimation()->QueueEvents(); continue; } bool animationChanged = false; // Update the old from the new so we can keep the original object // identity (and any expando properties attached to it). if (oldAnim->GetEffect() && newAnim->GetEffect()) { KeyframeEffectReadOnly* oldEffect = oldAnim->GetEffect(); KeyframeEffectReadOnly* newEffect = newAnim->GetEffect(); animationChanged = oldEffect->Timing() != newEffect->Timing() || oldEffect->Properties() != newEffect->Properties(); oldEffect->SetTiming(newEffect->Timing()); // To preserve the mIsRunningOnCompositor value on each property, // we copy it from the old effect to the new effect since, in the // following step, we will completely clobber the properties on the // old effect with the values on the new effect. CopyIsRunningOnCompositor(*oldEffect, *newEffect); oldEffect->Properties() = newEffect->Properties(); } // Handle changes in play state. If the animation is idle, however, // changes to animation-play-state should *not* restart it. if (oldAnim->PlayState() != AnimationPlayState::Idle) { // CSSAnimation takes care of override behavior so that, // for example, if the author has called pause(), that will // override the animation-play-state. // (We should check newAnim->IsStylePaused() but that requires // downcasting to CSSAnimation and we happen to know that // newAnim will only ever be paused by calling PauseFromStyle // making IsPausedOrPausing synonymous in this case.) if (!oldAnim->IsStylePaused() && newAnim->IsPausedOrPausing()) { oldAnim->PauseFromStyle(); animationChanged = true; } else if (oldAnim->IsStylePaused() && !newAnim->IsPausedOrPausing()) { oldAnim->PlayFromStyle(); animationChanged = true; } } oldAnim->CopyAnimationIndex(*newAnim->AsCSSAnimation()); // Updating the effect timing above might already have caused the // animation to become irrelevant so only add a changed record if // the animation is still relevant. if (animationChanged && oldAnim->IsRelevant()) { nsNodeUtils::AnimationChanged(oldAnim); } // Replace new animation with the (updated) old one and remove the // old one from the array so we don't try to match it any more. // // Although we're doing this while iterating this is safe because // we're not changing the length of newAnimations and we've finished // iterating over the list of old iterations. newAnim->CancelFromStyle(); newAnim = nullptr; newAnimations.ReplaceElementAt(newIdx, oldAnim); collection->mAnimations.RemoveElementAt(oldIdx); } } } else { collection = GetAnimationCollection(aElement, aStyleContext->GetPseudoType(), true /* aCreateIfNeeded */); for (Animation* animation : newAnimations) { // FIXME: Bug 1134163 - As above, we have shouldn't actually need to // queue events here. (But we do for now since some tests expect // animationstart events to be dispatched immediately.) animation->AsCSSAnimation()->QueueEvents(); } } collection->mAnimations.SwapElements(newAnimations); collection->mStyleChanging = true; // Cancel removed animations for (size_t newAnimIdx = newAnimations.Length(); newAnimIdx-- != 0; ) { newAnimations[newAnimIdx]->CancelFromStyle(); } EffectCompositor::UpdateCascadeResults(aElement, aStyleContext->GetPseudoType(), aStyleContext); TimeStamp refreshTime = mPresContext->RefreshDriver()->MostRecentRefresh(); collection->EnsureStyleRuleFor(refreshTime); // We don't actually dispatch the pending events now. We'll either // dispatch them the next time we get a refresh driver notification // or the next time somebody calls // nsPresShell::FlushPendingNotifications. if (mEventDispatcher.HasQueuedEvents()) { mPresContext->Document()->SetNeedStyleFlush(); } return GetAnimationRule(aElement, aStyleContext->GetPseudoType()); }
Maybe<NonOwningAnimationTarget> nsNodeUtils::GetTargetForAnimation(const Animation* aAnimation) { KeyframeEffectReadOnly* effect = aAnimation->GetEffect(); return effect ? effect->GetTarget() : Nothing(); }
void Animation::ComposeStyle(RefPtr<AnimValuesStyleRule>& aStyleRule, nsCSSPropertyIDSet& aSetProperties) { if (!mEffect) { return; } if (!IsInEffect()) { return; } // In order to prevent flicker, there are a few cases where we want to use // a different time for rendering that would otherwise be returned by // GetCurrentTime. These are: // // (a) For animations that are pausing but which are still running on the // compositor. In this case we send a layer transaction that removes the // animation but which also contains the animation values calculated on // the main thread. To prevent flicker when this occurs we want to ensure // the timeline time used to calculate the main thread animation values // does not lag far behind the time used on the compositor. Ideally we // would like to use the "animation ready time" calculated at the end of // the layer transaction as the timeline time but it will be too late to // update the style rule at that point so instead we just use the current // wallclock time. // // (b) For animations that are pausing that we have already taken off the // compositor. In this case we record a pending ready time but we don't // apply it until the next tick. However, while waiting for the next tick, // we should still use the pending ready time as the timeline time. If we // use the regular timeline time the animation may appear jump backwards // if the main thread's timeline time lags behind the compositor. // // (c) For animations that are play-pending due to an aborted pause operation // (i.e. a pause operation that was interrupted before we entered the // paused state). When we cancel a pending pause we might momentarily take // the animation off the compositor, only to re-add it moments later. In // that case the compositor might have been ahead of the main thread so we // should use the current wallclock time to ensure the animation doesn't // temporarily jump backwards. // // To address each of these cases we temporarily tweak the hold time // immediately before updating the style rule and then restore it immediately // afterwards. This is purely to prevent visual flicker. Other behavior // such as dispatching events continues to rely on the regular timeline time. AnimationPlayState playState = PlayState(); { AutoRestore<Nullable<TimeDuration>> restoreHoldTime(mHoldTime); if (playState == AnimationPlayState::Pending && mHoldTime.IsNull() && !mStartTime.IsNull()) { Nullable<TimeDuration> timeToUse = mPendingReadyTime; if (timeToUse.IsNull() && mTimeline && mTimeline->TracksWallclockTime()) { timeToUse = mTimeline->ToTimelineTime(TimeStamp::Now()); } if (!timeToUse.IsNull()) { mHoldTime.SetValue((timeToUse.Value() - mStartTime.Value()) .MultDouble(mPlaybackRate)); } } KeyframeEffectReadOnly* keyframeEffect = mEffect->AsKeyframeEffect(); if (keyframeEffect) { keyframeEffect->ComposeStyle(aStyleRule, aSetProperties); } } MOZ_ASSERT(playState == PlayState(), "Play state should not change during the course of compositing"); mFinishedAtLastComposeStyle = (playState == AnimationPlayState::Finished); }