Bug 867089 - Validate the playbackRate before using it. r=ehsan
[gecko.git] / layout / style / nsAnimationManager.cpp
blobd9f669f516cb5ccdde7c77eea6e54ba995ce72a9
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"
15 #include <math.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,
23 aAnimationManager),
24 mNeedsRefreshes(true)
28 static void
29 ElementAnimationsPropertyDtor(void *aObject,
30 nsIAtom *aPropertyName,
31 void *aPropertyValue,
32 void *aData)
34 ElementAnimations *ea = static_cast<ElementAnimations*>(aPropertyValue);
35 #ifdef DEBUG
36 NS_ABORT_IF_FALSE(!ea->mCalledPropertyDtor, "can't call dtor twice");
37 ea->mCalledPropertyDtor = true;
38 #endif
39 delete ea;
42 double
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) {
56 if (aAnimation) {
57 // Dispatch 'animationend' when needed.
58 if (aIsForElement &&
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,
63 aElapsedDuration);
64 aEventsToDispatch->AppendElement(ei);
67 if (!aAnimation->FillsForwards()) {
68 // No animation data.
69 return -1;
71 } else {
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
76 // move on.
78 currentIterationCount = aIterationCount;
79 } else {
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()) {
86 // No animation data.
87 return -1;
89 currentIterationCount = 0.0;
90 } else {
91 dispatchStartOrIteration = aAnimation && !aAnimation->IsPaused();
95 // Set |positionInIteration| to the position from 0% to 100% along
96 // the keyframes.
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;
111 break;
112 case NS_STYLE_ANIMATION_DIRECTION_REVERSE:
113 thisIterationReverse = true;
114 break;
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;
121 break;
122 case NS_STYLE_ANIMATION_DIRECTION_ALTERNATE_REVERSE:
123 // see as previous case
124 thisIterationReverse = (uint64_t(whichIteration) & 1) == 0;
125 break;
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.
140 uint32_t message =
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,
146 aElapsedDuration);
147 aEventsToDispatch->AppendElement(ei);
150 return positionInIteration;
153 void
154 ElementAnimations::EnsureStyleRuleFor(TimeStamp aRefreshTime,
155 EventArray& aEventsToDispatch,
156 bool aIsThrottled)
158 if (!mNeedsRefreshes) {
159 mStyleRuleRefreshTime = aRefreshTime;
160 return;
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.
169 if (aIsThrottled) {
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) {
175 continue;
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
195 // changing.
196 if (anim.mLastNotification == ElementAnimation::LAST_NOTIFICATION_END &&
197 anim.mLastNotification != oldLastNotification) {
198 aIsThrottled = false;
199 break;
204 if (aIsThrottled) {
205 mStyleRuleRefreshTime = aRefreshTime;
206 return;
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.
228 continue;
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)
242 continue;
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
256 == 1.0,
257 "incorrect last to key");
259 if (properties.HasProperty(prop.mProperty)) {
260 // A later animation already set this property.
261 continue;
263 properties.AddProperty(prop.mProperty);
265 NS_ABORT_IF_FALSE(prop.mSegments.Length() > 0,
266 "property should not be in animations if it "
267 "has no segments");
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,
274 "incorrect keys");
275 ++segment;
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,
281 "incorrect keys");
283 if (segment == segmentEnd) {
284 continue;
286 NS_ABORT_IF_FALSE(segment->mFromKey < segment->mToKey,
287 "incorrect keys");
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");
293 if (!mStyleRule) {
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);
306 #ifdef DEBUG
307 bool result =
308 #endif
309 nsStyleAnimation::Interpolate(prop.mProperty,
310 segment->mFromValue, segment->mToValue,
311 valuePosition, *val);
312 NS_ABORT_IF_FALSE(result, "interpolate must succeed now");
318 bool
319 ElementAnimation::IsRunningAt(TimeStamp aTime) const
321 if (IsPaused()) {
322 return false;
325 double iterationsElapsed = ElapsedDurationAt(aTime) / mIterationDuration;
326 return 0.0 <= iterationsElapsed && iterationsElapsed < mIterationCount;
330 bool
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) {
336 return true;
339 return false;
343 bool
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)) {
349 return true;
352 return false;
355 bool
356 ElementAnimations::CanPerformOnCompositorThread(CanAnimateFlags aFlags) const
358 nsIFrame* frame = mElement->GetPrimaryFrame();
359 if (!frame) {
360 return false;
363 if (mElementProperty != nsGkAtoms::animationsProperty) {
364 if (nsLayoutUtils::IsAnimationLoggingEnabled()) {
365 nsCString message;
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);
371 return false;
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);
383 break;
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) {
393 // No animation data
394 continue;
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,
401 prop.mProperty,
402 aFlags)) {
403 return false;
405 if (prop.mProperty == eCSSProperty_opacity) {
406 hasOpacity = true;
407 } else if (prop.mProperty == eCSSProperty_transform) {
408 hasTransform = true;
412 // This animation can be done on the compositor. Mark the frame as active, in
413 // case we are able to throttle this animation.
414 if (hasOpacity) {
415 frame->MarkLayersActive(nsChangeHint_UpdateOpacityLayer);
417 if (hasTransform) {
418 frame->MarkLayersActive(nsChangeHint_UpdateTransformLayer);
420 return true;
423 ElementAnimations*
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.
430 return nullptr;
433 nsIAtom *propName;
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;
440 } else {
441 NS_ASSERTION(!aCreateIfNeeded,
442 "should never try to create transitions for pseudo "
443 "other than :before or :after");
444 return nullptr;
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);
453 if (NS_FAILED(rv)) {
454 NS_WARNING("SetProperty failed");
455 delete ea;
456 return nullptr;
458 if (propName == nsGkAtoms::animationsProperty) {
459 aElement->SetMayHaveAnimations();
462 AddElementData(ea);
465 return ea;
469 void
470 nsAnimationManager::EnsureStyleRuleFor(ElementAnimations* aET)
472 aET->EnsureStyleRuleFor(mPresContext->RefreshDriver()->MostRecentRefresh(),
473 mPendingEvents,
474 false);
477 /* virtual */ void
478 nsAnimationManager::RulesMatching(ElementRuleProcessorData* aData)
480 NS_ABORT_IF_FALSE(aData->mPresContext == mPresContext,
481 "pres context mismatch");
482 nsIStyleRule *rule =
483 GetAnimationRule(aData->mElement,
484 nsCSSPseudoElements::ePseudo_NotPseudoElement);
485 if (rule) {
486 aData->mRuleWalker->Forward(rule);
490 /* virtual */ void
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) {
497 return;
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);
504 if (rule) {
505 aData->mRuleWalker->Forward(rule);
509 /* virtual */ void
510 nsAnimationManager::RulesMatching(AnonBoxRuleProcessorData* aData)
514 #ifdef MOZ_XUL
515 /* virtual */ void
516 nsAnimationManager::RulesMatching(XULTreeRuleProcessorData* aData)
519 #endif
521 /* virtual */ size_t
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
527 // worthwhile:
528 // - mKeyframesRules
529 // - mPendingEvents
532 /* virtual */ size_t
533 nsAnimationManager::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf) const
535 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
538 nsIStyleRule*
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);
551 if (!ea &&
552 disp->mAnimationNameCount == 1 &&
553 disp->mAnimations[0].GetName().IsEmpty()) {
554 return nullptr;
557 // build the animations list
558 InfallibleTArray<ElementAnimation> newAnimations;
559 BuildAnimations(aStyleContext, newAnimations);
561 if (newAnimations.IsEmpty()) {
562 if (ea) {
563 ea->Destroy();
565 return nullptr;
568 TimeStamp refreshTime = mPresContext->RefreshDriver()->MostRecentRefresh();
570 if (ea) {
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
580 // them. See
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
597 // doing something.
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) {
602 oldAnim = a;
603 break;
606 if (!oldAnim) {
607 continue;
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;
617 } else {
618 // Handle change in pause state by adjusting start
619 // time to unpause.
620 newAnim->mStartTime += refreshTime - oldAnim->mPauseStart;
625 } else {
626 ea = GetElementAnimations(aElement, aStyleContext->GetPseudoType(),
627 true);
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
647 public:
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");
664 float key = *aKey;
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 };
670 private:
671 const float mValue;
674 struct KeyframeData {
675 float mKey;
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 {
690 public:
691 ResolvedStyleCache() {
692 mCache.Init(16); // FIXME: make infallible!
694 nsStyleContext* Get(nsPresContext *aPresContext,
695 nsStyleContext *aParentStyleContext,
696 nsCSSKeyframeRule *aKeyframe);
698 private:
699 nsRefPtrHashtable<nsPtrHashKey<nsCSSKeyframeRule>, nsStyleContext> mCache;
702 nsStyleContext*
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);
716 if (!result) {
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;
724 return result;
727 void
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;
752 } else {
753 aDest.mPauseStart = TimeStamp();
756 aDest.mIterationDuration = TimeDuration::FromMilliseconds(aSrc.GetDuration());
758 nsCSSKeyframesRule *rule = KeyframesRuleFor(aDest.mName);
759 if (!rule) {
760 // no segments
761 continue;
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
788 // clamp them.)
789 if (0.0f <= key && key <= 1.0f) {
790 KeyframeData *data = sortedKeyframes.AppendElement();
791 data->mKey = key;
792 data->mIndex = ruleIdx;
793 data->mRule = kfRule;
798 sortedKeyframes.Sort(KeyframeDataComparator());
800 if (sortedKeyframes.Length() == 0) {
801 // no segments
802 continue;
805 // Record the properties that are present in any keyframe rules we
806 // are using.
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) {
823 continue;
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)) {
836 continue;
838 if (kf.mKey == lastKey) {
839 // Replace previous occurrence of same key.
840 keyframesWithProperty[keyframesWithProperty.Length() - 1] = kfIdx;
841 } else {
842 keyframesWithProperty.AppendElement(kfIdx);
844 lastKey = kf.mKey;
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);
861 if (fromKeyframe) {
862 interpolated = interpolated &&
863 BuildSegment(propData.mSegments, prop, aSrc,
864 fromKeyframe->mKey, fromContext,
865 fromKeyframe->mRule->Declaration(),
866 toKeyframe.mKey, toContext);
867 } else {
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
897 // thing for now.)
898 if (!interpolated) {
899 aDest.mProperties.RemoveElementAt(aDest.mProperties.Length() - 1);
905 bool
906 nsAnimationManager::BuildSegment(InfallibleTArray<AnimationPropertySegment>&
907 aSegments,
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,
921 0.5, dummyValue)) {
922 return false;
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();
935 } else {
936 tf = &aAnimation.GetTimingFunction();
938 segment.mTimingFunction.Init(*tf);
940 return true;
943 nsIStyleRule*
944 nsAnimationManager::GetAnimationRule(mozilla::dom::Element* aElement,
945 nsCSSPseudoElements::Type aPseudoType)
947 NS_ABORT_IF_FALSE(
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);
955 if (!ea) {
956 return nullptr;
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);
968 return nullptr;
971 NS_WARN_IF_FALSE(ea->mStyleRuleRefreshTime ==
972 mPresContext->RefreshDriver()->MostRecentRefresh(),
973 "should already have refreshed style rule");
975 return ea->mStyleRule;
978 /* virtual */ void
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();
990 return;
993 FlushAnimations(Can_Throttle);
996 void
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);
1016 } else {
1017 didThrottle = true;
1021 if (didThrottle) {
1022 mPresContext->Document()->SetNeedStyleFlush();
1025 DispatchEvents(); // may destroy us
1028 void
1029 nsAnimationManager::DoDispatchEvents()
1031 EventArray events;
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) {
1038 break;
1043 nsCSSKeyframesRule*
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);