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 /* ***** BEGIN LICENSE BLOCK *****
4 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 * The contents of this file are subject to the Mozilla Public License Version
7 * 1.1 (the "License"); you may not use this file except in compliance with
8 * the License. You may obtain a copy of the License at
9 * http://www.mozilla.org/MPL/
11 * Software distributed under the License is distributed on an "AS IS" basis,
12 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 * for the specific language governing rights and limitations under the
16 * The Original Code is nsTransitionManager.
18 * The Initial Developer of the Original Code is the Mozilla Foundation.
19 * Portions created by the Initial Developer are Copyright (C) 2009
20 * the Initial Developer. All Rights Reserved.
23 * L. David Baron <dbaron@dbaron.org>, Mozilla Corporation (original author)
25 * Alternatively, the contents of this file may be used under the terms of
26 * either the GNU General Public License Version 2 or later (the "GPL"), or
27 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 * in which case the provisions of the GPL or the LGPL are applicable instead
29 * of those above. If you wish to allow use of your version of this file only
30 * under the terms of either the GPL or the LGPL, and not to allow others to
31 * use your version of this file under the terms of the MPL, indicate your
32 * decision by deleting the provisions above and replace them with the notice
33 * and other provisions required by the GPL or the LGPL. If you do not delete
34 * the provisions above, a recipient may use your version of this file under
35 * the terms of any one of the MPL, the GPL or the LGPL.
37 * ***** END LICENSE BLOCK ***** */
39 /* Code to start and animate CSS transitions. */
41 #include "nsTransitionManager.h"
42 #include "nsIContent.h"
43 #include "nsStyleContext.h"
44 #include "nsCSSProps.h"
45 #include "mozilla/TimeStamp.h"
46 #include "nsRefreshDriver.h"
47 #include "nsRuleProcessorData.h"
48 #include "nsIStyleRule.h"
49 #include "nsRuleWalker.h"
50 #include "nsRuleData.h"
51 #include "nsSMILKeySpline.h"
53 #include "nsCSSPropertySet.h"
54 #include "nsStyleAnimation.h"
55 #include "nsEventDispatcher.h"
56 #include "nsGUIEvent.h"
57 #include "mozilla/dom/Element.h"
59 using mozilla::TimeStamp
;
60 using mozilla::TimeDuration
;
62 namespace dom
= mozilla::dom
;
64 /*****************************************************************************
66 *****************************************************************************/
68 struct ElementPropertyTransition
70 nsCSSProperty mProperty
;
71 nsStyleAnimation::Value mStartValue
, mEndValue
;
72 TimeStamp mStartTime
; // actual start plus transition delay
74 // data from the relevant nsTransition
75 TimeDuration mDuration
;
76 nsSMILKeySpline mTimingFunction
;
78 // This is the start value to be used for a check for whether a
79 // transition is being reversed. Normally the same as mStartValue,
80 // except when this transition started as the reversal of another
81 // in-progress transition. Needed so we can handle two reverses in a
83 nsStyleAnimation::Value mStartForReversingTest
;
84 // Likewise, the portion (in value space) of the "full" reversed
85 // transition that we're actually covering. For example, if a :hover
86 // effect has a transition that moves the element 10px to the right
87 // (by changing 'left' from 0px to 10px), and the mouse moves in to
88 // the element (starting the transition) but then moves out after the
89 // transition has advanced 4px, the second transition (from 10px/4px
90 // to 0px) will have mReversePortion of 0.4. (If the mouse then moves
91 // in again when the transition is back to 2px, the mReversePortion
92 // for the third transition (from 0px/2px to 10px) will be 0.8.
93 double mReversePortion
;
95 // Compute the portion of the *value* space that we should be through
96 // at the given time. (The input to the transition timing function
97 // has time units, the output has value units.)
98 double ValuePortionFor(TimeStamp aRefreshTime
) const;
100 PRBool
IsRemovedSentinel() const
102 return mStartTime
.IsNull();
105 void SetRemovedSentinel()
107 // assign the null time stamp
108 mStartTime
= TimeStamp();
113 ElementPropertyTransition::ValuePortionFor(TimeStamp aRefreshTime
) const
115 // Set |timePortion| to the portion of the way we are through the time
116 // input to the transition's timing function (always within the range
118 double duration
= mDuration
.ToSeconds();
119 NS_ABORT_IF_FALSE(duration
>= 0.0, "negative duration forbidden");
121 if (duration
== 0.0) {
122 // When duration is zero, we can still have a transition when delay
123 // is nonzero. mStartTime already incorporates delay.
124 if (aRefreshTime
>= mStartTime
) {
130 timePortion
= (aRefreshTime
- mStartTime
).ToSeconds() / duration
;
131 if (timePortion
< 0.0)
132 timePortion
= 0.0; // use start value during transition-delay
133 if (timePortion
> 1.0)
134 timePortion
= 1.0; // we might be behind on flushing
137 return mTimingFunction
.GetSplineValue(timePortion
);
141 * A style rule that maps property-nsStyleAnimation::Value pairs.
143 class AnimValuesStyleRule
: public nsIStyleRule
146 // nsISupports implementation
149 // nsIStyleRule implementation
150 virtual void MapRuleInfoInto(nsRuleData
* aRuleData
);
152 virtual void List(FILE* out
= stdout
, PRInt32 aIndent
= 0) const;
155 void AddValue(nsCSSProperty aProperty
, nsStyleAnimation::Value
&aStartValue
)
157 PropertyValuePair v
= { aProperty
, aStartValue
};
158 mPropertyValuePairs
.AppendElement(v
);
161 // Caller must fill in returned value, when non-null.
162 nsStyleAnimation::Value
* AddEmptyValue(nsCSSProperty aProperty
)
164 PropertyValuePair
*p
= mPropertyValuePairs
.AppendElement();
168 p
->mProperty
= aProperty
;
172 struct PropertyValuePair
{
173 nsCSSProperty mProperty
;
174 nsStyleAnimation::Value mValue
;
178 nsTArray
<PropertyValuePair
> mPropertyValuePairs
;
181 struct ElementTransitions
: public PRCList
183 ElementTransitions(dom::Element
*aElement
, nsIAtom
*aElementProperty
,
184 nsTransitionManager
*aTransitionManager
)
186 , mElementProperty(aElementProperty
)
187 , mTransitionManager(aTransitionManager
)
191 ~ElementTransitions()
193 PR_REMOVE_LINK(this);
194 mTransitionManager
->TransitionsRemoved();
199 // This will call our destructor.
200 mElement
->DeleteProperty(mElementProperty
);
203 void EnsureStyleRuleFor(TimeStamp aRefreshTime
);
206 // Either zero or one for each CSS property:
207 nsTArray
<ElementPropertyTransition
> mPropertyTransitions
;
209 // This style rule overrides style data with the currently
210 // transitioning value for an element that is executing a transition.
211 // It only matches when styling with animation. When we style without
212 // animation, we need to not use it so that we can detect any new
213 // changes; if necessary we restyle immediately afterwards with
215 nsRefPtr
<AnimValuesStyleRule
> mStyleRule
;
216 // The refresh time associated with mStyleRule.
217 TimeStamp mStyleRuleRefreshTime
;
219 dom::Element
*mElement
;
221 // the atom we use in mElement's prop table (must be a static atom,
222 // i.e., in an atom list)
223 nsIAtom
*mElementProperty
;
225 nsTransitionManager
*mTransitionManager
;
229 ElementTransitionsPropertyDtor(void *aObject
,
230 nsIAtom
*aPropertyName
,
231 void *aPropertyValue
,
234 ElementTransitions
*et
= static_cast<ElementTransitions
*>(aPropertyValue
);
239 ElementTransitions::EnsureStyleRuleFor(TimeStamp aRefreshTime
)
241 if (!mStyleRule
|| mStyleRuleRefreshTime
!= aRefreshTime
) {
242 mStyleRule
= new AnimValuesStyleRule();
243 mStyleRuleRefreshTime
= aRefreshTime
;
245 for (PRUint32 i
= 0, i_end
= mPropertyTransitions
.Length(); i
< i_end
; ++i
)
247 ElementPropertyTransition
&pt
= mPropertyTransitions
[i
];
248 if (pt
.IsRemovedSentinel()) {
252 nsStyleAnimation::Value
*val
= mStyleRule
->AddEmptyValue(pt
.mProperty
);
257 double valuePortion
= pt
.ValuePortionFor(aRefreshTime
);
261 nsStyleAnimation::Interpolate(pt
.mProperty
,
262 pt
.mStartValue
, pt
.mEndValue
,
264 NS_ABORT_IF_FALSE(ok
, "could not interpolate values");
269 NS_IMPL_ISUPPORTS1(AnimValuesStyleRule
, nsIStyleRule
)
272 AnimValuesStyleRule::MapRuleInfoInto(nsRuleData
* aRuleData
)
274 nsStyleContext
*contextParent
= aRuleData
->mStyleContext
->GetParent();
275 if (contextParent
&& contextParent
->HasPseudoElementData()) {
276 // Don't apply transitions to things inside of pseudo-elements.
277 // FIXME (Bug 522599): Add tests for this.
281 for (PRUint32 i
= 0, i_end
= mPropertyValuePairs
.Length(); i
< i_end
; ++i
) {
282 PropertyValuePair
&cv
= mPropertyValuePairs
[i
];
283 if (aRuleData
->mSIDs
& nsCachedStyleData::GetBitForSID(
284 nsCSSProps::kSIDTable
[cv
.mProperty
]))
286 nsCSSValue
*prop
= aRuleData
->ValueFor(cv
.mProperty
);
287 if (prop
->GetUnit() == eCSSUnit_Null
) {
291 nsStyleAnimation::UncomputeValue(cv
.mProperty
,
292 aRuleData
->mPresContext
,
294 NS_ABORT_IF_FALSE(ok
, "could not store computed value");
302 AnimValuesStyleRule::List(FILE* out
, PRInt32 aIndent
) const
308 /*****************************************************************************
309 * nsTransitionManager *
310 *****************************************************************************/
312 nsTransitionManager::nsTransitionManager(nsPresContext
*aPresContext
)
313 : mPresContext(aPresContext
)
315 PR_INIT_CLIST(&mElementTransitions
);
318 nsTransitionManager::~nsTransitionManager()
320 NS_ABORT_IF_FALSE(!mPresContext
, "Disconnect should have been called");
324 nsTransitionManager::Disconnect()
326 // Content nodes might outlive the transition manager.
327 RemoveAllTransitions();
329 mPresContext
= nsnull
;
333 nsTransitionManager::RemoveAllTransitions()
335 while (!PR_CLIST_IS_EMPTY(&mElementTransitions
)) {
336 ElementTransitions
*head
= static_cast<ElementTransitions
*>(
337 PR_LIST_HEAD(&mElementTransitions
));
343 TransExtractComputedValue(nsCSSProperty aProperty
,
344 nsStyleContext
* aStyleContext
,
345 nsStyleAnimation::Value
& aComputedValue
)
348 nsStyleAnimation::ExtractComputedValue(aProperty
, aStyleContext
,
350 if (aProperty
== eCSSProperty_visibility
) {
351 NS_ABORT_IF_FALSE(aComputedValue
.GetUnit() ==
352 nsStyleAnimation::eUnit_Enumerated
,
354 aComputedValue
.SetIntValue(aComputedValue
.GetIntValue(),
355 nsStyleAnimation::eUnit_Visibility
);
360 already_AddRefed
<nsIStyleRule
>
361 nsTransitionManager::StyleContextChanged(dom::Element
*aElement
,
362 nsStyleContext
*aOldStyleContext
,
363 nsStyleContext
*aNewStyleContext
)
365 NS_PRECONDITION(aOldStyleContext
->GetPseudo() ==
366 aNewStyleContext
->GetPseudo(),
367 "pseudo type mismatch");
368 // If we were called from ReparentStyleContext, this assertion would
369 // actually fire. If we need to be called from there, we can probably
370 // just remove it; the condition probably isn't critical, although
371 // it's worth thinking about some more.
372 NS_PRECONDITION(aOldStyleContext
->HasPseudoElementData() ==
373 aNewStyleContext
->HasPseudoElementData(),
374 "pseudo type mismatch");
376 // NOTE: Things in this function (and ConsiderStartingTransition)
377 // should never call PeekStyleData because we don't preserve gotten
378 // structs across reframes.
380 // Return sooner (before the startedAny check below) for the most
381 // common case: no transitions specified or running.
382 const nsStyleDisplay
*disp
= aNewStyleContext
->GetStyleDisplay();
383 nsCSSPseudoElements::Type pseudoType
= aNewStyleContext
->GetPseudoType();
384 if (pseudoType
!= nsCSSPseudoElements::ePseudo_NotPseudoElement
) {
385 if (pseudoType
!= nsCSSPseudoElements::ePseudo_before
&&
386 pseudoType
!= nsCSSPseudoElements::ePseudo_after
) {
390 NS_ASSERTION((pseudoType
== nsCSSPseudoElements::ePseudo_before
&&
391 aElement
->Tag() == nsGkAtoms::mozgeneratedcontentbefore
) ||
392 (pseudoType
== nsCSSPseudoElements::ePseudo_after
&&
393 aElement
->Tag() == nsGkAtoms::mozgeneratedcontentafter
),
394 "Unexpected aElement coming through");
396 // Else the element we want to use from now on is the element the
397 // :before or :after is attached to.
398 aElement
= aElement
->GetParent()->AsElement();
401 ElementTransitions
*et
=
402 GetElementTransitions(aElement
, pseudoType
, PR_FALSE
);
404 disp
->mTransitionPropertyCount
== 1 &&
405 disp
->mTransitions
[0].GetDelay() == 0.0f
&&
406 disp
->mTransitions
[0].GetDuration() == 0.0f
) {
411 if (aNewStyleContext
->PresContext()->IsProcessingAnimationStyleChange()) {
415 if (aNewStyleContext
->GetParent() &&
416 aNewStyleContext
->GetParent()->HasPseudoElementData()) {
417 // Ignore transitions on things that inherit properties from
419 // FIXME (Bug 522599): Add tests for this.
423 // Per http://lists.w3.org/Archives/Public/www-style/2009Aug/0109.html
424 // I'll consider only the transitions from the number of items in
425 // 'transition-property' on down, and later ones will override earlier
426 // ones (tracked using |whichStarted|).
427 PRBool startedAny
= PR_FALSE
;
428 nsCSSPropertySet whichStarted
;
429 for (PRUint32 i
= disp
->mTransitionPropertyCount
; i
-- != 0; ) {
430 const nsTransition
& t
= disp
->mTransitions
[i
];
431 // Check delay and duration first, since they default to zero, and
432 // when they're both zero, we can ignore the transition.
433 if (t
.GetDelay() != 0.0f
|| t
.GetDuration() != 0.0f
) {
434 // We might have something to transition. See if any of the
435 // properties in question changed and are animatable.
436 // FIXME: Would be good to find a way to share code between this
437 // interpretation of transition-property and the one below.
438 nsCSSProperty property
= t
.GetProperty();
439 if (property
== eCSSPropertyExtra_no_properties
||
440 property
== eCSSProperty_UNKNOWN
) {
441 // Nothing to do, but need to exclude this from cases below.
442 } else if (property
== eCSSPropertyExtra_all_properties
) {
443 for (nsCSSProperty p
= nsCSSProperty(0);
444 p
< eCSSProperty_COUNT_no_shorthands
;
445 p
= nsCSSProperty(p
+ 1)) {
446 ConsiderStartingTransition(p
, t
, aElement
, et
,
447 aOldStyleContext
, aNewStyleContext
,
448 &startedAny
, &whichStarted
);
450 } else if (nsCSSProps::IsShorthand(property
)) {
451 CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(subprop
, property
) {
452 ConsiderStartingTransition(*subprop
, t
, aElement
, et
,
453 aOldStyleContext
, aNewStyleContext
,
454 &startedAny
, &whichStarted
);
457 ConsiderStartingTransition(property
, t
, aElement
, et
,
458 aOldStyleContext
, aNewStyleContext
,
459 &startedAny
, &whichStarted
);
464 // Stop any transitions for properties that are no longer in
465 // 'transition-property'.
466 // Also stop any transitions for properties that just changed (and are
467 // still in the set of properties to transition), but we didn't just
468 // start the transition because delay and duration are both zero.
470 PRBool checkProperties
=
471 disp
->mTransitions
[0].GetProperty() != eCSSPropertyExtra_all_properties
;
472 nsCSSPropertySet allTransitionProperties
;
473 if (checkProperties
) {
474 for (PRUint32 i
= disp
->mTransitionPropertyCount
; i
-- != 0; ) {
475 const nsTransition
& t
= disp
->mTransitions
[i
];
476 // FIXME: Would be good to find a way to share code between this
477 // interpretation of transition-property and the one above.
478 nsCSSProperty property
= t
.GetProperty();
479 if (property
== eCSSPropertyExtra_no_properties
||
480 property
== eCSSProperty_UNKNOWN
) {
481 // Nothing to do, but need to exclude this from cases below.
482 } else if (property
== eCSSPropertyExtra_all_properties
) {
483 for (nsCSSProperty p
= nsCSSProperty(0);
484 p
< eCSSProperty_COUNT_no_shorthands
;
485 p
= nsCSSProperty(p
+ 1)) {
486 allTransitionProperties
.AddProperty(p
);
488 } else if (nsCSSProps::IsShorthand(property
)) {
489 CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(subprop
, property
) {
490 allTransitionProperties
.AddProperty(*subprop
);
493 allTransitionProperties
.AddProperty(property
);
498 nsTArray
<ElementPropertyTransition
> &pts
= et
->mPropertyTransitions
;
499 PRUint32 i
= pts
.Length();
500 NS_ABORT_IF_FALSE(i
!= 0, "empty transitions list?");
501 nsStyleAnimation::Value currentValue
;
504 ElementPropertyTransition
&pt
= pts
[i
];
505 // properties no longer in 'transition-property'
506 if ((checkProperties
&&
507 !allTransitionProperties
.HasProperty(pt
.mProperty
)) ||
508 // properties whose computed values changed but delay and
509 // duration are both zero
510 !TransExtractComputedValue(pt
.mProperty
, aNewStyleContext
,
512 currentValue
!= pt
.mEndValue
) {
513 // stop the transition
514 pts
.RemoveElementAt(i
);
528 NS_ABORT_IF_FALSE(et
, "must have element transitions if we started "
531 // In the CSS working group discussion (2009 Jul 15 telecon,
532 // http://www.w3.org/mid/4A5E1470.4030904@inkedblade.net ) of
533 // http://lists.w3.org/Archives/Public/www-style/2009Jun/0121.html ,
534 // the working group decided that a transition property on an
535 // element should not cause any transitions if the property change
536 // is itself inheriting a value that is transitioning on an
537 // ancestor. So, to get the correct behavior, we continue the
538 // restyle that caused this transition using a "covering" rule that
539 // covers up any changes on which we started transitions, so that
540 // descendants don't start their own transitions. (In the case of
541 // negative transition delay, this covering rule produces different
542 // results than applying the transition rule immediately would).
543 // Our caller is responsible for restyling again using this covering
546 nsRefPtr
<AnimValuesStyleRule
> coverRule
= new AnimValuesStyleRule
;
548 NS_WARNING("out of memory");
552 nsTArray
<ElementPropertyTransition
> &pts
= et
->mPropertyTransitions
;
553 for (PRUint32 i
= 0, i_end
= pts
.Length(); i
< i_end
; ++i
) {
554 ElementPropertyTransition
&pt
= pts
[i
];
555 if (whichStarted
.HasProperty(pt
.mProperty
)) {
556 coverRule
->AddValue(pt
.mProperty
, pt
.mStartValue
);
560 return coverRule
.forget();
564 nsTransitionManager::ConsiderStartingTransition(nsCSSProperty aProperty
,
565 const nsTransition
& aTransition
,
566 dom::Element
*aElement
,
567 ElementTransitions
*&aElementTransitions
,
568 nsStyleContext
*aOldStyleContext
,
569 nsStyleContext
*aNewStyleContext
,
571 nsCSSPropertySet
*aWhichStarted
)
573 // IsShorthand itself will assert if aProperty is not a property.
574 NS_ABORT_IF_FALSE(!nsCSSProps::IsShorthand(aProperty
),
575 "property out of range");
577 if (aWhichStarted
->HasProperty(aProperty
)) {
578 // A later item in transition-property already started a
579 // transition for this property, so we ignore this one.
580 // See comment above and
581 // http://lists.w3.org/Archives/Public/www-style/2009Aug/0109.html .
585 if (nsCSSProps::kAnimTypeTable
[aProperty
] == eStyleAnimType_None
) {
589 ElementPropertyTransition pt
;
590 nsStyleAnimation::Value dummyValue
;
592 TransExtractComputedValue(aProperty
, aOldStyleContext
, pt
.mStartValue
) &&
593 TransExtractComputedValue(aProperty
, aNewStyleContext
, pt
.mEndValue
);
594 PRBool shouldAnimate
=
596 pt
.mStartValue
!= pt
.mEndValue
&&
597 // Check that we can interpolate between these values
598 // (If this is ever a performance problem, we could add a
599 // CanInterpolate method, but it seems fine for now.)
600 nsStyleAnimation::Interpolate(aProperty
, pt
.mStartValue
, pt
.mEndValue
,
603 PRUint32 currentIndex
= nsTArray
<ElementPropertyTransition
>::NoIndex
;
604 if (aElementTransitions
) {
605 nsTArray
<ElementPropertyTransition
> &pts
=
606 aElementTransitions
->mPropertyTransitions
;
607 for (PRUint32 i
= 0, i_end
= pts
.Length(); i
< i_end
; ++i
) {
608 if (pts
[i
].mProperty
== aProperty
) {
615 nsPresContext
*presContext
= aNewStyleContext
->PresContext();
617 if (!shouldAnimate
) {
618 nsTArray
<ElementPropertyTransition
> &pts
=
619 aElementTransitions
->mPropertyTransitions
;
620 if (currentIndex
!= nsTArray
<ElementPropertyTransition
>::NoIndex
&&
621 (!haveValues
|| pts
[currentIndex
].mEndValue
!= pt
.mEndValue
)) {
622 // We're in the middle of a transition, but just got a
623 // non-transition style change changing to exactly the
624 // current in-progress value. (This is quite easy to cause
625 // using 'transition-delay'.)
627 // We also check that this current in-progress value is different
628 // from the end value; we don't want to cancel a transition that
629 // is almost done (and whose current value rounds to its end
630 // value) just because we got an unrelated style change.
631 pts
.RemoveElementAt(currentIndex
);
633 aElementTransitions
->Destroy();
634 // |aElementTransitions| is now a dangling pointer!
635 aElementTransitions
= nsnull
;
637 // WalkTransitionRule already called RestyleForAnimation.
642 TimeStamp mostRecentRefresh
=
643 presContext
->RefreshDriver()->MostRecentRefresh();
645 const nsTimingFunction
&tf
= aTransition
.GetTimingFunction();
646 float delay
= aTransition
.GetDelay();
647 float duration
= aTransition
.GetDuration();
648 if (duration
< 0.0) {
649 // The spec says a negative duration is treated as zero.
652 pt
.mStartForReversingTest
= pt
.mStartValue
;
653 pt
.mReversePortion
= 1.0;
655 // We need to check two things if we have a currently running
656 // transition for this property.
657 if (currentIndex
!= nsTArray
<ElementPropertyTransition
>::NoIndex
) {
658 const ElementPropertyTransition
&oldPT
=
659 aElementTransitions
->mPropertyTransitions
[currentIndex
];
661 if (oldPT
.mEndValue
== pt
.mEndValue
) {
662 // If we got a style change that changed the value to the endpoint
663 // of the currently running transition, we don't want to interrupt
664 // its timing function.
665 // WalkTransitionRule already called RestyleForAnimation.
669 // If the new transition reverses the old one, we'll need to handle
670 // the timing differently.
671 if (!oldPT
.IsRemovedSentinel() &&
672 oldPT
.mStartForReversingTest
== pt
.mEndValue
) {
673 // Compute the appropriate negative transition-delay such that right
674 // now we'd end up at the current position.
675 double valuePortion
=
676 oldPT
.ValuePortionFor(mostRecentRefresh
) * oldPT
.mReversePortion
+
677 (1.0 - oldPT
.mReversePortion
);
678 // A timing function with negative y1 (or y2!) might make
679 // valuePortion negative. In this case, we still want to apply our
680 // reversing logic based on relative distances, not make duration
682 if (valuePortion
< 0.0)
683 valuePortion
= -valuePortion
;
684 // A timing function with y2 (or y1!) greater than one might
685 // advance past its terminal value. It's probably a good idea to
686 // clamp valuePortion to be at most one to preserve the invariant
687 // that a transition will complete within at most its specified
689 if (valuePortion
> 1.0)
692 // Negative delays are essentially part of the transition
693 // function, so reduce them along with the duration, but don't
694 // reduce positive delays.
696 delay
*= valuePortion
;
697 duration
*= valuePortion
;
699 pt
.mStartForReversingTest
= oldPT
.mEndValue
;
700 pt
.mReversePortion
= valuePortion
;
704 pt
.mProperty
= aProperty
;
705 pt
.mStartTime
= mostRecentRefresh
+ TimeDuration::FromMilliseconds(delay
);
706 pt
.mDuration
= TimeDuration::FromMilliseconds(duration
);
707 pt
.mTimingFunction
.Init(tf
.mX1
, tf
.mY1
, tf
.mX2
, tf
.mY2
);
709 if (!aElementTransitions
) {
710 aElementTransitions
=
711 GetElementTransitions(aElement
, aNewStyleContext
->GetPseudoType(),
713 if (!aElementTransitions
) {
714 NS_WARNING("allocating ElementTransitions failed");
719 nsTArray
<ElementPropertyTransition
> &pts
=
720 aElementTransitions
->mPropertyTransitions
;
722 for (PRUint32 i
= 0, i_end
= pts
.Length(); i
< i_end
; ++i
) {
723 NS_ABORT_IF_FALSE(i
== currentIndex
||
724 pts
[i
].mProperty
!= aProperty
,
725 "duplicate transitions for property");
728 if (currentIndex
!= nsTArray
<ElementPropertyTransition
>::NoIndex
) {
729 pts
[currentIndex
] = pt
;
731 if (!pts
.AppendElement(pt
)) {
732 NS_WARNING("out of memory");
738 aNewStyleContext
->GetPseudoType() ==
739 nsCSSPseudoElements::ePseudo_NotPseudoElement
?
740 eRestyle_Self
: eRestyle_Subtree
;
741 presContext
->PresShell()->RestyleForAnimation(aElement
, hint
);
743 *aStartedAny
= PR_TRUE
;
744 aWhichStarted
->AddProperty(aProperty
);
748 nsTransitionManager::GetElementTransitions(dom::Element
*aElement
,
749 nsCSSPseudoElements::Type aPseudoType
,
750 PRBool aCreateIfNeeded
)
752 if (!aCreateIfNeeded
&& PR_CLIST_IS_EMPTY(&mElementTransitions
)) {
753 // Early return for the most common case.
758 if (aPseudoType
== nsCSSPseudoElements::ePseudo_NotPseudoElement
) {
759 propName
= nsGkAtoms::transitionsProperty
;
760 } else if (aPseudoType
== nsCSSPseudoElements::ePseudo_before
) {
761 propName
= nsGkAtoms::transitionsOfBeforeProperty
;
762 } else if (aPseudoType
== nsCSSPseudoElements::ePseudo_after
) {
763 propName
= nsGkAtoms::transitionsOfAfterProperty
;
765 NS_ASSERTION(!aCreateIfNeeded
,
766 "should never try to create transitions for pseudo "
767 "other than :before or :after");
770 ElementTransitions
*et
= static_cast<ElementTransitions
*>(
771 aElement
->GetProperty(propName
));
772 if (!et
&& aCreateIfNeeded
) {
773 // FIXME: Consider arena-allocating?
774 et
= new ElementTransitions(aElement
, propName
, this);
776 NS_WARNING("out of memory");
779 nsresult rv
= aElement
->SetProperty(propName
, et
,
780 ElementTransitionsPropertyDtor
, nsnull
);
782 NS_WARNING("SetProperty failed");
787 AddElementTransitions(et
);
794 nsTransitionManager::AddElementTransitions(ElementTransitions
* aElementTransitions
)
796 if (PR_CLIST_IS_EMPTY(&mElementTransitions
)) {
797 // We need to observe the refresh driver.
798 nsRefreshDriver
*rd
= mPresContext
->RefreshDriver();
799 rd
->AddRefreshObserver(this, Flush_Style
);
802 PR_INSERT_BEFORE(aElementTransitions
, &mElementTransitions
);
806 * nsISupports implementation
809 NS_IMPL_ISUPPORTS1(nsTransitionManager
, nsIStyleRuleProcessor
)
812 * nsIStyleRuleProcessor implementation
816 nsTransitionManager::WalkTransitionRule(RuleProcessorData
* aData
,
817 nsCSSPseudoElements::Type aPseudoType
)
819 ElementTransitions
*et
=
820 GetElementTransitions(aData
->mElement
, aPseudoType
, PR_FALSE
);
825 if (aData
->mPresContext
->IsProcessingRestyles() &&
826 !aData
->mPresContext
->IsProcessingAnimationStyleChange()) {
827 // If we're processing a normal style change rather than one from
828 // animation, don't add the transition rule. This allows us to
829 // compute the new style value rather than having the transition
830 // override it, so that we can start transitioning differently.
832 // We need to immediately restyle with animation
836 aPseudoType
== nsCSSPseudoElements::ePseudo_NotPseudoElement
?
837 eRestyle_Self
: eRestyle_Subtree
;
838 mPresContext
->PresShell()->RestyleForAnimation(aData
->mElement
, hint
);
843 et
->EnsureStyleRuleFor(
844 aData
->mPresContext
->RefreshDriver()->MostRecentRefresh());
846 aData
->mRuleWalker
->Forward(et
->mStyleRule
);
850 nsTransitionManager::RulesMatching(ElementRuleProcessorData
* aData
)
852 NS_ABORT_IF_FALSE(aData
->mPresContext
== mPresContext
,
853 "pres context mismatch");
854 WalkTransitionRule(aData
,
855 nsCSSPseudoElements::ePseudo_NotPseudoElement
);
859 nsTransitionManager::RulesMatching(PseudoElementRuleProcessorData
* aData
)
861 NS_ABORT_IF_FALSE(aData
->mPresContext
== mPresContext
,
862 "pres context mismatch");
864 // Note: If we're the only thing keeping a pseudo-element frame alive
865 // (per ProbePseudoStyleContext), we still want to keep it alive, so
867 WalkTransitionRule(aData
, aData
->mPseudoType
);
871 nsTransitionManager::RulesMatching(AnonBoxRuleProcessorData
* aData
)
877 nsTransitionManager::RulesMatching(XULTreeRuleProcessorData
* aData
)
883 nsTransitionManager::HasStateDependentStyle(StateRuleProcessorData
* aData
)
885 return nsRestyleHint(0);
889 nsTransitionManager::HasDocumentStateDependentStyle(StateRuleProcessorData
* aData
)
895 nsTransitionManager::HasAttributeDependentStyle(AttributeRuleProcessorData
* aData
)
897 return nsRestyleHint(0);
901 nsTransitionManager::MediumFeaturesChanged(nsPresContext
* aPresContext
)
906 struct TransitionEventInfo
{
907 nsCOMPtr
<nsIContent
> mElement
;
908 nsTransitionEvent mEvent
;
910 TransitionEventInfo(nsIContent
*aElement
, nsCSSProperty aProperty
,
911 TimeDuration aDuration
)
912 : mElement(aElement
),
913 mEvent(PR_TRUE
, NS_TRANSITION_END
,
914 NS_ConvertUTF8toUTF16(nsCSSProps::GetStringValue(aProperty
)),
915 aDuration
.ToSeconds())
919 // nsTransitionEvent doesn't support copy-construction, so we need
920 // to ourselves in order to work with nsTArray
921 TransitionEventInfo(const TransitionEventInfo
&aOther
)
922 : mElement(aOther
.mElement
),
923 mEvent(PR_TRUE
, NS_TRANSITION_END
,
924 aOther
.mEvent
.propertyName
, aOther
.mEvent
.elapsedTime
)
930 nsTransitionManager::WillRefresh(mozilla::TimeStamp aTime
)
932 NS_ABORT_IF_FALSE(mPresContext
,
933 "refresh driver should not notify additional observers "
934 "after pres context has been destroyed");
935 if (!mPresContext
->GetPresShell()) {
936 // Someone might be keeping mPresContext alive past the point
937 // where it has been torn down; don't bother doing anything in
938 // this case. But do get rid of all our transitions so we stop
939 // triggering refreshes.
940 RemoveAllTransitions();
944 nsTArray
<TransitionEventInfo
> events
;
946 // Trim transitions that have completed, and post restyle events for
947 // frames that are still transitioning.
949 PRCList
*next
= PR_LIST_HEAD(&mElementTransitions
);
950 while (next
!= &mElementTransitions
) {
951 ElementTransitions
*et
= static_cast<ElementTransitions
*>(next
);
952 next
= PR_NEXT_LINK(next
);
954 NS_ABORT_IF_FALSE(et
->mElement
->GetCurrentDoc() ==
955 mPresContext
->Document(),
956 "nsGenericElement::UnbindFromTree should have "
957 "destroyed the element transitions object");
959 PRUint32 i
= et
->mPropertyTransitions
.Length();
960 NS_ABORT_IF_FALSE(i
!= 0, "empty transitions list?");
963 ElementPropertyTransition
&pt
= et
->mPropertyTransitions
[i
];
964 if (pt
.IsRemovedSentinel()) {
965 // Actually remove transitions one cycle after their
966 // completion. See comment below.
967 et
->mPropertyTransitions
.RemoveElementAt(i
);
968 } else if (pt
.mStartTime
+ pt
.mDuration
<= aTime
) {
969 // This transition has completed.
971 // Fire transitionend events only for transitions on elements
972 // and not those on pseudo-elements, since we can't target an
973 // event at pseudo-elements.
974 if (et
->mElementProperty
== nsGkAtoms::transitionsProperty
) {
975 nsCSSProperty prop
= pt
.mProperty
;
976 if (nsCSSProps::PropHasFlags(prop
, CSS_PROPERTY_REPORT_OTHER_NAME
))
978 prop
= nsCSSProps::OtherNameFor(prop
);
980 events
.AppendElement(
981 TransitionEventInfo(et
->mElement
, prop
, pt
.mDuration
));
984 // Leave this transition in the list for one more refresh
985 // cycle, since we haven't yet processed its style change, and
986 // if we also have (already, or will have from processing
987 // transitionend events or other refresh driver notifications)
988 // a non-animation style change that would affect it, we need
989 // to know not to start a new transition for the transition
990 // from the almost-completed value to the final value.
991 pt
.SetRemovedSentinel();
995 // We need to restyle even if the transition rule no longer
996 // applies (in which case we just made it not apply).
997 NS_ASSERTION(et
->mElementProperty
== nsGkAtoms::transitionsProperty
||
998 et
->mElementProperty
== nsGkAtoms::transitionsOfBeforeProperty
||
999 et
->mElementProperty
== nsGkAtoms::transitionsOfAfterProperty
,
1000 "Unexpected element property; might restyle too much");
1001 nsRestyleHint hint
= et
->mElementProperty
== nsGkAtoms::transitionsProperty
?
1002 eRestyle_Self
: eRestyle_Subtree
;
1003 mPresContext
->PresShell()->RestyleForAnimation(et
->mElement
, hint
);
1005 if (et
->mPropertyTransitions
.IsEmpty()) {
1007 // |et| is now a dangling pointer!
1013 // We might have removed transitions above.
1014 TransitionsRemoved();
1016 for (PRUint32 i
= 0, i_end
= events
.Length(); i
< i_end
; ++i
) {
1017 TransitionEventInfo
&info
= events
[i
];
1018 nsEventDispatcher::Dispatch(info
.mElement
, mPresContext
, &info
.mEvent
);
1020 if (!mPresContext
) {
1027 nsTransitionManager::TransitionsRemoved()
1029 // If we have no transitions left, remove ourselves from the refresh
1031 if (PR_CLIST_IS_EMPTY(&mElementTransitions
)) {
1032 mPresContext
->RefreshDriver()->RemoveRefreshObserver(this, Flush_Style
);