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"
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
;
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
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 "
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
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
81 return AnimationPlayer::PlayStateFromJS();
85 CSSTransitionPlayer::PlayFromJS()
88 AnimationPlayer::PlayFromJS();
91 CommonAnimationManager
*
92 CSSTransitionPlayer::GetAnimationManager() const
94 nsPresContext
* context
= GetPresContext();
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
124 if (!mPresContext
->IsDynamic()) {
125 // For print or print preview, ignore transitions.
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
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
) {
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);
177 disp
->mTransitionPropertyCount
== 1 &&
178 disp
->mTransitions
[0].GetDelay() == 0.0f
&&
179 disp
->mTransitions
[0].GetDuration() == 0.0f
) {
184 // FIXME (bug 960465): This test should go away.
185 if (aNewStyleContext
->PresContext()->RestyleManager()->
186 IsProcessingAnimationStyleChange()) {
190 if (aNewStyleContext
->GetParent() &&
191 aNewStyleContext
->GetParent()->HasPseudoElementData()) {
192 // Ignore transitions on things that inherit properties from
194 // FIXME (Bug 522599): Add tests for this.
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
);
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.
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
);
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
;
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
,
301 currentValue
!= segment
.mToValue
) {
302 // stop the transition
303 players
.RemoveElementAt(i
);
304 collection
->UpdateAnimationGeneration(mPresContext
);
308 if (players
.IsEmpty()) {
309 collection
->Destroy();
310 collection
= nullptr;
318 NS_ABORT_IF_FALSE(collection
, "must have element transitions if we started "
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
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();
360 nsTransitionManager::ConsiderStartingTransition(
361 nsCSSProperty aProperty
,
362 const StyleTransition
& aTransition
,
363 dom::Element
* aElement
,
364 AnimationPlayerCollection
*& aElementTransitions
,
365 nsStyleContext
* aOldStyleContext
,
366 nsStyleContext
* aNewStyleContext
,
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 .
384 if (nsCSSProps::kAnimTypeTable
[aProperty
] == eStyleAnimType_None
) {
388 dom::AnimationTimeline
* timeline
= aElement
->OwnerDoc()->Timeline();
390 StyleAnimationValue startValue
, endValue
, dummyValue
;
392 ExtractComputedValueForTransition(aProperty
, aOldStyleContext
,
394 ExtractComputedValueForTransition(aProperty
, aNewStyleContext
,
397 bool haveChange
= startValue
!= endValue
;
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
,
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;
420 oldPT
= players
[currentIndex
]->GetSource()->AsTransition();
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.
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.
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.
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
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
501 if (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.
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;
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");
554 AnimationPlayerPtrArray
& players
= aElementTransitions
->mPlayers
;
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
564 "duplicate transitions for property");
567 if (haveCurrentTransition
) {
568 players
[currentIndex
] = player
;
570 if (!players
.AppendElement(player
)) {
571 NS_WARNING("out of memory");
575 aElementTransitions
->UpdateAnimationGeneration(mPresContext
);
576 aElementTransitions
->PostRestyleForAnimation(presContext
);
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.
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
;
601 NS_ASSERTION(!aCreateIfNeeded
,
602 "should never try to create transitions for pseudo "
603 "other than :before or :after");
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);
612 aElement
->SetProperty(propName
, collection
,
613 &AnimationPlayerCollection::PropertyDtor
, false);
615 NS_WARNING("SetProperty failed");
619 if (propName
== nsGkAtoms::transitionsProperty
) {
620 aElement
->SetMayHaveAnimations();
623 AddElementCollection(collection
);
630 * nsIStyleRuleProcessor implementation
634 nsTransitionManager::WalkTransitionRule(dom::Element
* aElement
,
635 nsCSSPseudoElements::Type aPseudoType
,
636 nsRuleWalker
* aRuleWalker
)
638 AnimationPlayerCollection
* collection
=
639 GetAnimationPlayers(aElement
, aPseudoType
, false);
644 if (!mPresContext
->IsDynamic()) {
645 // For print or print preview, ignore animations.
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
659 collection
->PostRestyleForAnimation(mPresContext
);
664 collection
->mNeedsRefreshes
= true;
665 collection
->EnsureStyleRuleFor(
666 mPresContext
->RefreshDriver()->MostRecentRefresh(),
667 EnsureStyleRule_IsNotThrottled
);
669 if (collection
->mStyleRule
) {
670 aRuleWalker
->Forward(collection
->mStyleRule
);
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
,
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
693 WalkTransitionRule(aData
->mElement
, aData
->mPseudoType
,
698 nsTransitionManager::RulesMatching(AnonBoxRuleProcessorData
* aData
)
704 nsTransitionManager::RulesMatching(XULTreeRuleProcessorData
* aData
)
710 nsTransitionManager::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf
) const
712 return CommonAnimationManager::SizeOfExcludingThis(aMallocSizeOf
);
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
)
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);
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();
762 FlushTransitions(Can_Throttle
);
766 nsTransitionManager::FlushTransitions(FlushFlags aFlags
)
768 if (PR_CLIST_IS_EMPTY(&mElementCollections
)) {
769 // no transitions, leave early
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
);
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;
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
);
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
,
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
) &&
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;
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
);
868 if (collection
->mPlayers
.IsEmpty()) {
869 collection
->Destroy();
870 // |collection| is now a dangling pointer!
871 collection
= nullptr;
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
);