Back out changeset fecc8ed9e813.
[mozilla-central.git] / layout / style / nsTransitionManager.cpp
blob82c95fd369381d6a36ac077e29017cc9ea7716d6
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
13 * License.
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.
21 * Contributor(s):
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"
51 #include "gfxColor.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 /*****************************************************************************
65 * Per-Element data *
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();
94 /**
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
100 * animation.
102 class ElementTransitionsStyleRule : public nsIStyleRule
104 public:
105 // nsISupportsImplementation
106 NS_DECL_ISUPPORTS
108 // nsIStyleRule implementation
109 virtual void MapRuleInfoInto(nsRuleData* aRuleData);
110 #ifdef DEBUG
111 virtual void List(FILE* out = stdout, PRInt32 aIndent = 0) const;
112 #endif
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; }
125 private:
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
140 public:
141 // nsISupportsImplementation
142 NS_DECL_ISUPPORTS
144 // nsIStyleRule implementation
145 virtual void MapRuleInfoInto(nsRuleData* aRuleData);
146 #ifdef DEBUG
147 virtual void List(FILE* out = stdout, PRInt32 aIndent = 0) const;
148 #endif
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;
161 private:
162 nsTArray<CoveredValue> mCoveredValues;
165 struct ElementTransitions : public PRCList
167 ElementTransitions(dom::Element *aElement, nsIAtom *aElementProperty,
168 nsTransitionManager *aTransitionManager)
169 : mElement(aElement)
170 , mElementProperty(aElementProperty)
171 , mTransitionManager(aTransitionManager)
173 PR_INIT_CLIST(this);
175 ~ElementTransitions()
177 DropStyleRule();
178 PR_REMOVE_LINK(this);
179 mTransitionManager->TransitionsRemoved();
182 void Destroy()
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;
208 static void
209 ElementTransitionsPropertyDtor(void *aObject,
210 nsIAtom *aPropertyName,
211 void *aPropertyValue,
212 void *aData)
214 ElementTransitions *et = static_cast<ElementTransitions*>(aPropertyValue);
215 delete et;
218 NS_IMPL_ISUPPORTS1(ElementTransitionsStyleRule, nsIStyleRule)
220 /* virtual */ void
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.
227 return;
230 ElementTransitions *et = ElementData();
231 if (NS_UNLIKELY(!et)) { // FIXME (Bug 522597): Why can this be null?
232 NS_WARNING("ElementData returned null");
233 return;
236 for (PRUint32 i = 0, i_end = et->mPropertyTransitions.Length();
237 i < i_end; ++i)
239 ElementPropertyTransition &pt = et->mPropertyTransitions[i];
240 if (pt.IsRemovedSentinel()) {
241 continue;
244 if (aRuleData->mSIDs & nsCachedStyleData::GetBitForSID(
245 nsCSSProps::kSIDTable[pt.mProperty]))
247 double timePortion =
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);
256 #ifdef DEBUG
257 PRBool ok =
258 #endif
259 nsStyleAnimation::Interpolate(pt.mProperty,
260 pt.mStartValue, pt.mEndValue,
261 valuePortion, pt.mCurrentValue);
262 NS_ABORT_IF_FALSE(ok, "could not interpolate values");
264 void *prop =
265 nsCSSExpandedDataBlock::RuleDataPropertyAt(aRuleData, pt.mProperty);
266 #ifdef DEBUG
267 ok =
268 #endif
269 nsStyleAnimation::UncomputeValue(pt.mProperty, aRuleData->mPresContext,
270 pt.mCurrentValue, prop);
271 NS_ABORT_IF_FALSE(ok, "could not store computed value");
276 #ifdef DEBUG
277 /* virtual */ void
278 ElementTransitionsStyleRule::List(FILE* out, PRInt32 aIndent) const
280 // WRITE ME?
282 #endif
284 void
285 ElementTransitions::DropStyleRule()
287 if (mStyleRule) {
288 mStyleRule->Disconnect();
289 mStyleRule = nsnull;
293 PRBool
294 ElementTransitions::EnsureStyleRuleFor(TimeStamp aRefreshTime)
296 if (!mStyleRule || mStyleRule->RefreshTime() != aRefreshTime) {
297 DropStyleRule();
299 ElementTransitionsStyleRule *newRule =
300 new ElementTransitionsStyleRule(this, aRefreshTime);
301 if (!newRule) {
302 NS_WARNING("out of memory");
303 return PR_FALSE;
306 mStyleRule = newRule;
309 return PR_TRUE;
312 NS_IMPL_ISUPPORTS1(CoverTransitionStartStyleRule, nsIStyleRule)
314 /* virtual */ void
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]))
322 void *prop =
323 nsCSSExpandedDataBlock::RuleDataPropertyAt(aRuleData, cv.mProperty);
324 #ifdef DEBUG
325 PRBool ok =
326 #endif
327 nsStyleAnimation::UncomputeValue(cv.mProperty, aRuleData->mPresContext,
328 cv.mCoveredValue, prop);
329 NS_ABORT_IF_FALSE(ok, "could not store computed value");
334 #ifdef DEBUG
335 /* virtual */ void
336 CoverTransitionStartStyleRule::List(FILE* out, PRInt32 aIndent) const
338 // WRITE ME?
340 #endif
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");
357 void
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));
364 head->Destroy();
367 mPresContext = nsnull;
370 static PRBool
371 TransExtractComputedValue(nsCSSProperty aProperty,
372 nsStyleContext* aStyleContext,
373 nsStyleAnimation::Value& aComputedValue)
375 PRBool result =
376 nsStyleAnimation::ExtractComputedValue(aProperty, aStyleContext,
377 aComputedValue);
378 if (aProperty == eCSSProperty_visibility) {
379 NS_ABORT_IF_FALSE(aComputedValue.GetUnit() ==
380 nsStyleAnimation::eUnit_Enumerated,
381 "unexpected unit");
382 aComputedValue.SetIntValue(aComputedValue.GetIntValue(),
383 nsStyleAnimation::eUnit_Visibility);
385 return result;
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);
414 if (!et &&
415 disp->mTransitionPropertyCount == 1 &&
416 disp->mTransitions[0].GetDelay() == 0.0f &&
417 disp->mTransitions[0].GetDuration() == 0.0f) {
418 return nsnull;
422 if (aNewStyleContext->PresContext()->IsProcessingAnimationStyleChange()) {
423 return nsnull;
426 if (pseudoType != nsCSSPseudoElements::ePseudo_NotPseudoElement &&
427 pseudoType != nsCSSPseudoElements::ePseudo_before &&
428 pseudoType != nsCSSPseudoElements::ePseudo_after) {
429 return nsnull;
431 if (aNewStyleContext->GetParent() &&
432 aNewStyleContext->GetParent()->HasPseudoElementData()) {
433 // Ignore transitions on things that inherit properties from
434 // pseudo-elements.
435 // FIXME (Bug 522599): Add tests for this.
436 return nsnull;
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);
472 } else {
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.
485 if (et) {
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);
508 } else {
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;
518 do {
519 --i;
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,
527 currentValue) ||
528 currentValue != pt.mEndValue) {
529 // stop the transition
530 pts.RemoveElementAt(i);
532 } while (i != 0);
534 if (pts.IsEmpty()) {
535 et->Destroy();
536 et = nsnull;
540 if (!startedAny) {
541 return nsnull;
544 NS_ABORT_IF_FALSE(et, "must have element transitions if we started "
545 "any transitions");
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
560 // rule.
562 nsRefPtr<CoverTransitionStartStyleRule> coverRule =
563 new CoverTransitionStartStyleRule;
564 if (!coverRule) {
565 NS_WARNING("out of memory");
566 return nsnull;
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()));
581 void
582 nsTransitionManager::ConsiderStartingTransition(nsCSSProperty aProperty,
583 const nsTransition& aTransition,
584 dom::Element *aElement,
585 ElementTransitions *&aElementTransitions,
586 nsStyleContext *aOldStyleContext,
587 nsStyleContext *aNewStyleContext,
588 PRBool *aStartedAny,
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 .
600 return;
603 if (nsCSSProps::kAnimTypeTable[aProperty] == eStyleAnimType_None) {
604 return;
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,
617 0.5, dummyValue);
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) {
625 currentIndex = i;
626 break;
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);
642 if (pts.IsEmpty()) {
643 aElementTransitions->Destroy();
644 // |aElementTransitions| is now a dangling pointer!
645 aElementTransitions = nsnull;
647 // WalkTransitionRule already called RestyleForAnimation.
649 return;
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.
670 return;
673 double fullDistance, remainingDistance;
674 #ifdef DEBUG
675 PRBool ok =
676 #endif
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.)
704 if (delay < 0.0f)
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(),
717 PR_TRUE);
718 if (!aElementTransitions) {
719 NS_WARNING("allocating ElementTransitions failed");
720 return;
724 nsTArray<ElementPropertyTransition> &pts =
725 aElementTransitions->mPropertyTransitions;
726 #ifdef DEBUG
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");
732 #endif
733 if (currentIndex != nsTArray<ElementPropertyTransition>::NoIndex) {
734 pts[currentIndex] = pt;
735 } else {
736 if (!pts.AppendElement(pt)) {
737 NS_WARNING("out of memory");
738 return;
742 presContext->PresShell()->RestyleForAnimation(aElement);
744 *aStartedAny = PR_TRUE;
745 aWhichStarted->AddProperty(aProperty);
748 ElementTransitions*
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.
755 return nsnull;
758 nsIAtom *propName;
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;
765 } else {
766 NS_ASSERTION(!aCreateIfNeeded,
767 "should never try to create transitions for pseudo "
768 "other than :before or :after");
769 return nsnull;
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);
776 if (!et) {
777 NS_WARNING("out of memory");
778 return nsnull;
780 nsresult rv = aElement->SetProperty(propName, et,
781 ElementTransitionsPropertyDtor, nsnull);
782 if (NS_FAILED(rv)) {
783 NS_WARNING("SetProperty failed");
784 delete et;
785 return nsnull;
788 AddElementTransitions(et);
791 return et;
794 void
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
816 nsresult
817 nsTransitionManager::WalkTransitionRule(RuleProcessorData* aData,
818 nsCSSPseudoElements::Type aPseudoType)
820 ElementTransitions *et =
821 GetElementTransitions(aData->mElement, aPseudoType, PR_FALSE);
822 if (!et) {
823 return NS_OK;
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
833 // after doing this.
834 if (et) {
835 mPresContext->PresShell()->RestyleForAnimation(aData->mElement);
837 return NS_OK;
840 if (!et->EnsureStyleRuleFor(
841 aData->mPresContext->RefreshDriver()->MostRecentRefresh())) {
842 return NS_ERROR_OUT_OF_MEMORY;
845 aData->mRuleWalker->Forward(et->mStyleRule);
847 return NS_OK;
850 NS_IMETHODIMP
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);
859 NS_IMETHODIMP
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
867 // this is ok.
868 return WalkTransitionRule(aData, aData->mPseudoType);
871 NS_IMETHODIMP
872 nsTransitionManager::RulesMatching(AnonBoxRuleProcessorData* aData)
874 return NS_OK;
877 #ifdef MOZ_XUL
878 NS_IMETHODIMP
879 nsTransitionManager::RulesMatching(XULTreeRuleProcessorData* aData)
881 return NS_OK;
883 #endif
885 nsRestyleHint
886 nsTransitionManager::HasStateDependentStyle(StateRuleProcessorData* aData)
888 return nsRestyleHint(0);
891 PRBool
892 nsTransitionManager::HasDocumentStateDependentStyle(StateRuleProcessorData* aData)
894 return PR_FALSE;
897 nsRestyleHint
898 nsTransitionManager::HasAttributeDependentStyle(AttributeRuleProcessorData* aData)
900 return nsRestyleHint(0);
903 NS_IMETHODIMP
904 nsTransitionManager::MediumFeaturesChanged(nsPresContext* aPresContext,
905 PRBool* aRulesChanged)
907 *aRulesChanged = PR_FALSE;
908 return NS_OK;
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)
934 /* virtual */ void
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?");
958 do {
959 --i;
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();
983 } while (i != 0);
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()) {
990 et->Destroy();
991 // |et| is now a dangling pointer!
992 et = nsnull;
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) {
1005 break;
1010 void
1011 nsTransitionManager::TransitionsRemoved()
1013 // If we have no transitions left, remove ourselves from the refresh
1014 // driver.
1015 if (PR_CLIST_IS_EMPTY(&mElementTransitions)) {
1016 mPresContext->RefreshDriver()->RemoveRefreshObserver(this, Flush_Style);