Bumping manifests a=b2g-bump
[gecko.git] / layout / style / nsTransitionManager.cpp
blobffbc26bd222ff9f34071b37b69add5189d8bf077
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 player->Cancel();
304 players.RemoveElementAt(i);
305 collection->UpdateAnimationGeneration(mPresContext);
307 } while (i != 0);
309 if (players.IsEmpty()) {
310 collection->Destroy();
311 collection = nullptr;
315 if (!startedAny) {
316 return nullptr;
319 NS_ABORT_IF_FALSE(collection, "must have element transitions if we started "
320 "any transitions");
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
335 // rule.
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();
360 void
361 nsTransitionManager::ConsiderStartingTransition(
362 nsCSSProperty aProperty,
363 const StyleTransition& aTransition,
364 dom::Element* aElement,
365 AnimationPlayerCollection*& aElementTransitions,
366 nsStyleContext* aOldStyleContext,
367 nsStyleContext* aNewStyleContext,
368 bool* aStartedAny,
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 .
382 return;
385 if (nsCSSProps::kAnimTypeTable[aProperty] == eStyleAnimType_None) {
386 return;
389 dom::AnimationTimeline* timeline = aElement->OwnerDoc()->Timeline();
391 StyleAnimationValue startValue, endValue, dummyValue;
392 bool haveValues =
393 ExtractComputedValueForTransition(aProperty, aOldStyleContext,
394 startValue) &&
395 ExtractComputedValueForTransition(aProperty, aNewStyleContext,
396 endValue);
398 bool haveChange = startValue != endValue;
400 bool shouldAnimate =
401 haveValues &&
402 haveChange &&
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,
407 0.5, dummyValue);
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;
420 currentIndex = i;
421 oldPT = players[currentIndex]->GetSource()->AsTransition();
422 break;
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.
441 return;
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.
467 return;
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.
475 duration = 0.0;
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
494 // negative.
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
502 // time.
503 if (valuePortion > 1.0) {
504 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.
510 if (delay < 0.0f) {
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;
540 segment.mToKey = 1;
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");
556 return;
560 AnimationPlayerPtrArray& players = aElementTransitions->mPlayers;
561 #ifdef DEBUG
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
569 != aProperty),
570 "duplicate transitions for property");
572 #endif
573 if (haveCurrentTransition) {
574 players[currentIndex] = player;
575 } else {
576 if (!players.AppendElement(player)) {
577 NS_WARNING("out of memory");
578 return;
581 aElementTransitions->UpdateAnimationGeneration(mPresContext);
582 aElementTransitions->PostRestyleForAnimation(presContext);
584 *aStartedAny = true;
585 aWhichStarted->AddProperty(aProperty);
589 * nsIStyleRuleProcessor implementation
592 /* virtual */ size_t
593 nsTransitionManager::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
595 return CommonAnimationManager::SizeOfExcludingThis(aMallocSizeOf);
598 /* virtual */ size_t
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)
610 : mElement(aElement)
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);
630 /* virtual */ void
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();
642 return;
645 FlushTransitions(Can_Throttle);
648 void
649 nsTransitionManager::FlushTransitions(FlushFlags aFlags)
651 if (PR_CLIST_IS_EMPTY(&mElementCollections)) {
652 // no transitions, leave early
653 return;
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);
668 collection->Tick();
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;
682 do {
683 --i;
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);
694 } else {
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,
711 duration,
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) &&
726 canThrottleTick &&
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;
734 } while (i != 0);
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);
747 } else {
748 didThrottle = true;
751 if (collection->mPlayers.IsEmpty()) {
752 collection->Destroy();
753 // |collection| is now a dangling pointer!
754 collection = nullptr;
759 if (didThrottle) {
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);
767 if (!mPresContext) {
768 break;