1 /* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsAnimationManager.h"
7 #include "nsPresContext.h"
8 #include "nsRuleProcessorData.h"
9 #include "nsStyleSet.h"
10 #include "nsCSSRules.h"
11 #include "nsStyleAnimation.h"
12 #include "nsSMILKeySpline.h"
13 #include "nsEventDispatcher.h"
14 #include "nsCSSFrameConstructor.h"
17 using namespace mozilla
;
18 using namespace mozilla::css
;
20 ElementAnimations::ElementAnimations(mozilla::dom::Element
*aElement
, nsIAtom
*aElementProperty
,
21 nsAnimationManager
*aAnimationManager
)
22 : CommonElementAnimationData(aElement
, aElementProperty
,
29 ElementAnimationsPropertyDtor(void *aObject
,
30 nsIAtom
*aPropertyName
,
34 ElementAnimations
*ea
= static_cast<ElementAnimations
*>(aPropertyValue
);
36 NS_ABORT_IF_FALSE(!ea
->mCalledPropertyDtor
, "can't call dtor twice");
37 ea
->mCalledPropertyDtor
= true;
43 ElementAnimations::GetPositionInIteration(TimeDuration aElapsedDuration
,
44 TimeDuration aIterationDuration
,
45 double aIterationCount
,
46 uint32_t aDirection
, bool aIsForElement
,
47 ElementAnimation
* aAnimation
,
48 ElementAnimations
* aEa
,
49 EventArray
* aEventsToDispatch
)
51 // Set |currentIterationCount| to the (fractional) number of
52 // iterations we've completed up to the current position.
53 double currentIterationCount
= aElapsedDuration
/ aIterationDuration
;
54 bool dispatchStartOrIteration
= false;
55 if (currentIterationCount
>= aIterationCount
) {
57 // Dispatch 'animationend' when needed.
59 aAnimation
->mLastNotification
!=
60 ElementAnimation::LAST_NOTIFICATION_END
) {
61 aAnimation
->mLastNotification
= ElementAnimation::LAST_NOTIFICATION_END
;
62 AnimationEventInfo
ei(aEa
->mElement
, aAnimation
->mName
, NS_ANIMATION_END
,
64 aEventsToDispatch
->AppendElement(ei
);
67 if (!aAnimation
->FillsForwards()) {
72 // If aAnimation is null, that means we're on the compositor
73 // thread. We want to just keep filling forwards until the main
74 // thread gets around to updating the compositor thread (which
75 // might take a little while). So just assume we fill fowards and
78 currentIterationCount
= aIterationCount
;
80 if (aAnimation
&& !aAnimation
->IsPaused()) {
81 aEa
->mNeedsRefreshes
= true;
83 if (currentIterationCount
< 0.0) {
84 NS_ASSERTION(aAnimation
, "Should not run animation that hasn't started yet on the compositor");
85 if (!aAnimation
->FillsBackwards()) {
89 currentIterationCount
= 0.0;
91 dispatchStartOrIteration
= aAnimation
&& !aAnimation
->IsPaused();
95 // Set |positionInIteration| to the position from 0% to 100% along
97 NS_ABORT_IF_FALSE(currentIterationCount
>= 0.0, "must be positive");
98 double whichIteration
= floor(currentIterationCount
);
99 if (whichIteration
== aIterationCount
&& whichIteration
!= 0.0) {
100 // When the animation's iteration count is an integer (as it
101 // normally is), we need to end at 100% of its last iteration
102 // rather than 0% of the next one (unless it's zero).
103 whichIteration
-= 1.0;
105 double positionInIteration
= currentIterationCount
- whichIteration
;
107 bool thisIterationReverse
= false;
108 switch (aDirection
) {
109 case NS_STYLE_ANIMATION_DIRECTION_NORMAL
:
110 thisIterationReverse
= false;
112 case NS_STYLE_ANIMATION_DIRECTION_REVERSE
:
113 thisIterationReverse
= true;
115 case NS_STYLE_ANIMATION_DIRECTION_ALTERNATE
:
116 // uint64_t has more integer precision than double does, so if
117 // whichIteration is that large, we've already lost and we're just
118 // guessing. But the animation is presumably oscillating so fast
119 // it doesn't matter anyway.
120 thisIterationReverse
= (uint64_t(whichIteration
) & 1) == 1;
122 case NS_STYLE_ANIMATION_DIRECTION_ALTERNATE_REVERSE
:
123 // see as previous case
124 thisIterationReverse
= (uint64_t(whichIteration
) & 1) == 0;
127 if (thisIterationReverse
) {
128 positionInIteration
= 1.0 - positionInIteration
;
131 // Dispatch 'animationstart' or 'animationiteration' when needed.
132 if (aAnimation
&& aIsForElement
&& dispatchStartOrIteration
&&
133 whichIteration
!= aAnimation
->mLastNotification
) {
134 // Notify 'animationstart' even if a negative delay puts us
135 // past the first iteration.
136 // Note that when somebody changes the animation-duration
137 // dynamically, this will fire an extra iteration event
138 // immediately in many cases. It's not clear to me if that's the
139 // right thing to do.
141 aAnimation
->mLastNotification
== ElementAnimation::LAST_NOTIFICATION_NONE
142 ? NS_ANIMATION_START
: NS_ANIMATION_ITERATION
;
144 aAnimation
->mLastNotification
= whichIteration
;
145 AnimationEventInfo
ei(aEa
->mElement
, aAnimation
->mName
, message
,
147 aEventsToDispatch
->AppendElement(ei
);
150 return positionInIteration
;
154 ElementAnimations::EnsureStyleRuleFor(TimeStamp aRefreshTime
,
155 EventArray
& aEventsToDispatch
,
158 if (!mNeedsRefreshes
) {
159 mStyleRuleRefreshTime
= aRefreshTime
;
163 // If we're performing animations on the compositor thread, then we can skip
164 // most of the work in this method. But even if we are throttled, then we
165 // have to do the work if an animation is ending in order to get correct end
166 // of animation behaviour (the styles of the animation disappear, or the fill
167 // mode behaviour). This loop checks for any finishing animations and forces
168 // the style recalculation if we find any.
170 for (uint32_t animIdx
= mAnimations
.Length(); animIdx
-- != 0; ) {
171 ElementAnimation
&anim
= mAnimations
[animIdx
];
173 if (anim
.mProperties
.Length() == 0 ||
174 anim
.mIterationDuration
.ToMilliseconds() <= 0.0) {
178 uint32_t oldLastNotification
= anim
.mLastNotification
;
180 // We need to call GetPositionInIteration here to populate
181 // aEventsToDispatch.
182 // The ElapsedDurationAt() call here handles pausing. But:
183 // FIXME: avoid recalculating every time when paused.
184 GetPositionInIteration(anim
.ElapsedDurationAt(aRefreshTime
),
185 anim
.mIterationDuration
, anim
.mIterationCount
,
186 anim
.mDirection
, IsForElement(),
187 &anim
, this, &aEventsToDispatch
);
189 // GetPositionInIteration just adjusted mLastNotification; check
190 // its new value against the value before we called
191 // GetPositionInIteration.
192 // XXX We shouldn't really be using mLastNotification as a general
193 // indicator that the animation has finished, it should be reserved for
194 // events. If we use it differently in the future this use might need
196 if (anim
.mLastNotification
== ElementAnimation::LAST_NOTIFICATION_END
&&
197 anim
.mLastNotification
!= oldLastNotification
) {
198 aIsThrottled
= false;
205 mStyleRuleRefreshTime
= aRefreshTime
;
209 // mStyleRule may be null and valid, if we have no style to apply.
210 if (mStyleRuleRefreshTime
.IsNull() ||
211 mStyleRuleRefreshTime
!= aRefreshTime
) {
212 mStyleRuleRefreshTime
= aRefreshTime
;
213 mStyleRule
= nullptr;
214 // We'll set mNeedsRefreshes to true below in all cases where we need them.
215 mNeedsRefreshes
= false;
217 // FIXME(spec): assume that properties in higher animations override
218 // those in lower ones.
219 // Therefore, we iterate from last animation to first.
220 nsCSSPropertySet properties
;
222 for (uint32_t animIdx
= mAnimations
.Length(); animIdx
-- != 0; ) {
223 ElementAnimation
&anim
= mAnimations
[animIdx
];
225 if (anim
.mProperties
.Length() == 0 ||
226 anim
.mIterationDuration
.ToMilliseconds() <= 0.0) {
227 // No animation data.
231 // The ElapsedDurationAt() call here handles pausing. But:
232 // FIXME: avoid recalculating every time when paused.
233 double positionInIteration
=
234 GetPositionInIteration(anim
.ElapsedDurationAt(aRefreshTime
),
235 anim
.mIterationDuration
, anim
.mIterationCount
,
236 anim
.mDirection
, IsForElement(),
237 &anim
, this, &aEventsToDispatch
);
239 // The position is -1 when we don't have fill data for the current time,
240 // so we shouldn't animate.
241 if (positionInIteration
== -1)
244 NS_ABORT_IF_FALSE(0.0 <= positionInIteration
&&
245 positionInIteration
<= 1.0,
246 "position should be in [0-1]");
248 for (uint32_t propIdx
= 0, propEnd
= anim
.mProperties
.Length();
249 propIdx
!= propEnd
; ++propIdx
)
251 const AnimationProperty
&prop
= anim
.mProperties
[propIdx
];
253 NS_ABORT_IF_FALSE(prop
.mSegments
[0].mFromKey
== 0.0,
254 "incorrect first from key");
255 NS_ABORT_IF_FALSE(prop
.mSegments
[prop
.mSegments
.Length() - 1].mToKey
257 "incorrect last to key");
259 if (properties
.HasProperty(prop
.mProperty
)) {
260 // A later animation already set this property.
263 properties
.AddProperty(prop
.mProperty
);
265 NS_ABORT_IF_FALSE(prop
.mSegments
.Length() > 0,
266 "property should not be in animations if it "
269 // FIXME: Maybe cache the current segment?
270 const AnimationPropertySegment
*segment
= prop
.mSegments
.Elements(),
271 *segmentEnd
= segment
+ prop
.mSegments
.Length();
272 while (segment
->mToKey
< positionInIteration
) {
273 NS_ABORT_IF_FALSE(segment
->mFromKey
< segment
->mToKey
,
276 if (segment
== segmentEnd
) {
277 NS_ABORT_IF_FALSE(false, "incorrect positionInIteration");
278 break; // in order to continue in outer loop (just below)
280 NS_ABORT_IF_FALSE(segment
->mFromKey
== (segment
-1)->mToKey
,
283 if (segment
== segmentEnd
) {
286 NS_ABORT_IF_FALSE(segment
->mFromKey
< segment
->mToKey
,
288 NS_ABORT_IF_FALSE(segment
>= prop
.mSegments
.Elements() &&
289 size_t(segment
- prop
.mSegments
.Elements()) <
290 prop
.mSegments
.Length(),
291 "out of array bounds");
294 // Allocate the style rule now that we know we have animation data.
295 mStyleRule
= new css::AnimValuesStyleRule();
298 double positionInSegment
= (positionInIteration
- segment
->mFromKey
) /
299 (segment
->mToKey
- segment
->mFromKey
);
300 double valuePosition
=
301 segment
->mTimingFunction
.GetValue(positionInSegment
);
303 nsStyleAnimation::Value
*val
=
304 mStyleRule
->AddEmptyValue(prop
.mProperty
);
309 nsStyleAnimation::Interpolate(prop
.mProperty
,
310 segment
->mFromValue
, segment
->mToValue
,
311 valuePosition
, *val
);
312 NS_ABORT_IF_FALSE(result
, "interpolate must succeed now");
319 ElementAnimation::IsRunningAt(TimeStamp aTime
) const
325 double iterationsElapsed
= ElapsedDurationAt(aTime
) / mIterationDuration
;
326 return 0.0 <= iterationsElapsed
&& iterationsElapsed
< mIterationCount
;
331 ElementAnimation::HasAnimationOfProperty(nsCSSProperty aProperty
) const
333 for (uint32_t propIdx
= 0, propEnd
= mProperties
.Length();
334 propIdx
!= propEnd
; ++propIdx
) {
335 if (aProperty
== mProperties
[propIdx
].mProperty
) {
344 ElementAnimations::HasAnimationOfProperty(nsCSSProperty aProperty
) const
346 for (uint32_t animIdx
= mAnimations
.Length(); animIdx
-- != 0; ) {
347 const ElementAnimation
&anim
= mAnimations
[animIdx
];
348 if (anim
.HasAnimationOfProperty(aProperty
)) {
356 ElementAnimations::CanPerformOnCompositorThread(CanAnimateFlags aFlags
) const
358 nsIFrame
* frame
= mElement
->GetPrimaryFrame();
363 if (mElementProperty
!= nsGkAtoms::animationsProperty
) {
364 if (nsLayoutUtils::IsAnimationLoggingEnabled()) {
366 message
.AppendLiteral("Gecko bug: Async animation of pseudoelements not supported. See bug 771367 (");
367 message
.Append(nsAtomCString(mElementProperty
));
368 message
.AppendLiteral(")");
369 LogAsyncAnimationFailure(message
, mElement
);
374 TimeStamp now
= frame
->PresContext()->RefreshDriver()->MostRecentRefresh();
376 for (uint32_t animIdx
= mAnimations
.Length(); animIdx
-- != 0; ) {
377 const ElementAnimation
& anim
= mAnimations
[animIdx
];
378 for (uint32_t propIdx
= 0, propEnd
= anim
.mProperties
.Length();
379 propIdx
!= propEnd
; ++propIdx
) {
380 if (IsGeometricProperty(anim
.mProperties
[propIdx
].mProperty
) &&
381 anim
.IsRunningAt(now
)) {
382 aFlags
= CanAnimateFlags(aFlags
| CanAnimate_HasGeometricProperty
);
388 bool hasOpacity
= false;
389 bool hasTransform
= false;
390 for (uint32_t animIdx
= mAnimations
.Length(); animIdx
-- != 0; ) {
391 const ElementAnimation
& anim
= mAnimations
[animIdx
];
392 if (anim
.mIterationDuration
.ToMilliseconds() <= 0.0) {
397 for (uint32_t propIdx
= 0, propEnd
= anim
.mProperties
.Length();
398 propIdx
!= propEnd
; ++propIdx
) {
399 const AnimationProperty
& prop
= anim
.mProperties
[propIdx
];
400 if (!CanAnimatePropertyOnCompositor(mElement
,
405 if (prop
.mProperty
== eCSSProperty_opacity
) {
407 } else if (prop
.mProperty
== eCSSProperty_transform
) {
412 // This animation can be done on the compositor. Mark the frame as active, in
413 // case we are able to throttle this animation.
415 frame
->MarkLayersActive(nsChangeHint_UpdateOpacityLayer
);
418 frame
->MarkLayersActive(nsChangeHint_UpdateTransformLayer
);
424 nsAnimationManager::GetElementAnimations(dom::Element
*aElement
,
425 nsCSSPseudoElements::Type aPseudoType
,
426 bool aCreateIfNeeded
)
428 if (!aCreateIfNeeded
&& PR_CLIST_IS_EMPTY(&mElementData
)) {
429 // Early return for the most common case.
434 if (aPseudoType
== nsCSSPseudoElements::ePseudo_NotPseudoElement
) {
435 propName
= nsGkAtoms::animationsProperty
;
436 } else if (aPseudoType
== nsCSSPseudoElements::ePseudo_before
) {
437 propName
= nsGkAtoms::animationsOfBeforeProperty
;
438 } else if (aPseudoType
== nsCSSPseudoElements::ePseudo_after
) {
439 propName
= nsGkAtoms::animationsOfAfterProperty
;
441 NS_ASSERTION(!aCreateIfNeeded
,
442 "should never try to create transitions for pseudo "
443 "other than :before or :after");
446 ElementAnimations
*ea
= static_cast<ElementAnimations
*>(
447 aElement
->GetProperty(propName
));
448 if (!ea
&& aCreateIfNeeded
) {
449 // FIXME: Consider arena-allocating?
450 ea
= new ElementAnimations(aElement
, propName
, this);
451 nsresult rv
= aElement
->SetProperty(propName
, ea
,
452 ElementAnimationsPropertyDtor
, false);
454 NS_WARNING("SetProperty failed");
458 if (propName
== nsGkAtoms::animationsProperty
) {
459 aElement
->SetMayHaveAnimations();
470 nsAnimationManager::EnsureStyleRuleFor(ElementAnimations
* aET
)
472 aET
->EnsureStyleRuleFor(mPresContext
->RefreshDriver()->MostRecentRefresh(),
478 nsAnimationManager::RulesMatching(ElementRuleProcessorData
* aData
)
480 NS_ABORT_IF_FALSE(aData
->mPresContext
== mPresContext
,
481 "pres context mismatch");
483 GetAnimationRule(aData
->mElement
,
484 nsCSSPseudoElements::ePseudo_NotPseudoElement
);
486 aData
->mRuleWalker
->Forward(rule
);
491 nsAnimationManager::RulesMatching(PseudoElementRuleProcessorData
* aData
)
493 NS_ABORT_IF_FALSE(aData
->mPresContext
== mPresContext
,
494 "pres context mismatch");
495 if (aData
->mPseudoType
!= nsCSSPseudoElements::ePseudo_before
&&
496 aData
->mPseudoType
!= nsCSSPseudoElements::ePseudo_after
) {
500 // FIXME: Do we really want to be the only thing keeping a
501 // pseudo-element alive? I *think* the non-animation restyle should
502 // handle that, but should add a test.
503 nsIStyleRule
*rule
= GetAnimationRule(aData
->mElement
, aData
->mPseudoType
);
505 aData
->mRuleWalker
->Forward(rule
);
510 nsAnimationManager::RulesMatching(AnonBoxRuleProcessorData
* aData
)
516 nsAnimationManager::RulesMatching(XULTreeRuleProcessorData
* aData
)
522 nsAnimationManager::SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf
) const
524 return CommonAnimationManager::SizeOfExcludingThis(aMallocSizeOf
);
526 // Measurement of the following members may be added later if DMD finds it is
533 nsAnimationManager::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf
) const
535 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf
);
539 nsAnimationManager::CheckAnimationRule(nsStyleContext
* aStyleContext
,
540 mozilla::dom::Element
* aElement
)
542 if (!mPresContext
->IsProcessingAnimationStyleChange()) {
543 // Everything that causes our animation data to change triggers a
544 // style change, which in turn triggers a non-animation restyle.
545 // Likewise, when we initially construct frames, we're not in a
546 // style change, but also not in an animation restyle.
548 const nsStyleDisplay
*disp
= aStyleContext
->StyleDisplay();
549 ElementAnimations
*ea
=
550 GetElementAnimations(aElement
, aStyleContext
->GetPseudoType(), false);
552 disp
->mAnimationNameCount
== 1 &&
553 disp
->mAnimations
[0].GetName().IsEmpty()) {
557 // build the animations list
558 InfallibleTArray
<ElementAnimation
> newAnimations
;
559 BuildAnimations(aStyleContext
, newAnimations
);
561 if (newAnimations
.IsEmpty()) {
568 TimeStamp refreshTime
= mPresContext
->RefreshDriver()->MostRecentRefresh();
571 ea
->mStyleRule
= nullptr;
572 ea
->mStyleRuleRefreshTime
= TimeStamp();
573 ea
->UpdateAnimationGeneration(mPresContext
);
575 // Copy over the start times and (if still paused) pause starts
576 // for each animation (matching on name only) that was also in the
577 // old list of animations.
578 // This means that we honor dynamic changes, which isn't what the
579 // spec says to do, but WebKit seems to honor at least some of
581 // http://lists.w3.org/Archives/Public/www-style/2011Apr/0079.html
582 // In order to honor what the spec said, we'd copy more data over
583 // (or potentially optimize BuildAnimations to avoid rebuilding it
584 // in the first place).
585 if (!ea
->mAnimations
.IsEmpty()) {
586 for (uint32_t newIdx
= 0, newEnd
= newAnimations
.Length();
587 newIdx
!= newEnd
; ++newIdx
) {
588 ElementAnimation
*newAnim
= &newAnimations
[newIdx
];
590 // Find the matching animation with this name in the old list
591 // of animations. Because of this code, they must all have
592 // the same start time, though they might differ in pause
593 // state. So if a page uses multiple copies of the same
594 // animation in one element's animation list, and gives them
595 // different pause states, they, well, get what they deserve.
596 // We'll use the last one since it's more likely to be the one
598 const ElementAnimation
*oldAnim
= nullptr;
599 for (uint32_t oldIdx
= ea
->mAnimations
.Length(); oldIdx
-- != 0; ) {
600 const ElementAnimation
*a
= &ea
->mAnimations
[oldIdx
];
601 if (a
->mName
== newAnim
->mName
) {
610 newAnim
->mStartTime
= oldAnim
->mStartTime
;
611 newAnim
->mLastNotification
= oldAnim
->mLastNotification
;
613 if (oldAnim
->IsPaused()) {
614 if (newAnim
->IsPaused()) {
615 // Copy pause start just like start time.
616 newAnim
->mPauseStart
= oldAnim
->mPauseStart
;
618 // Handle change in pause state by adjusting start
620 newAnim
->mStartTime
+= refreshTime
- oldAnim
->mPauseStart
;
626 ea
= GetElementAnimations(aElement
, aStyleContext
->GetPseudoType(),
629 ea
->mAnimations
.SwapElements(newAnimations
);
630 ea
->mNeedsRefreshes
= true;
632 ea
->EnsureStyleRuleFor(refreshTime
, mPendingEvents
, false);
633 // We don't actually dispatch the mPendingEvents now. We'll either
634 // dispatch them the next time we get a refresh driver notification
635 // or the next time somebody calls
636 // nsPresShell::FlushPendingNotifications.
637 if (!mPendingEvents
.IsEmpty()) {
638 mPresContext
->Document()->SetNeedStyleFlush();
642 return GetAnimationRule(aElement
, aStyleContext
->GetPseudoType());
645 class PercentageHashKey
: public PLDHashEntryHdr
648 typedef const float& KeyType
;
649 typedef const float* KeyTypePointer
;
651 PercentageHashKey(KeyTypePointer aKey
) : mValue(*aKey
) { }
652 PercentageHashKey(const PercentageHashKey
& toCopy
) : mValue(toCopy
.mValue
) { }
653 ~PercentageHashKey() { }
655 KeyType
GetKey() const { return mValue
; }
656 bool KeyEquals(KeyTypePointer aKey
) const { return *aKey
== mValue
; }
658 static KeyTypePointer
KeyToPointer(KeyType aKey
) { return &aKey
; }
659 static PLDHashNumber
HashKey(KeyTypePointer aKey
) {
660 MOZ_STATIC_ASSERT(sizeof(PLDHashNumber
) == sizeof(uint32_t),
661 "this hash function assumes PLDHashNumber is uint32_t");
662 MOZ_STATIC_ASSERT(PLDHashNumber(-1) > PLDHashNumber(0),
663 "this hash function assumes PLDHashNumber is uint32_t");
665 NS_ABORT_IF_FALSE(0.0f
<= key
&& key
<= 1.0f
, "out of range");
666 return PLDHashNumber(key
* UINT32_MAX
);
668 enum { ALLOW_MEMMOVE
= true };
674 struct KeyframeData
{
676 uint32_t mIndex
; // store original order since sort algorithm is not stable
677 nsCSSKeyframeRule
*mRule
;
680 struct KeyframeDataComparator
{
681 bool Equals(const KeyframeData
& A
, const KeyframeData
& B
) const {
682 return A
.mKey
== B
.mKey
&& A
.mIndex
== B
.mIndex
;
684 bool LessThan(const KeyframeData
& A
, const KeyframeData
& B
) const {
685 return A
.mKey
< B
.mKey
|| (A
.mKey
== B
.mKey
&& A
.mIndex
< B
.mIndex
);
689 class ResolvedStyleCache
{
691 ResolvedStyleCache() {
692 mCache
.Init(16); // FIXME: make infallible!
694 nsStyleContext
* Get(nsPresContext
*aPresContext
,
695 nsStyleContext
*aParentStyleContext
,
696 nsCSSKeyframeRule
*aKeyframe
);
699 nsRefPtrHashtable
<nsPtrHashKey
<nsCSSKeyframeRule
>, nsStyleContext
> mCache
;
703 ResolvedStyleCache::Get(nsPresContext
*aPresContext
,
704 nsStyleContext
*aParentStyleContext
,
705 nsCSSKeyframeRule
*aKeyframe
)
707 // FIXME (spec): The css3-animations spec isn't very clear about how
708 // properties are resolved when they have values that depend on other
709 // properties (e.g., values in 'em'). I presume that they're resolved
710 // relative to the other styles of the element. The question is
711 // whether they are resolved relative to other animations: I assume
712 // that they're not, since that would prevent us from caching a lot of
713 // data that we'd really like to cache (in particular, the
714 // nsStyleAnimation::Value values in AnimationPropertySegment).
715 nsStyleContext
*result
= mCache
.GetWeak(aKeyframe
);
717 nsCOMArray
<nsIStyleRule
> rules
;
718 rules
.AppendObject(aKeyframe
);
719 nsRefPtr
<nsStyleContext
> resultStrong
= aPresContext
->StyleSet()->
720 ResolveStyleByAddingRules(aParentStyleContext
, rules
);
721 mCache
.Put(aKeyframe
, resultStrong
);
722 result
= resultStrong
;
728 nsAnimationManager::BuildAnimations(nsStyleContext
* aStyleContext
,
729 InfallibleTArray
<ElementAnimation
>& aAnimations
)
731 NS_ABORT_IF_FALSE(aAnimations
.IsEmpty(), "expect empty array");
733 ResolvedStyleCache resolvedStyles
;
735 const nsStyleDisplay
*disp
= aStyleContext
->StyleDisplay();
736 TimeStamp now
= mPresContext
->RefreshDriver()->MostRecentRefresh();
737 for (uint32_t animIdx
= 0, animEnd
= disp
->mAnimationNameCount
;
738 animIdx
!= animEnd
; ++animIdx
) {
739 const nsAnimation
& aSrc
= disp
->mAnimations
[animIdx
];
740 ElementAnimation
& aDest
= *aAnimations
.AppendElement();
742 aDest
.mName
= aSrc
.GetName();
743 aDest
.mIterationCount
= aSrc
.GetIterationCount();
744 aDest
.mDirection
= aSrc
.GetDirection();
745 aDest
.mFillMode
= aSrc
.GetFillMode();
746 aDest
.mPlayState
= aSrc
.GetPlayState();
748 aDest
.mDelay
= TimeDuration::FromMilliseconds(aSrc
.GetDelay());
749 aDest
.mStartTime
= now
;
750 if (aDest
.IsPaused()) {
751 aDest
.mPauseStart
= now
;
753 aDest
.mPauseStart
= TimeStamp();
756 aDest
.mIterationDuration
= TimeDuration::FromMilliseconds(aSrc
.GetDuration());
758 nsCSSKeyframesRule
*rule
= KeyframesRuleFor(aDest
.mName
);
764 // While current drafts of css3-animations say that later keyframes
765 // with the same key entirely replace earlier ones (no cascading),
766 // this is a bad idea and contradictory to the rest of CSS. So
767 // we're going to keep all the keyframes for each key and then do
768 // the replacement on a per-property basis rather than a per-rule
769 // basis, just like everything else in CSS.
771 AutoInfallibleTArray
<KeyframeData
, 16> sortedKeyframes
;
773 for (uint32_t ruleIdx
= 0, ruleEnd
= rule
->StyleRuleCount();
774 ruleIdx
!= ruleEnd
; ++ruleIdx
) {
775 css::Rule
* cssRule
= rule
->GetStyleRuleAt(ruleIdx
);
776 NS_ABORT_IF_FALSE(cssRule
, "must have rule");
777 NS_ABORT_IF_FALSE(cssRule
->GetType() == css::Rule::KEYFRAME_RULE
,
778 "must be keyframe rule");
779 nsCSSKeyframeRule
*kfRule
= static_cast<nsCSSKeyframeRule
*>(cssRule
);
781 const nsTArray
<float> &keys
= kfRule
->GetKeys();
782 for (uint32_t keyIdx
= 0, keyEnd
= keys
.Length();
783 keyIdx
!= keyEnd
; ++keyIdx
) {
784 float key
= keys
[keyIdx
];
785 // FIXME (spec): The spec doesn't say what to do with
786 // out-of-range keyframes. We'll ignore them.
787 // (And PercentageHashKey currently assumes we either ignore or
789 if (0.0f
<= key
&& key
<= 1.0f
) {
790 KeyframeData
*data
= sortedKeyframes
.AppendElement();
792 data
->mIndex
= ruleIdx
;
793 data
->mRule
= kfRule
;
798 sortedKeyframes
.Sort(KeyframeDataComparator());
800 if (sortedKeyframes
.Length() == 0) {
805 // Record the properties that are present in any keyframe rules we
807 nsCSSPropertySet properties
;
809 for (uint32_t kfIdx
= 0, kfEnd
= sortedKeyframes
.Length();
810 kfIdx
!= kfEnd
; ++kfIdx
) {
811 css::Declaration
*decl
= sortedKeyframes
[kfIdx
].mRule
->Declaration();
812 for (uint32_t propIdx
= 0, propEnd
= decl
->Count();
813 propIdx
!= propEnd
; ++propIdx
) {
814 properties
.AddProperty(decl
->OrderValueAt(propIdx
));
818 for (nsCSSProperty prop
= nsCSSProperty(0);
819 prop
< eCSSProperty_COUNT_no_shorthands
;
820 prop
= nsCSSProperty(prop
+ 1)) {
821 if (!properties
.HasProperty(prop
) ||
822 nsCSSProps::kAnimTypeTable
[prop
] == eStyleAnimType_None
) {
826 // Build a list of the keyframes to use for this property. This
827 // means we need every keyframe with the property in it, except
828 // for those keyframes where a later keyframe with the *same key*
829 // also has the property.
830 AutoInfallibleTArray
<uint32_t, 16> keyframesWithProperty
;
831 float lastKey
= 100.0f
; // an invalid key
832 for (uint32_t kfIdx
= 0, kfEnd
= sortedKeyframes
.Length();
833 kfIdx
!= kfEnd
; ++kfIdx
) {
834 KeyframeData
&kf
= sortedKeyframes
[kfIdx
];
835 if (!kf
.mRule
->Declaration()->HasProperty(prop
)) {
838 if (kf
.mKey
== lastKey
) {
839 // Replace previous occurrence of same key.
840 keyframesWithProperty
[keyframesWithProperty
.Length() - 1] = kfIdx
;
842 keyframesWithProperty
.AppendElement(kfIdx
);
847 AnimationProperty
&propData
= *aDest
.mProperties
.AppendElement();
848 propData
.mProperty
= prop
;
850 KeyframeData
*fromKeyframe
= nullptr;
851 nsRefPtr
<nsStyleContext
> fromContext
;
852 bool interpolated
= true;
853 for (uint32_t wpIdx
= 0, wpEnd
= keyframesWithProperty
.Length();
854 wpIdx
!= wpEnd
; ++wpIdx
) {
855 uint32_t kfIdx
= keyframesWithProperty
[wpIdx
];
856 KeyframeData
&toKeyframe
= sortedKeyframes
[kfIdx
];
858 nsRefPtr
<nsStyleContext
> toContext
=
859 resolvedStyles
.Get(mPresContext
, aStyleContext
, toKeyframe
.mRule
);
862 interpolated
= interpolated
&&
863 BuildSegment(propData
.mSegments
, prop
, aSrc
,
864 fromKeyframe
->mKey
, fromContext
,
865 fromKeyframe
->mRule
->Declaration(),
866 toKeyframe
.mKey
, toContext
);
868 if (toKeyframe
.mKey
!= 0.0f
) {
869 // There's no data for this property at 0%, so use the
870 // cascaded value above us.
871 interpolated
= interpolated
&&
872 BuildSegment(propData
.mSegments
, prop
, aSrc
,
873 0.0f
, aStyleContext
, nullptr,
874 toKeyframe
.mKey
, toContext
);
878 fromContext
= toContext
;
879 fromKeyframe
= &toKeyframe
;
882 if (fromKeyframe
->mKey
!= 1.0f
) {
883 // There's no data for this property at 100%, so use the
884 // cascaded value above us.
885 interpolated
= interpolated
&&
886 BuildSegment(propData
.mSegments
, prop
, aSrc
,
887 fromKeyframe
->mKey
, fromContext
,
888 fromKeyframe
->mRule
->Declaration(),
889 1.0f
, aStyleContext
);
892 // If we failed to build any segments due to inability to
893 // interpolate, remove the property from the animation. (It's not
894 // clear if this is the right thing to do -- we could run some of
895 // the segments, but it's really not clear whether we should skip
896 // values (which?) or skip segments, so best to skip the whole
899 aDest
.mProperties
.RemoveElementAt(aDest
.mProperties
.Length() - 1);
906 nsAnimationManager::BuildSegment(InfallibleTArray
<AnimationPropertySegment
>&
908 nsCSSProperty aProperty
,
909 const nsAnimation
& aAnimation
,
910 float aFromKey
, nsStyleContext
* aFromContext
,
911 mozilla::css::Declaration
* aFromDeclaration
,
912 float aToKey
, nsStyleContext
* aToContext
)
914 nsStyleAnimation::Value fromValue
, toValue
, dummyValue
;
915 if (!ExtractComputedValueForTransition(aProperty
, aFromContext
, fromValue
) ||
916 !ExtractComputedValueForTransition(aProperty
, aToContext
, toValue
) ||
917 // Check that we can interpolate between these values
918 // (If this is ever a performance problem, we could add a
919 // CanInterpolate method, but it seems fine for now.)
920 !nsStyleAnimation::Interpolate(aProperty
, fromValue
, toValue
,
925 AnimationPropertySegment
&segment
= *aSegments
.AppendElement();
927 segment
.mFromValue
= fromValue
;
928 segment
.mToValue
= toValue
;
929 segment
.mFromKey
= aFromKey
;
930 segment
.mToKey
= aToKey
;
931 const nsTimingFunction
*tf
;
932 if (aFromDeclaration
&&
933 aFromDeclaration
->HasProperty(eCSSProperty_animation_timing_function
)) {
934 tf
= &aFromContext
->StyleDisplay()->mAnimations
[0].GetTimingFunction();
936 tf
= &aAnimation
.GetTimingFunction();
938 segment
.mTimingFunction
.Init(*tf
);
944 nsAnimationManager::GetAnimationRule(mozilla::dom::Element
* aElement
,
945 nsCSSPseudoElements::Type aPseudoType
)
948 aPseudoType
== nsCSSPseudoElements::ePseudo_NotPseudoElement
||
949 aPseudoType
== nsCSSPseudoElements::ePseudo_before
||
950 aPseudoType
== nsCSSPseudoElements::ePseudo_after
,
951 "forbidden pseudo type");
953 ElementAnimations
*ea
=
954 GetElementAnimations(aElement
, aPseudoType
, false);
959 if (mPresContext
->IsProcessingRestyles() &&
960 !mPresContext
->IsProcessingAnimationStyleChange()) {
961 // During the non-animation part of processing restyles, we don't
962 // add the animation rule.
964 if (ea
->mStyleRule
) {
965 ea
->PostRestyleForAnimation(mPresContext
);
971 NS_WARN_IF_FALSE(ea
->mStyleRuleRefreshTime
==
972 mPresContext
->RefreshDriver()->MostRecentRefresh(),
973 "should already have refreshed style rule");
975 return ea
->mStyleRule
;
979 nsAnimationManager::WillRefresh(mozilla::TimeStamp aTime
)
981 NS_ABORT_IF_FALSE(mPresContext
,
982 "refresh driver should not notify additional observers "
983 "after pres context has been destroyed");
984 if (!mPresContext
->GetPresShell()) {
985 // Someone might be keeping mPresContext alive past the point
986 // where it has been torn down; don't bother doing anything in
987 // this case. But do get rid of all our transitions so we stop
988 // triggering refreshes.
989 RemoveAllElementData();
993 FlushAnimations(Can_Throttle
);
997 nsAnimationManager::FlushAnimations(FlushFlags aFlags
)
999 // FIXME: check that there's at least one style rule that's not
1000 // in its "done" state, and if there isn't, remove ourselves from
1001 // the refresh driver (but leave the animations!).
1002 TimeStamp now
= mPresContext
->RefreshDriver()->MostRecentRefresh();
1003 bool didThrottle
= false;
1004 for (PRCList
*l
= PR_LIST_HEAD(&mElementData
); l
!= &mElementData
;
1005 l
= PR_NEXT_LINK(l
)) {
1006 ElementAnimations
*ea
= static_cast<ElementAnimations
*>(l
);
1007 bool canThrottleTick
= aFlags
== Can_Throttle
&&
1008 ea
->CanPerformOnCompositorThread(
1009 CommonElementAnimationData::CanAnimateFlags(0)) &&
1010 ea
->CanThrottleAnimation(now
);
1012 nsRefPtr
<css::AnimValuesStyleRule
> oldStyleRule
= ea
->mStyleRule
;
1013 ea
->EnsureStyleRuleFor(now
, mPendingEvents
, canThrottleTick
);
1014 if (oldStyleRule
!= ea
->mStyleRule
) {
1015 ea
->PostRestyleForAnimation(mPresContext
);
1022 mPresContext
->Document()->SetNeedStyleFlush();
1025 DispatchEvents(); // may destroy us
1029 nsAnimationManager::DoDispatchEvents()
1032 mPendingEvents
.SwapElements(events
);
1033 for (uint32_t i
= 0, i_end
= events
.Length(); i
< i_end
; ++i
) {
1034 AnimationEventInfo
&info
= events
[i
];
1035 nsEventDispatcher::Dispatch(info
.mElement
, mPresContext
, &info
.mEvent
);
1037 if (!mPresContext
) {
1044 nsAnimationManager::KeyframesRuleFor(const nsSubstring
& aName
)
1046 if (mKeyframesListIsDirty
) {
1047 mKeyframesListIsDirty
= false;
1049 nsTArray
<nsCSSKeyframesRule
*> rules
;
1050 mPresContext
->StyleSet()->AppendKeyframesRules(mPresContext
, rules
);
1052 // Per css3-animations, the last @keyframes rule specified wins.
1053 mKeyframesRules
.Clear();
1054 for (uint32_t i
= 0, i_end
= rules
.Length(); i
!= i_end
; ++i
) {
1055 nsCSSKeyframesRule
*rule
= rules
[i
];
1056 mKeyframesRules
.Put(rule
->GetName(), rule
);
1060 return mKeyframesRules
.Get(aName
);