layout/style/nsTransitionManager.cpp

Wed, 31 Dec 2014 13:27:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 13:27:57 +0100
branch
TOR_BUG_3246
changeset 6
8bccb770b82d
permissions
-rw-r--r--

Ignore runtime configuration files generated during quality assurance.

     1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 /* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
     3 /* This Source Code Form is subject to the terms of the Mozilla Public
     4  * License, v. 2.0. If a copy of the MPL was not distributed with this
     5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     7 /* Code to start and animate CSS transitions. */
     9 #include "nsTransitionManager.h"
    10 #include "nsAnimationManager.h"
    11 #include "nsIContent.h"
    12 #include "nsStyleContext.h"
    13 #include "nsCSSProps.h"
    14 #include "mozilla/MemoryReporting.h"
    15 #include "mozilla/TimeStamp.h"
    16 #include "nsRefreshDriver.h"
    17 #include "nsRuleProcessorData.h"
    18 #include "nsRuleWalker.h"
    19 #include "nsCSSPropertySet.h"
    20 #include "nsStyleAnimation.h"
    21 #include "mozilla/EventDispatcher.h"
    22 #include "mozilla/ContentEvents.h"
    23 #include "mozilla/dom/Element.h"
    24 #include "nsIFrame.h"
    25 #include "Layers.h"
    26 #include "FrameLayerBuilder.h"
    27 #include "nsDisplayList.h"
    28 #include "nsStyleChangeList.h"
    29 #include "nsStyleSet.h"
    30 #include "RestyleManager.h"
    31 #include "ActiveLayerTracker.h"
    33 using mozilla::TimeStamp;
    34 using mozilla::TimeDuration;
    36 using namespace mozilla;
    37 using namespace mozilla::layers;
    38 using namespace mozilla::css;
    40 ElementTransitions::ElementTransitions(mozilla::dom::Element *aElement,
    41                                        nsIAtom *aElementProperty,
    42                                        nsTransitionManager *aTransitionManager,
    43                                        TimeStamp aNow)
    44   : CommonElementAnimationData(aElement, aElementProperty,
    45                                aTransitionManager, aNow)
    46 {
    47 }
    49 double
    50 ElementPropertyTransition::ValuePortionFor(TimeStamp aRefreshTime) const
    51 {
    52   // Set |timePortion| to the portion of the way we are through the time
    53   // input to the transition's timing function (always within the range
    54   // 0-1).
    55   double duration = mIterationDuration.ToSeconds();
    56   NS_ABORT_IF_FALSE(duration >= 0.0, "negative duration forbidden");
    57   double timePortion;
    58   if (IsRemovedSentinel()) {
    59     // The transition is being removed, but we still want an update so that any
    60     // new transitions start in the right place.
    61     timePortion = 1.0;
    62   } else if (duration == 0.0) {
    63     // When duration is zero, we can still have a transition when delay
    64     // is nonzero.
    65     if (aRefreshTime >= mStartTime + mDelay) {
    66       timePortion = 1.0;
    67     } else {
    68       timePortion = 0.0;
    69     }
    70   } else {
    71     timePortion = (aRefreshTime - (mStartTime + mDelay)).ToSeconds() / duration;
    72     if (timePortion < 0.0)
    73       timePortion = 0.0; // use start value during transition-delay
    74     if (timePortion > 1.0)
    75       timePortion = 1.0; // we might be behind on flushing
    76   }
    77   MOZ_ASSERT(mProperties.Length() == 1,
    78              "Should have one animation property for a transition");
    79   MOZ_ASSERT(mProperties[0].mSegments.Length() == 1,
    80              "Animation property should have one segment for a transition");
    82   return mProperties[0].mSegments[0].mTimingFunction.GetValue(timePortion);
    83 }
    85 static void
    86 ElementTransitionsPropertyDtor(void           *aObject,
    87                                nsIAtom        *aPropertyName,
    88                                void           *aPropertyValue,
    89                                void           *aData)
    90 {
    91   ElementTransitions *et = static_cast<ElementTransitions*>(aPropertyValue);
    92 #ifdef DEBUG
    93   NS_ABORT_IF_FALSE(!et->mCalledPropertyDtor, "can't call dtor twice");
    94   et->mCalledPropertyDtor = true;
    95 #endif
    96   delete et;
    97 }
    99 void
   100 ElementTransitions::EnsureStyleRuleFor(TimeStamp aRefreshTime)
   101 {
   102   if (!mStyleRule || mStyleRuleRefreshTime != aRefreshTime) {
   103     mStyleRule = new css::AnimValuesStyleRule();
   104     mStyleRuleRefreshTime = aRefreshTime;
   106     for (uint32_t i = 0, i_end = mPropertyTransitions.Length(); i < i_end; ++i)
   107     {
   108       ElementPropertyTransition &pt = mPropertyTransitions[i];
   109       if (pt.IsRemovedSentinel()) {
   110         continue;
   111       }
   113       MOZ_ASSERT(pt.mProperties.Length() == 1,
   114                  "Should have one animation property for a transition");
   115       const AnimationProperty &prop = pt.mProperties[0];
   117       nsStyleAnimation::Value *val = mStyleRule->AddEmptyValue(prop.mProperty);
   119       double valuePortion = pt.ValuePortionFor(aRefreshTime);
   121       MOZ_ASSERT(prop.mSegments.Length() == 1,
   122                  "Animation property should have one segment for a transition");
   123 #ifdef DEBUG
   124       bool ok =
   125 #endif
   126         nsStyleAnimation::Interpolate(prop.mProperty,
   127                                       prop.mSegments[0].mFromValue,
   128                                       prop.mSegments[0].mToValue,
   129                                       valuePortion, *val);
   130       NS_ABORT_IF_FALSE(ok, "could not interpolate values");
   131     }
   132   }
   133 }
   135 bool
   136 ElementTransitions::HasAnimationOfProperty(nsCSSProperty aProperty) const
   137 {
   138   for (uint32_t tranIdx = mPropertyTransitions.Length(); tranIdx-- != 0; ) {
   139     const ElementPropertyTransition& pt = mPropertyTransitions[tranIdx];
   140     if (pt.HasAnimationOfProperty(aProperty) && !pt.IsRemovedSentinel()) {
   141       return true;
   142     }
   143   }
   144   return false;
   145 }
   147 bool
   148 ElementTransitions::CanPerformOnCompositorThread(CanAnimateFlags aFlags) const
   149 {
   150   nsIFrame* frame = nsLayoutUtils::GetStyleFrame(mElement);
   151   if (!frame) {
   152     return false;
   153   }
   155   if (mElementProperty != nsGkAtoms::transitionsProperty) {
   156     if (nsLayoutUtils::IsAnimationLoggingEnabled()) {
   157       nsCString message;
   158       message.AppendLiteral("Gecko bug: Async transition of pseudoelements not supported.  See bug 771367");
   159       LogAsyncAnimationFailure(message, mElement);
   160     }
   161     return false;
   162   }
   164   TimeStamp now = frame->PresContext()->RefreshDriver()->MostRecentRefresh();
   166   for (uint32_t i = 0, i_end = mPropertyTransitions.Length(); i < i_end; ++i) {
   167     const ElementPropertyTransition& pt = mPropertyTransitions[i];
   168     MOZ_ASSERT(pt.mProperties.Length() == 1,
   169                "Should have one animation property for a transition");
   170     if (css::IsGeometricProperty(pt.mProperties[0].mProperty) &&
   171         pt.IsRunningAt(now)) {
   172       aFlags = CanAnimateFlags(aFlags | CanAnimate_HasGeometricProperty);
   173       break;
   174     }
   175   }
   177   bool hasOpacity = false;
   178   bool hasTransform = false;
   179   bool existsProperty = false;
   180   for (uint32_t i = 0, i_end = mPropertyTransitions.Length(); i < i_end; ++i) {
   181     const ElementPropertyTransition& pt = mPropertyTransitions[i];
   182     if (!pt.IsRunningAt(now)) {
   183       continue;
   184     }
   186     existsProperty = true;
   188     MOZ_ASSERT(pt.mProperties.Length() == 1,
   189                "Should have one animation property for a transition");
   190     const AnimationProperty& prop = pt.mProperties[0];
   192     if (!css::CommonElementAnimationData::CanAnimatePropertyOnCompositor(
   193           mElement, prop.mProperty, aFlags) ||
   194         css::CommonElementAnimationData::IsCompositorAnimationDisabledForFrame(frame)) {
   195       return false;
   196     }
   197     if (prop.mProperty == eCSSProperty_opacity) {
   198       hasOpacity = true;
   199     } else if (prop.mProperty == eCSSProperty_transform) {
   200       hasTransform = true;
   201     }
   202   }
   204   // No properties to animate
   205   if (!existsProperty) {
   206     return false;
   207   }
   209   // This transition can be done on the compositor.  Mark the frame as active, in
   210   // case we are able to throttle this transition.
   211   if (hasOpacity) {
   212     ActiveLayerTracker::NotifyAnimated(frame, eCSSProperty_opacity);
   213   }
   214   if (hasTransform) {
   215     ActiveLayerTracker::NotifyAnimated(frame, eCSSProperty_transform);
   216   }
   217   return true;
   218 }
   220 /*****************************************************************************
   221  * nsTransitionManager                                                       *
   222  *****************************************************************************/
   224 void
   225 nsTransitionManager::UpdateThrottledStylesForSubtree(nsIContent* aContent,
   226                                                      nsStyleContext* aParentStyle,
   227                                                      nsStyleChangeList& aChangeList)
   228 {
   229   dom::Element* element;
   230   if (aContent->IsElement()) {
   231     element = aContent->AsElement();
   232   } else {
   233     element = nullptr;
   234   }
   236   nsRefPtr<nsStyleContext> newStyle;
   238   ElementTransitions* et;
   239   if (element &&
   240       (et = GetElementTransitions(element,
   241                                   nsCSSPseudoElements::ePseudo_NotPseudoElement,
   242                                   false))) {
   243     // re-resolve our style
   244     newStyle = UpdateThrottledStyle(element, aParentStyle, aChangeList);
   245     // remove the current transition from the working set
   246     et->mFlushGeneration = mPresContext->RefreshDriver()->MostRecentRefresh();
   247   } else {
   248     newStyle = ReparentContent(aContent, aParentStyle);
   249   }
   251   // walk the children
   252   if (newStyle) {
   253     for (nsIContent *child = aContent->GetFirstChild(); child;
   254          child = child->GetNextSibling()) {
   255       UpdateThrottledStylesForSubtree(child, newStyle, aChangeList);
   256     }
   257   }
   258 }
   260 IMPL_UPDATE_ALL_THROTTLED_STYLES_INTERNAL(nsTransitionManager,
   261                                           GetElementTransitions)
   263 void
   264 nsTransitionManager::UpdateAllThrottledStyles()
   265 {
   266   if (PR_CLIST_IS_EMPTY(&mElementData)) {
   267     // no throttled transitions, leave early
   268     mPresContext->TickLastUpdateThrottledTransitionStyle();
   269     return;
   270   }
   272   if (mPresContext->ThrottledTransitionStyleIsUpToDate()) {
   273     // throttled transitions are up to date, leave early
   274     return;
   275   }
   277   mPresContext->TickLastUpdateThrottledTransitionStyle();
   278   UpdateAllThrottledStylesInternal();
   279 }
   281 void
   282 nsTransitionManager::ElementDataRemoved()
   283 {
   284   // If we have no transitions or animations left, remove ourselves from
   285   // the refresh driver.
   286   if (PR_CLIST_IS_EMPTY(&mElementData)) {
   287     mPresContext->RefreshDriver()->RemoveRefreshObserver(this, Flush_Style);
   288   }
   289 }
   291 void
   292 nsTransitionManager::AddElementData(CommonElementAnimationData* aData)
   293 {
   294   if (PR_CLIST_IS_EMPTY(&mElementData)) {
   295     // We need to observe the refresh driver.
   296     nsRefreshDriver *rd = mPresContext->RefreshDriver();
   297     rd->AddRefreshObserver(this, Flush_Style);
   298   }
   300   PR_INSERT_BEFORE(aData, &mElementData);
   301 }
   303 already_AddRefed<nsIStyleRule>
   304 nsTransitionManager::StyleContextChanged(dom::Element *aElement,
   305                                          nsStyleContext *aOldStyleContext,
   306                                          nsStyleContext *aNewStyleContext)
   307 {
   308   NS_PRECONDITION(aOldStyleContext->GetPseudo() ==
   309                       aNewStyleContext->GetPseudo(),
   310                   "pseudo type mismatch");
   311   // If we were called from ReparentStyleContext, this assertion would
   312   // actually fire.  If we need to be called from there, we can probably
   313   // just remove it; the condition probably isn't critical, although
   314   // it's worth thinking about some more.
   315   NS_PRECONDITION(aOldStyleContext->HasPseudoElementData() ==
   316                       aNewStyleContext->HasPseudoElementData(),
   317                   "pseudo type mismatch");
   319   if (!mPresContext->IsDynamic()) {
   320     // For print or print preview, ignore transitions.
   321     return nullptr;
   322   }
   324   // NOTE: Things in this function (and ConsiderStartingTransition)
   325   // should never call PeekStyleData because we don't preserve gotten
   326   // structs across reframes.
   328   // Return sooner (before the startedAny check below) for the most
   329   // common case: no transitions specified or running.
   330   const nsStyleDisplay *disp = aNewStyleContext->StyleDisplay();
   331   nsCSSPseudoElements::Type pseudoType = aNewStyleContext->GetPseudoType();
   332   if (pseudoType != nsCSSPseudoElements::ePseudo_NotPseudoElement) {
   333     if (pseudoType != nsCSSPseudoElements::ePseudo_before &&
   334         pseudoType != nsCSSPseudoElements::ePseudo_after) {
   335       return nullptr;
   336     }
   338     NS_ASSERTION((pseudoType == nsCSSPseudoElements::ePseudo_before &&
   339                   aElement->Tag() == nsGkAtoms::mozgeneratedcontentbefore) ||
   340                  (pseudoType == nsCSSPseudoElements::ePseudo_after &&
   341                   aElement->Tag() == nsGkAtoms::mozgeneratedcontentafter),
   342                  "Unexpected aElement coming through");
   344     // Else the element we want to use from now on is the element the
   345     // :before or :after is attached to.
   346     aElement = aElement->GetParent()->AsElement();
   347   }
   349   ElementTransitions *et =
   350       GetElementTransitions(aElement, pseudoType, false);
   351   if (!et &&
   352       disp->mTransitionPropertyCount == 1 &&
   353       disp->mTransitions[0].GetDelay() == 0.0f &&
   354       disp->mTransitions[0].GetDuration() == 0.0f) {
   355     return nullptr;
   356   }
   359   if (aNewStyleContext->PresContext()->IsProcessingAnimationStyleChange()) {
   360     return nullptr;
   361   }
   363   if (aNewStyleContext->GetParent() &&
   364       aNewStyleContext->GetParent()->HasPseudoElementData()) {
   365     // Ignore transitions on things that inherit properties from
   366     // pseudo-elements.
   367     // FIXME (Bug 522599): Add tests for this.
   368     return nullptr;
   369   }
   371   NS_WARN_IF_FALSE(!nsLayoutUtils::AreAsyncAnimationsEnabled() ||
   372                      mPresContext->ThrottledTransitionStyleIsUpToDate(),
   373                    "throttled animations not up to date");
   375   // Per http://lists.w3.org/Archives/Public/www-style/2009Aug/0109.html
   376   // I'll consider only the transitions from the number of items in
   377   // 'transition-property' on down, and later ones will override earlier
   378   // ones (tracked using |whichStarted|).
   379   bool startedAny = false;
   380   nsCSSPropertySet whichStarted;
   381   for (uint32_t i = disp->mTransitionPropertyCount; i-- != 0; ) {
   382     const nsTransition& t = disp->mTransitions[i];
   383     // Check delay and duration first, since they default to zero, and
   384     // when they're both zero, we can ignore the transition.
   385     if (t.GetDelay() != 0.0f || t.GetDuration() != 0.0f) {
   386       // We might have something to transition.  See if any of the
   387       // properties in question changed and are animatable.
   388       // FIXME: Would be good to find a way to share code between this
   389       // interpretation of transition-property and the one below.
   390       nsCSSProperty property = t.GetProperty();
   391       if (property == eCSSPropertyExtra_no_properties ||
   392           property == eCSSPropertyExtra_variable ||
   393           property == eCSSProperty_UNKNOWN) {
   394         // Nothing to do, but need to exclude this from cases below.
   395       } else if (property == eCSSPropertyExtra_all_properties) {
   396         for (nsCSSProperty p = nsCSSProperty(0);
   397              p < eCSSProperty_COUNT_no_shorthands;
   398              p = nsCSSProperty(p + 1)) {
   399           ConsiderStartingTransition(p, t, aElement, et,
   400                                      aOldStyleContext, aNewStyleContext,
   401                                      &startedAny, &whichStarted);
   402         }
   403       } else if (nsCSSProps::IsShorthand(property)) {
   404         CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(subprop, property) {
   405           ConsiderStartingTransition(*subprop, t, aElement, et,
   406                                      aOldStyleContext, aNewStyleContext,
   407                                      &startedAny, &whichStarted);
   408         }
   409       } else {
   410         ConsiderStartingTransition(property, t, aElement, et,
   411                                    aOldStyleContext, aNewStyleContext,
   412                                    &startedAny, &whichStarted);
   413       }
   414     }
   415   }
   417   // Stop any transitions for properties that are no longer in
   418   // 'transition-property'.
   419   // Also stop any transitions for properties that just changed (and are
   420   // still in the set of properties to transition), but we didn't just
   421   // start the transition because delay and duration are both zero.
   422   if (et) {
   423     bool checkProperties =
   424       disp->mTransitions[0].GetProperty() != eCSSPropertyExtra_all_properties;
   425     nsCSSPropertySet allTransitionProperties;
   426     if (checkProperties) {
   427       for (uint32_t i = disp->mTransitionPropertyCount; i-- != 0; ) {
   428         const nsTransition& t = disp->mTransitions[i];
   429         // FIXME: Would be good to find a way to share code between this
   430         // interpretation of transition-property and the one above.
   431         nsCSSProperty property = t.GetProperty();
   432         if (property == eCSSPropertyExtra_no_properties ||
   433             property == eCSSPropertyExtra_variable ||
   434             property == eCSSProperty_UNKNOWN) {
   435           // Nothing to do, but need to exclude this from cases below.
   436         } else if (property == eCSSPropertyExtra_all_properties) {
   437           for (nsCSSProperty p = nsCSSProperty(0);
   438                p < eCSSProperty_COUNT_no_shorthands;
   439                p = nsCSSProperty(p + 1)) {
   440             allTransitionProperties.AddProperty(p);
   441           }
   442         } else if (nsCSSProps::IsShorthand(property)) {
   443           CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(subprop, property) {
   444             allTransitionProperties.AddProperty(*subprop);
   445           }
   446         } else {
   447           allTransitionProperties.AddProperty(property);
   448         }
   449       }
   450     }
   452     nsTArray<ElementPropertyTransition> &pts = et->mPropertyTransitions;
   453     uint32_t i = pts.Length();
   454     NS_ABORT_IF_FALSE(i != 0, "empty transitions list?");
   455     nsStyleAnimation::Value currentValue;
   456     do {
   457       --i;
   458       ElementPropertyTransition &pt = pts[i];
   459       MOZ_ASSERT(pt.mProperties.Length() == 1,
   460                  "Should have one animation property for a transition");
   461       MOZ_ASSERT(pt.mProperties[0].mSegments.Length() == 1,
   462                  "Animation property should have one segment for a transition");
   463       const AnimationProperty& prop = pt.mProperties[0];
   464       const AnimationPropertySegment& segment = prop.mSegments[0];
   465           // properties no longer in 'transition-property'
   466       if ((checkProperties &&
   467            !allTransitionProperties.HasProperty(prop.mProperty)) ||
   468           // properties whose computed values changed but delay and
   469           // duration are both zero
   470           !ExtractComputedValueForTransition(prop.mProperty, aNewStyleContext,
   471                                              currentValue) ||
   472           currentValue != segment.mToValue) {
   473         // stop the transition
   474         pts.RemoveElementAt(i);
   475         et->UpdateAnimationGeneration(mPresContext);
   476       }
   477     } while (i != 0);
   479     if (pts.IsEmpty()) {
   480       et->Destroy();
   481       et = nullptr;
   482     }
   483   }
   485   if (!startedAny) {
   486     return nullptr;
   487   }
   489   NS_ABORT_IF_FALSE(et, "must have element transitions if we started "
   490                         "any transitions");
   492   // In the CSS working group discussion (2009 Jul 15 telecon,
   493   // http://www.w3.org/mid/4A5E1470.4030904@inkedblade.net ) of
   494   // http://lists.w3.org/Archives/Public/www-style/2009Jun/0121.html ,
   495   // the working group decided that a transition property on an
   496   // element should not cause any transitions if the property change
   497   // is itself inheriting a value that is transitioning on an
   498   // ancestor.  So, to get the correct behavior, we continue the
   499   // restyle that caused this transition using a "covering" rule that
   500   // covers up any changes on which we started transitions, so that
   501   // descendants don't start their own transitions.  (In the case of
   502   // negative transition delay, this covering rule produces different
   503   // results than applying the transition rule immediately would).
   504   // Our caller is responsible for restyling again using this covering
   505   // rule.
   507   nsRefPtr<css::AnimValuesStyleRule> coverRule = new css::AnimValuesStyleRule;
   509   nsTArray<ElementPropertyTransition> &pts = et->mPropertyTransitions;
   510   for (uint32_t i = 0, i_end = pts.Length(); i < i_end; ++i) {
   511     ElementPropertyTransition &pt = pts[i];
   512     MOZ_ASSERT(pt.mProperties.Length() == 1,
   513                "Should have one animation property for a transition");
   514     MOZ_ASSERT(pt.mProperties[0].mSegments.Length() == 1,
   515                "Animation property should have one segment for a transition");
   516     AnimationProperty& prop = pt.mProperties[0];
   517     AnimationPropertySegment& segment = prop.mSegments[0];
   518     if (whichStarted.HasProperty(prop.mProperty)) {
   519       coverRule->AddValue(prop.mProperty, segment.mFromValue);
   520     }
   521   }
   523   et->mStyleRule = nullptr;
   525   return coverRule.forget();
   526 }
   528 void
   529 nsTransitionManager::ConsiderStartingTransition(nsCSSProperty aProperty,
   530                                                 const nsTransition& aTransition,
   531                                                 dom::Element* aElement,
   532                                                 ElementTransitions*& aElementTransitions,
   533                                                 nsStyleContext* aOldStyleContext,
   534                                                 nsStyleContext* aNewStyleContext,
   535                                                 bool* aStartedAny,
   536                                                 nsCSSPropertySet* aWhichStarted)
   537 {
   538   // IsShorthand itself will assert if aProperty is not a property.
   539   NS_ABORT_IF_FALSE(!nsCSSProps::IsShorthand(aProperty),
   540                     "property out of range");
   541   NS_ASSERTION(!aElementTransitions ||
   542                aElementTransitions->mElement == aElement, "Element mismatch");
   544   if (aWhichStarted->HasProperty(aProperty)) {
   545     // A later item in transition-property already started a
   546     // transition for this property, so we ignore this one.
   547     // See comment above and
   548     // http://lists.w3.org/Archives/Public/www-style/2009Aug/0109.html .
   549     return;
   550   }
   552   if (nsCSSProps::kAnimTypeTable[aProperty] == eStyleAnimType_None) {
   553     return;
   554   }
   556   ElementPropertyTransition pt;
   558   nsStyleAnimation::Value startValue, endValue, dummyValue;
   559   bool haveValues =
   560     ExtractComputedValueForTransition(aProperty, aOldStyleContext,
   561                                       startValue) &&
   562     ExtractComputedValueForTransition(aProperty, aNewStyleContext,
   563                                       endValue);
   565   bool haveChange = startValue != endValue;
   567   bool shouldAnimate =
   568     haveValues &&
   569     haveChange &&
   570     // Check that we can interpolate between these values
   571     // (If this is ever a performance problem, we could add a
   572     // CanInterpolate method, but it seems fine for now.)
   573     nsStyleAnimation::Interpolate(aProperty, startValue, endValue,
   574                                   0.5, dummyValue);
   576   bool haveCurrentTransition = false;
   577   uint32_t currentIndex = nsTArray<ElementPropertyTransition>::NoIndex;
   578   const ElementPropertyTransition *oldPT = nullptr;
   579   if (aElementTransitions) {
   580     nsTArray<ElementPropertyTransition> &pts =
   581       aElementTransitions->mPropertyTransitions;
   582     for (uint32_t i = 0, i_end = pts.Length(); i < i_end; ++i) {
   583       MOZ_ASSERT(pts[i].mProperties.Length() == 1,
   584                  "Should have one animation property for a transition");
   585       if (pts[i].mProperties[0].mProperty == aProperty) {
   586         haveCurrentTransition = true;
   587         currentIndex = i;
   588         oldPT = &aElementTransitions->mPropertyTransitions[currentIndex];
   589         break;
   590       }
   591     }
   592   }
   594   // If we got a style change that changed the value to the endpoint
   595   // of the currently running transition, we don't want to interrupt
   596   // its timing function.
   597   // This needs to be before the !shouldAnimate && haveCurrentTransition
   598   // case below because we might be close enough to the end of the
   599   // transition that the current value rounds to the final value.  In
   600   // this case, we'll end up with shouldAnimate as false (because
   601   // there's no value change), but we need to return early here rather
   602   // than cancel the running transition because shouldAnimate is false!
   603   MOZ_ASSERT(!oldPT || oldPT->mProperties[0].mSegments.Length() == 1,
   604              "Should have one animation property segment for a transition");
   605   if (haveCurrentTransition && haveValues &&
   606       oldPT->mProperties[0].mSegments[0].mToValue == endValue) {
   607     // WalkTransitionRule already called RestyleForAnimation.
   608     return;
   609   }
   611   nsPresContext *presContext = aNewStyleContext->PresContext();
   613   if (!shouldAnimate) {
   614     if (haveCurrentTransition) {
   615       // We're in the middle of a transition, and just got a non-transition
   616       // style change to something that we can't animate.  This might happen
   617       // because we got a non-transition style change changing to the current
   618       // in-progress value (which is particularly easy to cause when we're
   619       // currently in the 'transition-delay').  It also might happen because we
   620       // just got a style change to a value that can't be interpolated.
   621       nsTArray<ElementPropertyTransition> &pts =
   622         aElementTransitions->mPropertyTransitions;
   623       pts.RemoveElementAt(currentIndex);
   624       aElementTransitions->UpdateAnimationGeneration(mPresContext);
   626       if (pts.IsEmpty()) {
   627         aElementTransitions->Destroy();
   628         // |aElementTransitions| is now a dangling pointer!
   629         aElementTransitions = nullptr;
   630       }
   631       // WalkTransitionRule already called RestyleForAnimation.
   632     }
   633     return;
   634   }
   636   TimeStamp mostRecentRefresh =
   637     presContext->RefreshDriver()->MostRecentRefresh();
   639   const nsTimingFunction &tf = aTransition.GetTimingFunction();
   640   float delay = aTransition.GetDelay();
   641   float duration = aTransition.GetDuration();
   642   if (duration < 0.0) {
   643     // The spec says a negative duration is treated as zero.
   644     duration = 0.0;
   645   }
   646   pt.mStartForReversingTest = startValue;
   647   pt.mReversePortion = 1.0;
   649   // If the new transition reverses an existing one, we'll need to
   650   // handle the timing differently.
   651   if (haveCurrentTransition &&
   652       !oldPT->IsRemovedSentinel() &&
   653       oldPT->mStartForReversingTest == endValue) {
   654     // Compute the appropriate negative transition-delay such that right
   655     // now we'd end up at the current position.
   656     double valuePortion =
   657       oldPT->ValuePortionFor(mostRecentRefresh) * oldPT->mReversePortion +
   658       (1.0 - oldPT->mReversePortion);
   659     // A timing function with negative y1 (or y2!) might make
   660     // valuePortion negative.  In this case, we still want to apply our
   661     // reversing logic based on relative distances, not make duration
   662     // negative.
   663     if (valuePortion < 0.0) {
   664       valuePortion = -valuePortion;
   665     }
   666     // A timing function with y2 (or y1!) greater than one might
   667     // advance past its terminal value.  It's probably a good idea to
   668     // clamp valuePortion to be at most one to preserve the invariant
   669     // that a transition will complete within at most its specified
   670     // time.
   671     if (valuePortion > 1.0) {
   672       valuePortion = 1.0;
   673     }
   675     // Negative delays are essentially part of the transition
   676     // function, so reduce them along with the duration, but don't
   677     // reduce positive delays.
   678     if (delay < 0.0f) {
   679       delay *= valuePortion;
   680     }
   682     duration *= valuePortion;
   684     pt.mStartForReversingTest = oldPT->mProperties[0].mSegments[0].mToValue;
   685     pt.mReversePortion = valuePortion;
   686   }
   688   AnimationProperty& prop = *pt.mProperties.AppendElement();
   689   prop.mProperty = aProperty;
   691   AnimationPropertySegment& segment = *prop.mSegments.AppendElement();
   692   segment.mFromValue = startValue;
   693   segment.mToValue = endValue;
   694   segment.mFromKey = 0;
   695   segment.mToKey = 1;
   696   segment.mTimingFunction.Init(tf);
   698   pt.mStartTime = mostRecentRefresh;
   699   pt.mDelay = TimeDuration::FromMilliseconds(delay);
   700   pt.mIterationDuration = TimeDuration::FromMilliseconds(duration);
   701   pt.mIterationCount = 1;
   702   pt.mDirection = NS_STYLE_ANIMATION_DIRECTION_NORMAL;
   703   pt.mFillMode = NS_STYLE_ANIMATION_FILL_MODE_BACKWARDS;
   704   pt.mPlayState = NS_STYLE_ANIMATION_PLAY_STATE_RUNNING;
   705   pt.mPauseStart = TimeStamp();
   707   if (!aElementTransitions) {
   708     aElementTransitions =
   709       GetElementTransitions(aElement, aNewStyleContext->GetPseudoType(),
   710                             true);
   711     if (!aElementTransitions) {
   712       NS_WARNING("allocating ElementTransitions failed");
   713       return;
   714     }
   715   }
   717   nsTArray<ElementPropertyTransition> &pts =
   718     aElementTransitions->mPropertyTransitions;
   719 #ifdef DEBUG
   720   for (uint32_t i = 0, i_end = pts.Length(); i < i_end; ++i) {
   721     NS_ABORT_IF_FALSE(pts[i].mProperties.Length() == 1,
   722                       "Should have one animation property for a transition");
   723     NS_ABORT_IF_FALSE(i == currentIndex ||
   724                       pts[i].mProperties[0].mProperty != aProperty,
   725                       "duplicate transitions for property");
   726   }
   727 #endif
   728   if (haveCurrentTransition) {
   729     pts[currentIndex] = pt;
   730   } else {
   731     if (!pts.AppendElement(pt)) {
   732       NS_WARNING("out of memory");
   733       return;
   734     }
   735   }
   736   aElementTransitions->UpdateAnimationGeneration(mPresContext);
   738   nsRestyleHint hint =
   739     aNewStyleContext->GetPseudoType() ==
   740       nsCSSPseudoElements::ePseudo_NotPseudoElement ?
   741     eRestyle_Self : eRestyle_Subtree;
   742   presContext->PresShell()->RestyleForAnimation(aElement, hint);
   744   *aStartedAny = true;
   745   aWhichStarted->AddProperty(aProperty);
   746 }
   748 ElementTransitions*
   749 nsTransitionManager::GetElementTransitions(dom::Element *aElement,
   750                                            nsCSSPseudoElements::Type aPseudoType,
   751                                            bool aCreateIfNeeded)
   752 {
   753   if (!aCreateIfNeeded && PR_CLIST_IS_EMPTY(&mElementData)) {
   754     // Early return for the most common case.
   755     return nullptr;
   756   }
   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 nullptr;
   770   }
   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       mPresContext->RefreshDriver()->MostRecentRefresh());
   777     nsresult rv = aElement->SetProperty(propName, et,
   778                                         ElementTransitionsPropertyDtor, false);
   779     if (NS_FAILED(rv)) {
   780       NS_WARNING("SetProperty failed");
   781       delete et;
   782       return nullptr;
   783     }
   784     if (propName == nsGkAtoms::transitionsProperty) {
   785       aElement->SetMayHaveAnimations();
   786     }
   788     AddElementData(et);
   789   }
   791   return et;
   792 }
   794 /*
   795  * nsIStyleRuleProcessor implementation
   796  */
   798 void
   799 nsTransitionManager::WalkTransitionRule(ElementDependentRuleProcessorData* aData,
   800                                         nsCSSPseudoElements::Type aPseudoType)
   801 {
   802   ElementTransitions *et =
   803     GetElementTransitions(aData->mElement, aPseudoType, false);
   804   if (!et) {
   805     return;
   806   }
   808   if (!mPresContext->IsDynamic()) {
   809     // For print or print preview, ignore animations.
   810     return;
   811   }
   813   if (aData->mPresContext->IsProcessingRestyles() &&
   814       !aData->mPresContext->IsProcessingAnimationStyleChange()) {
   815     // If we're processing a normal style change rather than one from
   816     // animation, don't add the transition rule.  This allows us to
   817     // compute the new style value rather than having the transition
   818     // override it, so that we can start transitioning differently.
   820     // We need to immediately restyle with animation
   821     // after doing this.
   822     nsRestyleHint hint =
   823       aPseudoType == nsCSSPseudoElements::ePseudo_NotPseudoElement ?
   824       eRestyle_Self : eRestyle_Subtree;
   825     mPresContext->PresShell()->RestyleForAnimation(aData->mElement, hint);
   826     return;
   827   }
   829   et->EnsureStyleRuleFor(
   830     aData->mPresContext->RefreshDriver()->MostRecentRefresh());
   832   aData->mRuleWalker->Forward(et->mStyleRule);
   833 }
   835 /* virtual */ void
   836 nsTransitionManager::RulesMatching(ElementRuleProcessorData* aData)
   837 {
   838   NS_ABORT_IF_FALSE(aData->mPresContext == mPresContext,
   839                     "pres context mismatch");
   840   WalkTransitionRule(aData,
   841                      nsCSSPseudoElements::ePseudo_NotPseudoElement);
   842 }
   844 /* virtual */ void
   845 nsTransitionManager::RulesMatching(PseudoElementRuleProcessorData* aData)
   846 {
   847   NS_ABORT_IF_FALSE(aData->mPresContext == mPresContext,
   848                     "pres context mismatch");
   850   // Note:  If we're the only thing keeping a pseudo-element frame alive
   851   // (per ProbePseudoStyleContext), we still want to keep it alive, so
   852   // this is ok.
   853   WalkTransitionRule(aData, aData->mPseudoType);
   854 }
   856 /* virtual */ void
   857 nsTransitionManager::RulesMatching(AnonBoxRuleProcessorData* aData)
   858 {
   859 }
   861 #ifdef MOZ_XUL
   862 /* virtual */ void
   863 nsTransitionManager::RulesMatching(XULTreeRuleProcessorData* aData)
   864 {
   865 }
   866 #endif
   868 /* virtual */ size_t
   869 nsTransitionManager::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
   870 {
   871   return CommonAnimationManager::SizeOfExcludingThis(aMallocSizeOf);
   872 }
   874 /* virtual */ size_t
   875 nsTransitionManager::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
   876 {
   877   return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
   878 }
   880 struct TransitionEventInfo {
   881   nsCOMPtr<nsIContent> mElement;
   882   InternalTransitionEvent mEvent;
   884   TransitionEventInfo(nsIContent *aElement, nsCSSProperty aProperty,
   885                       TimeDuration aDuration, const nsAString& aPseudoElement)
   886     : mElement(aElement)
   887     , mEvent(true, NS_TRANSITION_END)
   888   {
   889     // XXX Looks like nobody initialize WidgetEvent::time
   890     mEvent.propertyName =
   891       NS_ConvertUTF8toUTF16(nsCSSProps::GetStringValue(aProperty));
   892     mEvent.elapsedTime = aDuration.ToSeconds();
   893     mEvent.pseudoElement = aPseudoElement;
   894   }
   896   // InternalTransitionEvent doesn't support copy-construction, so we need
   897   // to ourselves in order to work with nsTArray
   898   TransitionEventInfo(const TransitionEventInfo &aOther)
   899     : mElement(aOther.mElement)
   900     , mEvent(true, NS_TRANSITION_END)
   901   {
   902     mEvent.AssignTransitionEventData(aOther.mEvent, false);
   903   }
   904 };
   906 /* virtual */ void
   907 nsTransitionManager::WillRefresh(mozilla::TimeStamp aTime)
   908 {
   909   NS_ABORT_IF_FALSE(mPresContext,
   910                     "refresh driver should not notify additional observers "
   911                     "after pres context has been destroyed");
   912   if (!mPresContext->GetPresShell()) {
   913     // Someone might be keeping mPresContext alive past the point
   914     // where it has been torn down; don't bother doing anything in
   915     // this case.  But do get rid of all our transitions so we stop
   916     // triggering refreshes.
   917     RemoveAllElementData();
   918     return;
   919   }
   921   FlushTransitions(Can_Throttle);
   922 }
   924 void
   925 nsTransitionManager::FlushTransitions(FlushFlags aFlags)
   926 { 
   927   if (PR_CLIST_IS_EMPTY(&mElementData)) {
   928     // no transitions, leave early
   929     return;
   930   }
   932   nsTArray<TransitionEventInfo> events;
   933   TimeStamp now = mPresContext->RefreshDriver()->MostRecentRefresh();
   934   bool didThrottle = false;
   935   // Trim transitions that have completed, post restyle events for frames that
   936   // are still transitioning, and start transitions with delays.
   937   {
   938     PRCList *next = PR_LIST_HEAD(&mElementData);
   939     while (next != &mElementData) {
   940       ElementTransitions *et = static_cast<ElementTransitions*>(next);
   941       next = PR_NEXT_LINK(next);
   943       bool canThrottleTick = aFlags == Can_Throttle &&
   944         et->CanPerformOnCompositorThread(
   945           CommonElementAnimationData::CanAnimateFlags(0)) &&
   946         et->CanThrottleAnimation(now);
   948       NS_ABORT_IF_FALSE(et->mElement->GetCurrentDoc() ==
   949                           mPresContext->Document(),
   950                         "Element::UnbindFromTree should have "
   951                         "destroyed the element transitions object");
   953       uint32_t i = et->mPropertyTransitions.Length();
   954       NS_ABORT_IF_FALSE(i != 0, "empty transitions list?");
   955       bool transitionStartedOrEnded = false;
   956       do {
   957         --i;
   958         ElementPropertyTransition &pt = et->mPropertyTransitions[i];
   959         if (pt.IsRemovedSentinel()) {
   960           // Actually remove transitions one throttle-able cycle after their
   961           // completion. We only clear on a throttle-able cycle because that
   962           // means it is a regular restyle tick and thus it is safe to discard
   963           // the transition. If the flush is not throttle-able, we might still
   964           // have new transitions left to process. See comment below.
   965           if (aFlags == Can_Throttle) {
   966             et->mPropertyTransitions.RemoveElementAt(i);
   967           }
   968         } else if (pt.mStartTime + pt.mDelay + pt.mIterationDuration <= now) {
   969           MOZ_ASSERT(pt.mProperties.Length() == 1,
   970                      "Should have one animation property for a transition");
   971           nsCSSProperty prop = pt.mProperties[0].mProperty;
   972           if (nsCSSProps::PropHasFlags(prop, CSS_PROPERTY_REPORT_OTHER_NAME))
   973           {
   974             prop = nsCSSProps::OtherNameFor(prop);
   975           }
   976           nsIAtom* ep = et->mElementProperty;
   977           NS_NAMED_LITERAL_STRING(before, "::before");
   978           NS_NAMED_LITERAL_STRING(after, "::after");
   979           events.AppendElement(
   980             TransitionEventInfo(et->mElement, prop, pt.mIterationDuration,
   981                                 ep == nsGkAtoms::transitionsProperty ?
   982                                   EmptyString() :
   983                                   ep == nsGkAtoms::transitionsOfBeforeProperty ?
   984                                     before :
   985                                     after));
   987           // Leave this transition in the list for one more refresh
   988           // cycle, since we haven't yet processed its style change, and
   989           // if we also have (already, or will have from processing
   990           // transitionend events or other refresh driver notifications)
   991           // a non-animation style change that would affect it, we need
   992           // to know not to start a new transition for the transition
   993           // from the almost-completed value to the final value.
   994           pt.SetRemovedSentinel();
   995           et->UpdateAnimationGeneration(mPresContext);
   996           transitionStartedOrEnded = true;
   997         } else if (pt.mStartTime + pt.mDelay <= now && canThrottleTick &&
   998                    !pt.mIsRunningOnCompositor) {
   999           // Start a transition with a delay where we should start the
  1000           // transition proper.
  1001           et->UpdateAnimationGeneration(mPresContext);
  1002           transitionStartedOrEnded = true;
  1004       } while (i != 0);
  1006       // We need to restyle even if the transition rule no longer
  1007       // applies (in which case we just made it not apply).
  1008       NS_ASSERTION(et->mElementProperty == nsGkAtoms::transitionsProperty ||
  1009                    et->mElementProperty == nsGkAtoms::transitionsOfBeforeProperty ||
  1010                    et->mElementProperty == nsGkAtoms::transitionsOfAfterProperty,
  1011                    "Unexpected element property; might restyle too much");
  1012       if (!canThrottleTick || transitionStartedOrEnded) {
  1013         nsRestyleHint hint = et->mElementProperty == nsGkAtoms::transitionsProperty ?
  1014           eRestyle_Self : eRestyle_Subtree;
  1015         mPresContext->PresShell()->RestyleForAnimation(et->mElement, hint);
  1016       } else {
  1017         didThrottle = true;
  1020       if (et->mPropertyTransitions.IsEmpty()) {
  1021         et->Destroy();
  1022         // |et| is now a dangling pointer!
  1023         et = nullptr;
  1028   if (didThrottle) {
  1029     mPresContext->Document()->SetNeedStyleFlush();
  1032   for (uint32_t i = 0, i_end = events.Length(); i < i_end; ++i) {
  1033     TransitionEventInfo &info = events[i];
  1034     EventDispatcher::Dispatch(info.mElement, mPresContext, &info.mEvent);
  1036     if (!mPresContext) {
  1037       break;

mercurial