michael@0: /* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsAnimationManager.h" michael@0: #include "nsTransitionManager.h" michael@0: michael@0: #include "mozilla/EventDispatcher.h" michael@0: #include "mozilla/MemoryReporting.h" michael@0: michael@0: #include "nsPresContext.h" michael@0: #include "nsRuleProcessorData.h" michael@0: #include "nsStyleSet.h" michael@0: #include "nsStyleChangeList.h" michael@0: #include "nsCSSRules.h" michael@0: #include "RestyleManager.h" michael@0: #include "nsStyleAnimation.h" michael@0: #include "nsLayoutUtils.h" michael@0: #include "nsIFrame.h" michael@0: #include "nsIDocument.h" michael@0: #include "ActiveLayerTracker.h" michael@0: #include michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::css; michael@0: michael@0: ElementAnimations::ElementAnimations(mozilla::dom::Element *aElement, michael@0: nsIAtom *aElementProperty, michael@0: nsAnimationManager *aAnimationManager, michael@0: TimeStamp aNow) michael@0: : CommonElementAnimationData(aElement, aElementProperty, michael@0: aAnimationManager, aNow), michael@0: mNeedsRefreshes(true) michael@0: { michael@0: } michael@0: michael@0: static void michael@0: ElementAnimationsPropertyDtor(void *aObject, michael@0: nsIAtom *aPropertyName, michael@0: void *aPropertyValue, michael@0: void *aData) michael@0: { michael@0: ElementAnimations *ea = static_cast(aPropertyValue); michael@0: #ifdef DEBUG michael@0: NS_ABORT_IF_FALSE(!ea->mCalledPropertyDtor, "can't call dtor twice"); michael@0: ea->mCalledPropertyDtor = true; michael@0: #endif michael@0: delete ea; michael@0: } michael@0: michael@0: double michael@0: ElementAnimations::GetPositionInIteration(TimeDuration aElapsedDuration, michael@0: TimeDuration aIterationDuration, michael@0: double aIterationCount, michael@0: uint32_t aDirection, michael@0: StyleAnimation* aAnimation, michael@0: ElementAnimations* aEa, michael@0: EventArray* aEventsToDispatch) michael@0: { michael@0: MOZ_ASSERT(!aAnimation == !aEa && !aAnimation == !aEventsToDispatch); michael@0: michael@0: // Set |currentIterationCount| to the (fractional) number of michael@0: // iterations we've completed up to the current position. michael@0: double currentIterationCount = aElapsedDuration / aIterationDuration; michael@0: bool dispatchStartOrIteration = false; michael@0: if (currentIterationCount >= aIterationCount) { michael@0: if (aAnimation) { michael@0: // Dispatch 'animationend' when needed. michael@0: if (aAnimation->mLastNotification != michael@0: StyleAnimation::LAST_NOTIFICATION_END) { michael@0: aAnimation->mLastNotification = StyleAnimation::LAST_NOTIFICATION_END; michael@0: AnimationEventInfo ei(aEa->mElement, aAnimation->mName, NS_ANIMATION_END, michael@0: aElapsedDuration, aEa->PseudoElement()); michael@0: aEventsToDispatch->AppendElement(ei); michael@0: } michael@0: michael@0: if (!aAnimation->FillsForwards()) { michael@0: // No animation data. michael@0: return -1; michael@0: } michael@0: } else { michael@0: // If aAnimation is null, that means we're on the compositor michael@0: // thread. We want to just keep filling forwards until the main michael@0: // thread gets around to updating the compositor thread (which michael@0: // might take a little while). So just assume we fill fowards and michael@0: // move on. michael@0: } michael@0: currentIterationCount = aIterationCount; michael@0: } else { michael@0: if (aAnimation && !aAnimation->IsPaused()) { michael@0: aEa->mNeedsRefreshes = true; michael@0: } michael@0: if (currentIterationCount < 0.0) { michael@0: NS_ASSERTION(aAnimation, "Should not run animation that hasn't started yet on the compositor"); michael@0: if (!aAnimation->FillsBackwards()) { michael@0: // No animation data. michael@0: return -1; michael@0: } michael@0: currentIterationCount = 0.0; michael@0: } else { michael@0: dispatchStartOrIteration = aAnimation && !aAnimation->IsPaused(); michael@0: } michael@0: } michael@0: michael@0: // Set |positionInIteration| to the position from 0% to 100% along michael@0: // the keyframes. michael@0: NS_ABORT_IF_FALSE(currentIterationCount >= 0.0, "must be positive"); michael@0: double whichIteration = floor(currentIterationCount); michael@0: if (whichIteration == aIterationCount && whichIteration != 0.0) { michael@0: // When the animation's iteration count is an integer (as it michael@0: // normally is), we need to end at 100% of its last iteration michael@0: // rather than 0% of the next one (unless it's zero). michael@0: whichIteration -= 1.0; michael@0: } michael@0: double positionInIteration = currentIterationCount - whichIteration; michael@0: michael@0: bool thisIterationReverse = false; michael@0: switch (aDirection) { michael@0: case NS_STYLE_ANIMATION_DIRECTION_NORMAL: michael@0: thisIterationReverse = false; michael@0: break; michael@0: case NS_STYLE_ANIMATION_DIRECTION_REVERSE: michael@0: thisIterationReverse = true; michael@0: break; michael@0: case NS_STYLE_ANIMATION_DIRECTION_ALTERNATE: michael@0: // uint64_t has more integer precision than double does, so if michael@0: // whichIteration is that large, we've already lost and we're just michael@0: // guessing. But the animation is presumably oscillating so fast michael@0: // it doesn't matter anyway. michael@0: thisIterationReverse = (uint64_t(whichIteration) & 1) == 1; michael@0: break; michael@0: case NS_STYLE_ANIMATION_DIRECTION_ALTERNATE_REVERSE: michael@0: // see as previous case michael@0: thisIterationReverse = (uint64_t(whichIteration) & 1) == 0; michael@0: break; michael@0: } michael@0: if (thisIterationReverse) { michael@0: positionInIteration = 1.0 - positionInIteration; michael@0: } michael@0: michael@0: // Dispatch 'animationstart' or 'animationiteration' when needed. michael@0: if (aAnimation && dispatchStartOrIteration && michael@0: whichIteration != aAnimation->mLastNotification) { michael@0: // Notify 'animationstart' even if a negative delay puts us michael@0: // past the first iteration. michael@0: // Note that when somebody changes the animation-duration michael@0: // dynamically, this will fire an extra iteration event michael@0: // immediately in many cases. It's not clear to me if that's the michael@0: // right thing to do. michael@0: uint32_t message = michael@0: aAnimation->mLastNotification == StyleAnimation::LAST_NOTIFICATION_NONE michael@0: ? NS_ANIMATION_START : NS_ANIMATION_ITERATION; michael@0: michael@0: aAnimation->mLastNotification = whichIteration; michael@0: AnimationEventInfo ei(aEa->mElement, aAnimation->mName, message, michael@0: aElapsedDuration, aEa->PseudoElement()); michael@0: aEventsToDispatch->AppendElement(ei); michael@0: } michael@0: michael@0: return positionInIteration; michael@0: } michael@0: michael@0: void michael@0: ElementAnimations::EnsureStyleRuleFor(TimeStamp aRefreshTime, michael@0: EventArray& aEventsToDispatch, michael@0: bool aIsThrottled) michael@0: { michael@0: if (!mNeedsRefreshes) { michael@0: mStyleRuleRefreshTime = aRefreshTime; michael@0: return; michael@0: } michael@0: michael@0: // If we're performing animations on the compositor thread, then we can skip michael@0: // most of the work in this method. But even if we are throttled, then we michael@0: // have to do the work if an animation is ending in order to get correct end michael@0: // of animation behaviour (the styles of the animation disappear, or the fill michael@0: // mode behaviour). This loop checks for any finishing animations and forces michael@0: // the style recalculation if we find any. michael@0: if (aIsThrottled) { michael@0: for (uint32_t animIdx = mAnimations.Length(); animIdx-- != 0; ) { michael@0: StyleAnimation &anim = mAnimations[animIdx]; michael@0: michael@0: if (anim.mProperties.Length() == 0 || michael@0: anim.mIterationDuration.ToMilliseconds() <= 0.0) { michael@0: continue; michael@0: } michael@0: michael@0: uint32_t oldLastNotification = anim.mLastNotification; michael@0: michael@0: // We need to call GetPositionInIteration here to populate michael@0: // aEventsToDispatch. michael@0: // The ElapsedDurationAt() call here handles pausing. But: michael@0: // FIXME: avoid recalculating every time when paused. michael@0: GetPositionInIteration(anim.ElapsedDurationAt(aRefreshTime), michael@0: anim.mIterationDuration, anim.mIterationCount, michael@0: anim.mDirection, &anim, this, &aEventsToDispatch); michael@0: michael@0: // GetPositionInIteration just adjusted mLastNotification; check michael@0: // its new value against the value before we called michael@0: // GetPositionInIteration. michael@0: // XXX We shouldn't really be using mLastNotification as a general michael@0: // indicator that the animation has finished, it should be reserved for michael@0: // events. If we use it differently in the future this use might need michael@0: // changing. michael@0: if (!anim.mIsRunningOnCompositor || michael@0: (anim.mLastNotification != oldLastNotification && michael@0: anim.mLastNotification == StyleAnimation::LAST_NOTIFICATION_END)) { michael@0: aIsThrottled = false; michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (aIsThrottled) { michael@0: return; michael@0: } michael@0: michael@0: // mStyleRule may be null and valid, if we have no style to apply. michael@0: if (mStyleRuleRefreshTime.IsNull() || michael@0: mStyleRuleRefreshTime != aRefreshTime) { michael@0: mStyleRuleRefreshTime = aRefreshTime; michael@0: mStyleRule = nullptr; michael@0: // We'll set mNeedsRefreshes to true below in all cases where we need them. michael@0: mNeedsRefreshes = false; michael@0: michael@0: // FIXME(spec): assume that properties in higher animations override michael@0: // those in lower ones. michael@0: // Therefore, we iterate from last animation to first. michael@0: nsCSSPropertySet properties; michael@0: michael@0: for (uint32_t animIdx = mAnimations.Length(); animIdx-- != 0; ) { michael@0: StyleAnimation &anim = mAnimations[animIdx]; michael@0: michael@0: if (anim.mProperties.Length() == 0 || michael@0: anim.mIterationDuration.ToMilliseconds() <= 0.0) { michael@0: // No animation data. michael@0: continue; michael@0: } michael@0: michael@0: // The ElapsedDurationAt() call here handles pausing. But: michael@0: // FIXME: avoid recalculating every time when paused. michael@0: double positionInIteration = michael@0: GetPositionInIteration(anim.ElapsedDurationAt(aRefreshTime), michael@0: anim.mIterationDuration, anim.mIterationCount, michael@0: anim.mDirection, &anim, this, michael@0: &aEventsToDispatch); michael@0: michael@0: // The position is -1 when we don't have fill data for the current time, michael@0: // so we shouldn't animate. michael@0: if (positionInIteration == -1) michael@0: continue; michael@0: michael@0: NS_ABORT_IF_FALSE(0.0 <= positionInIteration && michael@0: positionInIteration <= 1.0, michael@0: "position should be in [0-1]"); michael@0: michael@0: for (uint32_t propIdx = 0, propEnd = anim.mProperties.Length(); michael@0: propIdx != propEnd; ++propIdx) michael@0: { michael@0: const AnimationProperty &prop = anim.mProperties[propIdx]; michael@0: michael@0: NS_ABORT_IF_FALSE(prop.mSegments[0].mFromKey == 0.0, michael@0: "incorrect first from key"); michael@0: NS_ABORT_IF_FALSE(prop.mSegments[prop.mSegments.Length() - 1].mToKey michael@0: == 1.0, michael@0: "incorrect last to key"); michael@0: michael@0: if (properties.HasProperty(prop.mProperty)) { michael@0: // A later animation already set this property. michael@0: continue; michael@0: } michael@0: properties.AddProperty(prop.mProperty); michael@0: michael@0: NS_ABORT_IF_FALSE(prop.mSegments.Length() > 0, michael@0: "property should not be in animations if it " michael@0: "has no segments"); michael@0: michael@0: // FIXME: Maybe cache the current segment? michael@0: const AnimationPropertySegment *segment = prop.mSegments.Elements(), michael@0: *segmentEnd = segment + prop.mSegments.Length(); michael@0: while (segment->mToKey < positionInIteration) { michael@0: NS_ABORT_IF_FALSE(segment->mFromKey < segment->mToKey, michael@0: "incorrect keys"); michael@0: ++segment; michael@0: if (segment == segmentEnd) { michael@0: NS_ABORT_IF_FALSE(false, "incorrect positionInIteration"); michael@0: break; // in order to continue in outer loop (just below) michael@0: } michael@0: NS_ABORT_IF_FALSE(segment->mFromKey == (segment-1)->mToKey, michael@0: "incorrect keys"); michael@0: } michael@0: if (segment == segmentEnd) { michael@0: continue; michael@0: } michael@0: NS_ABORT_IF_FALSE(segment->mFromKey < segment->mToKey, michael@0: "incorrect keys"); michael@0: NS_ABORT_IF_FALSE(segment >= prop.mSegments.Elements() && michael@0: size_t(segment - prop.mSegments.Elements()) < michael@0: prop.mSegments.Length(), michael@0: "out of array bounds"); michael@0: michael@0: if (!mStyleRule) { michael@0: // Allocate the style rule now that we know we have animation data. michael@0: mStyleRule = new css::AnimValuesStyleRule(); michael@0: } michael@0: michael@0: double positionInSegment = (positionInIteration - segment->mFromKey) / michael@0: (segment->mToKey - segment->mFromKey); michael@0: double valuePosition = michael@0: segment->mTimingFunction.GetValue(positionInSegment); michael@0: michael@0: nsStyleAnimation::Value *val = michael@0: mStyleRule->AddEmptyValue(prop.mProperty); michael@0: michael@0: #ifdef DEBUG michael@0: bool result = michael@0: #endif michael@0: nsStyleAnimation::Interpolate(prop.mProperty, michael@0: segment->mFromValue, segment->mToValue, michael@0: valuePosition, *val); michael@0: NS_ABORT_IF_FALSE(result, "interpolate must succeed now"); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: michael@0: bool michael@0: ElementAnimations::HasAnimationOfProperty(nsCSSProperty aProperty) const michael@0: { michael@0: for (uint32_t animIdx = mAnimations.Length(); animIdx-- != 0; ) { michael@0: const StyleAnimation &anim = mAnimations[animIdx]; michael@0: if (anim.HasAnimationOfProperty(aProperty)) { michael@0: return true; michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: ElementAnimations::CanPerformOnCompositorThread(CanAnimateFlags aFlags) const michael@0: { michael@0: nsIFrame* frame = nsLayoutUtils::GetStyleFrame(mElement); michael@0: if (!frame) { michael@0: return false; michael@0: } michael@0: michael@0: if (mElementProperty != nsGkAtoms::animationsProperty) { michael@0: if (nsLayoutUtils::IsAnimationLoggingEnabled()) { michael@0: nsCString message; michael@0: message.AppendLiteral("Gecko bug: Async animation of pseudoelements not supported. See bug 771367 ("); michael@0: message.Append(nsAtomCString(mElementProperty)); michael@0: message.AppendLiteral(")"); michael@0: LogAsyncAnimationFailure(message, mElement); michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: TimeStamp now = frame->PresContext()->RefreshDriver()->MostRecentRefresh(); michael@0: michael@0: for (uint32_t animIdx = mAnimations.Length(); animIdx-- != 0; ) { michael@0: const StyleAnimation& anim = mAnimations[animIdx]; michael@0: for (uint32_t propIdx = 0, propEnd = anim.mProperties.Length(); michael@0: propIdx != propEnd; ++propIdx) { michael@0: if (IsGeometricProperty(anim.mProperties[propIdx].mProperty) && michael@0: anim.IsRunningAt(now)) { michael@0: aFlags = CanAnimateFlags(aFlags | CanAnimate_HasGeometricProperty); michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: bool hasOpacity = false; michael@0: bool hasTransform = false; michael@0: for (uint32_t animIdx = mAnimations.Length(); animIdx-- != 0; ) { michael@0: const StyleAnimation& anim = mAnimations[animIdx]; michael@0: if (!anim.IsRunningAt(now)) { michael@0: continue; michael@0: } michael@0: michael@0: for (uint32_t propIdx = 0, propEnd = anim.mProperties.Length(); michael@0: propIdx != propEnd; ++propIdx) { michael@0: const AnimationProperty& prop = anim.mProperties[propIdx]; michael@0: if (!CanAnimatePropertyOnCompositor(mElement, michael@0: prop.mProperty, michael@0: aFlags) || michael@0: IsCompositorAnimationDisabledForFrame(frame)) { michael@0: return false; michael@0: } michael@0: if (prop.mProperty == eCSSProperty_opacity) { michael@0: hasOpacity = true; michael@0: } else if (prop.mProperty == eCSSProperty_transform) { michael@0: hasTransform = true; michael@0: } michael@0: } michael@0: } michael@0: // This animation can be done on the compositor. Mark the frame as active, in michael@0: // case we are able to throttle this animation. michael@0: if (hasOpacity) { michael@0: ActiveLayerTracker::NotifyAnimated(frame, eCSSProperty_opacity); michael@0: } michael@0: if (hasTransform) { michael@0: ActiveLayerTracker::NotifyAnimated(frame, eCSSProperty_transform); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: ElementAnimations* michael@0: nsAnimationManager::GetElementAnimations(dom::Element *aElement, michael@0: nsCSSPseudoElements::Type aPseudoType, michael@0: bool aCreateIfNeeded) michael@0: { michael@0: if (!aCreateIfNeeded && PR_CLIST_IS_EMPTY(&mElementData)) { michael@0: // Early return for the most common case. michael@0: return nullptr; michael@0: } michael@0: michael@0: nsIAtom *propName; michael@0: if (aPseudoType == nsCSSPseudoElements::ePseudo_NotPseudoElement) { michael@0: propName = nsGkAtoms::animationsProperty; michael@0: } else if (aPseudoType == nsCSSPseudoElements::ePseudo_before) { michael@0: propName = nsGkAtoms::animationsOfBeforeProperty; michael@0: } else if (aPseudoType == nsCSSPseudoElements::ePseudo_after) { michael@0: propName = nsGkAtoms::animationsOfAfterProperty; michael@0: } else { michael@0: NS_ASSERTION(!aCreateIfNeeded, michael@0: "should never try to create transitions for pseudo " michael@0: "other than :before or :after"); michael@0: return nullptr; michael@0: } michael@0: ElementAnimations *ea = static_cast( michael@0: aElement->GetProperty(propName)); michael@0: if (!ea && aCreateIfNeeded) { michael@0: // FIXME: Consider arena-allocating? michael@0: ea = new ElementAnimations(aElement, propName, this, michael@0: mPresContext->RefreshDriver()->MostRecentRefresh()); michael@0: nsresult rv = aElement->SetProperty(propName, ea, michael@0: ElementAnimationsPropertyDtor, false); michael@0: if (NS_FAILED(rv)) { michael@0: NS_WARNING("SetProperty failed"); michael@0: delete ea; michael@0: return nullptr; michael@0: } michael@0: if (propName == nsGkAtoms::animationsProperty) { michael@0: aElement->SetMayHaveAnimations(); michael@0: } michael@0: michael@0: AddElementData(ea); michael@0: } michael@0: michael@0: return ea; michael@0: } michael@0: michael@0: michael@0: void michael@0: nsAnimationManager::EnsureStyleRuleFor(ElementAnimations* aET) michael@0: { michael@0: aET->EnsureStyleRuleFor(mPresContext->RefreshDriver()->MostRecentRefresh(), michael@0: mPendingEvents, michael@0: false); michael@0: CheckNeedsRefresh(); michael@0: } michael@0: michael@0: /* virtual */ void michael@0: nsAnimationManager::RulesMatching(ElementRuleProcessorData* aData) michael@0: { michael@0: NS_ABORT_IF_FALSE(aData->mPresContext == mPresContext, michael@0: "pres context mismatch"); michael@0: nsIStyleRule *rule = michael@0: GetAnimationRule(aData->mElement, michael@0: nsCSSPseudoElements::ePseudo_NotPseudoElement); michael@0: if (rule) { michael@0: aData->mRuleWalker->Forward(rule); michael@0: } michael@0: } michael@0: michael@0: /* virtual */ void michael@0: nsAnimationManager::RulesMatching(PseudoElementRuleProcessorData* aData) michael@0: { michael@0: NS_ABORT_IF_FALSE(aData->mPresContext == mPresContext, michael@0: "pres context mismatch"); michael@0: if (aData->mPseudoType != nsCSSPseudoElements::ePseudo_before && michael@0: aData->mPseudoType != nsCSSPseudoElements::ePseudo_after) { michael@0: return; michael@0: } michael@0: michael@0: // FIXME: Do we really want to be the only thing keeping a michael@0: // pseudo-element alive? I *think* the non-animation restyle should michael@0: // handle that, but should add a test. michael@0: nsIStyleRule *rule = GetAnimationRule(aData->mElement, aData->mPseudoType); michael@0: if (rule) { michael@0: aData->mRuleWalker->Forward(rule); michael@0: } michael@0: } michael@0: michael@0: /* virtual */ void michael@0: nsAnimationManager::RulesMatching(AnonBoxRuleProcessorData* aData) michael@0: { michael@0: } michael@0: michael@0: #ifdef MOZ_XUL michael@0: /* virtual */ void michael@0: nsAnimationManager::RulesMatching(XULTreeRuleProcessorData* aData) michael@0: { michael@0: } michael@0: #endif michael@0: michael@0: /* virtual */ size_t michael@0: nsAnimationManager::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const michael@0: { michael@0: return CommonAnimationManager::SizeOfExcludingThis(aMallocSizeOf); michael@0: michael@0: // Measurement of the following members may be added later if DMD finds it is michael@0: // worthwhile: michael@0: // - mPendingEvents michael@0: } michael@0: michael@0: /* virtual */ size_t michael@0: nsAnimationManager::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const michael@0: { michael@0: return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); michael@0: } michael@0: michael@0: nsIStyleRule* michael@0: nsAnimationManager::CheckAnimationRule(nsStyleContext* aStyleContext, michael@0: mozilla::dom::Element* aElement) michael@0: { michael@0: if (!mPresContext->IsProcessingAnimationStyleChange()) { michael@0: if (!mPresContext->IsDynamic()) { michael@0: // For print or print preview, ignore animations. michael@0: return nullptr; michael@0: } michael@0: michael@0: // Everything that causes our animation data to change triggers a michael@0: // style change, which in turn triggers a non-animation restyle. michael@0: // Likewise, when we initially construct frames, we're not in a michael@0: // style change, but also not in an animation restyle. michael@0: michael@0: const nsStyleDisplay *disp = aStyleContext->StyleDisplay(); michael@0: ElementAnimations *ea = michael@0: GetElementAnimations(aElement, aStyleContext->GetPseudoType(), false); michael@0: if (!ea && michael@0: disp->mAnimationNameCount == 1 && michael@0: disp->mAnimations[0].GetName().IsEmpty()) { michael@0: return nullptr; michael@0: } michael@0: michael@0: // build the animations list michael@0: InfallibleTArray newAnimations; michael@0: BuildAnimations(aStyleContext, newAnimations); michael@0: michael@0: if (newAnimations.IsEmpty()) { michael@0: if (ea) { michael@0: ea->Destroy(); michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: TimeStamp refreshTime = mPresContext->RefreshDriver()->MostRecentRefresh(); michael@0: michael@0: if (ea) { michael@0: ea->mStyleRule = nullptr; michael@0: ea->mStyleRuleRefreshTime = TimeStamp(); michael@0: ea->UpdateAnimationGeneration(mPresContext); michael@0: michael@0: // Copy over the start times and (if still paused) pause starts michael@0: // for each animation (matching on name only) that was also in the michael@0: // old list of animations. michael@0: // This means that we honor dynamic changes, which isn't what the michael@0: // spec says to do, but WebKit seems to honor at least some of michael@0: // them. See michael@0: // http://lists.w3.org/Archives/Public/www-style/2011Apr/0079.html michael@0: // In order to honor what the spec said, we'd copy more data over michael@0: // (or potentially optimize BuildAnimations to avoid rebuilding it michael@0: // in the first place). michael@0: if (!ea->mAnimations.IsEmpty()) { michael@0: for (uint32_t newIdx = 0, newEnd = newAnimations.Length(); michael@0: newIdx != newEnd; ++newIdx) { michael@0: StyleAnimation *newAnim = &newAnimations[newIdx]; michael@0: michael@0: // Find the matching animation with this name in the old list michael@0: // of animations. Because of this code, they must all have michael@0: // the same start time, though they might differ in pause michael@0: // state. So if a page uses multiple copies of the same michael@0: // animation in one element's animation list, and gives them michael@0: // different pause states, they, well, get what they deserve. michael@0: // We'll use the last one since it's more likely to be the one michael@0: // doing something. michael@0: const StyleAnimation *oldAnim = nullptr; michael@0: for (uint32_t oldIdx = ea->mAnimations.Length(); oldIdx-- != 0; ) { michael@0: const StyleAnimation *a = &ea->mAnimations[oldIdx]; michael@0: if (a->mName == newAnim->mName) { michael@0: oldAnim = a; michael@0: break; michael@0: } michael@0: } michael@0: if (!oldAnim) { michael@0: continue; michael@0: } michael@0: michael@0: newAnim->mStartTime = oldAnim->mStartTime; michael@0: newAnim->mLastNotification = oldAnim->mLastNotification; michael@0: michael@0: if (oldAnim->IsPaused()) { michael@0: if (newAnim->IsPaused()) { michael@0: // Copy pause start just like start time. michael@0: newAnim->mPauseStart = oldAnim->mPauseStart; michael@0: } else { michael@0: // Handle change in pause state by adjusting start michael@0: // time to unpause. michael@0: newAnim->mStartTime += refreshTime - oldAnim->mPauseStart; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } else { michael@0: ea = GetElementAnimations(aElement, aStyleContext->GetPseudoType(), michael@0: true); michael@0: } michael@0: ea->mAnimations.SwapElements(newAnimations); michael@0: ea->mNeedsRefreshes = true; michael@0: michael@0: ea->EnsureStyleRuleFor(refreshTime, mPendingEvents, false); michael@0: CheckNeedsRefresh(); michael@0: // We don't actually dispatch the mPendingEvents now. We'll either michael@0: // dispatch them the next time we get a refresh driver notification michael@0: // or the next time somebody calls michael@0: // nsPresShell::FlushPendingNotifications. michael@0: if (!mPendingEvents.IsEmpty()) { michael@0: mPresContext->Document()->SetNeedStyleFlush(); michael@0: } michael@0: } michael@0: michael@0: return GetAnimationRule(aElement, aStyleContext->GetPseudoType()); michael@0: } michael@0: michael@0: class PercentageHashKey : public PLDHashEntryHdr michael@0: { michael@0: public: michael@0: typedef const float& KeyType; michael@0: typedef const float* KeyTypePointer; michael@0: michael@0: PercentageHashKey(KeyTypePointer aKey) : mValue(*aKey) { } michael@0: PercentageHashKey(const PercentageHashKey& toCopy) : mValue(toCopy.mValue) { } michael@0: ~PercentageHashKey() { } michael@0: michael@0: KeyType GetKey() const { return mValue; } michael@0: bool KeyEquals(KeyTypePointer aKey) const { return *aKey == mValue; } michael@0: michael@0: static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } michael@0: static PLDHashNumber HashKey(KeyTypePointer aKey) { michael@0: static_assert(sizeof(PLDHashNumber) == sizeof(uint32_t), michael@0: "this hash function assumes PLDHashNumber is uint32_t"); michael@0: static_assert(PLDHashNumber(-1) > PLDHashNumber(0), michael@0: "this hash function assumes PLDHashNumber is uint32_t"); michael@0: float key = *aKey; michael@0: NS_ABORT_IF_FALSE(0.0f <= key && key <= 1.0f, "out of range"); michael@0: return PLDHashNumber(key * UINT32_MAX); michael@0: } michael@0: enum { ALLOW_MEMMOVE = true }; michael@0: michael@0: private: michael@0: const float mValue; michael@0: }; michael@0: michael@0: struct KeyframeData { michael@0: float mKey; michael@0: uint32_t mIndex; // store original order since sort algorithm is not stable michael@0: nsCSSKeyframeRule *mRule; michael@0: }; michael@0: michael@0: struct KeyframeDataComparator { michael@0: bool Equals(const KeyframeData& A, const KeyframeData& B) const { michael@0: return A.mKey == B.mKey && A.mIndex == B.mIndex; michael@0: } michael@0: bool LessThan(const KeyframeData& A, const KeyframeData& B) const { michael@0: return A.mKey < B.mKey || (A.mKey == B.mKey && A.mIndex < B.mIndex); michael@0: } michael@0: }; michael@0: michael@0: class ResolvedStyleCache { michael@0: public: michael@0: ResolvedStyleCache() : mCache(16) {} michael@0: nsStyleContext* Get(nsPresContext *aPresContext, michael@0: nsStyleContext *aParentStyleContext, michael@0: nsCSSKeyframeRule *aKeyframe); michael@0: michael@0: private: michael@0: nsRefPtrHashtable, nsStyleContext> mCache; michael@0: }; michael@0: michael@0: nsStyleContext* michael@0: ResolvedStyleCache::Get(nsPresContext *aPresContext, michael@0: nsStyleContext *aParentStyleContext, michael@0: nsCSSKeyframeRule *aKeyframe) michael@0: { michael@0: // FIXME (spec): The css3-animations spec isn't very clear about how michael@0: // properties are resolved when they have values that depend on other michael@0: // properties (e.g., values in 'em'). I presume that they're resolved michael@0: // relative to the other styles of the element. The question is michael@0: // whether they are resolved relative to other animations: I assume michael@0: // that they're not, since that would prevent us from caching a lot of michael@0: // data that we'd really like to cache (in particular, the michael@0: // nsStyleAnimation::Value values in AnimationPropertySegment). michael@0: nsStyleContext *result = mCache.GetWeak(aKeyframe); michael@0: if (!result) { michael@0: nsCOMArray rules; michael@0: rules.AppendObject(aKeyframe); michael@0: nsRefPtr resultStrong = aPresContext->StyleSet()-> michael@0: ResolveStyleByAddingRules(aParentStyleContext, rules); michael@0: mCache.Put(aKeyframe, resultStrong); michael@0: result = resultStrong; michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: void michael@0: nsAnimationManager::BuildAnimations(nsStyleContext* aStyleContext, michael@0: InfallibleTArray& michael@0: aAnimations) michael@0: { michael@0: NS_ABORT_IF_FALSE(aAnimations.IsEmpty(), "expect empty array"); michael@0: michael@0: ResolvedStyleCache resolvedStyles; michael@0: michael@0: const nsStyleDisplay *disp = aStyleContext->StyleDisplay(); michael@0: TimeStamp now = mPresContext->RefreshDriver()->MostRecentRefresh(); michael@0: for (uint32_t animIdx = 0, animEnd = disp->mAnimationNameCount; michael@0: animIdx != animEnd; ++animIdx) { michael@0: const nsAnimation& aSrc = disp->mAnimations[animIdx]; michael@0: StyleAnimation& aDest = *aAnimations.AppendElement(); michael@0: michael@0: aDest.mName = aSrc.GetName(); michael@0: aDest.mIterationCount = aSrc.GetIterationCount(); michael@0: aDest.mDirection = aSrc.GetDirection(); michael@0: aDest.mFillMode = aSrc.GetFillMode(); michael@0: aDest.mPlayState = aSrc.GetPlayState(); michael@0: michael@0: aDest.mDelay = TimeDuration::FromMilliseconds(aSrc.GetDelay()); michael@0: aDest.mStartTime = now; michael@0: if (aDest.IsPaused()) { michael@0: aDest.mPauseStart = now; michael@0: } else { michael@0: aDest.mPauseStart = TimeStamp(); michael@0: } michael@0: michael@0: aDest.mIterationDuration = TimeDuration::FromMilliseconds(aSrc.GetDuration()); michael@0: michael@0: nsCSSKeyframesRule* rule = michael@0: mPresContext->StyleSet()->KeyframesRuleForName(mPresContext, aDest.mName); michael@0: if (!rule) { michael@0: // no segments michael@0: continue; michael@0: } michael@0: michael@0: // While current drafts of css3-animations say that later keyframes michael@0: // with the same key entirely replace earlier ones (no cascading), michael@0: // this is a bad idea and contradictory to the rest of CSS. So michael@0: // we're going to keep all the keyframes for each key and then do michael@0: // the replacement on a per-property basis rather than a per-rule michael@0: // basis, just like everything else in CSS. michael@0: michael@0: AutoInfallibleTArray sortedKeyframes; michael@0: michael@0: for (uint32_t ruleIdx = 0, ruleEnd = rule->StyleRuleCount(); michael@0: ruleIdx != ruleEnd; ++ruleIdx) { michael@0: css::Rule* cssRule = rule->GetStyleRuleAt(ruleIdx); michael@0: NS_ABORT_IF_FALSE(cssRule, "must have rule"); michael@0: NS_ABORT_IF_FALSE(cssRule->GetType() == css::Rule::KEYFRAME_RULE, michael@0: "must be keyframe rule"); michael@0: nsCSSKeyframeRule *kfRule = static_cast(cssRule); michael@0: michael@0: const nsTArray &keys = kfRule->GetKeys(); michael@0: for (uint32_t keyIdx = 0, keyEnd = keys.Length(); michael@0: keyIdx != keyEnd; ++keyIdx) { michael@0: float key = keys[keyIdx]; michael@0: // FIXME (spec): The spec doesn't say what to do with michael@0: // out-of-range keyframes. We'll ignore them. michael@0: // (And PercentageHashKey currently assumes we either ignore or michael@0: // clamp them.) michael@0: if (0.0f <= key && key <= 1.0f) { michael@0: KeyframeData *data = sortedKeyframes.AppendElement(); michael@0: data->mKey = key; michael@0: data->mIndex = ruleIdx; michael@0: data->mRule = kfRule; michael@0: } michael@0: } michael@0: } michael@0: michael@0: sortedKeyframes.Sort(KeyframeDataComparator()); michael@0: michael@0: if (sortedKeyframes.Length() == 0) { michael@0: // no segments michael@0: continue; michael@0: } michael@0: michael@0: // Record the properties that are present in any keyframe rules we michael@0: // are using. michael@0: nsCSSPropertySet properties; michael@0: michael@0: for (uint32_t kfIdx = 0, kfEnd = sortedKeyframes.Length(); michael@0: kfIdx != kfEnd; ++kfIdx) { michael@0: css::Declaration *decl = sortedKeyframes[kfIdx].mRule->Declaration(); michael@0: for (uint32_t propIdx = 0, propEnd = decl->Count(); michael@0: propIdx != propEnd; ++propIdx) { michael@0: nsCSSProperty prop = decl->GetPropertyAt(propIdx); michael@0: if (prop != eCSSPropertyExtra_variable) { michael@0: // CSS Variables are not animatable michael@0: properties.AddProperty(prop); michael@0: } michael@0: } michael@0: } michael@0: michael@0: for (nsCSSProperty prop = nsCSSProperty(0); michael@0: prop < eCSSProperty_COUNT_no_shorthands; michael@0: prop = nsCSSProperty(prop + 1)) { michael@0: if (!properties.HasProperty(prop) || michael@0: nsCSSProps::kAnimTypeTable[prop] == eStyleAnimType_None) { michael@0: continue; michael@0: } michael@0: michael@0: // Build a list of the keyframes to use for this property. This michael@0: // means we need every keyframe with the property in it, except michael@0: // for those keyframes where a later keyframe with the *same key* michael@0: // also has the property. michael@0: AutoInfallibleTArray keyframesWithProperty; michael@0: float lastKey = 100.0f; // an invalid key michael@0: for (uint32_t kfIdx = 0, kfEnd = sortedKeyframes.Length(); michael@0: kfIdx != kfEnd; ++kfIdx) { michael@0: KeyframeData &kf = sortedKeyframes[kfIdx]; michael@0: if (!kf.mRule->Declaration()->HasProperty(prop)) { michael@0: continue; michael@0: } michael@0: if (kf.mKey == lastKey) { michael@0: // Replace previous occurrence of same key. michael@0: keyframesWithProperty[keyframesWithProperty.Length() - 1] = kfIdx; michael@0: } else { michael@0: keyframesWithProperty.AppendElement(kfIdx); michael@0: } michael@0: lastKey = kf.mKey; michael@0: } michael@0: michael@0: AnimationProperty &propData = *aDest.mProperties.AppendElement(); michael@0: propData.mProperty = prop; michael@0: michael@0: KeyframeData *fromKeyframe = nullptr; michael@0: nsRefPtr fromContext; michael@0: bool interpolated = true; michael@0: for (uint32_t wpIdx = 0, wpEnd = keyframesWithProperty.Length(); michael@0: wpIdx != wpEnd; ++wpIdx) { michael@0: uint32_t kfIdx = keyframesWithProperty[wpIdx]; michael@0: KeyframeData &toKeyframe = sortedKeyframes[kfIdx]; michael@0: michael@0: nsRefPtr toContext = michael@0: resolvedStyles.Get(mPresContext, aStyleContext, toKeyframe.mRule); michael@0: michael@0: if (fromKeyframe) { michael@0: interpolated = interpolated && michael@0: BuildSegment(propData.mSegments, prop, aSrc, michael@0: fromKeyframe->mKey, fromContext, michael@0: fromKeyframe->mRule->Declaration(), michael@0: toKeyframe.mKey, toContext); michael@0: } else { michael@0: if (toKeyframe.mKey != 0.0f) { michael@0: // There's no data for this property at 0%, so use the michael@0: // cascaded value above us. michael@0: interpolated = interpolated && michael@0: BuildSegment(propData.mSegments, prop, aSrc, michael@0: 0.0f, aStyleContext, nullptr, michael@0: toKeyframe.mKey, toContext); michael@0: } michael@0: } michael@0: michael@0: fromContext = toContext; michael@0: fromKeyframe = &toKeyframe; michael@0: } michael@0: michael@0: if (fromKeyframe->mKey != 1.0f) { michael@0: // There's no data for this property at 100%, so use the michael@0: // cascaded value above us. michael@0: interpolated = interpolated && michael@0: BuildSegment(propData.mSegments, prop, aSrc, michael@0: fromKeyframe->mKey, fromContext, michael@0: fromKeyframe->mRule->Declaration(), michael@0: 1.0f, aStyleContext); michael@0: } michael@0: michael@0: // If we failed to build any segments due to inability to michael@0: // interpolate, remove the property from the animation. (It's not michael@0: // clear if this is the right thing to do -- we could run some of michael@0: // the segments, but it's really not clear whether we should skip michael@0: // values (which?) or skip segments, so best to skip the whole michael@0: // thing for now.) michael@0: if (!interpolated) { michael@0: aDest.mProperties.RemoveElementAt(aDest.mProperties.Length() - 1); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: bool michael@0: nsAnimationManager::BuildSegment(InfallibleTArray& michael@0: aSegments, michael@0: nsCSSProperty aProperty, michael@0: const nsAnimation& aAnimation, michael@0: float aFromKey, nsStyleContext* aFromContext, michael@0: mozilla::css::Declaration* aFromDeclaration, michael@0: float aToKey, nsStyleContext* aToContext) michael@0: { michael@0: nsStyleAnimation::Value fromValue, toValue, dummyValue; michael@0: if (!ExtractComputedValueForTransition(aProperty, aFromContext, fromValue) || michael@0: !ExtractComputedValueForTransition(aProperty, aToContext, toValue) || michael@0: // Check that we can interpolate between these values michael@0: // (If this is ever a performance problem, we could add a michael@0: // CanInterpolate method, but it seems fine for now.) michael@0: !nsStyleAnimation::Interpolate(aProperty, fromValue, toValue, michael@0: 0.5, dummyValue)) { michael@0: return false; michael@0: } michael@0: michael@0: AnimationPropertySegment &segment = *aSegments.AppendElement(); michael@0: michael@0: segment.mFromValue = fromValue; michael@0: segment.mToValue = toValue; michael@0: segment.mFromKey = aFromKey; michael@0: segment.mToKey = aToKey; michael@0: const nsTimingFunction *tf; michael@0: if (aFromDeclaration && michael@0: aFromDeclaration->HasProperty(eCSSProperty_animation_timing_function)) { michael@0: tf = &aFromContext->StyleDisplay()->mAnimations[0].GetTimingFunction(); michael@0: } else { michael@0: tf = &aAnimation.GetTimingFunction(); michael@0: } michael@0: segment.mTimingFunction.Init(*tf); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: nsIStyleRule* michael@0: nsAnimationManager::GetAnimationRule(mozilla::dom::Element* aElement, michael@0: nsCSSPseudoElements::Type aPseudoType) michael@0: { michael@0: NS_ABORT_IF_FALSE( michael@0: aPseudoType == nsCSSPseudoElements::ePseudo_NotPseudoElement || michael@0: aPseudoType == nsCSSPseudoElements::ePseudo_before || michael@0: aPseudoType == nsCSSPseudoElements::ePseudo_after, michael@0: "forbidden pseudo type"); michael@0: michael@0: if (!mPresContext->IsDynamic()) { michael@0: // For print or print preview, ignore animations. michael@0: return nullptr; michael@0: } michael@0: michael@0: ElementAnimations *ea = michael@0: GetElementAnimations(aElement, aPseudoType, false); michael@0: if (!ea) { michael@0: return nullptr; michael@0: } michael@0: michael@0: if (mPresContext->IsProcessingRestyles() && michael@0: !mPresContext->IsProcessingAnimationStyleChange()) { michael@0: // During the non-animation part of processing restyles, we don't michael@0: // add the animation rule. michael@0: michael@0: if (ea->mStyleRule) { michael@0: ea->PostRestyleForAnimation(mPresContext); michael@0: } michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: NS_WARN_IF_FALSE(!ea->mNeedsRefreshes || michael@0: ea->mStyleRuleRefreshTime == michael@0: mPresContext->RefreshDriver()->MostRecentRefresh(), michael@0: "should already have refreshed style rule"); michael@0: michael@0: return ea->mStyleRule; michael@0: } michael@0: michael@0: /* virtual */ void michael@0: nsAnimationManager::WillRefresh(mozilla::TimeStamp aTime) michael@0: { michael@0: NS_ABORT_IF_FALSE(mPresContext, michael@0: "refresh driver should not notify additional observers " michael@0: "after pres context has been destroyed"); michael@0: if (!mPresContext->GetPresShell()) { michael@0: // Someone might be keeping mPresContext alive past the point michael@0: // where it has been torn down; don't bother doing anything in michael@0: // this case. But do get rid of all our transitions so we stop michael@0: // triggering refreshes. michael@0: RemoveAllElementData(); michael@0: return; michael@0: } michael@0: michael@0: FlushAnimations(Can_Throttle); michael@0: } michael@0: michael@0: void michael@0: nsAnimationManager::AddElementData(CommonElementAnimationData* aData) michael@0: { michael@0: if (!mObservingRefreshDriver) { michael@0: NS_ASSERTION(static_cast(aData)->mNeedsRefreshes, michael@0: "Added data which doesn't need refreshing?"); michael@0: // We need to observe the refresh driver. michael@0: mPresContext->RefreshDriver()->AddRefreshObserver(this, Flush_Style); michael@0: mObservingRefreshDriver = true; michael@0: } michael@0: michael@0: PR_INSERT_BEFORE(aData, &mElementData); michael@0: } michael@0: michael@0: void michael@0: nsAnimationManager::CheckNeedsRefresh() michael@0: { michael@0: for (PRCList *l = PR_LIST_HEAD(&mElementData); l != &mElementData; michael@0: l = PR_NEXT_LINK(l)) { michael@0: if (static_cast(l)->mNeedsRefreshes) { michael@0: if (!mObservingRefreshDriver) { michael@0: mPresContext->RefreshDriver()->AddRefreshObserver(this, Flush_Style); michael@0: mObservingRefreshDriver = true; michael@0: } michael@0: return; michael@0: } michael@0: } michael@0: if (mObservingRefreshDriver) { michael@0: mObservingRefreshDriver = false; michael@0: mPresContext->RefreshDriver()->RemoveRefreshObserver(this, Flush_Style); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsAnimationManager::FlushAnimations(FlushFlags aFlags) michael@0: { michael@0: // FIXME: check that there's at least one style rule that's not michael@0: // in its "done" state, and if there isn't, remove ourselves from michael@0: // the refresh driver (but leave the animations!). michael@0: TimeStamp now = mPresContext->RefreshDriver()->MostRecentRefresh(); michael@0: bool didThrottle = false; michael@0: for (PRCList *l = PR_LIST_HEAD(&mElementData); l != &mElementData; michael@0: l = PR_NEXT_LINK(l)) { michael@0: ElementAnimations *ea = static_cast(l); michael@0: bool canThrottleTick = aFlags == Can_Throttle && michael@0: ea->CanPerformOnCompositorThread( michael@0: CommonElementAnimationData::CanAnimateFlags(0)) && michael@0: ea->CanThrottleAnimation(now); michael@0: michael@0: nsRefPtr oldStyleRule = ea->mStyleRule; michael@0: ea->EnsureStyleRuleFor(now, mPendingEvents, canThrottleTick); michael@0: CheckNeedsRefresh(); michael@0: if (oldStyleRule != ea->mStyleRule) { michael@0: ea->PostRestyleForAnimation(mPresContext); michael@0: } else { michael@0: didThrottle = true; michael@0: } michael@0: } michael@0: michael@0: if (didThrottle) { michael@0: mPresContext->Document()->SetNeedStyleFlush(); michael@0: } michael@0: michael@0: DispatchEvents(); // may destroy us michael@0: } michael@0: michael@0: void michael@0: nsAnimationManager::DoDispatchEvents() michael@0: { michael@0: EventArray events; michael@0: mPendingEvents.SwapElements(events); michael@0: for (uint32_t i = 0, i_end = events.Length(); i < i_end; ++i) { michael@0: AnimationEventInfo &info = events[i]; michael@0: EventDispatcher::Dispatch(info.mElement, mPresContext, &info.mEvent); michael@0: michael@0: if (!mPresContext) { michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsAnimationManager::UpdateThrottledStylesForSubtree(nsIContent* aContent, michael@0: nsStyleContext* aParentStyle, michael@0: nsStyleChangeList& aChangeList) michael@0: { michael@0: dom::Element* element; michael@0: if (aContent->IsElement()) { michael@0: element = aContent->AsElement(); michael@0: } else { michael@0: element = nullptr; michael@0: } michael@0: michael@0: nsRefPtr newStyle; michael@0: michael@0: ElementAnimations* ea; michael@0: if (element && michael@0: (ea = GetElementAnimations(element, michael@0: nsCSSPseudoElements::ePseudo_NotPseudoElement, michael@0: false))) { michael@0: // re-resolve our style michael@0: newStyle = UpdateThrottledStyle(element, aParentStyle, aChangeList); michael@0: // remove the current transition from the working set michael@0: ea->mFlushGeneration = mPresContext->RefreshDriver()->MostRecentRefresh(); michael@0: } else { michael@0: newStyle = ReparentContent(aContent, aParentStyle); michael@0: } michael@0: michael@0: // walk the children michael@0: if (newStyle) { michael@0: for (nsIContent *child = aContent->GetFirstChild(); child; michael@0: child = child->GetNextSibling()) { michael@0: UpdateThrottledStylesForSubtree(child, newStyle, aChangeList); michael@0: } michael@0: } michael@0: } michael@0: michael@0: IMPL_UPDATE_ALL_THROTTLED_STYLES_INTERNAL(nsAnimationManager, michael@0: GetElementAnimations) michael@0: michael@0: void michael@0: nsAnimationManager::UpdateAllThrottledStyles() michael@0: { michael@0: if (PR_CLIST_IS_EMPTY(&mElementData)) { michael@0: // no throttled animations, leave early michael@0: mPresContext->TickLastUpdateThrottledAnimationStyle(); michael@0: return; michael@0: } michael@0: michael@0: if (mPresContext->ThrottledAnimationStyleIsUpToDate()) { michael@0: // throttled transitions are up to date, leave early michael@0: return; michael@0: } michael@0: michael@0: mPresContext->TickLastUpdateThrottledAnimationStyle(); michael@0: michael@0: UpdateAllThrottledStylesInternal(); michael@0: } michael@0: