Bug 1073336 part 11 - Move GetAnimationPlayers to base CommonAnimationManager class...
[gecko.git] / layout / style / nsTransitionManager.cpp
blob7fe26a5e28f962f4617d5d286134547824920a89
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 /* Code to start and animate CSS transitions. */
9 #include "nsTransitionManager.h"
10 #include "nsAnimationManager.h"
11 #include "nsIContent.h"
12 #include "nsStyleContext.h"
13 #include "nsCSSProps.h"
14 #include "mozilla/MemoryReporting.h"
15 #include "mozilla/TimeStamp.h"
16 #include "nsRefreshDriver.h"
17 #include "nsRuleProcessorData.h"
18 #include "nsRuleWalker.h"
19 #include "nsCSSPropertySet.h"
20 #include "mozilla/EventDispatcher.h"
21 #include "mozilla/ContentEvents.h"
22 #include "mozilla/StyleAnimationValue.h"
23 #include "mozilla/dom/Element.h"
24 #include "nsIFrame.h"
25 #include "Layers.h"
26 #include "FrameLayerBuilder.h"
27 #include "nsDisplayList.h"
28 #include "nsStyleChangeList.h"
29 #include "nsStyleSet.h"
30 #include "RestyleManager.h"
32 using mozilla::TimeStamp;
33 using mozilla::TimeDuration;
34 using mozilla::dom::AnimationPlayer;
35 using mozilla::dom::Animation;
37 using namespace mozilla;
38 using namespace mozilla::layers;
39 using namespace mozilla::css;
41 double
42 ElementPropertyTransition::CurrentValuePortion() const
44 // It would be easy enough to handle finished transitions by using a time
45 // fraction of 1 but currently we should not be called for finished
46 // transitions.
47 MOZ_ASSERT(!IsFinishedTransition(),
48 "Getting the value portion of a finished transition");
49 MOZ_ASSERT(!GetLocalTime().IsNull(),
50 "Getting the value portion of an animation that's not being "
51 "sampled");
53 // Transitions use a fill mode of 'backwards' so GetComputedTiming will
54 // never return a null time fraction due to being *before* the animation
55 // interval. However, it might be possible that we're behind on flushing
56 // causing us to get called *after* the animation interval. So, just in
57 // case, we override the fill mode to 'both' to ensure the time fraction
58 // is never null.
59 AnimationTiming timingToUse = mTiming;
60 timingToUse.mFillMode = NS_STYLE_ANIMATION_FILL_MODE_BOTH;
61 ComputedTiming computedTiming = GetComputedTiming(&timingToUse);
63 MOZ_ASSERT(computedTiming.mTimeFraction != ComputedTiming::kNullTimeFraction,
64 "Got a null time fraction for a fill mode of 'both'");
65 MOZ_ASSERT(mProperties.Length() == 1,
66 "Should have one animation property for a transition");
67 MOZ_ASSERT(mProperties[0].mSegments.Length() == 1,
68 "Animation property should have one segment for a transition");
69 return mProperties[0].mSegments[0].mTimingFunction
70 .GetValue(computedTiming.mTimeFraction);
73 /*****************************************************************************
74 * CSSTransitionPlayer *
75 *****************************************************************************/
77 mozilla::dom::AnimationPlayState
78 CSSTransitionPlayer::PlayStateFromJS() const
80 FlushStyle();
81 return AnimationPlayer::PlayStateFromJS();
84 void
85 CSSTransitionPlayer::PlayFromJS()
87 FlushStyle();
88 AnimationPlayer::PlayFromJS();
91 CommonAnimationManager*
92 CSSTransitionPlayer::GetAnimationManager() const
94 nsPresContext* context = GetPresContext();
95 if (!context) {
96 return nullptr;
99 return context->TransitionManager();
102 /*****************************************************************************
103 * nsTransitionManager *
104 *****************************************************************************/
106 already_AddRefed<nsIStyleRule>
107 nsTransitionManager::StyleContextChanged(dom::Element *aElement,
108 nsStyleContext *aOldStyleContext,
109 nsStyleContext *aNewStyleContext)
111 NS_PRECONDITION(aOldStyleContext->GetPseudo() ==
112 aNewStyleContext->GetPseudo(),
113 "pseudo type mismatch");
115 if (mInAnimationOnlyStyleUpdate) {
116 // If we're doing an animation-only style update, return, since the
117 // purpose of an animation-only style update is to update only the
118 // animation styles so that we don't consider style changes
119 // resulting from changes in the animation time for starting a
120 // transition.
121 return nullptr;
124 if (!mPresContext->IsDynamic()) {
125 // For print or print preview, ignore transitions.
126 return nullptr;
129 if (aOldStyleContext->HasPseudoElementData() !=
130 aNewStyleContext->HasPseudoElementData()) {
131 // If the old style context and new style context differ in terms of
132 // whether they're inside ::first-letter, ::first-line, or similar,
133 // bail. We can't hit this codepath for normal style changes
134 // involving moving frames around the boundaries of these
135 // pseudo-elements since we don't call StyleContextChanged from
136 // ReparentStyleContext. However, we can hit this codepath during
137 // the handling of transitions that start across reframes.
139 // While there isn't an easy *perfect* way to handle this case, err
140 // on the side of missing some transitions that we ought to have
141 // rather than having bogus transitions that we shouldn't.
143 // We could consider changing this handling, although it's worth
144 // thinking about whether the code below could do anything weird in
145 // this case.
146 return nullptr;
149 // NOTE: Things in this function (and ConsiderStartingTransition)
150 // should never call PeekStyleData because we don't preserve gotten
151 // structs across reframes.
153 // Return sooner (before the startedAny check below) for the most
154 // common case: no transitions specified or running.
155 const nsStyleDisplay *disp = aNewStyleContext->StyleDisplay();
156 nsCSSPseudoElements::Type pseudoType = aNewStyleContext->GetPseudoType();
157 if (pseudoType != nsCSSPseudoElements::ePseudo_NotPseudoElement) {
158 if (pseudoType != nsCSSPseudoElements::ePseudo_before &&
159 pseudoType != nsCSSPseudoElements::ePseudo_after) {
160 return nullptr;
163 NS_ASSERTION((pseudoType == nsCSSPseudoElements::ePseudo_before &&
164 aElement->Tag() == nsGkAtoms::mozgeneratedcontentbefore) ||
165 (pseudoType == nsCSSPseudoElements::ePseudo_after &&
166 aElement->Tag() == nsGkAtoms::mozgeneratedcontentafter),
167 "Unexpected aElement coming through");
169 // Else the element we want to use from now on is the element the
170 // :before or :after is attached to.
171 aElement = aElement->GetParent()->AsElement();
174 AnimationPlayerCollection* collection =
175 GetAnimationPlayers(aElement, pseudoType, false);
176 if (!collection &&
177 disp->mTransitionPropertyCount == 1 &&
178 disp->mTransitions[0].GetDelay() == 0.0f &&
179 disp->mTransitions[0].GetDuration() == 0.0f) {
180 return nullptr;
184 // FIXME (bug 960465): This test should go away.
185 if (aNewStyleContext->PresContext()->RestyleManager()->
186 IsProcessingAnimationStyleChange()) {
187 return nullptr;
190 if (aNewStyleContext->GetParent() &&
191 aNewStyleContext->GetParent()->HasPseudoElementData()) {
192 // Ignore transitions on things that inherit properties from
193 // pseudo-elements.
194 // FIXME (Bug 522599): Add tests for this.
195 return nullptr;
198 NS_WARN_IF_FALSE(!nsLayoutUtils::AreAsyncAnimationsEnabled() ||
199 mPresContext->RestyleManager()->
200 ThrottledAnimationStyleIsUpToDate(),
201 "throttled animations not up to date");
203 // Per http://lists.w3.org/Archives/Public/www-style/2009Aug/0109.html
204 // I'll consider only the transitions from the number of items in
205 // 'transition-property' on down, and later ones will override earlier
206 // ones (tracked using |whichStarted|).
207 bool startedAny = false;
208 nsCSSPropertySet whichStarted;
209 for (uint32_t i = disp->mTransitionPropertyCount; i-- != 0; ) {
210 const StyleTransition& t = disp->mTransitions[i];
211 // Check delay and duration first, since they default to zero, and
212 // when they're both zero, we can ignore the transition.
213 if (t.GetDelay() != 0.0f || t.GetDuration() != 0.0f) {
214 // We might have something to transition. See if any of the
215 // properties in question changed and are animatable.
216 // FIXME: Would be good to find a way to share code between this
217 // interpretation of transition-property and the one below.
218 nsCSSProperty property = t.GetProperty();
219 if (property == eCSSPropertyExtra_no_properties ||
220 property == eCSSPropertyExtra_variable ||
221 property == eCSSProperty_UNKNOWN) {
222 // Nothing to do, but need to exclude this from cases below.
223 } else if (property == eCSSPropertyExtra_all_properties) {
224 for (nsCSSProperty p = nsCSSProperty(0);
225 p < eCSSProperty_COUNT_no_shorthands;
226 p = nsCSSProperty(p + 1)) {
227 ConsiderStartingTransition(p, t, aElement, collection,
228 aOldStyleContext, aNewStyleContext,
229 &startedAny, &whichStarted);
231 } else if (nsCSSProps::IsShorthand(property)) {
232 CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(subprop, property) {
233 ConsiderStartingTransition(*subprop, t, aElement, collection,
234 aOldStyleContext, aNewStyleContext,
235 &startedAny, &whichStarted);
237 } else {
238 ConsiderStartingTransition(property, t, aElement, collection,
239 aOldStyleContext, aNewStyleContext,
240 &startedAny, &whichStarted);
245 // Stop any transitions for properties that are no longer in
246 // 'transition-property'.
247 // Also stop any transitions for properties that just changed (and are
248 // still in the set of properties to transition), but we didn't just
249 // start the transition because delay and duration are both zero.
250 if (collection) {
251 bool checkProperties =
252 disp->mTransitions[0].GetProperty() != eCSSPropertyExtra_all_properties;
253 nsCSSPropertySet allTransitionProperties;
254 if (checkProperties) {
255 for (uint32_t i = disp->mTransitionPropertyCount; i-- != 0; ) {
256 const StyleTransition& t = disp->mTransitions[i];
257 // FIXME: Would be good to find a way to share code between this
258 // interpretation of transition-property and the one above.
259 nsCSSProperty property = t.GetProperty();
260 if (property == eCSSPropertyExtra_no_properties ||
261 property == eCSSPropertyExtra_variable ||
262 property == eCSSProperty_UNKNOWN) {
263 // Nothing to do, but need to exclude this from cases below.
264 } else if (property == eCSSPropertyExtra_all_properties) {
265 for (nsCSSProperty p = nsCSSProperty(0);
266 p < eCSSProperty_COUNT_no_shorthands;
267 p = nsCSSProperty(p + 1)) {
268 allTransitionProperties.AddProperty(p);
270 } else if (nsCSSProps::IsShorthand(property)) {
271 CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(subprop, property) {
272 allTransitionProperties.AddProperty(*subprop);
274 } else {
275 allTransitionProperties.AddProperty(property);
280 AnimationPlayerPtrArray& players = collection->mPlayers;
281 size_t i = players.Length();
282 NS_ABORT_IF_FALSE(i != 0, "empty transitions list?");
283 StyleAnimationValue currentValue;
284 do {
285 --i;
286 AnimationPlayer* player = players[i];
287 dom::Animation* anim = player->GetSource();
288 MOZ_ASSERT(anim && anim->Properties().Length() == 1,
289 "Should have one animation property for a transition");
290 MOZ_ASSERT(anim && anim->Properties()[0].mSegments.Length() == 1,
291 "Animation property should have one segment for a transition");
292 const AnimationProperty& prop = anim->Properties()[0];
293 const AnimationPropertySegment& segment = prop.mSegments[0];
294 // properties no longer in 'transition-property'
295 if ((checkProperties &&
296 !allTransitionProperties.HasProperty(prop.mProperty)) ||
297 // properties whose computed values changed but delay and
298 // duration are both zero
299 !ExtractComputedValueForTransition(prop.mProperty, aNewStyleContext,
300 currentValue) ||
301 currentValue != segment.mToValue) {
302 // stop the transition
303 players.RemoveElementAt(i);
304 collection->UpdateAnimationGeneration(mPresContext);
306 } while (i != 0);
308 if (players.IsEmpty()) {
309 collection->Destroy();
310 collection = nullptr;
314 if (!startedAny) {
315 return nullptr;
318 NS_ABORT_IF_FALSE(collection, "must have element transitions if we started "
319 "any transitions");
321 // In the CSS working group discussion (2009 Jul 15 telecon,
322 // http://www.w3.org/mid/4A5E1470.4030904@inkedblade.net ) of
323 // http://lists.w3.org/Archives/Public/www-style/2009Jun/0121.html ,
324 // the working group decided that a transition property on an
325 // element should not cause any transitions if the property change
326 // is itself inheriting a value that is transitioning on an
327 // ancestor. So, to get the correct behavior, we continue the
328 // restyle that caused this transition using a "covering" rule that
329 // covers up any changes on which we started transitions, so that
330 // descendants don't start their own transitions. (In the case of
331 // negative transition delay, this covering rule produces different
332 // results than applying the transition rule immediately would).
333 // Our caller is responsible for restyling again using this covering
334 // rule.
336 nsRefPtr<css::AnimValuesStyleRule> coverRule = new css::AnimValuesStyleRule;
338 AnimationPlayerPtrArray& players = collection->mPlayers;
339 for (size_t i = 0, i_end = players.Length(); i < i_end; ++i) {
340 dom::Animation* anim = players[i]->GetSource();
341 MOZ_ASSERT(anim && anim->Properties().Length() == 1,
342 "Should have one animation property for a transition");
343 MOZ_ASSERT(anim && anim->Properties()[0].mSegments.Length() == 1,
344 "Animation property should have one segment for a transition");
345 AnimationProperty& prop = anim->Properties()[0];
346 AnimationPropertySegment& segment = prop.mSegments[0];
347 if (whichStarted.HasProperty(prop.mProperty)) {
348 coverRule->AddValue(prop.mProperty, segment.mFromValue);
352 // Set the style rule refresh time to null so that EnsureStyleRuleFor
353 // creates a new style rule.
354 collection->mStyleRuleRefreshTime = TimeStamp();
356 return coverRule.forget();
359 void
360 nsTransitionManager::ConsiderStartingTransition(
361 nsCSSProperty aProperty,
362 const StyleTransition& aTransition,
363 dom::Element* aElement,
364 AnimationPlayerCollection*& aElementTransitions,
365 nsStyleContext* aOldStyleContext,
366 nsStyleContext* aNewStyleContext,
367 bool* aStartedAny,
368 nsCSSPropertySet* aWhichStarted)
370 // IsShorthand itself will assert if aProperty is not a property.
371 NS_ABORT_IF_FALSE(!nsCSSProps::IsShorthand(aProperty),
372 "property out of range");
373 NS_ASSERTION(!aElementTransitions ||
374 aElementTransitions->mElement == aElement, "Element mismatch");
376 if (aWhichStarted->HasProperty(aProperty)) {
377 // A later item in transition-property already started a
378 // transition for this property, so we ignore this one.
379 // See comment above and
380 // http://lists.w3.org/Archives/Public/www-style/2009Aug/0109.html .
381 return;
384 if (nsCSSProps::kAnimTypeTable[aProperty] == eStyleAnimType_None) {
385 return;
388 dom::AnimationTimeline* timeline = aElement->OwnerDoc()->Timeline();
390 StyleAnimationValue startValue, endValue, dummyValue;
391 bool haveValues =
392 ExtractComputedValueForTransition(aProperty, aOldStyleContext,
393 startValue) &&
394 ExtractComputedValueForTransition(aProperty, aNewStyleContext,
395 endValue);
397 bool haveChange = startValue != endValue;
399 bool shouldAnimate =
400 haveValues &&
401 haveChange &&
402 // Check that we can interpolate between these values
403 // (If this is ever a performance problem, we could add a
404 // CanInterpolate method, but it seems fine for now.)
405 StyleAnimationValue::Interpolate(aProperty, startValue, endValue,
406 0.5, dummyValue);
408 bool haveCurrentTransition = false;
409 size_t currentIndex = nsTArray<ElementPropertyTransition>::NoIndex;
410 const ElementPropertyTransition *oldPT = nullptr;
411 if (aElementTransitions) {
412 AnimationPlayerPtrArray& players = aElementTransitions->mPlayers;
413 for (size_t i = 0, i_end = players.Length(); i < i_end; ++i) {
414 MOZ_ASSERT(players[i]->GetSource() &&
415 players[i]->GetSource()->Properties().Length() == 1,
416 "Should have one animation property for a transition");
417 if (players[i]->GetSource()->Properties()[0].mProperty == aProperty) {
418 haveCurrentTransition = true;
419 currentIndex = i;
420 oldPT = players[currentIndex]->GetSource()->AsTransition();
421 break;
426 // If we got a style change that changed the value to the endpoint
427 // of the currently running transition, we don't want to interrupt
428 // its timing function.
429 // This needs to be before the !shouldAnimate && haveCurrentTransition
430 // case below because we might be close enough to the end of the
431 // transition that the current value rounds to the final value. In
432 // this case, we'll end up with shouldAnimate as false (because
433 // there's no value change), but we need to return early here rather
434 // than cancel the running transition because shouldAnimate is false!
435 MOZ_ASSERT(!oldPT || oldPT->Properties()[0].mSegments.Length() == 1,
436 "Should have one animation property segment for a transition");
437 if (haveCurrentTransition && haveValues &&
438 oldPT->Properties()[0].mSegments[0].mToValue == endValue) {
439 // WalkTransitionRule already called RestyleForAnimation.
440 return;
443 nsPresContext *presContext = aNewStyleContext->PresContext();
445 if (!shouldAnimate) {
446 if (haveCurrentTransition) {
447 // We're in the middle of a transition, and just got a non-transition
448 // style change to something that we can't animate. This might happen
449 // because we got a non-transition style change changing to the current
450 // in-progress value (which is particularly easy to cause when we're
451 // currently in the 'transition-delay'). It also might happen because we
452 // just got a style change to a value that can't be interpolated.
453 AnimationPlayerPtrArray& players = aElementTransitions->mPlayers;
454 oldPT = nullptr; // Clear pointer so it doesn't dangle
455 players.RemoveElementAt(currentIndex);
456 aElementTransitions->UpdateAnimationGeneration(mPresContext);
458 if (players.IsEmpty()) {
459 aElementTransitions->Destroy();
460 // |aElementTransitions| is now a dangling pointer!
461 aElementTransitions = nullptr;
463 // WalkTransitionRule already called RestyleForAnimation.
465 return;
468 const nsTimingFunction &tf = aTransition.GetTimingFunction();
469 float delay = aTransition.GetDelay();
470 float duration = aTransition.GetDuration();
471 if (duration < 0.0) {
472 // The spec says a negative duration is treated as zero.
473 duration = 0.0;
476 StyleAnimationValue startForReversingTest = startValue;
477 double reversePortion = 1.0;
479 // If the new transition reverses an existing one, we'll need to
480 // handle the timing differently.
481 if (haveCurrentTransition &&
482 !oldPT->IsFinishedTransition() &&
483 oldPT->mStartForReversingTest == endValue) {
484 // Compute the appropriate negative transition-delay such that right
485 // now we'd end up at the current position.
486 double valuePortion =
487 oldPT->CurrentValuePortion() * oldPT->mReversePortion +
488 (1.0 - oldPT->mReversePortion);
489 // A timing function with negative y1 (or y2!) might make
490 // valuePortion negative. In this case, we still want to apply our
491 // reversing logic based on relative distances, not make duration
492 // negative.
493 if (valuePortion < 0.0) {
494 valuePortion = -valuePortion;
496 // A timing function with y2 (or y1!) greater than one might
497 // advance past its terminal value. It's probably a good idea to
498 // clamp valuePortion to be at most one to preserve the invariant
499 // that a transition will complete within at most its specified
500 // time.
501 if (valuePortion > 1.0) {
502 valuePortion = 1.0;
505 // Negative delays are essentially part of the transition
506 // function, so reduce them along with the duration, but don't
507 // reduce positive delays.
508 if (delay < 0.0f) {
509 delay *= valuePortion;
512 duration *= valuePortion;
514 startForReversingTest = oldPT->Properties()[0].mSegments[0].mToValue;
515 reversePortion = valuePortion;
518 AnimationTiming timing;
519 timing.mIterationDuration = TimeDuration::FromMilliseconds(duration);
520 timing.mDelay = TimeDuration::FromMilliseconds(delay);
521 timing.mIterationCount = 1;
522 timing.mDirection = NS_STYLE_ANIMATION_DIRECTION_NORMAL;
523 timing.mFillMode = NS_STYLE_ANIMATION_FILL_MODE_BACKWARDS;
525 nsRefPtr<ElementPropertyTransition> pt =
526 new ElementPropertyTransition(aElement->OwnerDoc(), aElement,
527 aNewStyleContext->GetPseudoType(), timing);
528 pt->mStartForReversingTest = startForReversingTest;
529 pt->mReversePortion = reversePortion;
531 AnimationProperty& prop = *pt->Properties().AppendElement();
532 prop.mProperty = aProperty;
534 AnimationPropertySegment& segment = *prop.mSegments.AppendElement();
535 segment.mFromValue = startValue;
536 segment.mToValue = endValue;
537 segment.mFromKey = 0;
538 segment.mToKey = 1;
539 segment.mTimingFunction.Init(tf);
541 nsRefPtr<CSSTransitionPlayer> player = new CSSTransitionPlayer(timeline);
542 player->mStartTime = timeline->GetCurrentTime();
543 player->SetSource(pt);
545 if (!aElementTransitions) {
546 aElementTransitions =
547 GetAnimationPlayers(aElement, aNewStyleContext->GetPseudoType(), true);
548 if (!aElementTransitions) {
549 NS_WARNING("allocating CommonAnimationManager failed");
550 return;
554 AnimationPlayerPtrArray& players = aElementTransitions->mPlayers;
555 #ifdef DEBUG
556 for (size_t i = 0, i_end = players.Length(); i < i_end; ++i) {
557 NS_ABORT_IF_FALSE(players[i]->GetSource() &&
558 players[i]->GetSource()->Properties().Length() == 1,
559 "Should have one animation property for a transition");
560 NS_ABORT_IF_FALSE(i == currentIndex ||
561 (players[i]->GetSource() &&
562 players[i]->GetSource()->Properties()[0].mProperty
563 != aProperty),
564 "duplicate transitions for property");
566 #endif
567 if (haveCurrentTransition) {
568 players[currentIndex] = player;
569 } else {
570 if (!players.AppendElement(player)) {
571 NS_WARNING("out of memory");
572 return;
575 aElementTransitions->UpdateAnimationGeneration(mPresContext);
576 aElementTransitions->PostRestyleForAnimation(presContext);
578 *aStartedAny = true;
579 aWhichStarted->AddProperty(aProperty);
582 AnimationPlayerCollection*
583 nsTransitionManager::GetAnimationPlayers(
584 dom::Element *aElement,
585 nsCSSPseudoElements::Type aPseudoType,
586 bool aCreateIfNeeded)
588 if (!aCreateIfNeeded && PR_CLIST_IS_EMPTY(&mElementCollections)) {
589 // Early return for the most common case.
590 return nullptr;
593 nsIAtom *propName;
594 if (aPseudoType == nsCSSPseudoElements::ePseudo_NotPseudoElement) {
595 propName = nsGkAtoms::transitionsProperty;
596 } else if (aPseudoType == nsCSSPseudoElements::ePseudo_before) {
597 propName = nsGkAtoms::transitionsOfBeforeProperty;
598 } else if (aPseudoType == nsCSSPseudoElements::ePseudo_after) {
599 propName = nsGkAtoms::transitionsOfAfterProperty;
600 } else {
601 NS_ASSERTION(!aCreateIfNeeded,
602 "should never try to create transitions for pseudo "
603 "other than :before or :after");
604 return nullptr;
606 AnimationPlayerCollection* collection =
607 static_cast<AnimationPlayerCollection*>(aElement->GetProperty(propName));
608 if (!collection && aCreateIfNeeded) {
609 // FIXME: Consider arena-allocating?
610 collection = new AnimationPlayerCollection(aElement, propName, this);
611 nsresult rv =
612 aElement->SetProperty(propName, collection,
613 &AnimationPlayerCollection::PropertyDtor, false);
614 if (NS_FAILED(rv)) {
615 NS_WARNING("SetProperty failed");
616 delete collection;
617 return nullptr;
619 if (propName == nsGkAtoms::transitionsProperty) {
620 aElement->SetMayHaveAnimations();
623 AddElementCollection(collection);
626 return collection;
630 * nsIStyleRuleProcessor implementation
633 void
634 nsTransitionManager::WalkTransitionRule(dom::Element* aElement,
635 nsCSSPseudoElements::Type aPseudoType,
636 nsRuleWalker* aRuleWalker)
638 AnimationPlayerCollection* collection =
639 GetAnimationPlayers(aElement, aPseudoType, false);
640 if (!collection) {
641 return;
644 if (!mPresContext->IsDynamic()) {
645 // For print or print preview, ignore animations.
646 return;
649 RestyleManager* restyleManager = mPresContext->RestyleManager();
650 if (restyleManager->SkipAnimationRules()) {
651 // If we're processing a normal style change rather than one from
652 // animation, don't add the transition rule. This allows us to
653 // compute the new style value rather than having the transition
654 // override it, so that we can start transitioning differently.
656 if (restyleManager->PostAnimationRestyles()) {
657 // We need to immediately restyle with animation
658 // after doing this.
659 collection->PostRestyleForAnimation(mPresContext);
661 return;
664 collection->mNeedsRefreshes = true;
665 collection->EnsureStyleRuleFor(
666 mPresContext->RefreshDriver()->MostRecentRefresh(),
667 EnsureStyleRule_IsNotThrottled);
669 if (collection->mStyleRule) {
670 aRuleWalker->Forward(collection->mStyleRule);
674 /* virtual */ void
675 nsTransitionManager::RulesMatching(ElementRuleProcessorData* aData)
677 NS_ABORT_IF_FALSE(aData->mPresContext == mPresContext,
678 "pres context mismatch");
679 WalkTransitionRule(aData->mElement,
680 nsCSSPseudoElements::ePseudo_NotPseudoElement,
681 aData->mRuleWalker);
684 /* virtual */ void
685 nsTransitionManager::RulesMatching(PseudoElementRuleProcessorData* aData)
687 NS_ABORT_IF_FALSE(aData->mPresContext == mPresContext,
688 "pres context mismatch");
690 // Note: If we're the only thing keeping a pseudo-element frame alive
691 // (per ProbePseudoStyleContext), we still want to keep it alive, so
692 // this is ok.
693 WalkTransitionRule(aData->mElement, aData->mPseudoType,
694 aData->mRuleWalker);
697 /* virtual */ void
698 nsTransitionManager::RulesMatching(AnonBoxRuleProcessorData* aData)
702 #ifdef MOZ_XUL
703 /* virtual */ void
704 nsTransitionManager::RulesMatching(XULTreeRuleProcessorData* aData)
707 #endif
709 /* virtual */ size_t
710 nsTransitionManager::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
712 return CommonAnimationManager::SizeOfExcludingThis(aMallocSizeOf);
715 /* virtual */ size_t
716 nsTransitionManager::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
718 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
721 struct TransitionEventInfo {
722 nsCOMPtr<nsIContent> mElement;
723 InternalTransitionEvent mEvent;
725 TransitionEventInfo(nsIContent *aElement, nsCSSProperty aProperty,
726 TimeDuration aDuration, const nsAString& aPseudoElement)
727 : mElement(aElement)
728 , mEvent(true, NS_TRANSITION_END)
730 // XXX Looks like nobody initialize WidgetEvent::time
731 mEvent.propertyName =
732 NS_ConvertUTF8toUTF16(nsCSSProps::GetStringValue(aProperty));
733 mEvent.elapsedTime = aDuration.ToSeconds();
734 mEvent.pseudoElement = aPseudoElement;
737 // InternalTransitionEvent doesn't support copy-construction, so we need
738 // to ourselves in order to work with nsTArray
739 TransitionEventInfo(const TransitionEventInfo &aOther)
740 : mElement(aOther.mElement)
741 , mEvent(true, NS_TRANSITION_END)
743 mEvent.AssignTransitionEventData(aOther.mEvent, false);
747 /* virtual */ void
748 nsTransitionManager::WillRefresh(mozilla::TimeStamp aTime)
750 NS_ABORT_IF_FALSE(mPresContext,
751 "refresh driver should not notify additional observers "
752 "after pres context has been destroyed");
753 if (!mPresContext->GetPresShell()) {
754 // Someone might be keeping mPresContext alive past the point
755 // where it has been torn down; don't bother doing anything in
756 // this case. But do get rid of all our transitions so we stop
757 // triggering refreshes.
758 RemoveAllElementCollections();
759 return;
762 FlushTransitions(Can_Throttle);
765 void
766 nsTransitionManager::FlushTransitions(FlushFlags aFlags)
768 if (PR_CLIST_IS_EMPTY(&mElementCollections)) {
769 // no transitions, leave early
770 return;
773 nsTArray<TransitionEventInfo> events;
774 TimeStamp now = mPresContext->RefreshDriver()->MostRecentRefresh();
775 bool didThrottle = false;
776 // Trim transitions that have completed, post restyle events for frames that
777 // are still transitioning, and start transitions with delays.
779 PRCList *next = PR_LIST_HEAD(&mElementCollections);
780 while (next != &mElementCollections) {
781 AnimationPlayerCollection* collection =
782 static_cast<AnimationPlayerCollection*>(next);
783 next = PR_NEXT_LINK(next);
785 collection->Tick();
786 bool canThrottleTick = aFlags == Can_Throttle &&
787 collection->CanPerformOnCompositorThread(
788 AnimationPlayerCollection::CanAnimateFlags(0)) &&
789 collection->CanThrottleAnimation(now);
791 NS_ABORT_IF_FALSE(collection->mElement->GetCrossShadowCurrentDoc() ==
792 mPresContext->Document(),
793 "Element::UnbindFromTree should have "
794 "destroyed the element transitions object");
796 size_t i = collection->mPlayers.Length();
797 NS_ABORT_IF_FALSE(i != 0, "empty transitions list?");
798 bool transitionStartedOrEnded = false;
799 do {
800 --i;
801 AnimationPlayer* player = collection->mPlayers[i];
802 if (player->GetSource()->IsFinishedTransition()) {
803 // Actually remove transitions one throttle-able cycle after their
804 // completion. We only clear on a throttle-able cycle because that
805 // means it is a regular restyle tick and thus it is safe to discard
806 // the transition. If the flush is not throttle-able, we might still
807 // have new transitions left to process. See comment below.
808 if (aFlags == Can_Throttle) {
809 collection->mPlayers.RemoveElementAt(i);
811 } else {
812 MOZ_ASSERT(player->GetSource(),
813 "Transitions should have source content");
814 ComputedTiming computedTiming =
815 player->GetSource()->GetComputedTiming();
816 if (computedTiming.mPhase == ComputedTiming::AnimationPhase_After) {
817 MOZ_ASSERT(player->GetSource()->Properties().Length() == 1,
818 "Should have one animation property for a transition");
819 nsCSSProperty prop = player->GetSource()->Properties()[0].mProperty;
820 if (nsCSSProps::PropHasFlags(prop, CSS_PROPERTY_REPORT_OTHER_NAME))
822 prop = nsCSSProps::OtherNameFor(prop);
824 TimeDuration duration =
825 player->GetSource()->Timing().mIterationDuration;
826 events.AppendElement(
827 TransitionEventInfo(collection->mElement, prop,
828 duration,
829 collection->PseudoElement()));
831 // Leave this transition in the list for one more refresh
832 // cycle, since we haven't yet processed its style change, and
833 // if we also have (already, or will have from processing
834 // transitionend events or other refresh driver notifications)
835 // a non-animation style change that would affect it, we need
836 // to know not to start a new transition for the transition
837 // from the almost-completed value to the final value.
838 player->GetSource()->SetIsFinishedTransition();
839 collection->UpdateAnimationGeneration(mPresContext);
840 transitionStartedOrEnded = true;
841 } else if ((computedTiming.mPhase ==
842 ComputedTiming::AnimationPhase_Active) &&
843 canThrottleTick &&
844 !player->IsRunningOnCompositor()) {
845 // Start a transition with a delay where we should start the
846 // transition proper.
847 collection->UpdateAnimationGeneration(mPresContext);
848 transitionStartedOrEnded = true;
851 } while (i != 0);
853 // We need to restyle even if the transition rule no longer
854 // applies (in which case we just made it not apply).
855 MOZ_ASSERT(collection->mElementProperty ==
856 nsGkAtoms::transitionsProperty ||
857 collection->mElementProperty ==
858 nsGkAtoms::transitionsOfBeforeProperty ||
859 collection->mElementProperty ==
860 nsGkAtoms::transitionsOfAfterProperty,
861 "Unexpected element property; might restyle too much");
862 if (!canThrottleTick || transitionStartedOrEnded) {
863 collection->PostRestyleForAnimation(mPresContext);
864 } else {
865 didThrottle = true;
868 if (collection->mPlayers.IsEmpty()) {
869 collection->Destroy();
870 // |collection| is now a dangling pointer!
871 collection = nullptr;
876 if (didThrottle) {
877 mPresContext->Document()->SetNeedStyleFlush();
880 for (uint32_t i = 0, i_end = events.Length(); i < i_end; ++i) {
881 TransitionEventInfo &info = events[i];
882 EventDispatcher::Dispatch(info.mElement, mPresContext, &info.mEvent);
884 if (!mPresContext) {
885 break;