/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is nsAnimationManager, an implementation of part * of css3-animations. * * The Initial Developer of the Original Code is the Mozilla Foundation. * Portions created by the Initial Developer are Copyright (C) 2011 * the Initial Developer. All Rights Reserved. * * Contributor(s): * L. David Baron , Mozilla Corporation (original author) * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "nsAnimationManager.h" #include "nsPresContext.h" #include "nsRuleProcessorData.h" #include "nsStyleSet.h" #include "nsCSSRules.h" #include "nsStyleAnimation.h" #include "nsSMILKeySpline.h" #include "nsEventDispatcher.h" using namespace mozilla; struct AnimationSegmentProperty { nsCSSProperty mProperty; nsStyleAnimation::Value mFromValue, mToValue; }; struct AnimationSegment { float mFromKey, mToKey; css::ComputedTimingFunction mTimingFunction; InfallibleTArray mProperties; }; /** * Data about one animation (i.e., one of the values of * 'animation-name') running on an element. */ struct ElementAnimation { ElementAnimation() : mLastNotification(LAST_NOTIFICATION_NONE) { } nsString mName; // empty string for 'none' float mIterationCount; // NS_IEEEPositiveInfinity() means infinite PRUint8 mDirection; PRUint8 mFillMode; PRUint8 mPlayState; bool FillsForwards() const { return mFillMode == NS_STYLE_ANIMATION_FILL_MODE_BOTH || mFillMode == NS_STYLE_ANIMATION_FILL_MODE_FORWARDS; } bool FillsBackwards() const { return mFillMode == NS_STYLE_ANIMATION_FILL_MODE_BOTH || mFillMode == NS_STYLE_ANIMATION_FILL_MODE_BACKWARDS; } bool IsPaused() const { return mPlayState == NS_STYLE_ANIMATION_PLAY_STATE_PAUSED; } TimeStamp mStartTime; // with delay taken into account TimeStamp mPauseStart; TimeDuration mIterationDuration; enum { LAST_NOTIFICATION_NONE = PRUint32(-1), LAST_NOTIFICATION_END = PRUint32(-2) }; // One of the above constants, or an integer for the iteration // whose start we last notified on. PRUint32 mLastNotification; InfallibleTArray mSegments; }; typedef nsAnimationManager::EventArray EventArray; typedef nsAnimationManager::AnimationEventInfo AnimationEventInfo; /** * Data about all of the animations running on an element. */ struct ElementAnimations : public mozilla::css::CommonElementAnimationData { ElementAnimations(dom::Element *aElement, nsIAtom *aElementProperty, nsAnimationManager *aAnimationManager) : CommonElementAnimationData(aElement, aElementProperty, aAnimationManager), mNeedsRefreshes(true) { } void EnsureStyleRuleFor(TimeStamp aRefreshTime, EventArray &aEventsToDispatch); bool IsForElement() const { // rather than for a pseudo-element return mElementProperty == nsGkAtoms::animationsProperty; } void PostRestyleForAnimation(nsPresContext *aPresContext) { nsRestyleHint hint = IsForElement() ? eRestyle_Self : eRestyle_Subtree; aPresContext->PresShell()->RestyleForAnimation(mElement, hint); } // This style rule contains the style data for currently animating // values. It only matches when styling with animation. When we // style without animation, we need to not use it so that we can // detect any new changes; if necessary we restyle immediately // afterwards with animation. // NOTE: If we don't need to apply any styles, mStyleRule will be // null, but mStyleRuleRefreshTime will still be valid. nsRefPtr mStyleRule; // The refresh time associated with mStyleRule. TimeStamp mStyleRuleRefreshTime; // False when we know that our current style rule is valid // indefinitely into the future (because all of our animations are // either completed or paused). May be invalidated by a style change. bool mNeedsRefreshes; InfallibleTArray mAnimations; }; static void ElementAnimationsPropertyDtor(void *aObject, nsIAtom *aPropertyName, void *aPropertyValue, void *aData) { ElementAnimations *ea = static_cast(aPropertyValue); delete ea; } void ElementAnimations::EnsureStyleRuleFor(TimeStamp aRefreshTime, EventArray& aEventsToDispatch) { if (!mNeedsRefreshes) { // All of our animations are paused or completed. mStyleRuleRefreshTime = aRefreshTime; return; } mNeedsRefreshes = false; // mStyleRule may be null and valid, if we have no style to apply. if (mStyleRuleRefreshTime.IsNull() || mStyleRuleRefreshTime != aRefreshTime) { mStyleRuleRefreshTime = aRefreshTime; mStyleRule = nsnull; // FIXME(spec): assume that properties in higher animations override // those in lower ones (and that our |HasProperty| check in // |BuildSegment| matches the definition of when they should do so. // Therefore, we iterate from last animation to first. nsCSSPropertySet properties; for (PRUint32 i = mAnimations.Length(); i-- != 0; ) { ElementAnimation &anim = mAnimations[i]; if (anim.mSegments.Length() == 0 || anim.mIterationDuration.ToMilliseconds() <= 0.0) { // No animation data. continue; } TimeDuration currentTimeDuration; if (anim.IsPaused()) { // FIXME: avoid recalculating every time currentTimeDuration = anim.mPauseStart - anim.mStartTime; } else { currentTimeDuration = aRefreshTime - anim.mStartTime; } // Set |currentIterationCount| to the (fractional) number of // iterations we've completed up to the current position. double currentIterationCount = currentTimeDuration / anim.mIterationDuration; bool dispatchStartOrIteration = false; if (currentIterationCount >= double(anim.mIterationCount)) { // Dispatch 'animationend' when needed. if (IsForElement() && anim.mLastNotification != ElementAnimation::LAST_NOTIFICATION_END) { anim.mLastNotification = ElementAnimation::LAST_NOTIFICATION_END; AnimationEventInfo ei(mElement, anim.mName, NS_ANIMATION_END, currentTimeDuration); aEventsToDispatch.AppendElement(ei); } if (!anim.FillsForwards()) { // No animation data. continue; } currentIterationCount = double(anim.mIterationCount); } else { if (!anim.IsPaused()) { mNeedsRefreshes = true; } if (currentIterationCount < 0.0) { if (!anim.FillsBackwards()) { // No animation data. continue; } currentIterationCount = 0.0; } else { dispatchStartOrIteration = !anim.IsPaused(); } } // Set |positionInIteration| to the position from 0% to 100% along // the keyframes. NS_ABORT_IF_FALSE(currentIterationCount >= 0.0, "must be positive"); PRUint32 whichIteration = int(currentIterationCount); if (whichIteration == anim.mIterationCount) { // When the animation's iteration count is an integer (as it // normally is), we need to end at 100% of its last iteration // rather than 0% of the next one. --whichIteration; } double positionInIteration = currentIterationCount - double(whichIteration); if (anim.mDirection == NS_STYLE_ANIMATION_DIRECTION_ALTERNATE && (whichIteration & 1) == 1) { positionInIteration = 1.0 - positionInIteration; } // Dispatch 'animationstart' or 'animationiteration' when needed. if (IsForElement() && dispatchStartOrIteration && whichIteration != anim.mLastNotification) { // Notify 'animationstart' even if a negative delay puts us // past the first iteration. // Note that when somebody changes the animation-duration // dynamically, this will fire an extra iteration event // immediately in many cases. It's not clear to me if that's the // right thing to do. PRUint32 message = anim.mLastNotification == ElementAnimation::LAST_NOTIFICATION_NONE ? NS_ANIMATION_START : NS_ANIMATION_ITERATION; anim.mLastNotification = whichIteration; AnimationEventInfo ei(mElement, anim.mName, message, currentTimeDuration); aEventsToDispatch.AppendElement(ei); } NS_ABORT_IF_FALSE(0.0 <= positionInIteration && positionInIteration <= 1.0, "position should be in [0-1]"); NS_ABORT_IF_FALSE(anim.mSegments[0].mFromKey == 0.0, "incorrect first from key"); NS_ABORT_IF_FALSE(anim.mSegments[anim.mSegments.Length() - 1].mToKey == 1.0, "incorrect last to key"); // FIXME: Maybe cache the current segment? const AnimationSegment *segment = anim.mSegments.Elements(); while (segment->mToKey < positionInIteration) { NS_ABORT_IF_FALSE(segment->mFromKey < segment->mToKey, "incorrect keys"); ++segment; NS_ABORT_IF_FALSE(segment->mFromKey == (segment-1)->mToKey, "incorrect keys"); } NS_ABORT_IF_FALSE(segment->mFromKey < segment->mToKey, "incorrect keys"); NS_ABORT_IF_FALSE(segment - anim.mSegments.Elements() < anim.mSegments.Length(), "ran off end"); if (segment->mProperties.IsEmpty()) { // No animation data. continue; } if (!mStyleRule) { // Allocate the style rule now that we know we have animation data. mStyleRule = new css::AnimValuesStyleRule(); } double positionInSegment = (positionInIteration - segment->mFromKey) / (segment->mToKey - segment->mFromKey); double valuePosition = segment->mTimingFunction.GetValue(positionInSegment); for (PRUint32 j = 0, j_end = segment->mProperties.Length(); j != j_end; ++j) { const AnimationSegmentProperty &prop = segment->mProperties[j]; if (properties.HasProperty(prop.mProperty)) { // A later animation already set this property. continue; } properties.AddProperty(prop.mProperty); nsStyleAnimation::Value *val = mStyleRule->AddEmptyValue(prop.mProperty); #ifdef DEBUG PRBool result = #endif nsStyleAnimation::Interpolate(prop.mProperty, prop.mFromValue, prop.mToValue, valuePosition, *val); NS_ABORT_IF_FALSE(result, "interpolate must succeed now"); } } } } ElementAnimations* nsAnimationManager::GetElementAnimations(dom::Element *aElement, nsCSSPseudoElements::Type aPseudoType, PRBool aCreateIfNeeded) { if (!aCreateIfNeeded && PR_CLIST_IS_EMPTY(&mElementData)) { // Early return for the most common case. return nsnull; } nsIAtom *propName; if (aPseudoType == nsCSSPseudoElements::ePseudo_NotPseudoElement) { propName = nsGkAtoms::animationsProperty; } else if (aPseudoType == nsCSSPseudoElements::ePseudo_before) { propName = nsGkAtoms::animationsOfBeforeProperty; } else if (aPseudoType == nsCSSPseudoElements::ePseudo_after) { propName = nsGkAtoms::animationsOfAfterProperty; } else { NS_ASSERTION(!aCreateIfNeeded, "should never try to create transitions for pseudo " "other than :before or :after"); return nsnull; } ElementAnimations *ea = static_cast( aElement->GetProperty(propName)); if (!ea && aCreateIfNeeded) { // FIXME: Consider arena-allocating? ea = new ElementAnimations(aElement, propName, this); if (!ea) { NS_WARNING("out of memory"); return nsnull; } nsresult rv = aElement->SetProperty(propName, ea, ElementAnimationsPropertyDtor, nsnull); if (NS_FAILED(rv)) { NS_WARNING("SetProperty failed"); delete ea; return nsnull; } AddElementData(ea); } return ea; } /* virtual */ void nsAnimationManager::RulesMatching(ElementRuleProcessorData* aData) { NS_ABORT_IF_FALSE(aData->mPresContext == mPresContext, "pres context mismatch"); nsIStyleRule *rule = GetAnimationRule(aData->mElement, nsCSSPseudoElements::ePseudo_NotPseudoElement); if (rule) { aData->mRuleWalker->Forward(rule); } } /* virtual */ void nsAnimationManager::RulesMatching(PseudoElementRuleProcessorData* aData) { NS_ABORT_IF_FALSE(aData->mPresContext == mPresContext, "pres context mismatch"); if (aData->mPseudoType != nsCSSPseudoElements::ePseudo_before && aData->mPseudoType != nsCSSPseudoElements::ePseudo_after) { return; } // FIXME: Do we really want to be the only thing keeping a // pseudo-element alive? I *think* the non-animation restyle should // handle that, but should add a test. nsIStyleRule *rule = GetAnimationRule(aData->mElement, aData->mPseudoType); if (rule) { aData->mRuleWalker->Forward(rule); } } /* virtual */ void nsAnimationManager::RulesMatching(AnonBoxRuleProcessorData* aData) { } #ifdef MOZ_XUL /* virtual */ void nsAnimationManager::RulesMatching(XULTreeRuleProcessorData* aData) { } #endif nsIStyleRule* nsAnimationManager::CheckAnimationRule(nsStyleContext* aStyleContext, mozilla::dom::Element* aElement) { if (!mPresContext->IsProcessingAnimationStyleChange()) { // 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->GetStyleDisplay(); ElementAnimations *ea = GetElementAnimations(aElement, aStyleContext->GetPseudoType(), PR_FALSE); if (!ea && disp->mAnimations.Length() == 1 && disp->mAnimations[0].GetName().IsEmpty()) { return nsnull; } // build the animations list InfallibleTArray newAnimations; BuildAnimations(aStyleContext, newAnimations); if (newAnimations.IsEmpty()) { if (ea) { ea->Destroy(); } return nsnull; } TimeStamp refreshTime = mPresContext->RefreshDriver()->MostRecentRefresh(); if (ea) { // The cached style rule is invalid. ea->mStyleRule = nsnull; ea->mStyleRuleRefreshTime = TimeStamp(); // 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 (!ea->mAnimations.IsEmpty()) { for (PRUint32 i = 0, i_end = newAnimations.Length(); i != i_end; ++i) { ElementAnimation *newAnim = &newAnimations[i]; // Find the matching animation with this name in the old list // of animations. Because of this code, they must all have // the same start time, though they might differ in pause // state. So if a page uses multiple copies of the same // animation in one element's animation list, and gives them // different pause states, they, well, get what they deserve. // We'll use the last one since it's more likely to be the one // doing something. const ElementAnimation *oldAnim = nsnull; for (PRUint32 j = ea->mAnimations.Length(); j-- != 0; ) { const ElementAnimation *a = &ea->mAnimations[j]; if (a->mName == newAnim->mName) { oldAnim = a; break; } } if (!oldAnim) { continue; } newAnim->mStartTime = oldAnim->mStartTime; newAnim->mLastNotification = oldAnim->mLastNotification; if (oldAnim->IsPaused()) { if (newAnim->IsPaused()) { // Copy pause start just like start time. newAnim->mPauseStart = oldAnim->mPauseStart; } else { // Handle change in pause state by adjusting start // time to unpause. newAnim->mStartTime += refreshTime - oldAnim->mPauseStart; } } } } } else { ea = GetElementAnimations(aElement, aStyleContext->GetPseudoType(), PR_TRUE); } ea->mAnimations.SwapElements(newAnimations); ea->mNeedsRefreshes = true; ea->EnsureStyleRuleFor(refreshTime, mPendingEvents); // 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. } return GetAnimationRule(aElement, aStyleContext->GetPseudoType()); } class PercentageHashKey : public PLDHashEntryHdr { public: typedef const float& KeyType; typedef const float* KeyTypePointer; PercentageHashKey(KeyTypePointer aKey) : mValue(*aKey) { } PercentageHashKey(const PercentageHashKey& toCopy) : mValue(toCopy.mValue) { } ~PercentageHashKey() { } KeyType GetKey() const { return mValue; } PRBool KeyEquals(KeyTypePointer aKey) const { return *aKey == mValue; } static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } static PLDHashNumber HashKey(KeyTypePointer aKey) { PR_STATIC_ASSERT(sizeof(PLDHashNumber) == sizeof(PRUint32)); PR_STATIC_ASSERT(PLDHashNumber(-1) > PLDHashNumber(0)); float key = *aKey; NS_ABORT_IF_FALSE(0.0f <= key && key <= 1.0f, "out of range"); return PLDHashNumber(key * PR_UINT32_MAX); } enum { ALLOW_MEMMOVE = PR_TRUE }; private: const float mValue; }; struct KeyframeData { float mKey; nsCSSKeyframeRule *mRule; }; typedef InfallibleTArray KeyframeDataArray; static PLDHashOperator AppendKeyframeData(const float &aKey, nsCSSKeyframeRule *aRule, void *aData) { KeyframeDataArray *array = static_cast(aData); KeyframeData *data = array->AppendElement(); data->mKey = aKey; data->mRule = aRule; return PL_DHASH_NEXT; } struct KeyframeDataComparator { PRBool Equals(const KeyframeData& A, const KeyframeData& B) const { return A.mKey == B.mKey; } PRBool LessThan(const KeyframeData& A, const KeyframeData& B) const { return A.mKey < B.mKey; } }; class ResolvedStyleCache { public: ResolvedStyleCache() { mCache.Init(16); // FIXME: make infallible! } nsStyleContext* Get(nsPresContext *aPresContext, nsStyleContext *aParentStyleContext, nsCSSKeyframeRule *aKeyframe); private: nsRefPtrHashtable, nsStyleContext> mCache; }; nsStyleContext* ResolvedStyleCache::Get(nsPresContext *aPresContext, nsStyleContext *aParentStyleContext, nsCSSKeyframeRule *aKeyframe) { // FIXME (spec): The css3-animations spec isn't very clear about how // properties are resolved when they have values that depend on other // properties (e.g., values in 'em'). I presume that they're resolved // relative to the other styles of the element. The question is // whether they are resolved relative to other animations: I assume // that they're not, since that would prevent us from caching a lot of // data that we'd really like to cache (in particular, the // nsStyleAnimation::Value values in AnimationSegmentProperty). nsStyleContext *result = mCache.GetWeak(aKeyframe); if (!result) { nsCOMArray rules; rules.AppendObject(aKeyframe); nsRefPtr resultStrong = aPresContext->StyleSet()-> ResolveStyleByAddingRules(aParentStyleContext, rules); mCache.Put(aKeyframe, resultStrong); result = resultStrong; } return result; } void nsAnimationManager::BuildAnimations(nsStyleContext* aStyleContext, InfallibleTArray& aAnimations) { NS_ABORT_IF_FALSE(aAnimations.IsEmpty(), "expect empty array"); ResolvedStyleCache resolvedStyles; const nsStyleDisplay *disp = aStyleContext->GetStyleDisplay(); TimeStamp now = mPresContext->RefreshDriver()->MostRecentRefresh(); for (PRUint32 i = 0, i_end = disp->mAnimations.Length(); i != i_end; ++i) { const nsAnimation& aSrc = disp->mAnimations[i]; ElementAnimation& aDest = *aAnimations.AppendElement(); aDest.mName = aSrc.GetName(); aDest.mIterationCount = aSrc.GetIterationCount(); aDest.mDirection = aSrc.GetDirection(); aDest.mFillMode = aSrc.GetFillMode(); aDest.mPlayState = aSrc.GetPlayState(); aDest.mStartTime = now + TimeDuration::FromMilliseconds(aSrc.GetDelay()); if (aDest.IsPaused()) { aDest.mPauseStart = now; } else { aDest.mPauseStart = TimeStamp(); } aDest.mIterationDuration = TimeDuration::FromMilliseconds(aSrc.GetDuration()); nsCSSKeyframesRule *rule = KeyframesRuleFor(aDest.mName); if (!rule) { // no segments continue; } // Build the set of unique keyframes in the @keyframes rule. Per // css3-animations, later keyframes with the same key replace // earlier ones (no cascading). nsDataHashtable keyframes; keyframes.Init(16); // FIXME: make infallible! for (PRUint32 j = 0, j_end = rule->StyleRuleCount(); j != j_end; ++j) { nsICSSRule* cssRule = rule->GetStyleRuleAt(j); NS_ABORT_IF_FALSE(cssRule, "must have rule"); NS_ABORT_IF_FALSE(cssRule->GetType() == nsICSSRule::KEYFRAME_RULE, "must be keyframe rule"); nsCSSKeyframeRule *kfRule = static_cast(cssRule); const nsTArray &keys = kfRule->GetKeys(); for (PRUint32 k = 0, k_end = keys.Length(); k != k_end; ++k) { float key = keys[k]; // FIXME (spec): The spec doesn't say what to do with // out-of-range keyframes. We'll ignore them. // (And PercentageHashKey currently assumes we either ignore or // clamp them.) if (0.0f <= key && key <= 1.0f) { keyframes.Put(key, kfRule); } } } KeyframeDataArray sortedKeyframes; keyframes.EnumerateRead(AppendKeyframeData, &sortedKeyframes); sortedKeyframes.Sort(KeyframeDataComparator()); if (sortedKeyframes.Length() == 0) { // no segments continue; } KeyframeData fromKeyframe = sortedKeyframes[0]; nsRefPtr fromContext = resolvedStyles.Get(mPresContext, aStyleContext, fromKeyframe.mRule); // If there's no rule for 0%, there's implicitly an empty rule. if (fromKeyframe.mKey != 0.0f) { BuildSegment(aDest.mSegments, aSrc, 0.0f, aStyleContext, nsnull, fromKeyframe.mKey, fromContext, fromKeyframe.mRule->Declaration()); } for (PRUint32 j = 1, j_end = sortedKeyframes.Length(); j != j_end; ++j) { KeyframeData toKeyframe = sortedKeyframes[j]; nsRefPtr toContext = resolvedStyles.Get(mPresContext, aStyleContext, toKeyframe.mRule); BuildSegment(aDest.mSegments, aSrc, fromKeyframe.mKey, fromContext, fromKeyframe.mRule->Declaration(), toKeyframe.mKey, toContext, toKeyframe.mRule->Declaration()); fromContext = toContext; fromKeyframe = toKeyframe; } // If there's no rule for 100%, there's implicitly an empty rule. if (fromKeyframe.mKey != 1.0f) { BuildSegment(aDest.mSegments, aSrc, fromKeyframe.mKey, fromContext, fromKeyframe.mRule->Declaration(), 1.0f, aStyleContext, nsnull); } } } void nsAnimationManager::BuildSegment(InfallibleTArray& aSegments, const nsAnimation& aAnimation, float aFromKey, nsStyleContext* aFromContext, mozilla::css::Declaration* aFromDeclaration, float aToKey, nsStyleContext* aToContext, mozilla::css::Declaration* aToDeclaration) { AnimationSegment &segment = *aSegments.AppendElement(); segment.mFromKey = aFromKey; segment.mToKey = aToKey; const nsTimingFunction *tf; if (aFromDeclaration && aFromDeclaration->HasProperty(eCSSProperty_animation_timing_function)) { tf = &aFromContext->GetStyleDisplay()->mAnimations[0].GetTimingFunction(); } else { tf = &aAnimation.GetTimingFunction(); } segment.mTimingFunction.Init(*tf); for (nsCSSProperty prop = nsCSSProperty(0); prop < eCSSProperty_COUNT_no_shorthands; prop = nsCSSProperty(prop + 1)) { if (nsCSSProps::kAnimTypeTable[prop] == eStyleAnimType_None) { continue; } if (!(aFromDeclaration && aFromDeclaration->HasProperty(prop)) && !(aToDeclaration && aToDeclaration->HasProperty(prop))) { // Don't store an animation if neither declaration has the property. continue; } nsStyleAnimation::Value fromValue, toValue, dummyValue; if (ExtractComputedValueForTransition(prop, aFromContext, fromValue) && ExtractComputedValueForTransition(prop, aToContext, toValue) && // Check that we can interpolate between these values // (If this is ever a performance problem, we could add a // CanInterpolate method, but it seems fine for now.) nsStyleAnimation::Interpolate(prop, fromValue, toValue, 0.5, dummyValue)) { AnimationSegmentProperty &p = *segment.mProperties.AppendElement(); p.mProperty = prop; p.mFromValue = fromValue; p.mToValue = toValue; } } } nsIStyleRule* nsAnimationManager::GetAnimationRule(mozilla::dom::Element* aElement, nsCSSPseudoElements::Type aPseudoType) { NS_ABORT_IF_FALSE( aPseudoType == nsCSSPseudoElements::ePseudo_NotPseudoElement || aPseudoType == nsCSSPseudoElements::ePseudo_before || aPseudoType == nsCSSPseudoElements::ePseudo_after, "forbidden pseudo type"); ElementAnimations *ea = GetElementAnimations(aElement, aPseudoType, PR_FALSE); if (!ea) { return nsnull; } NS_ABORT_IF_FALSE(ea->mStyleRuleRefreshTime == mPresContext->RefreshDriver()->MostRecentRefresh(), "should already have refreshed style rule"); if (mPresContext->IsProcessingRestyles() && !mPresContext->IsProcessingAnimationStyleChange()) { // During the non-animation part of processing restyles, we don't // add the animation rule. if (ea->mStyleRule) { ea->PostRestyleForAnimation(mPresContext); } return nsnull; } return ea->mStyleRule; } /* virtual */ void nsAnimationManager::WillRefresh(mozilla::TimeStamp aTime) { NS_ABORT_IF_FALSE(mPresContext, "refresh driver should not notify additional observers " "after pres context has been destroyed"); if (!mPresContext->GetPresShell()) { // Someone might be keeping mPresContext alive past the point // where it has been torn down; don't bother doing anything in // this case. But do get rid of all our transitions so we stop // triggering refreshes. RemoveAllElementData(); return; } // FIXME: check that there's at least one style rule that's not // in its "done" state, and if there isn't, remove ourselves from // the refresh driver (but leave the animations!). for (PRCList *l = PR_LIST_HEAD(&mElementData); l != &mElementData; l = PR_NEXT_LINK(l)) { ElementAnimations *ea = static_cast(l); nsRefPtr oldStyleRule = ea->mStyleRule; ea->EnsureStyleRuleFor(mPresContext->RefreshDriver()->MostRecentRefresh(), mPendingEvents); if (oldStyleRule != ea->mStyleRule) { ea->PostRestyleForAnimation(mPresContext); } } DispatchEvents(); // may destroy us } void nsAnimationManager::DispatchEvents() { EventArray events; mPendingEvents.SwapElements(events); for (PRUint32 i = 0, i_end = events.Length(); i < i_end; ++i) { AnimationEventInfo &info = events[i]; nsEventDispatcher::Dispatch(info.mElement, mPresContext, &info.mEvent); if (!mPresContext) { break; } } } nsCSSKeyframesRule* nsAnimationManager::KeyframesRuleFor(const nsSubstring& aName) { if (mKeyframesListIsDirty) { mKeyframesListIsDirty = false; nsTArray rules; mPresContext->StyleSet()->AppendKeyframesRules(mPresContext, rules); // Per css3-animations, the last @keyframes rule specified wins. mKeyframesRules.Clear(); for (PRUint32 i = 0, i_end = rules.Length(); i != i_end; ++i) { nsCSSKeyframesRule *rule = rules[i]; mKeyframesRules.Put(rule->GetName(), rule); } } return mKeyframesRules.Get(aName); }