Merge mozilla-central and tracemonkey. (a=blockers)
[mozilla-central.git] / layout / style / nsTransitionManager.cpp
blob166e467ebd23612dd687a9fec2b2800090e6c056
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
14 * License.
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.
22 * Contributor(s):
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"
52 #include "gfxColor.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 /*****************************************************************************
65 * Per-Element data *
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
82 // row.
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();
112 double
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
117 // 0-1).
118 double duration = mDuration.ToSeconds();
119 NS_ABORT_IF_FALSE(duration >= 0.0, "negative duration forbidden");
120 double timePortion;
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) {
125 timePortion = 1.0;
126 } else {
127 timePortion = 0.0;
129 } else {
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
145 public:
146 // nsISupports implementation
147 NS_DECL_ISUPPORTS
149 // nsIStyleRule implementation
150 virtual void MapRuleInfoInto(nsRuleData* aRuleData);
151 #ifdef DEBUG
152 virtual void List(FILE* out = stdout, PRInt32 aIndent = 0) const;
153 #endif
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();
165 if (!p) {
166 return nsnull;
168 p->mProperty = aProperty;
169 return &p->mValue;
172 struct PropertyValuePair {
173 nsCSSProperty mProperty;
174 nsStyleAnimation::Value mValue;
177 private:
178 nsTArray<PropertyValuePair> mPropertyValuePairs;
181 struct ElementTransitions : public PRCList
183 ElementTransitions(dom::Element *aElement, nsIAtom *aElementProperty,
184 nsTransitionManager *aTransitionManager)
185 : mElement(aElement)
186 , mElementProperty(aElementProperty)
187 , mTransitionManager(aTransitionManager)
189 PR_INIT_CLIST(this);
191 ~ElementTransitions()
193 PR_REMOVE_LINK(this);
194 mTransitionManager->TransitionsRemoved();
197 void Destroy()
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
214 // animation.
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;
228 static void
229 ElementTransitionsPropertyDtor(void *aObject,
230 nsIAtom *aPropertyName,
231 void *aPropertyValue,
232 void *aData)
234 ElementTransitions *et = static_cast<ElementTransitions*>(aPropertyValue);
235 delete et;
238 void
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()) {
249 continue;
252 nsStyleAnimation::Value *val = mStyleRule->AddEmptyValue(pt.mProperty);
253 if (!val) {
254 continue;
257 double valuePortion = pt.ValuePortionFor(aRefreshTime);
258 #ifdef DEBUG
259 PRBool ok =
260 #endif
261 nsStyleAnimation::Interpolate(pt.mProperty,
262 pt.mStartValue, pt.mEndValue,
263 valuePortion, *val);
264 NS_ABORT_IF_FALSE(ok, "could not interpolate values");
269 NS_IMPL_ISUPPORTS1(AnimValuesStyleRule, nsIStyleRule)
271 /* virtual */ void
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.
278 return;
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) {
288 #ifdef DEBUG
289 PRBool ok =
290 #endif
291 nsStyleAnimation::UncomputeValue(cv.mProperty,
292 aRuleData->mPresContext,
293 cv.mValue, *prop);
294 NS_ABORT_IF_FALSE(ok, "could not store computed value");
300 #ifdef DEBUG
301 /* virtual */ void
302 AnimValuesStyleRule::List(FILE* out, PRInt32 aIndent) const
304 // WRITE ME?
306 #endif
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");
323 void
324 nsTransitionManager::Disconnect()
326 // Content nodes might outlive the transition manager.
327 RemoveAllTransitions();
329 mPresContext = nsnull;
332 void
333 nsTransitionManager::RemoveAllTransitions()
335 while (!PR_CLIST_IS_EMPTY(&mElementTransitions)) {
336 ElementTransitions *head = static_cast<ElementTransitions*>(
337 PR_LIST_HEAD(&mElementTransitions));
338 head->Destroy();
342 static PRBool
343 TransExtractComputedValue(nsCSSProperty aProperty,
344 nsStyleContext* aStyleContext,
345 nsStyleAnimation::Value& aComputedValue)
347 PRBool result =
348 nsStyleAnimation::ExtractComputedValue(aProperty, aStyleContext,
349 aComputedValue);
350 if (aProperty == eCSSProperty_visibility) {
351 NS_ABORT_IF_FALSE(aComputedValue.GetUnit() ==
352 nsStyleAnimation::eUnit_Enumerated,
353 "unexpected unit");
354 aComputedValue.SetIntValue(aComputedValue.GetIntValue(),
355 nsStyleAnimation::eUnit_Visibility);
357 return result;
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) {
387 return nsnull;
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);
403 if (!et &&
404 disp->mTransitionPropertyCount == 1 &&
405 disp->mTransitions[0].GetDelay() == 0.0f &&
406 disp->mTransitions[0].GetDuration() == 0.0f) {
407 return nsnull;
411 if (aNewStyleContext->PresContext()->IsProcessingAnimationStyleChange()) {
412 return nsnull;
415 if (aNewStyleContext->GetParent() &&
416 aNewStyleContext->GetParent()->HasPseudoElementData()) {
417 // Ignore transitions on things that inherit properties from
418 // pseudo-elements.
419 // FIXME (Bug 522599): Add tests for this.
420 return nsnull;
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);
456 } else {
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.
469 if (et) {
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);
492 } else {
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;
502 do {
503 --i;
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,
511 currentValue) ||
512 currentValue != pt.mEndValue) {
513 // stop the transition
514 pts.RemoveElementAt(i);
516 } while (i != 0);
518 if (pts.IsEmpty()) {
519 et->Destroy();
520 et = nsnull;
524 if (!startedAny) {
525 return nsnull;
528 NS_ABORT_IF_FALSE(et, "must have element transitions if we started "
529 "any transitions");
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
544 // rule.
546 nsRefPtr<AnimValuesStyleRule> coverRule = new AnimValuesStyleRule;
547 if (!coverRule) {
548 NS_WARNING("out of memory");
549 return nsnull;
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();
563 void
564 nsTransitionManager::ConsiderStartingTransition(nsCSSProperty aProperty,
565 const nsTransition& aTransition,
566 dom::Element *aElement,
567 ElementTransitions *&aElementTransitions,
568 nsStyleContext *aOldStyleContext,
569 nsStyleContext *aNewStyleContext,
570 PRBool *aStartedAny,
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 .
582 return;
585 if (nsCSSProps::kAnimTypeTable[aProperty] == eStyleAnimType_None) {
586 return;
589 ElementPropertyTransition pt;
590 nsStyleAnimation::Value dummyValue;
591 PRBool haveValues =
592 TransExtractComputedValue(aProperty, aOldStyleContext, pt.mStartValue) &&
593 TransExtractComputedValue(aProperty, aNewStyleContext, pt.mEndValue);
594 PRBool shouldAnimate =
595 haveValues &&
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,
601 0.5, dummyValue);
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) {
609 currentIndex = i;
610 break;
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);
632 if (pts.IsEmpty()) {
633 aElementTransitions->Destroy();
634 // |aElementTransitions| is now a dangling pointer!
635 aElementTransitions = nsnull;
637 // WalkTransitionRule already called RestyleForAnimation.
639 return;
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.
650 duration = 0.0;
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.
666 return;
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
681 // negative.
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
688 // time.
689 if (valuePortion > 1.0)
690 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.
695 if (delay < 0.0f)
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(),
712 PR_TRUE);
713 if (!aElementTransitions) {
714 NS_WARNING("allocating ElementTransitions failed");
715 return;
719 nsTArray<ElementPropertyTransition> &pts =
720 aElementTransitions->mPropertyTransitions;
721 #ifdef DEBUG
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");
727 #endif
728 if (currentIndex != nsTArray<ElementPropertyTransition>::NoIndex) {
729 pts[currentIndex] = pt;
730 } else {
731 if (!pts.AppendElement(pt)) {
732 NS_WARNING("out of memory");
733 return;
737 nsRestyleHint hint =
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);
747 ElementTransitions*
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.
754 return nsnull;
757 nsIAtom *propName;
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;
764 } else {
765 NS_ASSERTION(!aCreateIfNeeded,
766 "should never try to create transitions for pseudo "
767 "other than :before or :after");
768 return nsnull;
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);
775 if (!et) {
776 NS_WARNING("out of memory");
777 return nsnull;
779 nsresult rv = aElement->SetProperty(propName, et,
780 ElementTransitionsPropertyDtor, nsnull);
781 if (NS_FAILED(rv)) {
782 NS_WARNING("SetProperty failed");
783 delete et;
784 return nsnull;
787 AddElementTransitions(et);
790 return et;
793 void
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
815 void
816 nsTransitionManager::WalkTransitionRule(RuleProcessorData* aData,
817 nsCSSPseudoElements::Type aPseudoType)
819 ElementTransitions *et =
820 GetElementTransitions(aData->mElement, aPseudoType, PR_FALSE);
821 if (!et) {
822 return;
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
833 // after doing this.
834 if (et) {
835 nsRestyleHint hint =
836 aPseudoType == nsCSSPseudoElements::ePseudo_NotPseudoElement ?
837 eRestyle_Self : eRestyle_Subtree;
838 mPresContext->PresShell()->RestyleForAnimation(aData->mElement, hint);
840 return;
843 et->EnsureStyleRuleFor(
844 aData->mPresContext->RefreshDriver()->MostRecentRefresh());
846 aData->mRuleWalker->Forward(et->mStyleRule);
849 /* virtual */ void
850 nsTransitionManager::RulesMatching(ElementRuleProcessorData* aData)
852 NS_ABORT_IF_FALSE(aData->mPresContext == mPresContext,
853 "pres context mismatch");
854 WalkTransitionRule(aData,
855 nsCSSPseudoElements::ePseudo_NotPseudoElement);
858 /* virtual */ void
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
866 // this is ok.
867 WalkTransitionRule(aData, aData->mPseudoType);
870 /* virtual */ void
871 nsTransitionManager::RulesMatching(AnonBoxRuleProcessorData* aData)
875 #ifdef MOZ_XUL
876 /* virtual */ void
877 nsTransitionManager::RulesMatching(XULTreeRuleProcessorData* aData)
880 #endif
882 nsRestyleHint
883 nsTransitionManager::HasStateDependentStyle(StateRuleProcessorData* aData)
885 return nsRestyleHint(0);
888 PRBool
889 nsTransitionManager::HasDocumentStateDependentStyle(StateRuleProcessorData* aData)
891 return PR_FALSE;
894 nsRestyleHint
895 nsTransitionManager::HasAttributeDependentStyle(AttributeRuleProcessorData* aData)
897 return nsRestyleHint(0);
900 /* virtual */ PRBool
901 nsTransitionManager::MediumFeaturesChanged(nsPresContext* aPresContext)
903 return PR_FALSE;
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)
929 /* virtual */ void
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();
941 return;
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?");
961 do {
962 --i;
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();
993 } while (i != 0);
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()) {
1006 et->Destroy();
1007 // |et| is now a dangling pointer!
1008 et = nsnull;
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) {
1021 break;
1026 void
1027 nsTransitionManager::TransitionsRemoved()
1029 // If we have no transitions left, remove ourselves from the refresh
1030 // driver.
1031 if (PR_CLIST_IS_EMPTY(&mElementTransitions)) {
1032 mPresContext->RefreshDriver()->RemoveRefreshObserver(this, Flush_Style);