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
304 players
.RemoveElementAt(i
);
305 collection
->UpdateAnimationGeneration(mPresContext
);
309 if (players
.IsEmpty()) {
310 collection
->Destroy();
311 collection
= nullptr;
319 NS_ABORT_IF_FALSE(collection
, "must have element transitions if we started "
322 // In the CSS working group discussion (2009 Jul 15 telecon,
323 // http://www.w3.org/mid/4A5E1470.4030904@inkedblade.net ) of
324 // http://lists.w3.org/Archives/Public/www-style/2009Jun/0121.html ,
325 // the working group decided that a transition property on an
326 // element should not cause any transitions if the property change
327 // is itself inheriting a value that is transitioning on an
328 // ancestor. So, to get the correct behavior, we continue the
329 // restyle that caused this transition using a "covering" rule that
330 // covers up any changes on which we started transitions, so that
331 // descendants don't start their own transitions. (In the case of
332 // negative transition delay, this covering rule produces different
333 // results than applying the transition rule immediately would).
334 // Our caller is responsible for restyling again using this covering
337 nsRefPtr
<css::AnimValuesStyleRule
> coverRule
= new css::AnimValuesStyleRule
;
339 AnimationPlayerPtrArray
& players
= collection
->mPlayers
;
340 for (size_t i
= 0, i_end
= players
.Length(); i
< i_end
; ++i
) {
341 dom::Animation
* anim
= players
[i
]->GetSource();
342 MOZ_ASSERT(anim
&& anim
->Properties().Length() == 1,
343 "Should have one animation property for a transition");
344 MOZ_ASSERT(anim
&& anim
->Properties()[0].mSegments
.Length() == 1,
345 "Animation property should have one segment for a transition");
346 AnimationProperty
& prop
= anim
->Properties()[0];
347 AnimationPropertySegment
& segment
= prop
.mSegments
[0];
348 if (whichStarted
.HasProperty(prop
.mProperty
)) {
349 coverRule
->AddValue(prop
.mProperty
, segment
.mFromValue
);
353 // Set the style rule refresh time to null so that EnsureStyleRuleFor
354 // creates a new style rule.
355 collection
->mStyleRuleRefreshTime
= TimeStamp();
357 return coverRule
.forget();
361 nsTransitionManager::ConsiderStartingTransition(
362 nsCSSProperty aProperty
,
363 const StyleTransition
& aTransition
,
364 dom::Element
* aElement
,
365 AnimationPlayerCollection
*& aElementTransitions
,
366 nsStyleContext
* aOldStyleContext
,
367 nsStyleContext
* aNewStyleContext
,
369 nsCSSPropertySet
* aWhichStarted
)
371 // IsShorthand itself will assert if aProperty is not a property.
372 NS_ABORT_IF_FALSE(!nsCSSProps::IsShorthand(aProperty
),
373 "property out of range");
374 NS_ASSERTION(!aElementTransitions
||
375 aElementTransitions
->mElement
== aElement
, "Element mismatch");
377 if (aWhichStarted
->HasProperty(aProperty
)) {
378 // A later item in transition-property already started a
379 // transition for this property, so we ignore this one.
380 // See comment above and
381 // http://lists.w3.org/Archives/Public/www-style/2009Aug/0109.html .
385 if (nsCSSProps::kAnimTypeTable
[aProperty
] == eStyleAnimType_None
) {
389 dom::AnimationTimeline
* timeline
= aElement
->OwnerDoc()->Timeline();
391 StyleAnimationValue startValue
, endValue
, dummyValue
;
393 ExtractComputedValueForTransition(aProperty
, aOldStyleContext
,
395 ExtractComputedValueForTransition(aProperty
, aNewStyleContext
,
398 bool haveChange
= startValue
!= endValue
;
403 // Check that we can interpolate between these values
404 // (If this is ever a performance problem, we could add a
405 // CanInterpolate method, but it seems fine for now.)
406 StyleAnimationValue::Interpolate(aProperty
, startValue
, endValue
,
409 bool haveCurrentTransition
= false;
410 size_t currentIndex
= nsTArray
<ElementPropertyTransition
>::NoIndex
;
411 const ElementPropertyTransition
*oldPT
= nullptr;
412 if (aElementTransitions
) {
413 AnimationPlayerPtrArray
& players
= aElementTransitions
->mPlayers
;
414 for (size_t i
= 0, i_end
= players
.Length(); i
< i_end
; ++i
) {
415 MOZ_ASSERT(players
[i
]->GetSource() &&
416 players
[i
]->GetSource()->Properties().Length() == 1,
417 "Should have one animation property for a transition");
418 if (players
[i
]->GetSource()->Properties()[0].mProperty
== aProperty
) {
419 haveCurrentTransition
= true;
421 oldPT
= players
[currentIndex
]->GetSource()->AsTransition();
427 // If we got a style change that changed the value to the endpoint
428 // of the currently running transition, we don't want to interrupt
429 // its timing function.
430 // This needs to be before the !shouldAnimate && haveCurrentTransition
431 // case below because we might be close enough to the end of the
432 // transition that the current value rounds to the final value. In
433 // this case, we'll end up with shouldAnimate as false (because
434 // there's no value change), but we need to return early here rather
435 // than cancel the running transition because shouldAnimate is false!
436 MOZ_ASSERT(!oldPT
|| oldPT
->Properties()[0].mSegments
.Length() == 1,
437 "Should have one animation property segment for a transition");
438 if (haveCurrentTransition
&& haveValues
&&
439 oldPT
->Properties()[0].mSegments
[0].mToValue
== endValue
) {
440 // GetAnimationRule already called RestyleForAnimation.
444 nsPresContext
*presContext
= aNewStyleContext
->PresContext();
446 if (!shouldAnimate
) {
447 if (haveCurrentTransition
) {
448 // We're in the middle of a transition, and just got a non-transition
449 // style change to something that we can't animate. This might happen
450 // because we got a non-transition style change changing to the current
451 // in-progress value (which is particularly easy to cause when we're
452 // currently in the 'transition-delay'). It also might happen because we
453 // just got a style change to a value that can't be interpolated.
454 AnimationPlayerPtrArray
& players
= aElementTransitions
->mPlayers
;
455 players
[currentIndex
]->Cancel();
456 oldPT
= nullptr; // Clear pointer so it doesn't dangle
457 players
.RemoveElementAt(currentIndex
);
458 aElementTransitions
->UpdateAnimationGeneration(mPresContext
);
460 if (players
.IsEmpty()) {
461 aElementTransitions
->Destroy();
462 // |aElementTransitions| is now a dangling pointer!
463 aElementTransitions
= nullptr;
465 // GetAnimationRule already called RestyleForAnimation.
470 const nsTimingFunction
&tf
= aTransition
.GetTimingFunction();
471 float delay
= aTransition
.GetDelay();
472 float duration
= aTransition
.GetDuration();
473 if (duration
< 0.0) {
474 // The spec says a negative duration is treated as zero.
478 StyleAnimationValue startForReversingTest
= startValue
;
479 double reversePortion
= 1.0;
481 // If the new transition reverses an existing one, we'll need to
482 // handle the timing differently.
483 if (haveCurrentTransition
&&
484 !oldPT
->IsFinishedTransition() &&
485 oldPT
->mStartForReversingTest
== endValue
) {
486 // Compute the appropriate negative transition-delay such that right
487 // now we'd end up at the current position.
488 double valuePortion
=
489 oldPT
->CurrentValuePortion() * oldPT
->mReversePortion
+
490 (1.0 - oldPT
->mReversePortion
);
491 // A timing function with negative y1 (or y2!) might make
492 // valuePortion negative. In this case, we still want to apply our
493 // reversing logic based on relative distances, not make duration
495 if (valuePortion
< 0.0) {
496 valuePortion
= -valuePortion
;
498 // A timing function with y2 (or y1!) greater than one might
499 // advance past its terminal value. It's probably a good idea to
500 // clamp valuePortion to be at most one to preserve the invariant
501 // that a transition will complete within at most its specified
503 if (valuePortion
> 1.0) {
507 // Negative delays are essentially part of the transition
508 // function, so reduce them along with the duration, but don't
509 // reduce positive delays.
511 delay
*= valuePortion
;
514 duration
*= valuePortion
;
516 startForReversingTest
= oldPT
->Properties()[0].mSegments
[0].mToValue
;
517 reversePortion
= valuePortion
;
520 AnimationTiming timing
;
521 timing
.mIterationDuration
= TimeDuration::FromMilliseconds(duration
);
522 timing
.mDelay
= TimeDuration::FromMilliseconds(delay
);
523 timing
.mIterationCount
= 1;
524 timing
.mDirection
= NS_STYLE_ANIMATION_DIRECTION_NORMAL
;
525 timing
.mFillMode
= NS_STYLE_ANIMATION_FILL_MODE_BACKWARDS
;
527 nsRefPtr
<ElementPropertyTransition
> pt
=
528 new ElementPropertyTransition(aElement
->OwnerDoc(), aElement
,
529 aNewStyleContext
->GetPseudoType(), timing
);
530 pt
->mStartForReversingTest
= startForReversingTest
;
531 pt
->mReversePortion
= reversePortion
;
533 AnimationProperty
& prop
= *pt
->Properties().AppendElement();
534 prop
.mProperty
= aProperty
;
536 AnimationPropertySegment
& segment
= *prop
.mSegments
.AppendElement();
537 segment
.mFromValue
= startValue
;
538 segment
.mToValue
= endValue
;
539 segment
.mFromKey
= 0;
541 segment
.mTimingFunction
.Init(tf
);
543 nsRefPtr
<CSSTransitionPlayer
> player
= new CSSTransitionPlayer(timeline
);
544 // The order of the following two calls is important since PlayFromStyle
545 // will add the player to the PendingPlayerTracker of its source content's
546 // document. When we come to make source writeable (bug 1049975) we should
547 // remove this dependency.
548 player
->SetSource(pt
);
549 player
->PlayFromStyle();
551 if (!aElementTransitions
) {
552 aElementTransitions
=
553 GetAnimationPlayers(aElement
, aNewStyleContext
->GetPseudoType(), true);
554 if (!aElementTransitions
) {
555 NS_WARNING("allocating CommonAnimationManager failed");
560 AnimationPlayerPtrArray
& players
= aElementTransitions
->mPlayers
;
562 for (size_t i
= 0, i_end
= players
.Length(); i
< i_end
; ++i
) {
563 NS_ABORT_IF_FALSE(players
[i
]->GetSource() &&
564 players
[i
]->GetSource()->Properties().Length() == 1,
565 "Should have one animation property for a transition");
566 NS_ABORT_IF_FALSE(i
== currentIndex
||
567 (players
[i
]->GetSource() &&
568 players
[i
]->GetSource()->Properties()[0].mProperty
570 "duplicate transitions for property");
573 if (haveCurrentTransition
) {
574 players
[currentIndex
] = player
;
576 if (!players
.AppendElement(player
)) {
577 NS_WARNING("out of memory");
581 aElementTransitions
->UpdateAnimationGeneration(mPresContext
);
582 aElementTransitions
->PostRestyleForAnimation(presContext
);
585 aWhichStarted
->AddProperty(aProperty
);
589 * nsIStyleRuleProcessor implementation
593 nsTransitionManager::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf
) const
595 return CommonAnimationManager::SizeOfExcludingThis(aMallocSizeOf
);
599 nsTransitionManager::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf
) const
601 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf
);
604 struct TransitionEventInfo
{
605 nsCOMPtr
<nsIContent
> mElement
;
606 InternalTransitionEvent mEvent
;
608 TransitionEventInfo(nsIContent
*aElement
, nsCSSProperty aProperty
,
609 TimeDuration aDuration
, const nsAString
& aPseudoElement
)
611 , mEvent(true, NS_TRANSITION_END
)
613 // XXX Looks like nobody initialize WidgetEvent::time
614 mEvent
.propertyName
=
615 NS_ConvertUTF8toUTF16(nsCSSProps::GetStringValue(aProperty
));
616 mEvent
.elapsedTime
= aDuration
.ToSeconds();
617 mEvent
.pseudoElement
= aPseudoElement
;
620 // InternalTransitionEvent doesn't support copy-construction, so we need
621 // to ourselves in order to work with nsTArray
622 TransitionEventInfo(const TransitionEventInfo
&aOther
)
623 : mElement(aOther
.mElement
)
624 , mEvent(true, NS_TRANSITION_END
)
626 mEvent
.AssignTransitionEventData(aOther
.mEvent
, false);
631 nsTransitionManager::WillRefresh(mozilla::TimeStamp aTime
)
633 NS_ABORT_IF_FALSE(mPresContext
,
634 "refresh driver should not notify additional observers "
635 "after pres context has been destroyed");
636 if (!mPresContext
->GetPresShell()) {
637 // Someone might be keeping mPresContext alive past the point
638 // where it has been torn down; don't bother doing anything in
639 // this case. But do get rid of all our transitions so we stop
640 // triggering refreshes.
641 RemoveAllElementCollections();
645 FlushTransitions(Can_Throttle
);
649 nsTransitionManager::FlushTransitions(FlushFlags aFlags
)
651 if (PR_CLIST_IS_EMPTY(&mElementCollections
)) {
652 // no transitions, leave early
656 nsTArray
<TransitionEventInfo
> events
;
657 TimeStamp now
= mPresContext
->RefreshDriver()->MostRecentRefresh();
658 bool didThrottle
= false;
659 // Trim transitions that have completed, post restyle events for frames that
660 // are still transitioning, and start transitions with delays.
662 PRCList
*next
= PR_LIST_HEAD(&mElementCollections
);
663 while (next
!= &mElementCollections
) {
664 AnimationPlayerCollection
* collection
=
665 static_cast<AnimationPlayerCollection
*>(next
);
666 next
= PR_NEXT_LINK(next
);
669 bool canThrottleTick
= aFlags
== Can_Throttle
&&
670 collection
->CanPerformOnCompositorThread(
671 AnimationPlayerCollection::CanAnimateFlags(0)) &&
672 collection
->CanThrottleAnimation(now
);
674 NS_ABORT_IF_FALSE(collection
->mElement
->GetCrossShadowCurrentDoc() ==
675 mPresContext
->Document(),
676 "Element::UnbindFromTree should have "
677 "destroyed the element transitions object");
679 size_t i
= collection
->mPlayers
.Length();
680 NS_ABORT_IF_FALSE(i
!= 0, "empty transitions list?");
681 bool transitionStartedOrEnded
= false;
684 AnimationPlayer
* player
= collection
->mPlayers
[i
];
685 if (player
->GetSource()->IsFinishedTransition()) {
686 // Actually remove transitions one throttle-able cycle after their
687 // completion. We only clear on a throttle-able cycle because that
688 // means it is a regular restyle tick and thus it is safe to discard
689 // the transition. If the flush is not throttle-able, we might still
690 // have new transitions left to process. See comment below.
691 if (aFlags
== Can_Throttle
) {
692 collection
->mPlayers
.RemoveElementAt(i
);
695 MOZ_ASSERT(player
->GetSource(),
696 "Transitions should have source content");
697 ComputedTiming computedTiming
=
698 player
->GetSource()->GetComputedTiming();
699 if (computedTiming
.mPhase
== ComputedTiming::AnimationPhase_After
) {
700 MOZ_ASSERT(player
->GetSource()->Properties().Length() == 1,
701 "Should have one animation property for a transition");
702 nsCSSProperty prop
= player
->GetSource()->Properties()[0].mProperty
;
703 if (nsCSSProps::PropHasFlags(prop
, CSS_PROPERTY_REPORT_OTHER_NAME
))
705 prop
= nsCSSProps::OtherNameFor(prop
);
707 TimeDuration duration
=
708 player
->GetSource()->Timing().mIterationDuration
;
709 events
.AppendElement(
710 TransitionEventInfo(collection
->mElement
, prop
,
712 collection
->PseudoElement()));
714 // Leave this transition in the list for one more refresh
715 // cycle, since we haven't yet processed its style change, and
716 // if we also have (already, or will have from processing
717 // transitionend events or other refresh driver notifications)
718 // a non-animation style change that would affect it, we need
719 // to know not to start a new transition for the transition
720 // from the almost-completed value to the final value.
721 player
->GetSource()->SetIsFinishedTransition();
722 collection
->UpdateAnimationGeneration(mPresContext
);
723 transitionStartedOrEnded
= true;
724 } else if ((computedTiming
.mPhase
==
725 ComputedTiming::AnimationPhase_Active
) &&
727 !player
->IsRunningOnCompositor()) {
728 // Start a transition with a delay where we should start the
729 // transition proper.
730 collection
->UpdateAnimationGeneration(mPresContext
);
731 transitionStartedOrEnded
= true;
736 // We need to restyle even if the transition rule no longer
737 // applies (in which case we just made it not apply).
738 MOZ_ASSERT(collection
->mElementProperty
==
739 nsGkAtoms::transitionsProperty
||
740 collection
->mElementProperty
==
741 nsGkAtoms::transitionsOfBeforeProperty
||
742 collection
->mElementProperty
==
743 nsGkAtoms::transitionsOfAfterProperty
,
744 "Unexpected element property; might restyle too much");
745 if (!canThrottleTick
|| transitionStartedOrEnded
) {
746 collection
->PostRestyleForAnimation(mPresContext
);
751 if (collection
->mPlayers
.IsEmpty()) {
752 collection
->Destroy();
753 // |collection| is now a dangling pointer!
754 collection
= nullptr;
760 mPresContext
->Document()->SetNeedStyleFlush();
763 for (uint32_t i
= 0, i_end
= events
.Length(); i
< i_end
; ++i
) {
764 TransitionEventInfo
&info
= events
[i
];
765 EventDispatcher::Dispatch(info
.mElement
, mPresContext
, &info
.mEvent
);