1 /* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
15 * The Original Code is nsTransitionManager.
17 * The Initial Developer of the Original Code is the Mozilla Foundation.
18 * Portions created by the Initial Developer are Copyright (C) 2009
19 * the Initial Developer. All Rights Reserved.
22 * L. David Baron <dbaron@dbaron.org>, Mozilla Corporation (original author)
24 * Alternatively, the contents of this file may be used under the terms of
25 * either the GNU General Public License Version 2 or later (the "GPL"), or
26 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27 * in which case the provisions of the GPL or the LGPL are applicable instead
28 * of those above. If you wish to allow use of your version of this file only
29 * under the terms of either the GPL or the LGPL, and not to allow others to
30 * use your version of this file under the terms of the MPL, indicate your
31 * decision by deleting the provisions above and replace them with the notice
32 * and other provisions required by the GPL or the LGPL. If you do not delete
33 * the provisions above, a recipient may use your version of this file under
34 * the terms of any one of the MPL, the GPL or the LGPL.
36 * ***** END LICENSE BLOCK ***** */
38 /* Code to start and animate CSS transitions. */
40 #include "nsTransitionManager.h"
41 #include "nsIContent.h"
42 #include "nsStyleContext.h"
43 #include "nsCSSProps.h"
44 #include "mozilla/TimeStamp.h"
45 #include "nsRefreshDriver.h"
46 #include "nsRuleProcessorData.h"
47 #include "nsIStyleRule.h"
48 #include "nsRuleWalker.h"
49 #include "nsRuleData.h"
50 #include "nsSMILKeySpline.h"
52 #include "nsCSSPropertySet.h"
53 #include "nsStyleAnimation.h"
54 #include "nsCSSDataBlock.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 // We need to have mCurrentValue as a member of this structure because
72 // the result of the calls to |Interpolate| might hold data that this
73 // object's owning style rule needs to keep alive (after calling
74 // UncomputeValue on it in MapRuleInfoInto).
75 nsStyleAnimation::Value mStartValue
, mEndValue
, mCurrentValue
;
76 TimeStamp mStartTime
; // actual start plus transition delay
78 // data from the relevant nsTransition
79 TimeDuration mDuration
;
80 nsSMILKeySpline mTimingFunction
;
82 PRBool
IsRemovedSentinel() const
84 return mStartTime
.IsNull();
87 void SetRemovedSentinel()
89 // assign the null time stamp
90 mStartTime
= TimeStamp();
95 * An ElementTransitionsStyleRule overrides style data with the
96 * currently-transitioning value for an element that is executing a
97 * transition. It only matches when styling with animation. When we
98 * style without animation, we need to not use it so that we can detect
99 * any new changes; if necessary we restyle immediately afterwards with
102 class ElementTransitionsStyleRule
: public nsIStyleRule
105 // nsISupportsImplementation
108 // nsIStyleRule implementation
109 virtual void MapRuleInfoInto(nsRuleData
* aRuleData
);
111 virtual void List(FILE* out
= stdout
, PRInt32 aIndent
= 0) const;
114 ElementTransitionsStyleRule(ElementTransitions
*aOwner
,
115 TimeStamp aRefreshTime
)
116 : mElementTransitions(aOwner
)
117 , mRefreshTime(aRefreshTime
)
120 void Disconnect() { mElementTransitions
= nsnull
; }
122 ElementTransitions
*ElementData() { return mElementTransitions
; }
123 TimeStamp
RefreshTime() { return mRefreshTime
; }
126 ElementTransitions
*mElementTransitions
;
127 // The time stamp for which this style rule is valid.
128 TimeStamp mRefreshTime
;
132 * A CoverTransitionStyleRule sets any value for which we're starting a
133 * transition back to the pre-transition value for the period when we're
134 * resolving style on its descendants, so that we have the required
135 * behavior for initiating transitions on such descendants. For more
136 * detail, see comment below, above "new CoverTransitionStartStyleRule".
138 class CoverTransitionStartStyleRule
: public nsIStyleRule
141 // nsISupportsImplementation
144 // nsIStyleRule implementation
145 virtual void MapRuleInfoInto(nsRuleData
* aRuleData
);
147 virtual void List(FILE* out
= stdout
, PRInt32 aIndent
= 0) const;
150 void CoverValue(nsCSSProperty aProperty
, nsStyleAnimation::Value
&aStartValue
)
152 CoveredValue v
= { aProperty
, aStartValue
};
153 mCoveredValues
.AppendElement(v
);
156 struct CoveredValue
{
157 nsCSSProperty mProperty
;
158 nsStyleAnimation::Value mCoveredValue
;
162 nsTArray
<CoveredValue
> mCoveredValues
;
165 struct ElementTransitions
: public PRCList
167 ElementTransitions(dom::Element
*aElement
, nsIAtom
*aElementProperty
,
168 nsTransitionManager
*aTransitionManager
)
170 , mElementProperty(aElementProperty
)
171 , mTransitionManager(aTransitionManager
)
175 ~ElementTransitions()
178 PR_REMOVE_LINK(this);
179 mTransitionManager
->TransitionsRemoved();
184 // This will call our destructor.
185 mElement
->DeleteProperty(mElementProperty
);
188 void DropStyleRule();
189 PRBool
EnsureStyleRuleFor(TimeStamp aRefreshTime
);
192 // Either zero or one for each CSS property:
193 nsTArray
<ElementPropertyTransition
> mPropertyTransitions
;
195 // The style rule for the transitions (which contains the time stamp
196 // for which it is valid).
197 nsRefPtr
<ElementTransitionsStyleRule
> mStyleRule
;
199 dom::Element
*mElement
;
201 // the atom we use in mElement's prop table (must be a static atom,
202 // i.e., in an atom list)
203 nsIAtom
*mElementProperty
;
205 nsTransitionManager
*mTransitionManager
;
209 ElementTransitionsPropertyDtor(void *aObject
,
210 nsIAtom
*aPropertyName
,
211 void *aPropertyValue
,
214 ElementTransitions
*et
= static_cast<ElementTransitions
*>(aPropertyValue
);
218 NS_IMPL_ISUPPORTS1(ElementTransitionsStyleRule
, nsIStyleRule
)
221 ElementTransitionsStyleRule::MapRuleInfoInto(nsRuleData
* aRuleData
)
223 nsStyleContext
*contextParent
= aRuleData
->mStyleContext
->GetParent();
224 if (contextParent
&& contextParent
->HasPseudoElementData()) {
225 // Don't apply transitions to things inside of pseudo-elements.
226 // FIXME (Bug 522599): Add tests for this.
230 ElementTransitions
*et
= ElementData();
231 if (NS_UNLIKELY(!et
)) { // FIXME (Bug 522597): Why can this be null?
232 NS_WARNING("ElementData returned null");
236 for (PRUint32 i
= 0, i_end
= et
->mPropertyTransitions
.Length();
239 ElementPropertyTransition
&pt
= et
->mPropertyTransitions
[i
];
240 if (pt
.IsRemovedSentinel()) {
244 if (aRuleData
->mSIDs
& nsCachedStyleData::GetBitForSID(
245 nsCSSProps::kSIDTable
[pt
.mProperty
]))
248 (RefreshTime() - pt
.mStartTime
).ToSeconds() / pt
.mDuration
.ToSeconds();
249 if (timePortion
< 0.0)
250 timePortion
= 0.0; // use start value during transition-delay
251 if (timePortion
> 1.0)
252 timePortion
= 1.0; // we might be behind on flushing
254 double valuePortion
=
255 pt
.mTimingFunction
.GetSplineValue(timePortion
);
259 nsStyleAnimation::Interpolate(pt
.mProperty
,
260 pt
.mStartValue
, pt
.mEndValue
,
261 valuePortion
, pt
.mCurrentValue
);
262 NS_ABORT_IF_FALSE(ok
, "could not interpolate values");
265 nsCSSExpandedDataBlock::RuleDataPropertyAt(aRuleData
, pt
.mProperty
);
269 nsStyleAnimation::UncomputeValue(pt
.mProperty
, aRuleData
->mPresContext
,
270 pt
.mCurrentValue
, prop
);
271 NS_ABORT_IF_FALSE(ok
, "could not store computed value");
278 ElementTransitionsStyleRule::List(FILE* out
, PRInt32 aIndent
) const
285 ElementTransitions::DropStyleRule()
288 mStyleRule
->Disconnect();
294 ElementTransitions::EnsureStyleRuleFor(TimeStamp aRefreshTime
)
296 if (!mStyleRule
|| mStyleRule
->RefreshTime() != aRefreshTime
) {
299 ElementTransitionsStyleRule
*newRule
=
300 new ElementTransitionsStyleRule(this, aRefreshTime
);
302 NS_WARNING("out of memory");
306 mStyleRule
= newRule
;
312 NS_IMPL_ISUPPORTS1(CoverTransitionStartStyleRule
, nsIStyleRule
)
315 CoverTransitionStartStyleRule::MapRuleInfoInto(nsRuleData
* aRuleData
)
317 for (PRUint32 i
= 0, i_end
= mCoveredValues
.Length(); i
< i_end
; ++i
) {
318 CoveredValue
&cv
= mCoveredValues
[i
];
319 if (aRuleData
->mSIDs
& nsCachedStyleData::GetBitForSID(
320 nsCSSProps::kSIDTable
[cv
.mProperty
]))
323 nsCSSExpandedDataBlock::RuleDataPropertyAt(aRuleData
, cv
.mProperty
);
327 nsStyleAnimation::UncomputeValue(cv
.mProperty
, aRuleData
->mPresContext
,
328 cv
.mCoveredValue
, prop
);
329 NS_ABORT_IF_FALSE(ok
, "could not store computed value");
336 CoverTransitionStartStyleRule::List(FILE* out
, PRInt32 aIndent
) const
342 /*****************************************************************************
343 * nsTransitionManager *
344 *****************************************************************************/
346 nsTransitionManager::nsTransitionManager(nsPresContext
*aPresContext
)
347 : mPresContext(aPresContext
)
349 PR_INIT_CLIST(&mElementTransitions
);
352 nsTransitionManager::~nsTransitionManager()
354 NS_ABORT_IF_FALSE(!mPresContext
, "Disconnect should have been called");
358 nsTransitionManager::Disconnect()
360 // Content nodes might outlive the transition manager.
361 while (!PR_CLIST_IS_EMPTY(&mElementTransitions
)) {
362 ElementTransitions
*head
= static_cast<ElementTransitions
*>(
363 PR_LIST_HEAD(&mElementTransitions
));
367 mPresContext
= nsnull
;
371 TransExtractComputedValue(nsCSSProperty aProperty
,
372 nsStyleContext
* aStyleContext
,
373 nsStyleAnimation::Value
& aComputedValue
)
376 nsStyleAnimation::ExtractComputedValue(aProperty
, aStyleContext
,
378 if (aProperty
== eCSSProperty_visibility
) {
379 NS_ABORT_IF_FALSE(aComputedValue
.GetUnit() ==
380 nsStyleAnimation::eUnit_Enumerated
,
382 aComputedValue
.SetIntValue(aComputedValue
.GetIntValue(),
383 nsStyleAnimation::eUnit_Visibility
);
388 already_AddRefed
<nsIStyleRule
>
389 nsTransitionManager::StyleContextChanged(dom::Element
*aElement
,
390 nsStyleContext
*aOldStyleContext
,
391 nsStyleContext
*aNewStyleContext
)
393 NS_PRECONDITION(aOldStyleContext
->GetPseudo() ==
394 aNewStyleContext
->GetPseudo(),
395 "pseudo type mismatch");
396 // If we were called from ReparentStyleContext, this assertion would
397 // actually fire. If we need to be called from there, we can probably
398 // just remove it; the condition probably isn't critical, although
399 // it's worth thinking about some more.
400 NS_PRECONDITION(aOldStyleContext
->HasPseudoElementData() ==
401 aNewStyleContext
->HasPseudoElementData(),
402 "pseudo type mismatch");
404 // NOTE: Things in this function (and ConsiderStartingTransition)
405 // should never call PeekStyleData because we don't preserve gotten
406 // structs across reframes.
408 // Return sooner (before the startedAny check below) for the most
409 // common case: no transitions specified or running.
410 const nsStyleDisplay
*disp
= aNewStyleContext
->GetStyleDisplay();
411 nsCSSPseudoElements::Type pseudoType
= aNewStyleContext
->GetPseudoType();
412 ElementTransitions
*et
=
413 GetElementTransitions(aElement
, pseudoType
, PR_FALSE
);
415 disp
->mTransitionPropertyCount
== 1 &&
416 disp
->mTransitions
[0].GetDelay() == 0.0f
&&
417 disp
->mTransitions
[0].GetDuration() == 0.0f
) {
422 if (aNewStyleContext
->PresContext()->IsProcessingAnimationStyleChange()) {
426 if (pseudoType
!= nsCSSPseudoElements::ePseudo_NotPseudoElement
&&
427 pseudoType
!= nsCSSPseudoElements::ePseudo_before
&&
428 pseudoType
!= nsCSSPseudoElements::ePseudo_after
) {
431 if (aNewStyleContext
->GetParent() &&
432 aNewStyleContext
->GetParent()->HasPseudoElementData()) {
433 // Ignore transitions on things that inherit properties from
435 // FIXME (Bug 522599): Add tests for this.
439 // Per http://lists.w3.org/Archives/Public/www-style/2009Aug/0109.html
440 // I'll consider only the transitions from the number of items in
441 // 'transition-property' on down, and later ones will override earlier
442 // ones (tracked using |whichStarted|).
443 PRBool startedAny
= PR_FALSE
;
444 nsCSSPropertySet whichStarted
;
445 for (PRUint32 i
= disp
->mTransitionPropertyCount
; i
-- != 0; ) {
446 const nsTransition
& t
= disp
->mTransitions
[i
];
447 // Check delay and duration first, since they default to zero, and
448 // when they're both zero, we can ignore the transition.
449 if (t
.GetDelay() != 0.0f
|| t
.GetDuration() != 0.0f
) {
450 // We might have something to transition. See if any of the
451 // properties in question changed and are animatable.
452 // FIXME: Would be good to find a way to share code between this
453 // interpretation of transition-property and the one below.
454 nsCSSProperty property
= t
.GetProperty();
455 if (property
== eCSSPropertyExtra_no_properties
||
456 property
== eCSSProperty_UNKNOWN
) {
457 // Nothing to do, but need to exclude this from cases below.
458 } else if (property
== eCSSPropertyExtra_all_properties
) {
459 for (nsCSSProperty p
= nsCSSProperty(0);
460 p
< eCSSProperty_COUNT_no_shorthands
;
461 p
= nsCSSProperty(p
+ 1)) {
462 ConsiderStartingTransition(p
, t
, aElement
, et
,
463 aOldStyleContext
, aNewStyleContext
,
464 &startedAny
, &whichStarted
);
466 } else if (nsCSSProps::IsShorthand(property
)) {
467 CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(subprop
, property
) {
468 ConsiderStartingTransition(*subprop
, t
, aElement
, et
,
469 aOldStyleContext
, aNewStyleContext
,
470 &startedAny
, &whichStarted
);
473 ConsiderStartingTransition(property
, t
, aElement
, et
,
474 aOldStyleContext
, aNewStyleContext
,
475 &startedAny
, &whichStarted
);
480 // Stop any transitions for properties that are no longer in
481 // 'transition-property'.
482 // Also stop any transitions for properties that just changed (and are
483 // still in the set of properties to transition), but we didn't just
484 // start the transition because delay and duration are both zero.
486 PRBool checkProperties
=
487 disp
->mTransitions
[0].GetProperty() != eCSSPropertyExtra_all_properties
;
488 nsCSSPropertySet allTransitionProperties
;
489 if (checkProperties
) {
490 for (PRUint32 i
= disp
->mTransitionPropertyCount
; i
-- != 0; ) {
491 const nsTransition
& t
= disp
->mTransitions
[i
];
492 // FIXME: Would be good to find a way to share code between this
493 // interpretation of transition-property and the one above.
494 nsCSSProperty property
= t
.GetProperty();
495 if (property
== eCSSPropertyExtra_no_properties
||
496 property
== eCSSProperty_UNKNOWN
) {
497 // Nothing to do, but need to exclude this from cases below.
498 } else if (property
== eCSSPropertyExtra_all_properties
) {
499 for (nsCSSProperty p
= nsCSSProperty(0);
500 p
< eCSSProperty_COUNT_no_shorthands
;
501 p
= nsCSSProperty(p
+ 1)) {
502 allTransitionProperties
.AddProperty(p
);
504 } else if (nsCSSProps::IsShorthand(property
)) {
505 CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(subprop
, property
) {
506 allTransitionProperties
.AddProperty(*subprop
);
509 allTransitionProperties
.AddProperty(property
);
514 nsTArray
<ElementPropertyTransition
> &pts
= et
->mPropertyTransitions
;
515 PRUint32 i
= pts
.Length();
516 NS_ABORT_IF_FALSE(i
!= 0, "empty transitions list?");
517 nsStyleAnimation::Value currentValue
;
520 ElementPropertyTransition
&pt
= pts
[i
];
521 // properties no longer in 'transition-property'
522 if ((checkProperties
&&
523 !allTransitionProperties
.HasProperty(pt
.mProperty
)) ||
524 // properties whose computed values changed but delay and
525 // duration are both zero
526 !TransExtractComputedValue(pt
.mProperty
, aNewStyleContext
,
528 currentValue
!= pt
.mEndValue
) {
529 // stop the transition
530 pts
.RemoveElementAt(i
);
544 NS_ABORT_IF_FALSE(et
, "must have element transitions if we started "
547 // In the CSS working group discussion (2009 Jul 15 telecon,
548 // http://www.w3.org/mid/4A5E1470.4030904@inkedblade.net ) of
549 // http://lists.w3.org/Archives/Public/www-style/2009Jun/0121.html ,
550 // the working group decided that a transition property on an
551 // element should not cause any transitions if the property change
552 // is itself inheriting a value that is transitioning on an
553 // ancestor. So, to get the correct behavior, we continue the
554 // restyle that caused this transition using a "covering" rule that
555 // covers up any changes on which we started transitions, so that
556 // descendants don't start their own transitions. (In the case of
557 // negative transition delay, this covering rule produces different
558 // results than applying the transition rule immediately would).
559 // Our caller is responsible for restyling again using this covering
562 nsRefPtr
<CoverTransitionStartStyleRule
> coverRule
=
563 new CoverTransitionStartStyleRule
;
565 NS_WARNING("out of memory");
569 nsTArray
<ElementPropertyTransition
> &pts
= et
->mPropertyTransitions
;
570 for (PRUint32 i
= 0, i_end
= pts
.Length(); i
< i_end
; ++i
) {
571 ElementPropertyTransition
&pt
= pts
[i
];
572 if (whichStarted
.HasProperty(pt
.mProperty
)) {
573 coverRule
->CoverValue(pt
.mProperty
, pt
.mStartValue
);
577 return already_AddRefed
<nsIStyleRule
>(
578 static_cast<nsIStyleRule
*>(coverRule
.forget().get()));
582 nsTransitionManager::ConsiderStartingTransition(nsCSSProperty aProperty
,
583 const nsTransition
& aTransition
,
584 dom::Element
*aElement
,
585 ElementTransitions
*&aElementTransitions
,
586 nsStyleContext
*aOldStyleContext
,
587 nsStyleContext
*aNewStyleContext
,
589 nsCSSPropertySet
*aWhichStarted
)
591 // IsShorthand itself will assert if aProperty is not a property.
592 NS_ABORT_IF_FALSE(!nsCSSProps::IsShorthand(aProperty
),
593 "property out of range");
595 if (aWhichStarted
->HasProperty(aProperty
)) {
596 // A later item in transition-property already started a
597 // transition for this property, so we ignore this one.
598 // See comment above and
599 // http://lists.w3.org/Archives/Public/www-style/2009Aug/0109.html .
603 if (nsCSSProps::kAnimTypeTable
[aProperty
] == eStyleAnimType_None
) {
607 ElementPropertyTransition pt
;
608 nsStyleAnimation::Value dummyValue
;
609 PRBool shouldAnimate
=
610 TransExtractComputedValue(aProperty
, aOldStyleContext
, pt
.mStartValue
) &&
611 TransExtractComputedValue(aProperty
, aNewStyleContext
, pt
.mEndValue
) &&
612 pt
.mStartValue
!= pt
.mEndValue
&&
613 // Check that we can interpolate between these values
614 // (If this is ever a performance problem, we could add a
615 // CanInterpolate method, but it seems fine for now.)
616 nsStyleAnimation::Interpolate(aProperty
, pt
.mStartValue
, pt
.mEndValue
,
619 PRUint32 currentIndex
= nsTArray
<ElementPropertyTransition
>::NoIndex
;
620 if (aElementTransitions
) {
621 nsTArray
<ElementPropertyTransition
> &pts
=
622 aElementTransitions
->mPropertyTransitions
;
623 for (PRUint32 i
= 0, i_end
= pts
.Length(); i
< i_end
; ++i
) {
624 if (pts
[i
].mProperty
== aProperty
) {
631 nsPresContext
*presContext
= aNewStyleContext
->PresContext();
633 if (!shouldAnimate
) {
634 if (currentIndex
!= nsTArray
<ElementPropertyTransition
>::NoIndex
) {
635 // We're in the middle of a transition, but just got a
636 // non-transition style change changing to exactly the
637 // current in-progress value. (This is quite easy to cause
638 // using 'transition-delay'.)
639 nsTArray
<ElementPropertyTransition
> &pts
=
640 aElementTransitions
->mPropertyTransitions
;
641 pts
.RemoveElementAt(currentIndex
);
643 aElementTransitions
->Destroy();
644 // |aElementTransitions| is now a dangling pointer!
645 aElementTransitions
= nsnull
;
647 // WalkTransitionRule already called RestyleForAnimation.
652 // When we interrupt a running transition, we want to reduce the
653 // duration of the new transition *if* the new transition would have
654 // been longer had it started from the endpoint of the currently
655 // running transition.
656 double durationFraction
= 1.0;
658 // We need to check two things if we have a currently running
659 // transition for this property: see durationFraction comment above
660 // and the endpoint check below.
661 if (currentIndex
!= nsTArray
<ElementPropertyTransition
>::NoIndex
) {
662 const ElementPropertyTransition
&oldPT
=
663 aElementTransitions
->mPropertyTransitions
[currentIndex
];
665 if (oldPT
.mEndValue
== pt
.mEndValue
) {
666 // If we got a style change that changed the value to the endpoint
667 // of the currently running transition, we don't want to interrupt
668 // its timing function.
669 // WalkTransitionRule already called RestyleForAnimation.
673 double fullDistance
, remainingDistance
;
677 nsStyleAnimation::ComputeDistance(aProperty
, pt
.mStartValue
,
678 pt
.mEndValue
, fullDistance
);
679 NS_ABORT_IF_FALSE(ok
, "could not compute distance");
680 NS_ABORT_IF_FALSE(fullDistance
>= 0.0, "distance must be positive");
682 if (!oldPT
.IsRemovedSentinel() &&
683 nsStyleAnimation::ComputeDistance(aProperty
, oldPT
.mEndValue
,
684 pt
.mEndValue
, remainingDistance
)) {
685 NS_ABORT_IF_FALSE(remainingDistance
>= 0.0, "distance must be positive");
686 durationFraction
= fullDistance
/ remainingDistance
;
687 if (durationFraction
> 1.0) {
688 durationFraction
= 1.0;
694 nsRefreshDriver
*rd
= presContext
->RefreshDriver();
696 pt
.mProperty
= aProperty
;
697 float delay
= aTransition
.GetDelay();
698 float duration
= aTransition
.GetDuration();
699 if (durationFraction
!= 1.0) {
700 // Negative delays are essentially part of the transition
701 // function, so reduce them along with the duration, but don't
702 // reduce positive delays. (See comment above about
703 // durationFraction.)
705 delay
*= durationFraction
;
706 duration
*= durationFraction
;
708 pt
.mStartTime
= rd
->MostRecentRefresh() +
709 TimeDuration::FromMilliseconds(delay
);
710 pt
.mDuration
= TimeDuration::FromMilliseconds(duration
);
711 const nsTimingFunction
&tf
= aTransition
.GetTimingFunction();
712 pt
.mTimingFunction
.Init(tf
.mX1
, tf
.mY1
, tf
.mX2
, tf
.mY2
);
714 if (!aElementTransitions
) {
715 aElementTransitions
=
716 GetElementTransitions(aElement
, aNewStyleContext
->GetPseudoType(),
718 if (!aElementTransitions
) {
719 NS_WARNING("allocating ElementTransitions failed");
724 nsTArray
<ElementPropertyTransition
> &pts
=
725 aElementTransitions
->mPropertyTransitions
;
727 for (PRUint32 i
= 0, i_end
= pts
.Length(); i
< i_end
; ++i
) {
728 NS_ABORT_IF_FALSE(i
== currentIndex
||
729 pts
[i
].mProperty
!= aProperty
,
730 "duplicate transitions for property");
733 if (currentIndex
!= nsTArray
<ElementPropertyTransition
>::NoIndex
) {
734 pts
[currentIndex
] = pt
;
736 if (!pts
.AppendElement(pt
)) {
737 NS_WARNING("out of memory");
742 presContext
->PresShell()->RestyleForAnimation(aElement
);
744 *aStartedAny
= PR_TRUE
;
745 aWhichStarted
->AddProperty(aProperty
);
749 nsTransitionManager::GetElementTransitions(dom::Element
*aElement
,
750 nsCSSPseudoElements::Type aPseudoType
,
751 PRBool aCreateIfNeeded
)
753 if (!aCreateIfNeeded
&& PR_CLIST_IS_EMPTY(&mElementTransitions
)) {
754 // Early return for the most common case.
759 if (aPseudoType
== nsCSSPseudoElements::ePseudo_NotPseudoElement
) {
760 propName
= nsGkAtoms::transitionsProperty
;
761 } else if (aPseudoType
== nsCSSPseudoElements::ePseudo_before
) {
762 propName
= nsGkAtoms::transitionsOfBeforeProperty
;
763 } else if (aPseudoType
== nsCSSPseudoElements::ePseudo_after
) {
764 propName
= nsGkAtoms::transitionsOfAfterProperty
;
766 NS_ASSERTION(!aCreateIfNeeded
,
767 "should never try to create transitions for pseudo "
768 "other than :before or :after");
771 ElementTransitions
*et
= static_cast<ElementTransitions
*>(
772 aElement
->GetProperty(propName
));
773 if (!et
&& aCreateIfNeeded
) {
774 // FIXME: Consider arena-allocating?
775 et
= new ElementTransitions(aElement
, propName
, this);
777 NS_WARNING("out of memory");
780 nsresult rv
= aElement
->SetProperty(propName
, et
,
781 ElementTransitionsPropertyDtor
, nsnull
);
783 NS_WARNING("SetProperty failed");
788 AddElementTransitions(et
);
795 nsTransitionManager::AddElementTransitions(ElementTransitions
* aElementTransitions
)
797 if (PR_CLIST_IS_EMPTY(&mElementTransitions
)) {
798 // We need to observe the refresh driver.
799 nsRefreshDriver
*rd
= mPresContext
->RefreshDriver();
800 rd
->AddRefreshObserver(this, Flush_Style
);
803 PR_INSERT_BEFORE(aElementTransitions
, &mElementTransitions
);
807 * nsISupports implementation
810 NS_IMPL_ISUPPORTS1(nsTransitionManager
, nsIStyleRuleProcessor
)
813 * nsIStyleRuleProcessor implementation
817 nsTransitionManager::WalkTransitionRule(RuleProcessorData
* aData
,
818 nsCSSPseudoElements::Type aPseudoType
)
820 ElementTransitions
*et
=
821 GetElementTransitions(aData
->mElement
, aPseudoType
, PR_FALSE
);
826 if (!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
835 mPresContext
->PresShell()->RestyleForAnimation(aData
->mElement
);
840 if (!et
->EnsureStyleRuleFor(
841 aData
->mPresContext
->RefreshDriver()->MostRecentRefresh())) {
842 return NS_ERROR_OUT_OF_MEMORY
;
845 aData
->mRuleWalker
->Forward(et
->mStyleRule
);
851 nsTransitionManager::RulesMatching(ElementRuleProcessorData
* aData
)
853 NS_ABORT_IF_FALSE(aData
->mPresContext
== mPresContext
,
854 "pres context mismatch");
855 return WalkTransitionRule(aData
,
856 nsCSSPseudoElements::ePseudo_NotPseudoElement
);
860 nsTransitionManager::RulesMatching(PseudoElementRuleProcessorData
* aData
)
862 NS_ABORT_IF_FALSE(aData
->mPresContext
== mPresContext
,
863 "pres context mismatch");
865 // Note: If we're the only thing keeping a pseudo-element frame alive
866 // (per ProbePseudoStyleContext), we still want to keep it alive, so
868 return WalkTransitionRule(aData
, aData
->mPseudoType
);
872 nsTransitionManager::RulesMatching(AnonBoxRuleProcessorData
* aData
)
879 nsTransitionManager::RulesMatching(XULTreeRuleProcessorData
* aData
)
886 nsTransitionManager::HasStateDependentStyle(StateRuleProcessorData
* aData
)
888 return nsRestyleHint(0);
892 nsTransitionManager::HasDocumentStateDependentStyle(StateRuleProcessorData
* aData
)
898 nsTransitionManager::HasAttributeDependentStyle(AttributeRuleProcessorData
* aData
)
900 return nsRestyleHint(0);
904 nsTransitionManager::MediumFeaturesChanged(nsPresContext
* aPresContext
,
905 PRBool
* aRulesChanged
)
907 *aRulesChanged
= PR_FALSE
;
911 struct TransitionEventInfo
{
912 nsCOMPtr
<nsIContent
> mElement
;
913 nsTransitionEvent mEvent
;
915 TransitionEventInfo(nsIContent
*aElement
, nsCSSProperty aProperty
,
916 TimeDuration aDuration
)
917 : mElement(aElement
),
918 mEvent(PR_TRUE
, NS_TRANSITION_END
,
919 NS_ConvertUTF8toUTF16(nsCSSProps::GetStringValue(aProperty
)),
920 aDuration
.ToSeconds())
924 // nsTransitionEvent doesn't support copy-construction, so we need
925 // to ourselves in order to work with nsTArray
926 TransitionEventInfo(const TransitionEventInfo
&aOther
)
927 : mElement(aOther
.mElement
),
928 mEvent(PR_TRUE
, NS_TRANSITION_END
,
929 aOther
.mEvent
.propertyName
, aOther
.mEvent
.elapsedTime
)
935 nsTransitionManager::WillRefresh(mozilla::TimeStamp aTime
)
937 NS_ABORT_IF_FALSE(mPresContext
,
938 "refresh driver should not notify additional observers "
939 "after pres context has been destroyed");
941 nsTArray
<TransitionEventInfo
> events
;
943 // Trim transitions that have completed, and post restyle events for
944 // frames that are still transitioning.
946 PRCList
*next
= PR_LIST_HEAD(&mElementTransitions
);
947 while (next
!= &mElementTransitions
) {
948 ElementTransitions
*et
= static_cast<ElementTransitions
*>(next
);
949 next
= PR_NEXT_LINK(next
);
951 NS_ABORT_IF_FALSE(et
->mElement
->GetCurrentDoc() ==
952 mPresContext
->Document(),
953 "nsGenericElement::UnbindFromTree should have "
954 "destroyed the element transitions object");
956 PRUint32 i
= et
->mPropertyTransitions
.Length();
957 NS_ABORT_IF_FALSE(i
!= 0, "empty transitions list?");
960 ElementPropertyTransition
&pt
= et
->mPropertyTransitions
[i
];
961 if (pt
.IsRemovedSentinel()) {
962 // Actually remove transitions one cycle after their
963 // completion. See comment below.
964 et
->mPropertyTransitions
.RemoveElementAt(i
);
965 } else if (pt
.mStartTime
+ pt
.mDuration
<= aTime
) {
966 // This transition has completed.
967 nsCSSProperty prop
= pt
.mProperty
;
968 if (nsCSSProps::PropHasFlags(prop
, CSS_PROPERTY_REPORT_OTHER_NAME
)) {
969 prop
= nsCSSProps::OtherNameFor(prop
);
971 events
.AppendElement(
972 TransitionEventInfo(et
->mElement
, prop
, pt
.mDuration
));
974 // Leave this transition in the list for one more refresh
975 // cycle, since we haven't yet processed its style change, and
976 // if we also have (already, or will have from processing
977 // transitionend events or other refresh driver notifications)
978 // a non-animation style change that would affect it, we need
979 // to know not to start a new transition for the transition
980 // from the almost-completed value to the final value.
981 pt
.SetRemovedSentinel();
985 // We need to restyle even if the transition rule no longer
986 // applies (in which case we just made it not apply).
987 mPresContext
->PresShell()->RestyleForAnimation(et
->mElement
);
989 if (et
->mPropertyTransitions
.IsEmpty()) {
991 // |et| is now a dangling pointer!
997 // We might have removed transitions above.
998 TransitionsRemoved();
1000 for (PRUint32 i
= 0, i_end
= events
.Length(); i
< i_end
; ++i
) {
1001 TransitionEventInfo
&info
= events
[i
];
1002 nsEventDispatcher::Dispatch(info
.mElement
, mPresContext
, &info
.mEvent
);
1004 if (!mPresContext
) {
1011 nsTransitionManager::TransitionsRemoved()
1013 // If we have no transitions left, remove ourselves from the refresh
1015 if (PR_CLIST_IS_EMPTY(&mElementTransitions
)) {
1016 mPresContext
->RefreshDriver()->RemoveRefreshObserver(this, Flush_Style
);