layout/style/nsAnimationManager.cpp

Fri, 16 Jan 2015 18:13:44 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Fri, 16 Jan 2015 18:13:44 +0100
branch
TOR_BUG_9701
changeset 14
925c144e1f1f
permissions
-rw-r--r--

Integrate suggestion from review to improve consistency with existing code.

     1 /* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
     2 /* This Source Code Form is subject to the terms of the Mozilla Public
     3  * License, v. 2.0. If a copy of the MPL was not distributed with this
     4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 #include "nsAnimationManager.h"
     7 #include "nsTransitionManager.h"
     9 #include "mozilla/EventDispatcher.h"
    10 #include "mozilla/MemoryReporting.h"
    12 #include "nsPresContext.h"
    13 #include "nsRuleProcessorData.h"
    14 #include "nsStyleSet.h"
    15 #include "nsStyleChangeList.h"
    16 #include "nsCSSRules.h"
    17 #include "RestyleManager.h"
    18 #include "nsStyleAnimation.h"
    19 #include "nsLayoutUtils.h"
    20 #include "nsIFrame.h"
    21 #include "nsIDocument.h"
    22 #include "ActiveLayerTracker.h"
    23 #include <math.h>
    25 using namespace mozilla;
    26 using namespace mozilla::css;
    28 ElementAnimations::ElementAnimations(mozilla::dom::Element *aElement,
    29                                      nsIAtom *aElementProperty,
    30                                      nsAnimationManager *aAnimationManager,
    31                                      TimeStamp aNow)
    32   : CommonElementAnimationData(aElement, aElementProperty,
    33                                aAnimationManager, aNow),
    34     mNeedsRefreshes(true)
    35 {
    36 }
    38 static void
    39 ElementAnimationsPropertyDtor(void           *aObject,
    40                               nsIAtom        *aPropertyName,
    41                               void           *aPropertyValue,
    42                               void           *aData)
    43 {
    44   ElementAnimations *ea = static_cast<ElementAnimations*>(aPropertyValue);
    45 #ifdef DEBUG
    46   NS_ABORT_IF_FALSE(!ea->mCalledPropertyDtor, "can't call dtor twice");
    47   ea->mCalledPropertyDtor = true;
    48 #endif
    49   delete ea;
    50 }
    52 double
    53 ElementAnimations::GetPositionInIteration(TimeDuration aElapsedDuration,
    54                                           TimeDuration aIterationDuration,
    55                                           double aIterationCount,
    56                                           uint32_t aDirection,
    57                                           StyleAnimation* aAnimation,
    58                                           ElementAnimations* aEa,
    59                                           EventArray* aEventsToDispatch)
    60 {
    61   MOZ_ASSERT(!aAnimation == !aEa && !aAnimation == !aEventsToDispatch);
    63   // Set |currentIterationCount| to the (fractional) number of
    64   // iterations we've completed up to the current position.
    65   double currentIterationCount = aElapsedDuration / aIterationDuration;
    66   bool dispatchStartOrIteration = false;
    67   if (currentIterationCount >= aIterationCount) {
    68     if (aAnimation) {
    69       // Dispatch 'animationend' when needed.
    70       if (aAnimation->mLastNotification !=
    71           StyleAnimation::LAST_NOTIFICATION_END) {
    72         aAnimation->mLastNotification = StyleAnimation::LAST_NOTIFICATION_END;
    73         AnimationEventInfo ei(aEa->mElement, aAnimation->mName, NS_ANIMATION_END,
    74                               aElapsedDuration, aEa->PseudoElement());
    75         aEventsToDispatch->AppendElement(ei);
    76       }
    78       if (!aAnimation->FillsForwards()) {
    79         // No animation data.
    80         return -1;
    81       }
    82     } else {
    83       // If aAnimation is null, that means we're on the compositor
    84       // thread.  We want to just keep filling forwards until the main
    85       // thread gets around to updating the compositor thread (which
    86       // might take a little while).  So just assume we fill fowards and
    87       // move on.
    88     }
    89     currentIterationCount = aIterationCount;
    90   } else {
    91     if (aAnimation && !aAnimation->IsPaused()) {
    92       aEa->mNeedsRefreshes = true;
    93     }
    94     if (currentIterationCount < 0.0) {
    95       NS_ASSERTION(aAnimation, "Should not run animation that hasn't started yet on the compositor");
    96       if (!aAnimation->FillsBackwards()) {
    97         // No animation data.
    98         return -1;
    99       }
   100       currentIterationCount = 0.0;
   101     } else {
   102       dispatchStartOrIteration = aAnimation && !aAnimation->IsPaused();
   103     }
   104   }
   106   // Set |positionInIteration| to the position from 0% to 100% along
   107   // the keyframes.
   108   NS_ABORT_IF_FALSE(currentIterationCount >= 0.0, "must be positive");
   109   double whichIteration = floor(currentIterationCount);
   110   if (whichIteration == aIterationCount && whichIteration != 0.0) {
   111     // When the animation's iteration count is an integer (as it
   112     // normally is), we need to end at 100% of its last iteration
   113     // rather than 0% of the next one (unless it's zero).
   114     whichIteration -= 1.0;
   115   }
   116   double positionInIteration = currentIterationCount - whichIteration;
   118   bool thisIterationReverse = false;
   119   switch (aDirection) {
   120     case NS_STYLE_ANIMATION_DIRECTION_NORMAL:
   121       thisIterationReverse = false;
   122       break;
   123     case NS_STYLE_ANIMATION_DIRECTION_REVERSE:
   124       thisIterationReverse = true;
   125       break;
   126     case NS_STYLE_ANIMATION_DIRECTION_ALTERNATE:
   127       // uint64_t has more integer precision than double does, so if
   128       // whichIteration is that large, we've already lost and we're just
   129       // guessing.  But the animation is presumably oscillating so fast
   130       // it doesn't matter anyway.
   131       thisIterationReverse = (uint64_t(whichIteration) & 1) == 1;
   132       break;
   133     case NS_STYLE_ANIMATION_DIRECTION_ALTERNATE_REVERSE:
   134       // see as previous case
   135       thisIterationReverse = (uint64_t(whichIteration) & 1) == 0;
   136       break;
   137   }
   138   if (thisIterationReverse) {
   139     positionInIteration = 1.0 - positionInIteration;
   140   }
   142   // Dispatch 'animationstart' or 'animationiteration' when needed.
   143   if (aAnimation && dispatchStartOrIteration &&
   144       whichIteration != aAnimation->mLastNotification) {
   145     // Notify 'animationstart' even if a negative delay puts us
   146     // past the first iteration.
   147     // Note that when somebody changes the animation-duration
   148     // dynamically, this will fire an extra iteration event
   149     // immediately in many cases.  It's not clear to me if that's the
   150     // right thing to do.
   151     uint32_t message =
   152       aAnimation->mLastNotification == StyleAnimation::LAST_NOTIFICATION_NONE
   153         ? NS_ANIMATION_START : NS_ANIMATION_ITERATION;
   155     aAnimation->mLastNotification = whichIteration;
   156     AnimationEventInfo ei(aEa->mElement, aAnimation->mName, message,
   157                           aElapsedDuration, aEa->PseudoElement());
   158     aEventsToDispatch->AppendElement(ei);
   159   }
   161   return positionInIteration;
   162 }
   164 void
   165 ElementAnimations::EnsureStyleRuleFor(TimeStamp aRefreshTime,
   166                                       EventArray& aEventsToDispatch,
   167                                       bool aIsThrottled)
   168 {
   169   if (!mNeedsRefreshes) {
   170     mStyleRuleRefreshTime = aRefreshTime;
   171     return;
   172   }
   174   // If we're performing animations on the compositor thread, then we can skip
   175   // most of the work in this method. But even if we are throttled, then we
   176   // have to do the work if an animation is ending in order to get correct end
   177   // of animation behaviour (the styles of the animation disappear, or the fill
   178   // mode behaviour). This loop checks for any finishing animations and forces
   179   // the style recalculation if we find any.
   180   if (aIsThrottled) {
   181     for (uint32_t animIdx = mAnimations.Length(); animIdx-- != 0; ) {
   182       StyleAnimation &anim = mAnimations[animIdx];
   184       if (anim.mProperties.Length() == 0 ||
   185           anim.mIterationDuration.ToMilliseconds() <= 0.0) {
   186         continue;
   187       }
   189       uint32_t oldLastNotification = anim.mLastNotification;
   191       // We need to call GetPositionInIteration here to populate
   192       // aEventsToDispatch.
   193       // The ElapsedDurationAt() call here handles pausing.  But:
   194       // FIXME: avoid recalculating every time when paused.
   195       GetPositionInIteration(anim.ElapsedDurationAt(aRefreshTime),
   196                              anim.mIterationDuration, anim.mIterationCount,
   197                              anim.mDirection, &anim, this, &aEventsToDispatch);
   199       // GetPositionInIteration just adjusted mLastNotification; check
   200       // its new value against the value before we called
   201       // GetPositionInIteration.
   202       // XXX We shouldn't really be using mLastNotification as a general
   203       // indicator that the animation has finished, it should be reserved for
   204       // events. If we use it differently in the future this use might need
   205       // changing.
   206       if (!anim.mIsRunningOnCompositor ||
   207           (anim.mLastNotification != oldLastNotification &&
   208            anim.mLastNotification == StyleAnimation::LAST_NOTIFICATION_END)) {
   209         aIsThrottled = false;
   210         break;
   211       }
   212     }
   213   }
   215   if (aIsThrottled) {
   216     return;
   217   }
   219   // mStyleRule may be null and valid, if we have no style to apply.
   220   if (mStyleRuleRefreshTime.IsNull() ||
   221       mStyleRuleRefreshTime != aRefreshTime) {
   222     mStyleRuleRefreshTime = aRefreshTime;
   223     mStyleRule = nullptr;
   224     // We'll set mNeedsRefreshes to true below in all cases where we need them.
   225     mNeedsRefreshes = false;
   227     // FIXME(spec): assume that properties in higher animations override
   228     // those in lower ones.
   229     // Therefore, we iterate from last animation to first.
   230     nsCSSPropertySet properties;
   232     for (uint32_t animIdx = mAnimations.Length(); animIdx-- != 0; ) {
   233       StyleAnimation &anim = mAnimations[animIdx];
   235       if (anim.mProperties.Length() == 0 ||
   236           anim.mIterationDuration.ToMilliseconds() <= 0.0) {
   237         // No animation data.
   238         continue;
   239       }
   241       // The ElapsedDurationAt() call here handles pausing.  But:
   242       // FIXME: avoid recalculating every time when paused.
   243       double positionInIteration =
   244         GetPositionInIteration(anim.ElapsedDurationAt(aRefreshTime),
   245                                anim.mIterationDuration, anim.mIterationCount,
   246                                anim.mDirection, &anim, this,
   247                                &aEventsToDispatch);
   249       // The position is -1 when we don't have fill data for the current time,
   250       // so we shouldn't animate.
   251       if (positionInIteration == -1)
   252         continue;
   254       NS_ABORT_IF_FALSE(0.0 <= positionInIteration &&
   255                           positionInIteration <= 1.0,
   256                         "position should be in [0-1]");
   258       for (uint32_t propIdx = 0, propEnd = anim.mProperties.Length();
   259            propIdx != propEnd; ++propIdx)
   260       {
   261         const AnimationProperty &prop = anim.mProperties[propIdx];
   263         NS_ABORT_IF_FALSE(prop.mSegments[0].mFromKey == 0.0,
   264                           "incorrect first from key");
   265         NS_ABORT_IF_FALSE(prop.mSegments[prop.mSegments.Length() - 1].mToKey
   266                             == 1.0,
   267                           "incorrect last to key");
   269         if (properties.HasProperty(prop.mProperty)) {
   270           // A later animation already set this property.
   271           continue;
   272         }
   273         properties.AddProperty(prop.mProperty);
   275         NS_ABORT_IF_FALSE(prop.mSegments.Length() > 0,
   276                           "property should not be in animations if it "
   277                           "has no segments");
   279         // FIXME: Maybe cache the current segment?
   280         const AnimationPropertySegment *segment = prop.mSegments.Elements(),
   281                                *segmentEnd = segment + prop.mSegments.Length();
   282         while (segment->mToKey < positionInIteration) {
   283           NS_ABORT_IF_FALSE(segment->mFromKey < segment->mToKey,
   284                             "incorrect keys");
   285           ++segment;
   286           if (segment == segmentEnd) {
   287             NS_ABORT_IF_FALSE(false, "incorrect positionInIteration");
   288             break; // in order to continue in outer loop (just below)
   289           }
   290           NS_ABORT_IF_FALSE(segment->mFromKey == (segment-1)->mToKey,
   291                             "incorrect keys");
   292         }
   293         if (segment == segmentEnd) {
   294           continue;
   295         }
   296         NS_ABORT_IF_FALSE(segment->mFromKey < segment->mToKey,
   297                           "incorrect keys");
   298         NS_ABORT_IF_FALSE(segment >= prop.mSegments.Elements() &&
   299                           size_t(segment - prop.mSegments.Elements()) <
   300                             prop.mSegments.Length(),
   301                           "out of array bounds");
   303         if (!mStyleRule) {
   304           // Allocate the style rule now that we know we have animation data.
   305           mStyleRule = new css::AnimValuesStyleRule();
   306         }
   308         double positionInSegment = (positionInIteration - segment->mFromKey) /
   309                                    (segment->mToKey - segment->mFromKey);
   310         double valuePosition =
   311           segment->mTimingFunction.GetValue(positionInSegment);
   313         nsStyleAnimation::Value *val =
   314           mStyleRule->AddEmptyValue(prop.mProperty);
   316 #ifdef DEBUG
   317         bool result =
   318 #endif
   319           nsStyleAnimation::Interpolate(prop.mProperty,
   320                                         segment->mFromValue, segment->mToValue,
   321                                         valuePosition, *val);
   322         NS_ABORT_IF_FALSE(result, "interpolate must succeed now");
   323       }
   324     }
   325   }
   326 }
   329 bool
   330 ElementAnimations::HasAnimationOfProperty(nsCSSProperty aProperty) const
   331 {
   332   for (uint32_t animIdx = mAnimations.Length(); animIdx-- != 0; ) {
   333     const StyleAnimation &anim = mAnimations[animIdx];
   334     if (anim.HasAnimationOfProperty(aProperty)) {
   335       return true;
   336     }
   337   }
   338   return false;
   339 }
   341 bool
   342 ElementAnimations::CanPerformOnCompositorThread(CanAnimateFlags aFlags) const
   343 {
   344   nsIFrame* frame = nsLayoutUtils::GetStyleFrame(mElement);
   345   if (!frame) {
   346     return false;
   347   }
   349   if (mElementProperty != nsGkAtoms::animationsProperty) {
   350     if (nsLayoutUtils::IsAnimationLoggingEnabled()) {
   351       nsCString message;
   352       message.AppendLiteral("Gecko bug: Async animation of pseudoelements not supported.  See bug 771367 (");
   353       message.Append(nsAtomCString(mElementProperty));
   354       message.AppendLiteral(")");
   355       LogAsyncAnimationFailure(message, mElement);
   356     }
   357     return false;
   358   }
   360   TimeStamp now = frame->PresContext()->RefreshDriver()->MostRecentRefresh();
   362   for (uint32_t animIdx = mAnimations.Length(); animIdx-- != 0; ) {
   363     const StyleAnimation& anim = mAnimations[animIdx];
   364     for (uint32_t propIdx = 0, propEnd = anim.mProperties.Length();
   365          propIdx != propEnd; ++propIdx) {
   366       if (IsGeometricProperty(anim.mProperties[propIdx].mProperty) &&
   367           anim.IsRunningAt(now)) {
   368         aFlags = CanAnimateFlags(aFlags | CanAnimate_HasGeometricProperty);
   369         break;
   370       }
   371     }
   372   }
   374   bool hasOpacity = false;
   375   bool hasTransform = false;
   376   for (uint32_t animIdx = mAnimations.Length(); animIdx-- != 0; ) {
   377     const StyleAnimation& anim = mAnimations[animIdx];
   378     if (!anim.IsRunningAt(now)) {
   379       continue;
   380     }
   382     for (uint32_t propIdx = 0, propEnd = anim.mProperties.Length();
   383          propIdx != propEnd; ++propIdx) {
   384       const AnimationProperty& prop = anim.mProperties[propIdx];
   385       if (!CanAnimatePropertyOnCompositor(mElement,
   386                                           prop.mProperty,
   387                                           aFlags) ||
   388           IsCompositorAnimationDisabledForFrame(frame)) {
   389         return false;
   390       }
   391       if (prop.mProperty == eCSSProperty_opacity) {
   392         hasOpacity = true;
   393       } else if (prop.mProperty == eCSSProperty_transform) {
   394         hasTransform = true;
   395       }
   396     }
   397   }
   398   // This animation can be done on the compositor.  Mark the frame as active, in
   399   // case we are able to throttle this animation.
   400   if (hasOpacity) {
   401     ActiveLayerTracker::NotifyAnimated(frame, eCSSProperty_opacity);
   402   }
   403   if (hasTransform) {
   404     ActiveLayerTracker::NotifyAnimated(frame, eCSSProperty_transform);
   405   }
   406   return true;
   407 }
   409 ElementAnimations*
   410 nsAnimationManager::GetElementAnimations(dom::Element *aElement,
   411                                          nsCSSPseudoElements::Type aPseudoType,
   412                                          bool aCreateIfNeeded)
   413 {
   414   if (!aCreateIfNeeded && PR_CLIST_IS_EMPTY(&mElementData)) {
   415     // Early return for the most common case.
   416     return nullptr;
   417   }
   419   nsIAtom *propName;
   420   if (aPseudoType == nsCSSPseudoElements::ePseudo_NotPseudoElement) {
   421     propName = nsGkAtoms::animationsProperty;
   422   } else if (aPseudoType == nsCSSPseudoElements::ePseudo_before) {
   423     propName = nsGkAtoms::animationsOfBeforeProperty;
   424   } else if (aPseudoType == nsCSSPseudoElements::ePseudo_after) {
   425     propName = nsGkAtoms::animationsOfAfterProperty;
   426   } else {
   427     NS_ASSERTION(!aCreateIfNeeded,
   428                  "should never try to create transitions for pseudo "
   429                  "other than :before or :after");
   430     return nullptr;
   431   }
   432   ElementAnimations *ea = static_cast<ElementAnimations*>(
   433                              aElement->GetProperty(propName));
   434   if (!ea && aCreateIfNeeded) {
   435     // FIXME: Consider arena-allocating?
   436     ea = new ElementAnimations(aElement, propName, this,
   437            mPresContext->RefreshDriver()->MostRecentRefresh());
   438     nsresult rv = aElement->SetProperty(propName, ea,
   439                                         ElementAnimationsPropertyDtor, false);
   440     if (NS_FAILED(rv)) {
   441       NS_WARNING("SetProperty failed");
   442       delete ea;
   443       return nullptr;
   444     }
   445     if (propName == nsGkAtoms::animationsProperty) {
   446       aElement->SetMayHaveAnimations();
   447     }
   449     AddElementData(ea);
   450   }
   452   return ea;
   453 }
   456 void
   457 nsAnimationManager::EnsureStyleRuleFor(ElementAnimations* aET)
   458 {
   459   aET->EnsureStyleRuleFor(mPresContext->RefreshDriver()->MostRecentRefresh(),
   460                           mPendingEvents,
   461                           false);
   462   CheckNeedsRefresh();
   463 }
   465 /* virtual */ void
   466 nsAnimationManager::RulesMatching(ElementRuleProcessorData* aData)
   467 {
   468   NS_ABORT_IF_FALSE(aData->mPresContext == mPresContext,
   469                     "pres context mismatch");
   470   nsIStyleRule *rule =
   471     GetAnimationRule(aData->mElement,
   472                      nsCSSPseudoElements::ePseudo_NotPseudoElement);
   473   if (rule) {
   474     aData->mRuleWalker->Forward(rule);
   475   }
   476 }
   478 /* virtual */ void
   479 nsAnimationManager::RulesMatching(PseudoElementRuleProcessorData* aData)
   480 {
   481   NS_ABORT_IF_FALSE(aData->mPresContext == mPresContext,
   482                     "pres context mismatch");
   483   if (aData->mPseudoType != nsCSSPseudoElements::ePseudo_before &&
   484       aData->mPseudoType != nsCSSPseudoElements::ePseudo_after) {
   485     return;
   486   }
   488   // FIXME: Do we really want to be the only thing keeping a
   489   // pseudo-element alive?  I *think* the non-animation restyle should
   490   // handle that, but should add a test.
   491   nsIStyleRule *rule = GetAnimationRule(aData->mElement, aData->mPseudoType);
   492   if (rule) {
   493     aData->mRuleWalker->Forward(rule);
   494   }
   495 }
   497 /* virtual */ void
   498 nsAnimationManager::RulesMatching(AnonBoxRuleProcessorData* aData)
   499 {
   500 }
   502 #ifdef MOZ_XUL
   503 /* virtual */ void
   504 nsAnimationManager::RulesMatching(XULTreeRuleProcessorData* aData)
   505 {
   506 }
   507 #endif
   509 /* virtual */ size_t
   510 nsAnimationManager::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
   511 {
   512   return CommonAnimationManager::SizeOfExcludingThis(aMallocSizeOf);
   514   // Measurement of the following members may be added later if DMD finds it is
   515   // worthwhile:
   516   // - mPendingEvents
   517 }
   519 /* virtual */ size_t
   520 nsAnimationManager::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
   521 {
   522   return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
   523 }
   525 nsIStyleRule*
   526 nsAnimationManager::CheckAnimationRule(nsStyleContext* aStyleContext,
   527                                        mozilla::dom::Element* aElement)
   528 {
   529   if (!mPresContext->IsProcessingAnimationStyleChange()) {
   530     if (!mPresContext->IsDynamic()) {
   531       // For print or print preview, ignore animations.
   532       return nullptr;
   533     }
   535     // Everything that causes our animation data to change triggers a
   536     // style change, which in turn triggers a non-animation restyle.
   537     // Likewise, when we initially construct frames, we're not in a
   538     // style change, but also not in an animation restyle.
   540     const nsStyleDisplay *disp = aStyleContext->StyleDisplay();
   541     ElementAnimations *ea =
   542       GetElementAnimations(aElement, aStyleContext->GetPseudoType(), false);
   543     if (!ea &&
   544         disp->mAnimationNameCount == 1 &&
   545         disp->mAnimations[0].GetName().IsEmpty()) {
   546       return nullptr;
   547     }
   549     // build the animations list
   550     InfallibleTArray<StyleAnimation> newAnimations;
   551     BuildAnimations(aStyleContext, newAnimations);
   553     if (newAnimations.IsEmpty()) {
   554       if (ea) {
   555         ea->Destroy();
   556       }
   557       return nullptr;
   558     }
   560     TimeStamp refreshTime = mPresContext->RefreshDriver()->MostRecentRefresh();
   562     if (ea) {
   563       ea->mStyleRule = nullptr;
   564       ea->mStyleRuleRefreshTime = TimeStamp();
   565       ea->UpdateAnimationGeneration(mPresContext);
   567       // Copy over the start times and (if still paused) pause starts
   568       // for each animation (matching on name only) that was also in the
   569       // old list of animations.
   570       // This means that we honor dynamic changes, which isn't what the
   571       // spec says to do, but WebKit seems to honor at least some of
   572       // them.  See
   573       // http://lists.w3.org/Archives/Public/www-style/2011Apr/0079.html
   574       // In order to honor what the spec said, we'd copy more data over
   575       // (or potentially optimize BuildAnimations to avoid rebuilding it
   576       // in the first place).
   577       if (!ea->mAnimations.IsEmpty()) {
   578         for (uint32_t newIdx = 0, newEnd = newAnimations.Length();
   579              newIdx != newEnd; ++newIdx) {
   580           StyleAnimation *newAnim = &newAnimations[newIdx];
   582           // Find the matching animation with this name in the old list
   583           // of animations.  Because of this code, they must all have
   584           // the same start time, though they might differ in pause
   585           // state.  So if a page uses multiple copies of the same
   586           // animation in one element's animation list, and gives them
   587           // different pause states, they, well, get what they deserve.
   588           // We'll use the last one since it's more likely to be the one
   589           // doing something.
   590           const StyleAnimation *oldAnim = nullptr;
   591           for (uint32_t oldIdx = ea->mAnimations.Length(); oldIdx-- != 0; ) {
   592             const StyleAnimation *a = &ea->mAnimations[oldIdx];
   593             if (a->mName == newAnim->mName) {
   594               oldAnim = a;
   595               break;
   596             }
   597           }
   598           if (!oldAnim) {
   599             continue;
   600           }
   602           newAnim->mStartTime = oldAnim->mStartTime;
   603           newAnim->mLastNotification = oldAnim->mLastNotification;
   605           if (oldAnim->IsPaused()) {
   606             if (newAnim->IsPaused()) {
   607               // Copy pause start just like start time.
   608               newAnim->mPauseStart = oldAnim->mPauseStart;
   609             } else {
   610               // Handle change in pause state by adjusting start
   611               // time to unpause.
   612               newAnim->mStartTime += refreshTime - oldAnim->mPauseStart;
   613             }
   614           }
   615         }
   616       }
   617     } else {
   618       ea = GetElementAnimations(aElement, aStyleContext->GetPseudoType(),
   619                                 true);
   620     }
   621     ea->mAnimations.SwapElements(newAnimations);
   622     ea->mNeedsRefreshes = true;
   624     ea->EnsureStyleRuleFor(refreshTime, mPendingEvents, false);
   625     CheckNeedsRefresh();
   626     // We don't actually dispatch the mPendingEvents now.  We'll either
   627     // dispatch them the next time we get a refresh driver notification
   628     // or the next time somebody calls
   629     // nsPresShell::FlushPendingNotifications.
   630     if (!mPendingEvents.IsEmpty()) {
   631       mPresContext->Document()->SetNeedStyleFlush();
   632     }
   633   }
   635   return GetAnimationRule(aElement, aStyleContext->GetPseudoType());
   636 }
   638 class PercentageHashKey : public PLDHashEntryHdr
   639 {
   640 public:
   641   typedef const float& KeyType;
   642   typedef const float* KeyTypePointer;
   644   PercentageHashKey(KeyTypePointer aKey) : mValue(*aKey) { }
   645   PercentageHashKey(const PercentageHashKey& toCopy) : mValue(toCopy.mValue) { }
   646   ~PercentageHashKey() { }
   648   KeyType GetKey() const { return mValue; }
   649   bool KeyEquals(KeyTypePointer aKey) const { return *aKey == mValue; }
   651   static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
   652   static PLDHashNumber HashKey(KeyTypePointer aKey) {
   653     static_assert(sizeof(PLDHashNumber) == sizeof(uint32_t),
   654                   "this hash function assumes PLDHashNumber is uint32_t");
   655     static_assert(PLDHashNumber(-1) > PLDHashNumber(0),
   656                   "this hash function assumes PLDHashNumber is uint32_t");
   657     float key = *aKey;
   658     NS_ABORT_IF_FALSE(0.0f <= key && key <= 1.0f, "out of range");
   659     return PLDHashNumber(key * UINT32_MAX);
   660   }
   661   enum { ALLOW_MEMMOVE = true };
   663 private:
   664   const float mValue;
   665 };
   667 struct KeyframeData {
   668   float mKey;
   669   uint32_t mIndex; // store original order since sort algorithm is not stable
   670   nsCSSKeyframeRule *mRule;
   671 };
   673 struct KeyframeDataComparator {
   674   bool Equals(const KeyframeData& A, const KeyframeData& B) const {
   675     return A.mKey == B.mKey && A.mIndex == B.mIndex;
   676   }
   677   bool LessThan(const KeyframeData& A, const KeyframeData& B) const {
   678     return A.mKey < B.mKey || (A.mKey == B.mKey && A.mIndex < B.mIndex);
   679   }
   680 };
   682 class ResolvedStyleCache {
   683 public:
   684   ResolvedStyleCache() : mCache(16) {}
   685   nsStyleContext* Get(nsPresContext *aPresContext,
   686                       nsStyleContext *aParentStyleContext,
   687                       nsCSSKeyframeRule *aKeyframe);
   689 private:
   690   nsRefPtrHashtable<nsPtrHashKey<nsCSSKeyframeRule>, nsStyleContext> mCache;
   691 };
   693 nsStyleContext*
   694 ResolvedStyleCache::Get(nsPresContext *aPresContext,
   695                         nsStyleContext *aParentStyleContext,
   696                         nsCSSKeyframeRule *aKeyframe)
   697 {
   698   // FIXME (spec):  The css3-animations spec isn't very clear about how
   699   // properties are resolved when they have values that depend on other
   700   // properties (e.g., values in 'em').  I presume that they're resolved
   701   // relative to the other styles of the element.  The question is
   702   // whether they are resolved relative to other animations:  I assume
   703   // that they're not, since that would prevent us from caching a lot of
   704   // data that we'd really like to cache (in particular, the
   705   // nsStyleAnimation::Value values in AnimationPropertySegment).
   706   nsStyleContext *result = mCache.GetWeak(aKeyframe);
   707   if (!result) {
   708     nsCOMArray<nsIStyleRule> rules;
   709     rules.AppendObject(aKeyframe);
   710     nsRefPtr<nsStyleContext> resultStrong = aPresContext->StyleSet()->
   711       ResolveStyleByAddingRules(aParentStyleContext, rules);
   712     mCache.Put(aKeyframe, resultStrong);
   713     result = resultStrong;
   714   }
   715   return result;
   716 }
   718 void
   719 nsAnimationManager::BuildAnimations(nsStyleContext* aStyleContext,
   720                                     InfallibleTArray<StyleAnimation>&
   721                                       aAnimations)
   722 {
   723   NS_ABORT_IF_FALSE(aAnimations.IsEmpty(), "expect empty array");
   725   ResolvedStyleCache resolvedStyles;
   727   const nsStyleDisplay *disp = aStyleContext->StyleDisplay();
   728   TimeStamp now = mPresContext->RefreshDriver()->MostRecentRefresh();
   729   for (uint32_t animIdx = 0, animEnd = disp->mAnimationNameCount;
   730        animIdx != animEnd; ++animIdx) {
   731     const nsAnimation& aSrc = disp->mAnimations[animIdx];
   732     StyleAnimation& aDest = *aAnimations.AppendElement();
   734     aDest.mName = aSrc.GetName();
   735     aDest.mIterationCount = aSrc.GetIterationCount();
   736     aDest.mDirection = aSrc.GetDirection();
   737     aDest.mFillMode = aSrc.GetFillMode();
   738     aDest.mPlayState = aSrc.GetPlayState();
   740     aDest.mDelay = TimeDuration::FromMilliseconds(aSrc.GetDelay());
   741     aDest.mStartTime = now;
   742     if (aDest.IsPaused()) {
   743       aDest.mPauseStart = now;
   744     } else {
   745       aDest.mPauseStart = TimeStamp();
   746     }
   748     aDest.mIterationDuration = TimeDuration::FromMilliseconds(aSrc.GetDuration());
   750     nsCSSKeyframesRule* rule =
   751       mPresContext->StyleSet()->KeyframesRuleForName(mPresContext, aDest.mName);
   752     if (!rule) {
   753       // no segments
   754       continue;
   755     }
   757     // While current drafts of css3-animations say that later keyframes
   758     // with the same key entirely replace earlier ones (no cascading),
   759     // this is a bad idea and contradictory to the rest of CSS.  So
   760     // we're going to keep all the keyframes for each key and then do
   761     // the replacement on a per-property basis rather than a per-rule
   762     // basis, just like everything else in CSS.
   764     AutoInfallibleTArray<KeyframeData, 16> sortedKeyframes;
   766     for (uint32_t ruleIdx = 0, ruleEnd = rule->StyleRuleCount();
   767          ruleIdx != ruleEnd; ++ruleIdx) {
   768       css::Rule* cssRule = rule->GetStyleRuleAt(ruleIdx);
   769       NS_ABORT_IF_FALSE(cssRule, "must have rule");
   770       NS_ABORT_IF_FALSE(cssRule->GetType() == css::Rule::KEYFRAME_RULE,
   771                         "must be keyframe rule");
   772       nsCSSKeyframeRule *kfRule = static_cast<nsCSSKeyframeRule*>(cssRule);
   774       const nsTArray<float> &keys = kfRule->GetKeys();
   775       for (uint32_t keyIdx = 0, keyEnd = keys.Length();
   776            keyIdx != keyEnd; ++keyIdx) {
   777         float key = keys[keyIdx];
   778         // FIXME (spec):  The spec doesn't say what to do with
   779         // out-of-range keyframes.  We'll ignore them.
   780         // (And PercentageHashKey currently assumes we either ignore or
   781         // clamp them.)
   782         if (0.0f <= key && key <= 1.0f) {
   783           KeyframeData *data = sortedKeyframes.AppendElement();
   784           data->mKey = key;
   785           data->mIndex = ruleIdx;
   786           data->mRule = kfRule;
   787         }
   788       }
   789     }
   791     sortedKeyframes.Sort(KeyframeDataComparator());
   793     if (sortedKeyframes.Length() == 0) {
   794       // no segments
   795       continue;
   796     }
   798     // Record the properties that are present in any keyframe rules we
   799     // are using.
   800     nsCSSPropertySet properties;
   802     for (uint32_t kfIdx = 0, kfEnd = sortedKeyframes.Length();
   803          kfIdx != kfEnd; ++kfIdx) {
   804       css::Declaration *decl = sortedKeyframes[kfIdx].mRule->Declaration();
   805       for (uint32_t propIdx = 0, propEnd = decl->Count();
   806            propIdx != propEnd; ++propIdx) {
   807         nsCSSProperty prop = decl->GetPropertyAt(propIdx);
   808         if (prop != eCSSPropertyExtra_variable) {
   809           // CSS Variables are not animatable
   810           properties.AddProperty(prop);
   811         }
   812       }
   813     }
   815     for (nsCSSProperty prop = nsCSSProperty(0);
   816          prop < eCSSProperty_COUNT_no_shorthands;
   817          prop = nsCSSProperty(prop + 1)) {
   818       if (!properties.HasProperty(prop) ||
   819           nsCSSProps::kAnimTypeTable[prop] == eStyleAnimType_None) {
   820         continue;
   821       }
   823       // Build a list of the keyframes to use for this property.  This
   824       // means we need every keyframe with the property in it, except
   825       // for those keyframes where a later keyframe with the *same key*
   826       // also has the property.
   827       AutoInfallibleTArray<uint32_t, 16> keyframesWithProperty;
   828       float lastKey = 100.0f; // an invalid key
   829       for (uint32_t kfIdx = 0, kfEnd = sortedKeyframes.Length();
   830            kfIdx != kfEnd; ++kfIdx) {
   831         KeyframeData &kf = sortedKeyframes[kfIdx];
   832         if (!kf.mRule->Declaration()->HasProperty(prop)) {
   833           continue;
   834         }
   835         if (kf.mKey == lastKey) {
   836           // Replace previous occurrence of same key.
   837           keyframesWithProperty[keyframesWithProperty.Length() - 1] = kfIdx;
   838         } else {
   839           keyframesWithProperty.AppendElement(kfIdx);
   840         }
   841         lastKey = kf.mKey;
   842       }
   844       AnimationProperty &propData = *aDest.mProperties.AppendElement();
   845       propData.mProperty = prop;
   847       KeyframeData *fromKeyframe = nullptr;
   848       nsRefPtr<nsStyleContext> fromContext;
   849       bool interpolated = true;
   850       for (uint32_t wpIdx = 0, wpEnd = keyframesWithProperty.Length();
   851            wpIdx != wpEnd; ++wpIdx) {
   852         uint32_t kfIdx = keyframesWithProperty[wpIdx];
   853         KeyframeData &toKeyframe = sortedKeyframes[kfIdx];
   855         nsRefPtr<nsStyleContext> toContext =
   856           resolvedStyles.Get(mPresContext, aStyleContext, toKeyframe.mRule);
   858         if (fromKeyframe) {
   859           interpolated = interpolated &&
   860             BuildSegment(propData.mSegments, prop, aSrc,
   861                          fromKeyframe->mKey, fromContext,
   862                          fromKeyframe->mRule->Declaration(),
   863                          toKeyframe.mKey, toContext);
   864         } else {
   865           if (toKeyframe.mKey != 0.0f) {
   866             // There's no data for this property at 0%, so use the
   867             // cascaded value above us.
   868             interpolated = interpolated &&
   869               BuildSegment(propData.mSegments, prop, aSrc,
   870                            0.0f, aStyleContext, nullptr,
   871                            toKeyframe.mKey, toContext);
   872           }
   873         }
   875         fromContext = toContext;
   876         fromKeyframe = &toKeyframe;
   877       }
   879       if (fromKeyframe->mKey != 1.0f) {
   880         // There's no data for this property at 100%, so use the
   881         // cascaded value above us.
   882         interpolated = interpolated &&
   883           BuildSegment(propData.mSegments, prop, aSrc,
   884                        fromKeyframe->mKey, fromContext,
   885                        fromKeyframe->mRule->Declaration(),
   886                        1.0f, aStyleContext);
   887       }
   889       // If we failed to build any segments due to inability to
   890       // interpolate, remove the property from the animation.  (It's not
   891       // clear if this is the right thing to do -- we could run some of
   892       // the segments, but it's really not clear whether we should skip
   893       // values (which?) or skip segments, so best to skip the whole
   894       // thing for now.)
   895       if (!interpolated) {
   896         aDest.mProperties.RemoveElementAt(aDest.mProperties.Length() - 1);
   897       }
   898     }
   899   }
   900 }
   902 bool
   903 nsAnimationManager::BuildSegment(InfallibleTArray<AnimationPropertySegment>&
   904                                    aSegments,
   905                                  nsCSSProperty aProperty,
   906                                  const nsAnimation& aAnimation,
   907                                  float aFromKey, nsStyleContext* aFromContext,
   908                                  mozilla::css::Declaration* aFromDeclaration,
   909                                  float aToKey, nsStyleContext* aToContext)
   910 {
   911   nsStyleAnimation::Value fromValue, toValue, dummyValue;
   912   if (!ExtractComputedValueForTransition(aProperty, aFromContext, fromValue) ||
   913       !ExtractComputedValueForTransition(aProperty, aToContext, toValue) ||
   914       // Check that we can interpolate between these values
   915       // (If this is ever a performance problem, we could add a
   916       // CanInterpolate method, but it seems fine for now.)
   917       !nsStyleAnimation::Interpolate(aProperty, fromValue, toValue,
   918                                     0.5, dummyValue)) {
   919     return false;
   920   }
   922   AnimationPropertySegment &segment = *aSegments.AppendElement();
   924   segment.mFromValue = fromValue;
   925   segment.mToValue = toValue;
   926   segment.mFromKey = aFromKey;
   927   segment.mToKey = aToKey;
   928   const nsTimingFunction *tf;
   929   if (aFromDeclaration &&
   930       aFromDeclaration->HasProperty(eCSSProperty_animation_timing_function)) {
   931     tf = &aFromContext->StyleDisplay()->mAnimations[0].GetTimingFunction();
   932   } else {
   933     tf = &aAnimation.GetTimingFunction();
   934   }
   935   segment.mTimingFunction.Init(*tf);
   937   return true;
   938 }
   940 nsIStyleRule*
   941 nsAnimationManager::GetAnimationRule(mozilla::dom::Element* aElement,
   942                                      nsCSSPseudoElements::Type aPseudoType)
   943 {
   944   NS_ABORT_IF_FALSE(
   945     aPseudoType == nsCSSPseudoElements::ePseudo_NotPseudoElement ||
   946     aPseudoType == nsCSSPseudoElements::ePseudo_before ||
   947     aPseudoType == nsCSSPseudoElements::ePseudo_after,
   948     "forbidden pseudo type");
   950   if (!mPresContext->IsDynamic()) {
   951     // For print or print preview, ignore animations.
   952     return nullptr;
   953   }
   955   ElementAnimations *ea =
   956     GetElementAnimations(aElement, aPseudoType, false);
   957   if (!ea) {
   958     return nullptr;
   959   }
   961   if (mPresContext->IsProcessingRestyles() &&
   962       !mPresContext->IsProcessingAnimationStyleChange()) {
   963     // During the non-animation part of processing restyles, we don't
   964     // add the animation rule.
   966     if (ea->mStyleRule) {
   967       ea->PostRestyleForAnimation(mPresContext);
   968     }
   970     return nullptr;
   971   }
   973   NS_WARN_IF_FALSE(!ea->mNeedsRefreshes ||
   974                    ea->mStyleRuleRefreshTime ==
   975                      mPresContext->RefreshDriver()->MostRecentRefresh(),
   976                    "should already have refreshed style rule");
   978   return ea->mStyleRule;
   979 }
   981 /* virtual */ void
   982 nsAnimationManager::WillRefresh(mozilla::TimeStamp aTime)
   983 {
   984   NS_ABORT_IF_FALSE(mPresContext,
   985                     "refresh driver should not notify additional observers "
   986                     "after pres context has been destroyed");
   987   if (!mPresContext->GetPresShell()) {
   988     // Someone might be keeping mPresContext alive past the point
   989     // where it has been torn down; don't bother doing anything in
   990     // this case.  But do get rid of all our transitions so we stop
   991     // triggering refreshes.
   992     RemoveAllElementData();
   993     return;
   994   }
   996   FlushAnimations(Can_Throttle);
   997 }
   999 void
  1000 nsAnimationManager::AddElementData(CommonElementAnimationData* aData)
  1002   if (!mObservingRefreshDriver) {
  1003     NS_ASSERTION(static_cast<ElementAnimations*>(aData)->mNeedsRefreshes,
  1004                  "Added data which doesn't need refreshing?");
  1005     // We need to observe the refresh driver.
  1006     mPresContext->RefreshDriver()->AddRefreshObserver(this, Flush_Style);
  1007     mObservingRefreshDriver = true;
  1010   PR_INSERT_BEFORE(aData, &mElementData);
  1013 void
  1014 nsAnimationManager::CheckNeedsRefresh()
  1016   for (PRCList *l = PR_LIST_HEAD(&mElementData); l != &mElementData;
  1017        l = PR_NEXT_LINK(l)) {
  1018     if (static_cast<ElementAnimations*>(l)->mNeedsRefreshes) {
  1019       if (!mObservingRefreshDriver) {
  1020         mPresContext->RefreshDriver()->AddRefreshObserver(this, Flush_Style);
  1021         mObservingRefreshDriver = true;
  1023       return;
  1026   if (mObservingRefreshDriver) {
  1027     mObservingRefreshDriver = false;
  1028     mPresContext->RefreshDriver()->RemoveRefreshObserver(this, Flush_Style);
  1032 void
  1033 nsAnimationManager::FlushAnimations(FlushFlags aFlags)
  1035   // FIXME: check that there's at least one style rule that's not
  1036   // in its "done" state, and if there isn't, remove ourselves from
  1037   // the refresh driver (but leave the animations!).
  1038   TimeStamp now = mPresContext->RefreshDriver()->MostRecentRefresh();
  1039   bool didThrottle = false;
  1040   for (PRCList *l = PR_LIST_HEAD(&mElementData); l != &mElementData;
  1041        l = PR_NEXT_LINK(l)) {
  1042     ElementAnimations *ea = static_cast<ElementAnimations*>(l);
  1043     bool canThrottleTick = aFlags == Can_Throttle &&
  1044       ea->CanPerformOnCompositorThread(
  1045         CommonElementAnimationData::CanAnimateFlags(0)) &&
  1046       ea->CanThrottleAnimation(now);
  1048     nsRefPtr<css::AnimValuesStyleRule> oldStyleRule = ea->mStyleRule;
  1049     ea->EnsureStyleRuleFor(now, mPendingEvents, canThrottleTick);
  1050     CheckNeedsRefresh();
  1051     if (oldStyleRule != ea->mStyleRule) {
  1052       ea->PostRestyleForAnimation(mPresContext);
  1053     } else {
  1054       didThrottle = true;
  1058   if (didThrottle) {
  1059     mPresContext->Document()->SetNeedStyleFlush();
  1062   DispatchEvents(); // may destroy us
  1065 void
  1066 nsAnimationManager::DoDispatchEvents()
  1068   EventArray events;
  1069   mPendingEvents.SwapElements(events);
  1070   for (uint32_t i = 0, i_end = events.Length(); i < i_end; ++i) {
  1071     AnimationEventInfo &info = events[i];
  1072     EventDispatcher::Dispatch(info.mElement, mPresContext, &info.mEvent);
  1074     if (!mPresContext) {
  1075       break;
  1080 void
  1081 nsAnimationManager::UpdateThrottledStylesForSubtree(nsIContent* aContent,
  1082                                                 nsStyleContext* aParentStyle,
  1083                                                 nsStyleChangeList& aChangeList)
  1085   dom::Element* element;
  1086   if (aContent->IsElement()) {
  1087     element = aContent->AsElement();
  1088   } else {
  1089     element = nullptr;
  1092   nsRefPtr<nsStyleContext> newStyle;
  1094   ElementAnimations* ea;
  1095   if (element &&
  1096       (ea = GetElementAnimations(element,
  1097                                  nsCSSPseudoElements::ePseudo_NotPseudoElement,
  1098                                  false))) {
  1099     // re-resolve our style
  1100     newStyle = UpdateThrottledStyle(element, aParentStyle, aChangeList);
  1101     // remove the current transition from the working set
  1102     ea->mFlushGeneration = mPresContext->RefreshDriver()->MostRecentRefresh();
  1103   } else {
  1104     newStyle = ReparentContent(aContent, aParentStyle);
  1107   // walk the children
  1108   if (newStyle) {
  1109     for (nsIContent *child = aContent->GetFirstChild(); child;
  1110          child = child->GetNextSibling()) {
  1111       UpdateThrottledStylesForSubtree(child, newStyle, aChangeList);
  1116 IMPL_UPDATE_ALL_THROTTLED_STYLES_INTERNAL(nsAnimationManager,
  1117                                           GetElementAnimations)
  1119 void
  1120 nsAnimationManager::UpdateAllThrottledStyles()
  1122   if (PR_CLIST_IS_EMPTY(&mElementData)) {
  1123     // no throttled animations, leave early
  1124     mPresContext->TickLastUpdateThrottledAnimationStyle();
  1125     return;
  1128   if (mPresContext->ThrottledAnimationStyleIsUpToDate()) {
  1129     // throttled transitions are up to date, leave early
  1130     return;
  1133   mPresContext->TickLastUpdateThrottledAnimationStyle();
  1135   UpdateAllThrottledStylesInternal();

mercurial