Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
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 "nsTransitionManager.h"
9 #include "mozilla/EventDispatcher.h"
10 #include "mozilla/MemoryReporting.h"
12 #include "nsPresContext.h"
13 #include "nsRuleProcessorData.h"
14 #include "nsStyleSet.h"
15 #include "nsStyleChangeList.h"
16 #include "nsCSSRules.h"
17 #include "RestyleManager.h"
18 #include "nsStyleAnimation.h"
19 #include "nsLayoutUtils.h"
20 #include "nsIFrame.h"
21 #include "nsIDocument.h"
22 #include "ActiveLayerTracker.h"
23 #include <math.h>
25 using namespace mozilla;
26 using namespace mozilla::css;
28 ElementAnimations::ElementAnimations(mozilla::dom::Element *aElement,
29 nsIAtom *aElementProperty,
30 nsAnimationManager *aAnimationManager,
31 TimeStamp aNow)
32 : CommonElementAnimationData(aElement, aElementProperty,
33 aAnimationManager, aNow),
34 mNeedsRefreshes(true)
35 {
36 }
38 static void
39 ElementAnimationsPropertyDtor(void *aObject,
40 nsIAtom *aPropertyName,
41 void *aPropertyValue,
42 void *aData)
43 {
44 ElementAnimations *ea = static_cast<ElementAnimations*>(aPropertyValue);
45 #ifdef DEBUG
46 NS_ABORT_IF_FALSE(!ea->mCalledPropertyDtor, "can't call dtor twice");
47 ea->mCalledPropertyDtor = true;
48 #endif
49 delete ea;
50 }
52 double
53 ElementAnimations::GetPositionInIteration(TimeDuration aElapsedDuration,
54 TimeDuration aIterationDuration,
55 double aIterationCount,
56 uint32_t aDirection,
57 StyleAnimation* aAnimation,
58 ElementAnimations* aEa,
59 EventArray* aEventsToDispatch)
60 {
61 MOZ_ASSERT(!aAnimation == !aEa && !aAnimation == !aEventsToDispatch);
63 // Set |currentIterationCount| to the (fractional) number of
64 // iterations we've completed up to the current position.
65 double currentIterationCount = aElapsedDuration / aIterationDuration;
66 bool dispatchStartOrIteration = false;
67 if (currentIterationCount >= aIterationCount) {
68 if (aAnimation) {
69 // Dispatch 'animationend' when needed.
70 if (aAnimation->mLastNotification !=
71 StyleAnimation::LAST_NOTIFICATION_END) {
72 aAnimation->mLastNotification = StyleAnimation::LAST_NOTIFICATION_END;
73 AnimationEventInfo ei(aEa->mElement, aAnimation->mName, NS_ANIMATION_END,
74 aElapsedDuration, aEa->PseudoElement());
75 aEventsToDispatch->AppendElement(ei);
76 }
78 if (!aAnimation->FillsForwards()) {
79 // No animation data.
80 return -1;
81 }
82 } else {
83 // If aAnimation is null, that means we're on the compositor
84 // thread. We want to just keep filling forwards until the main
85 // thread gets around to updating the compositor thread (which
86 // might take a little while). So just assume we fill fowards and
87 // move on.
88 }
89 currentIterationCount = aIterationCount;
90 } else {
91 if (aAnimation && !aAnimation->IsPaused()) {
92 aEa->mNeedsRefreshes = true;
93 }
94 if (currentIterationCount < 0.0) {
95 NS_ASSERTION(aAnimation, "Should not run animation that hasn't started yet on the compositor");
96 if (!aAnimation->FillsBackwards()) {
97 // No animation data.
98 return -1;
99 }
100 currentIterationCount = 0.0;
101 } else {
102 dispatchStartOrIteration = aAnimation && !aAnimation->IsPaused();
103 }
104 }
106 // Set |positionInIteration| to the position from 0% to 100% along
107 // the keyframes.
108 NS_ABORT_IF_FALSE(currentIterationCount >= 0.0, "must be positive");
109 double whichIteration = floor(currentIterationCount);
110 if (whichIteration == aIterationCount && whichIteration != 0.0) {
111 // When the animation's iteration count is an integer (as it
112 // normally is), we need to end at 100% of its last iteration
113 // rather than 0% of the next one (unless it's zero).
114 whichIteration -= 1.0;
115 }
116 double positionInIteration = currentIterationCount - whichIteration;
118 bool thisIterationReverse = false;
119 switch (aDirection) {
120 case NS_STYLE_ANIMATION_DIRECTION_NORMAL:
121 thisIterationReverse = false;
122 break;
123 case NS_STYLE_ANIMATION_DIRECTION_REVERSE:
124 thisIterationReverse = true;
125 break;
126 case NS_STYLE_ANIMATION_DIRECTION_ALTERNATE:
127 // uint64_t has more integer precision than double does, so if
128 // whichIteration is that large, we've already lost and we're just
129 // guessing. But the animation is presumably oscillating so fast
130 // it doesn't matter anyway.
131 thisIterationReverse = (uint64_t(whichIteration) & 1) == 1;
132 break;
133 case NS_STYLE_ANIMATION_DIRECTION_ALTERNATE_REVERSE:
134 // see as previous case
135 thisIterationReverse = (uint64_t(whichIteration) & 1) == 0;
136 break;
137 }
138 if (thisIterationReverse) {
139 positionInIteration = 1.0 - positionInIteration;
140 }
142 // Dispatch 'animationstart' or 'animationiteration' when needed.
143 if (aAnimation && dispatchStartOrIteration &&
144 whichIteration != aAnimation->mLastNotification) {
145 // Notify 'animationstart' even if a negative delay puts us
146 // past the first iteration.
147 // Note that when somebody changes the animation-duration
148 // dynamically, this will fire an extra iteration event
149 // immediately in many cases. It's not clear to me if that's the
150 // right thing to do.
151 uint32_t message =
152 aAnimation->mLastNotification == StyleAnimation::LAST_NOTIFICATION_NONE
153 ? NS_ANIMATION_START : NS_ANIMATION_ITERATION;
155 aAnimation->mLastNotification = whichIteration;
156 AnimationEventInfo ei(aEa->mElement, aAnimation->mName, message,
157 aElapsedDuration, aEa->PseudoElement());
158 aEventsToDispatch->AppendElement(ei);
159 }
161 return positionInIteration;
162 }
164 void
165 ElementAnimations::EnsureStyleRuleFor(TimeStamp aRefreshTime,
166 EventArray& aEventsToDispatch,
167 bool aIsThrottled)
168 {
169 if (!mNeedsRefreshes) {
170 mStyleRuleRefreshTime = aRefreshTime;
171 return;
172 }
174 // If we're performing animations on the compositor thread, then we can skip
175 // most of the work in this method. But even if we are throttled, then we
176 // have to do the work if an animation is ending in order to get correct end
177 // of animation behaviour (the styles of the animation disappear, or the fill
178 // mode behaviour). This loop checks for any finishing animations and forces
179 // the style recalculation if we find any.
180 if (aIsThrottled) {
181 for (uint32_t animIdx = mAnimations.Length(); animIdx-- != 0; ) {
182 StyleAnimation &anim = mAnimations[animIdx];
184 if (anim.mProperties.Length() == 0 ||
185 anim.mIterationDuration.ToMilliseconds() <= 0.0) {
186 continue;
187 }
189 uint32_t oldLastNotification = anim.mLastNotification;
191 // We need to call GetPositionInIteration here to populate
192 // aEventsToDispatch.
193 // The ElapsedDurationAt() call here handles pausing. But:
194 // FIXME: avoid recalculating every time when paused.
195 GetPositionInIteration(anim.ElapsedDurationAt(aRefreshTime),
196 anim.mIterationDuration, anim.mIterationCount,
197 anim.mDirection, &anim, this, &aEventsToDispatch);
199 // GetPositionInIteration just adjusted mLastNotification; check
200 // its new value against the value before we called
201 // GetPositionInIteration.
202 // XXX We shouldn't really be using mLastNotification as a general
203 // indicator that the animation has finished, it should be reserved for
204 // events. If we use it differently in the future this use might need
205 // changing.
206 if (!anim.mIsRunningOnCompositor ||
207 (anim.mLastNotification != oldLastNotification &&
208 anim.mLastNotification == StyleAnimation::LAST_NOTIFICATION_END)) {
209 aIsThrottled = false;
210 break;
211 }
212 }
213 }
215 if (aIsThrottled) {
216 return;
217 }
219 // mStyleRule may be null and valid, if we have no style to apply.
220 if (mStyleRuleRefreshTime.IsNull() ||
221 mStyleRuleRefreshTime != aRefreshTime) {
222 mStyleRuleRefreshTime = aRefreshTime;
223 mStyleRule = nullptr;
224 // We'll set mNeedsRefreshes to true below in all cases where we need them.
225 mNeedsRefreshes = false;
227 // FIXME(spec): assume that properties in higher animations override
228 // those in lower ones.
229 // Therefore, we iterate from last animation to first.
230 nsCSSPropertySet properties;
232 for (uint32_t animIdx = mAnimations.Length(); animIdx-- != 0; ) {
233 StyleAnimation &anim = mAnimations[animIdx];
235 if (anim.mProperties.Length() == 0 ||
236 anim.mIterationDuration.ToMilliseconds() <= 0.0) {
237 // No animation data.
238 continue;
239 }
241 // The ElapsedDurationAt() call here handles pausing. But:
242 // FIXME: avoid recalculating every time when paused.
243 double positionInIteration =
244 GetPositionInIteration(anim.ElapsedDurationAt(aRefreshTime),
245 anim.mIterationDuration, anim.mIterationCount,
246 anim.mDirection, &anim, this,
247 &aEventsToDispatch);
249 // The position is -1 when we don't have fill data for the current time,
250 // so we shouldn't animate.
251 if (positionInIteration == -1)
252 continue;
254 NS_ABORT_IF_FALSE(0.0 <= positionInIteration &&
255 positionInIteration <= 1.0,
256 "position should be in [0-1]");
258 for (uint32_t propIdx = 0, propEnd = anim.mProperties.Length();
259 propIdx != propEnd; ++propIdx)
260 {
261 const AnimationProperty &prop = anim.mProperties[propIdx];
263 NS_ABORT_IF_FALSE(prop.mSegments[0].mFromKey == 0.0,
264 "incorrect first from key");
265 NS_ABORT_IF_FALSE(prop.mSegments[prop.mSegments.Length() - 1].mToKey
266 == 1.0,
267 "incorrect last to key");
269 if (properties.HasProperty(prop.mProperty)) {
270 // A later animation already set this property.
271 continue;
272 }
273 properties.AddProperty(prop.mProperty);
275 NS_ABORT_IF_FALSE(prop.mSegments.Length() > 0,
276 "property should not be in animations if it "
277 "has no segments");
279 // FIXME: Maybe cache the current segment?
280 const AnimationPropertySegment *segment = prop.mSegments.Elements(),
281 *segmentEnd = segment + prop.mSegments.Length();
282 while (segment->mToKey < positionInIteration) {
283 NS_ABORT_IF_FALSE(segment->mFromKey < segment->mToKey,
284 "incorrect keys");
285 ++segment;
286 if (segment == segmentEnd) {
287 NS_ABORT_IF_FALSE(false, "incorrect positionInIteration");
288 break; // in order to continue in outer loop (just below)
289 }
290 NS_ABORT_IF_FALSE(segment->mFromKey == (segment-1)->mToKey,
291 "incorrect keys");
292 }
293 if (segment == segmentEnd) {
294 continue;
295 }
296 NS_ABORT_IF_FALSE(segment->mFromKey < segment->mToKey,
297 "incorrect keys");
298 NS_ABORT_IF_FALSE(segment >= prop.mSegments.Elements() &&
299 size_t(segment - prop.mSegments.Elements()) <
300 prop.mSegments.Length(),
301 "out of array bounds");
303 if (!mStyleRule) {
304 // Allocate the style rule now that we know we have animation data.
305 mStyleRule = new css::AnimValuesStyleRule();
306 }
308 double positionInSegment = (positionInIteration - segment->mFromKey) /
309 (segment->mToKey - segment->mFromKey);
310 double valuePosition =
311 segment->mTimingFunction.GetValue(positionInSegment);
313 nsStyleAnimation::Value *val =
314 mStyleRule->AddEmptyValue(prop.mProperty);
316 #ifdef DEBUG
317 bool result =
318 #endif
319 nsStyleAnimation::Interpolate(prop.mProperty,
320 segment->mFromValue, segment->mToValue,
321 valuePosition, *val);
322 NS_ABORT_IF_FALSE(result, "interpolate must succeed now");
323 }
324 }
325 }
326 }
329 bool
330 ElementAnimations::HasAnimationOfProperty(nsCSSProperty aProperty) const
331 {
332 for (uint32_t animIdx = mAnimations.Length(); animIdx-- != 0; ) {
333 const StyleAnimation &anim = mAnimations[animIdx];
334 if (anim.HasAnimationOfProperty(aProperty)) {
335 return true;
336 }
337 }
338 return false;
339 }
341 bool
342 ElementAnimations::CanPerformOnCompositorThread(CanAnimateFlags aFlags) const
343 {
344 nsIFrame* frame = nsLayoutUtils::GetStyleFrame(mElement);
345 if (!frame) {
346 return false;
347 }
349 if (mElementProperty != nsGkAtoms::animationsProperty) {
350 if (nsLayoutUtils::IsAnimationLoggingEnabled()) {
351 nsCString message;
352 message.AppendLiteral("Gecko bug: Async animation of pseudoelements not supported. See bug 771367 (");
353 message.Append(nsAtomCString(mElementProperty));
354 message.AppendLiteral(")");
355 LogAsyncAnimationFailure(message, mElement);
356 }
357 return false;
358 }
360 TimeStamp now = frame->PresContext()->RefreshDriver()->MostRecentRefresh();
362 for (uint32_t animIdx = mAnimations.Length(); animIdx-- != 0; ) {
363 const StyleAnimation& anim = mAnimations[animIdx];
364 for (uint32_t propIdx = 0, propEnd = anim.mProperties.Length();
365 propIdx != propEnd; ++propIdx) {
366 if (IsGeometricProperty(anim.mProperties[propIdx].mProperty) &&
367 anim.IsRunningAt(now)) {
368 aFlags = CanAnimateFlags(aFlags | CanAnimate_HasGeometricProperty);
369 break;
370 }
371 }
372 }
374 bool hasOpacity = false;
375 bool hasTransform = false;
376 for (uint32_t animIdx = mAnimations.Length(); animIdx-- != 0; ) {
377 const StyleAnimation& anim = mAnimations[animIdx];
378 if (!anim.IsRunningAt(now)) {
379 continue;
380 }
382 for (uint32_t propIdx = 0, propEnd = anim.mProperties.Length();
383 propIdx != propEnd; ++propIdx) {
384 const AnimationProperty& prop = anim.mProperties[propIdx];
385 if (!CanAnimatePropertyOnCompositor(mElement,
386 prop.mProperty,
387 aFlags) ||
388 IsCompositorAnimationDisabledForFrame(frame)) {
389 return false;
390 }
391 if (prop.mProperty == eCSSProperty_opacity) {
392 hasOpacity = true;
393 } else if (prop.mProperty == eCSSProperty_transform) {
394 hasTransform = true;
395 }
396 }
397 }
398 // This animation can be done on the compositor. Mark the frame as active, in
399 // case we are able to throttle this animation.
400 if (hasOpacity) {
401 ActiveLayerTracker::NotifyAnimated(frame, eCSSProperty_opacity);
402 }
403 if (hasTransform) {
404 ActiveLayerTracker::NotifyAnimated(frame, eCSSProperty_transform);
405 }
406 return true;
407 }
409 ElementAnimations*
410 nsAnimationManager::GetElementAnimations(dom::Element *aElement,
411 nsCSSPseudoElements::Type aPseudoType,
412 bool aCreateIfNeeded)
413 {
414 if (!aCreateIfNeeded && PR_CLIST_IS_EMPTY(&mElementData)) {
415 // Early return for the most common case.
416 return nullptr;
417 }
419 nsIAtom *propName;
420 if (aPseudoType == nsCSSPseudoElements::ePseudo_NotPseudoElement) {
421 propName = nsGkAtoms::animationsProperty;
422 } else if (aPseudoType == nsCSSPseudoElements::ePseudo_before) {
423 propName = nsGkAtoms::animationsOfBeforeProperty;
424 } else if (aPseudoType == nsCSSPseudoElements::ePseudo_after) {
425 propName = nsGkAtoms::animationsOfAfterProperty;
426 } else {
427 NS_ASSERTION(!aCreateIfNeeded,
428 "should never try to create transitions for pseudo "
429 "other than :before or :after");
430 return nullptr;
431 }
432 ElementAnimations *ea = static_cast<ElementAnimations*>(
433 aElement->GetProperty(propName));
434 if (!ea && aCreateIfNeeded) {
435 // FIXME: Consider arena-allocating?
436 ea = new ElementAnimations(aElement, propName, this,
437 mPresContext->RefreshDriver()->MostRecentRefresh());
438 nsresult rv = aElement->SetProperty(propName, ea,
439 ElementAnimationsPropertyDtor, false);
440 if (NS_FAILED(rv)) {
441 NS_WARNING("SetProperty failed");
442 delete ea;
443 return nullptr;
444 }
445 if (propName == nsGkAtoms::animationsProperty) {
446 aElement->SetMayHaveAnimations();
447 }
449 AddElementData(ea);
450 }
452 return ea;
453 }
456 void
457 nsAnimationManager::EnsureStyleRuleFor(ElementAnimations* aET)
458 {
459 aET->EnsureStyleRuleFor(mPresContext->RefreshDriver()->MostRecentRefresh(),
460 mPendingEvents,
461 false);
462 CheckNeedsRefresh();
463 }
465 /* virtual */ void
466 nsAnimationManager::RulesMatching(ElementRuleProcessorData* aData)
467 {
468 NS_ABORT_IF_FALSE(aData->mPresContext == mPresContext,
469 "pres context mismatch");
470 nsIStyleRule *rule =
471 GetAnimationRule(aData->mElement,
472 nsCSSPseudoElements::ePseudo_NotPseudoElement);
473 if (rule) {
474 aData->mRuleWalker->Forward(rule);
475 }
476 }
478 /* virtual */ void
479 nsAnimationManager::RulesMatching(PseudoElementRuleProcessorData* aData)
480 {
481 NS_ABORT_IF_FALSE(aData->mPresContext == mPresContext,
482 "pres context mismatch");
483 if (aData->mPseudoType != nsCSSPseudoElements::ePseudo_before &&
484 aData->mPseudoType != nsCSSPseudoElements::ePseudo_after) {
485 return;
486 }
488 // FIXME: Do we really want to be the only thing keeping a
489 // pseudo-element alive? I *think* the non-animation restyle should
490 // handle that, but should add a test.
491 nsIStyleRule *rule = GetAnimationRule(aData->mElement, aData->mPseudoType);
492 if (rule) {
493 aData->mRuleWalker->Forward(rule);
494 }
495 }
497 /* virtual */ void
498 nsAnimationManager::RulesMatching(AnonBoxRuleProcessorData* aData)
499 {
500 }
502 #ifdef MOZ_XUL
503 /* virtual */ void
504 nsAnimationManager::RulesMatching(XULTreeRuleProcessorData* aData)
505 {
506 }
507 #endif
509 /* virtual */ size_t
510 nsAnimationManager::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
511 {
512 return CommonAnimationManager::SizeOfExcludingThis(aMallocSizeOf);
514 // Measurement of the following members may be added later if DMD finds it is
515 // worthwhile:
516 // - mPendingEvents
517 }
519 /* virtual */ size_t
520 nsAnimationManager::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
521 {
522 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
523 }
525 nsIStyleRule*
526 nsAnimationManager::CheckAnimationRule(nsStyleContext* aStyleContext,
527 mozilla::dom::Element* aElement)
528 {
529 if (!mPresContext->IsProcessingAnimationStyleChange()) {
530 if (!mPresContext->IsDynamic()) {
531 // For print or print preview, ignore animations.
532 return nullptr;
533 }
535 // Everything that causes our animation data to change triggers a
536 // style change, which in turn triggers a non-animation restyle.
537 // Likewise, when we initially construct frames, we're not in a
538 // style change, but also not in an animation restyle.
540 const nsStyleDisplay *disp = aStyleContext->StyleDisplay();
541 ElementAnimations *ea =
542 GetElementAnimations(aElement, aStyleContext->GetPseudoType(), false);
543 if (!ea &&
544 disp->mAnimationNameCount == 1 &&
545 disp->mAnimations[0].GetName().IsEmpty()) {
546 return nullptr;
547 }
549 // build the animations list
550 InfallibleTArray<StyleAnimation> newAnimations;
551 BuildAnimations(aStyleContext, newAnimations);
553 if (newAnimations.IsEmpty()) {
554 if (ea) {
555 ea->Destroy();
556 }
557 return nullptr;
558 }
560 TimeStamp refreshTime = mPresContext->RefreshDriver()->MostRecentRefresh();
562 if (ea) {
563 ea->mStyleRule = nullptr;
564 ea->mStyleRuleRefreshTime = TimeStamp();
565 ea->UpdateAnimationGeneration(mPresContext);
567 // Copy over the start times and (if still paused) pause starts
568 // for each animation (matching on name only) that was also in the
569 // old list of animations.
570 // This means that we honor dynamic changes, which isn't what the
571 // spec says to do, but WebKit seems to honor at least some of
572 // them. See
573 // http://lists.w3.org/Archives/Public/www-style/2011Apr/0079.html
574 // In order to honor what the spec said, we'd copy more data over
575 // (or potentially optimize BuildAnimations to avoid rebuilding it
576 // in the first place).
577 if (!ea->mAnimations.IsEmpty()) {
578 for (uint32_t newIdx = 0, newEnd = newAnimations.Length();
579 newIdx != newEnd; ++newIdx) {
580 StyleAnimation *newAnim = &newAnimations[newIdx];
582 // Find the matching animation with this name in the old list
583 // of animations. Because of this code, they must all have
584 // the same start time, though they might differ in pause
585 // state. So if a page uses multiple copies of the same
586 // animation in one element's animation list, and gives them
587 // different pause states, they, well, get what they deserve.
588 // We'll use the last one since it's more likely to be the one
589 // doing something.
590 const StyleAnimation *oldAnim = nullptr;
591 for (uint32_t oldIdx = ea->mAnimations.Length(); oldIdx-- != 0; ) {
592 const StyleAnimation *a = &ea->mAnimations[oldIdx];
593 if (a->mName == newAnim->mName) {
594 oldAnim = a;
595 break;
596 }
597 }
598 if (!oldAnim) {
599 continue;
600 }
602 newAnim->mStartTime = oldAnim->mStartTime;
603 newAnim->mLastNotification = oldAnim->mLastNotification;
605 if (oldAnim->IsPaused()) {
606 if (newAnim->IsPaused()) {
607 // Copy pause start just like start time.
608 newAnim->mPauseStart = oldAnim->mPauseStart;
609 } else {
610 // Handle change in pause state by adjusting start
611 // time to unpause.
612 newAnim->mStartTime += refreshTime - oldAnim->mPauseStart;
613 }
614 }
615 }
616 }
617 } else {
618 ea = GetElementAnimations(aElement, aStyleContext->GetPseudoType(),
619 true);
620 }
621 ea->mAnimations.SwapElements(newAnimations);
622 ea->mNeedsRefreshes = true;
624 ea->EnsureStyleRuleFor(refreshTime, mPendingEvents, false);
625 CheckNeedsRefresh();
626 // We don't actually dispatch the mPendingEvents now. We'll either
627 // dispatch them the next time we get a refresh driver notification
628 // or the next time somebody calls
629 // nsPresShell::FlushPendingNotifications.
630 if (!mPendingEvents.IsEmpty()) {
631 mPresContext->Document()->SetNeedStyleFlush();
632 }
633 }
635 return GetAnimationRule(aElement, aStyleContext->GetPseudoType());
636 }
638 class PercentageHashKey : public PLDHashEntryHdr
639 {
640 public:
641 typedef const float& KeyType;
642 typedef const float* KeyTypePointer;
644 PercentageHashKey(KeyTypePointer aKey) : mValue(*aKey) { }
645 PercentageHashKey(const PercentageHashKey& toCopy) : mValue(toCopy.mValue) { }
646 ~PercentageHashKey() { }
648 KeyType GetKey() const { return mValue; }
649 bool KeyEquals(KeyTypePointer aKey) const { return *aKey == mValue; }
651 static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
652 static PLDHashNumber HashKey(KeyTypePointer aKey) {
653 static_assert(sizeof(PLDHashNumber) == sizeof(uint32_t),
654 "this hash function assumes PLDHashNumber is uint32_t");
655 static_assert(PLDHashNumber(-1) > PLDHashNumber(0),
656 "this hash function assumes PLDHashNumber is uint32_t");
657 float key = *aKey;
658 NS_ABORT_IF_FALSE(0.0f <= key && key <= 1.0f, "out of range");
659 return PLDHashNumber(key * UINT32_MAX);
660 }
661 enum { ALLOW_MEMMOVE = true };
663 private:
664 const float mValue;
665 };
667 struct KeyframeData {
668 float mKey;
669 uint32_t mIndex; // store original order since sort algorithm is not stable
670 nsCSSKeyframeRule *mRule;
671 };
673 struct KeyframeDataComparator {
674 bool Equals(const KeyframeData& A, const KeyframeData& B) const {
675 return A.mKey == B.mKey && A.mIndex == B.mIndex;
676 }
677 bool LessThan(const KeyframeData& A, const KeyframeData& B) const {
678 return A.mKey < B.mKey || (A.mKey == B.mKey && A.mIndex < B.mIndex);
679 }
680 };
682 class ResolvedStyleCache {
683 public:
684 ResolvedStyleCache() : mCache(16) {}
685 nsStyleContext* Get(nsPresContext *aPresContext,
686 nsStyleContext *aParentStyleContext,
687 nsCSSKeyframeRule *aKeyframe);
689 private:
690 nsRefPtrHashtable<nsPtrHashKey<nsCSSKeyframeRule>, nsStyleContext> mCache;
691 };
693 nsStyleContext*
694 ResolvedStyleCache::Get(nsPresContext *aPresContext,
695 nsStyleContext *aParentStyleContext,
696 nsCSSKeyframeRule *aKeyframe)
697 {
698 // FIXME (spec): The css3-animations spec isn't very clear about how
699 // properties are resolved when they have values that depend on other
700 // properties (e.g., values in 'em'). I presume that they're resolved
701 // relative to the other styles of the element. The question is
702 // whether they are resolved relative to other animations: I assume
703 // that they're not, since that would prevent us from caching a lot of
704 // data that we'd really like to cache (in particular, the
705 // nsStyleAnimation::Value values in AnimationPropertySegment).
706 nsStyleContext *result = mCache.GetWeak(aKeyframe);
707 if (!result) {
708 nsCOMArray<nsIStyleRule> rules;
709 rules.AppendObject(aKeyframe);
710 nsRefPtr<nsStyleContext> resultStrong = aPresContext->StyleSet()->
711 ResolveStyleByAddingRules(aParentStyleContext, rules);
712 mCache.Put(aKeyframe, resultStrong);
713 result = resultStrong;
714 }
715 return result;
716 }
718 void
719 nsAnimationManager::BuildAnimations(nsStyleContext* aStyleContext,
720 InfallibleTArray<StyleAnimation>&
721 aAnimations)
722 {
723 NS_ABORT_IF_FALSE(aAnimations.IsEmpty(), "expect empty array");
725 ResolvedStyleCache resolvedStyles;
727 const nsStyleDisplay *disp = aStyleContext->StyleDisplay();
728 TimeStamp now = mPresContext->RefreshDriver()->MostRecentRefresh();
729 for (uint32_t animIdx = 0, animEnd = disp->mAnimationNameCount;
730 animIdx != animEnd; ++animIdx) {
731 const nsAnimation& aSrc = disp->mAnimations[animIdx];
732 StyleAnimation& aDest = *aAnimations.AppendElement();
734 aDest.mName = aSrc.GetName();
735 aDest.mIterationCount = aSrc.GetIterationCount();
736 aDest.mDirection = aSrc.GetDirection();
737 aDest.mFillMode = aSrc.GetFillMode();
738 aDest.mPlayState = aSrc.GetPlayState();
740 aDest.mDelay = TimeDuration::FromMilliseconds(aSrc.GetDelay());
741 aDest.mStartTime = now;
742 if (aDest.IsPaused()) {
743 aDest.mPauseStart = now;
744 } else {
745 aDest.mPauseStart = TimeStamp();
746 }
748 aDest.mIterationDuration = TimeDuration::FromMilliseconds(aSrc.GetDuration());
750 nsCSSKeyframesRule* rule =
751 mPresContext->StyleSet()->KeyframesRuleForName(mPresContext, aDest.mName);
752 if (!rule) {
753 // no segments
754 continue;
755 }
757 // While current drafts of css3-animations say that later keyframes
758 // with the same key entirely replace earlier ones (no cascading),
759 // this is a bad idea and contradictory to the rest of CSS. So
760 // we're going to keep all the keyframes for each key and then do
761 // the replacement on a per-property basis rather than a per-rule
762 // basis, just like everything else in CSS.
764 AutoInfallibleTArray<KeyframeData, 16> sortedKeyframes;
766 for (uint32_t ruleIdx = 0, ruleEnd = rule->StyleRuleCount();
767 ruleIdx != ruleEnd; ++ruleIdx) {
768 css::Rule* cssRule = rule->GetStyleRuleAt(ruleIdx);
769 NS_ABORT_IF_FALSE(cssRule, "must have rule");
770 NS_ABORT_IF_FALSE(cssRule->GetType() == css::Rule::KEYFRAME_RULE,
771 "must be keyframe rule");
772 nsCSSKeyframeRule *kfRule = static_cast<nsCSSKeyframeRule*>(cssRule);
774 const nsTArray<float> &keys = kfRule->GetKeys();
775 for (uint32_t keyIdx = 0, keyEnd = keys.Length();
776 keyIdx != keyEnd; ++keyIdx) {
777 float key = keys[keyIdx];
778 // FIXME (spec): The spec doesn't say what to do with
779 // out-of-range keyframes. We'll ignore them.
780 // (And PercentageHashKey currently assumes we either ignore or
781 // clamp them.)
782 if (0.0f <= key && key <= 1.0f) {
783 KeyframeData *data = sortedKeyframes.AppendElement();
784 data->mKey = key;
785 data->mIndex = ruleIdx;
786 data->mRule = kfRule;
787 }
788 }
789 }
791 sortedKeyframes.Sort(KeyframeDataComparator());
793 if (sortedKeyframes.Length() == 0) {
794 // no segments
795 continue;
796 }
798 // Record the properties that are present in any keyframe rules we
799 // are using.
800 nsCSSPropertySet properties;
802 for (uint32_t kfIdx = 0, kfEnd = sortedKeyframes.Length();
803 kfIdx != kfEnd; ++kfIdx) {
804 css::Declaration *decl = sortedKeyframes[kfIdx].mRule->Declaration();
805 for (uint32_t propIdx = 0, propEnd = decl->Count();
806 propIdx != propEnd; ++propIdx) {
807 nsCSSProperty prop = decl->GetPropertyAt(propIdx);
808 if (prop != eCSSPropertyExtra_variable) {
809 // CSS Variables are not animatable
810 properties.AddProperty(prop);
811 }
812 }
813 }
815 for (nsCSSProperty prop = nsCSSProperty(0);
816 prop < eCSSProperty_COUNT_no_shorthands;
817 prop = nsCSSProperty(prop + 1)) {
818 if (!properties.HasProperty(prop) ||
819 nsCSSProps::kAnimTypeTable[prop] == eStyleAnimType_None) {
820 continue;
821 }
823 // Build a list of the keyframes to use for this property. This
824 // means we need every keyframe with the property in it, except
825 // for those keyframes where a later keyframe with the *same key*
826 // also has the property.
827 AutoInfallibleTArray<uint32_t, 16> keyframesWithProperty;
828 float lastKey = 100.0f; // an invalid key
829 for (uint32_t kfIdx = 0, kfEnd = sortedKeyframes.Length();
830 kfIdx != kfEnd; ++kfIdx) {
831 KeyframeData &kf = sortedKeyframes[kfIdx];
832 if (!kf.mRule->Declaration()->HasProperty(prop)) {
833 continue;
834 }
835 if (kf.mKey == lastKey) {
836 // Replace previous occurrence of same key.
837 keyframesWithProperty[keyframesWithProperty.Length() - 1] = kfIdx;
838 } else {
839 keyframesWithProperty.AppendElement(kfIdx);
840 }
841 lastKey = kf.mKey;
842 }
844 AnimationProperty &propData = *aDest.mProperties.AppendElement();
845 propData.mProperty = prop;
847 KeyframeData *fromKeyframe = nullptr;
848 nsRefPtr<nsStyleContext> fromContext;
849 bool interpolated = true;
850 for (uint32_t wpIdx = 0, wpEnd = keyframesWithProperty.Length();
851 wpIdx != wpEnd; ++wpIdx) {
852 uint32_t kfIdx = keyframesWithProperty[wpIdx];
853 KeyframeData &toKeyframe = sortedKeyframes[kfIdx];
855 nsRefPtr<nsStyleContext> toContext =
856 resolvedStyles.Get(mPresContext, aStyleContext, toKeyframe.mRule);
858 if (fromKeyframe) {
859 interpolated = interpolated &&
860 BuildSegment(propData.mSegments, prop, aSrc,
861 fromKeyframe->mKey, fromContext,
862 fromKeyframe->mRule->Declaration(),
863 toKeyframe.mKey, toContext);
864 } else {
865 if (toKeyframe.mKey != 0.0f) {
866 // There's no data for this property at 0%, so use the
867 // cascaded value above us.
868 interpolated = interpolated &&
869 BuildSegment(propData.mSegments, prop, aSrc,
870 0.0f, aStyleContext, nullptr,
871 toKeyframe.mKey, toContext);
872 }
873 }
875 fromContext = toContext;
876 fromKeyframe = &toKeyframe;
877 }
879 if (fromKeyframe->mKey != 1.0f) {
880 // There's no data for this property at 100%, so use the
881 // cascaded value above us.
882 interpolated = interpolated &&
883 BuildSegment(propData.mSegments, prop, aSrc,
884 fromKeyframe->mKey, fromContext,
885 fromKeyframe->mRule->Declaration(),
886 1.0f, aStyleContext);
887 }
889 // If we failed to build any segments due to inability to
890 // interpolate, remove the property from the animation. (It's not
891 // clear if this is the right thing to do -- we could run some of
892 // the segments, but it's really not clear whether we should skip
893 // values (which?) or skip segments, so best to skip the whole
894 // thing for now.)
895 if (!interpolated) {
896 aDest.mProperties.RemoveElementAt(aDest.mProperties.Length() - 1);
897 }
898 }
899 }
900 }
902 bool
903 nsAnimationManager::BuildSegment(InfallibleTArray<AnimationPropertySegment>&
904 aSegments,
905 nsCSSProperty aProperty,
906 const nsAnimation& aAnimation,
907 float aFromKey, nsStyleContext* aFromContext,
908 mozilla::css::Declaration* aFromDeclaration,
909 float aToKey, nsStyleContext* aToContext)
910 {
911 nsStyleAnimation::Value fromValue, toValue, dummyValue;
912 if (!ExtractComputedValueForTransition(aProperty, aFromContext, fromValue) ||
913 !ExtractComputedValueForTransition(aProperty, aToContext, toValue) ||
914 // Check that we can interpolate between these values
915 // (If this is ever a performance problem, we could add a
916 // CanInterpolate method, but it seems fine for now.)
917 !nsStyleAnimation::Interpolate(aProperty, fromValue, toValue,
918 0.5, dummyValue)) {
919 return false;
920 }
922 AnimationPropertySegment &segment = *aSegments.AppendElement();
924 segment.mFromValue = fromValue;
925 segment.mToValue = toValue;
926 segment.mFromKey = aFromKey;
927 segment.mToKey = aToKey;
928 const nsTimingFunction *tf;
929 if (aFromDeclaration &&
930 aFromDeclaration->HasProperty(eCSSProperty_animation_timing_function)) {
931 tf = &aFromContext->StyleDisplay()->mAnimations[0].GetTimingFunction();
932 } else {
933 tf = &aAnimation.GetTimingFunction();
934 }
935 segment.mTimingFunction.Init(*tf);
937 return true;
938 }
940 nsIStyleRule*
941 nsAnimationManager::GetAnimationRule(mozilla::dom::Element* aElement,
942 nsCSSPseudoElements::Type aPseudoType)
943 {
944 NS_ABORT_IF_FALSE(
945 aPseudoType == nsCSSPseudoElements::ePseudo_NotPseudoElement ||
946 aPseudoType == nsCSSPseudoElements::ePseudo_before ||
947 aPseudoType == nsCSSPseudoElements::ePseudo_after,
948 "forbidden pseudo type");
950 if (!mPresContext->IsDynamic()) {
951 // For print or print preview, ignore animations.
952 return nullptr;
953 }
955 ElementAnimations *ea =
956 GetElementAnimations(aElement, aPseudoType, false);
957 if (!ea) {
958 return nullptr;
959 }
961 if (mPresContext->IsProcessingRestyles() &&
962 !mPresContext->IsProcessingAnimationStyleChange()) {
963 // During the non-animation part of processing restyles, we don't
964 // add the animation rule.
966 if (ea->mStyleRule) {
967 ea->PostRestyleForAnimation(mPresContext);
968 }
970 return nullptr;
971 }
973 NS_WARN_IF_FALSE(!ea->mNeedsRefreshes ||
974 ea->mStyleRuleRefreshTime ==
975 mPresContext->RefreshDriver()->MostRecentRefresh(),
976 "should already have refreshed style rule");
978 return ea->mStyleRule;
979 }
981 /* virtual */ void
982 nsAnimationManager::WillRefresh(mozilla::TimeStamp aTime)
983 {
984 NS_ABORT_IF_FALSE(mPresContext,
985 "refresh driver should not notify additional observers "
986 "after pres context has been destroyed");
987 if (!mPresContext->GetPresShell()) {
988 // Someone might be keeping mPresContext alive past the point
989 // where it has been torn down; don't bother doing anything in
990 // this case. But do get rid of all our transitions so we stop
991 // triggering refreshes.
992 RemoveAllElementData();
993 return;
994 }
996 FlushAnimations(Can_Throttle);
997 }
999 void
1000 nsAnimationManager::AddElementData(CommonElementAnimationData* aData)
1001 {
1002 if (!mObservingRefreshDriver) {
1003 NS_ASSERTION(static_cast<ElementAnimations*>(aData)->mNeedsRefreshes,
1004 "Added data which doesn't need refreshing?");
1005 // We need to observe the refresh driver.
1006 mPresContext->RefreshDriver()->AddRefreshObserver(this, Flush_Style);
1007 mObservingRefreshDriver = true;
1008 }
1010 PR_INSERT_BEFORE(aData, &mElementData);
1011 }
1013 void
1014 nsAnimationManager::CheckNeedsRefresh()
1015 {
1016 for (PRCList *l = PR_LIST_HEAD(&mElementData); l != &mElementData;
1017 l = PR_NEXT_LINK(l)) {
1018 if (static_cast<ElementAnimations*>(l)->mNeedsRefreshes) {
1019 if (!mObservingRefreshDriver) {
1020 mPresContext->RefreshDriver()->AddRefreshObserver(this, Flush_Style);
1021 mObservingRefreshDriver = true;
1022 }
1023 return;
1024 }
1025 }
1026 if (mObservingRefreshDriver) {
1027 mObservingRefreshDriver = false;
1028 mPresContext->RefreshDriver()->RemoveRefreshObserver(this, Flush_Style);
1029 }
1030 }
1032 void
1033 nsAnimationManager::FlushAnimations(FlushFlags aFlags)
1034 {
1035 // FIXME: check that there's at least one style rule that's not
1036 // in its "done" state, and if there isn't, remove ourselves from
1037 // the refresh driver (but leave the animations!).
1038 TimeStamp now = mPresContext->RefreshDriver()->MostRecentRefresh();
1039 bool didThrottle = false;
1040 for (PRCList *l = PR_LIST_HEAD(&mElementData); l != &mElementData;
1041 l = PR_NEXT_LINK(l)) {
1042 ElementAnimations *ea = static_cast<ElementAnimations*>(l);
1043 bool canThrottleTick = aFlags == Can_Throttle &&
1044 ea->CanPerformOnCompositorThread(
1045 CommonElementAnimationData::CanAnimateFlags(0)) &&
1046 ea->CanThrottleAnimation(now);
1048 nsRefPtr<css::AnimValuesStyleRule> oldStyleRule = ea->mStyleRule;
1049 ea->EnsureStyleRuleFor(now, mPendingEvents, canThrottleTick);
1050 CheckNeedsRefresh();
1051 if (oldStyleRule != ea->mStyleRule) {
1052 ea->PostRestyleForAnimation(mPresContext);
1053 } else {
1054 didThrottle = true;
1055 }
1056 }
1058 if (didThrottle) {
1059 mPresContext->Document()->SetNeedStyleFlush();
1060 }
1062 DispatchEvents(); // may destroy us
1063 }
1065 void
1066 nsAnimationManager::DoDispatchEvents()
1067 {
1068 EventArray events;
1069 mPendingEvents.SwapElements(events);
1070 for (uint32_t i = 0, i_end = events.Length(); i < i_end; ++i) {
1071 AnimationEventInfo &info = events[i];
1072 EventDispatcher::Dispatch(info.mElement, mPresContext, &info.mEvent);
1074 if (!mPresContext) {
1075 break;
1076 }
1077 }
1078 }
1080 void
1081 nsAnimationManager::UpdateThrottledStylesForSubtree(nsIContent* aContent,
1082 nsStyleContext* aParentStyle,
1083 nsStyleChangeList& aChangeList)
1084 {
1085 dom::Element* element;
1086 if (aContent->IsElement()) {
1087 element = aContent->AsElement();
1088 } else {
1089 element = nullptr;
1090 }
1092 nsRefPtr<nsStyleContext> newStyle;
1094 ElementAnimations* ea;
1095 if (element &&
1096 (ea = GetElementAnimations(element,
1097 nsCSSPseudoElements::ePseudo_NotPseudoElement,
1098 false))) {
1099 // re-resolve our style
1100 newStyle = UpdateThrottledStyle(element, aParentStyle, aChangeList);
1101 // remove the current transition from the working set
1102 ea->mFlushGeneration = mPresContext->RefreshDriver()->MostRecentRefresh();
1103 } else {
1104 newStyle = ReparentContent(aContent, aParentStyle);
1105 }
1107 // walk the children
1108 if (newStyle) {
1109 for (nsIContent *child = aContent->GetFirstChild(); child;
1110 child = child->GetNextSibling()) {
1111 UpdateThrottledStylesForSubtree(child, newStyle, aChangeList);
1112 }
1113 }
1114 }
1116 IMPL_UPDATE_ALL_THROTTLED_STYLES_INTERNAL(nsAnimationManager,
1117 GetElementAnimations)
1119 void
1120 nsAnimationManager::UpdateAllThrottledStyles()
1121 {
1122 if (PR_CLIST_IS_EMPTY(&mElementData)) {
1123 // no throttled animations, leave early
1124 mPresContext->TickLastUpdateThrottledAnimationStyle();
1125 return;
1126 }
1128 if (mPresContext->ThrottledAnimationStyleIsUpToDate()) {
1129 // throttled transitions are up to date, leave early
1130 return;
1131 }
1133 mPresContext->TickLastUpdateThrottledAnimationStyle();
1135 UpdateAllThrottledStylesInternal();
1136 }