Bug 1078122 part 1 - Move checks for animation throttling to AnimationPlayer; r=dholbert
authorBrian Birtles <birtles@gmail.com>
Mon, 20 Oct 2014 13:55:45 +0900
changeset 211175 fe98ceaaafa7964c5eefd8ff13dbb87df04f5071
parent 211174 f6866bdaa73dcf0c26604b8c00bb9f8bc8af84c4
child 211176 8f7dfd335493a888690487b643e7b496ab24a56b
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersdholbert
bugs1078122
milestone36.0a1
Bug 1078122 part 1 - Move checks for animation throttling to AnimationPlayer; r=dholbert This patch moves code from AnimationPlayerCollection to AnimationPlayer. However, there is one subtle change in logic involved. Previously, we would test if the player had finished by getting the computed time of its source content and checking if it was in the after phase or not. In this patch, however, we simply check the play state to see if it is finished or not. These two approaches differ in the case where an animation is paused after it has finished. The animation phase approach will indicate the player has finished, but the play state approach will indicate the player has paused (since the "paused" state trumps the "finished" state). This, however, should not produce any observable effect because when an animation is paused mIsRunningOnCompositor will be false (we don't put paused animations on the compositor).
dom/animation/AnimationPlayer.cpp
dom/animation/AnimationPlayer.h
layout/style/AnimationCommon.cpp
--- a/dom/animation/AnimationPlayer.cpp
+++ b/dom/animation/AnimationPlayer.cpp
@@ -175,16 +175,46 @@ AnimationPlayer::IsRunning() const
   if (IsPaused() || !GetSource() || GetSource()->IsFinishedTransition()) {
     return false;
   }
 
   ComputedTiming computedTiming = GetSource()->GetComputedTiming();
   return computedTiming.mPhase == ComputedTiming::AnimationPhase_Active;
 }
 
+bool
+AnimationPlayer::CanThrottle() const
+{
+  if (!mSource ||
+      mSource->IsFinishedTransition() ||
+      mSource->Properties().IsEmpty()) {
+    return true;
+  }
+
+  if (!mIsRunningOnCompositor) {
+    return false;
+  }
+
+  if (PlayState() != AnimationPlayState::Finished) {
+    // Unfinished animations can be throttled.
+    return true;
+  }
+
+  // The animation has finished but, if this is the first sample since
+  // finishing, we need an unthrottled sample so we can apply the correct
+  // end-of-animation behavior on the main thread (either removing the
+  // animation style or applying the fill mode).
+  //
+  // XXX We shouldn't really be using LastNotification() below as a general
+  // indicator that the animation has finished, it should be reserved for
+  // events. If we use it differently in the future this use might need
+  // changing.
+  return mSource->LastNotification() == Animation::LAST_NOTIFICATION_END;
+}
+
 void
 AnimationPlayer::FlushStyle() const
 {
   if (mSource && mSource->GetTarget()) {
     nsIDocument* doc = mSource->GetTarget()->GetComposedDoc();
     if (doc) {
       doc->FlushPendingNotifications(Flush_Style);
     }
--- a/dom/animation/AnimationPlayer.h
+++ b/dom/animation/AnimationPlayer.h
@@ -78,26 +78,30 @@ public:
   void SetSource(Animation* aSource);
   void Tick();
 
   const nsString& Name() const {
     return mSource ? mSource->Name() : EmptyString();
   }
 
   bool IsPaused() const { return mIsPaused; }
-
   bool IsRunning() const;
 
   bool HasCurrentSource() const {
     return GetSource() && GetSource()->IsCurrent();
   }
   bool HasInEffectSource() const {
     return GetSource() && GetSource()->IsInEffect();
   }
 
+  // Returns true if this animation does not currently need to update
+  // style on the main thread (e.g. because it is empty, or is
+  // running on the compositor).
+  bool CanThrottle() const;
+
   // The beginning of the delay period.
   Nullable<TimeDuration> mStartTime; // Timeline timescale
   bool mIsRunningOnCompositor;
 
   nsRefPtr<AnimationTimeline> mTimeline;
   nsRefPtr<Animation> mSource;
 
 protected:
--- a/layout/style/AnimationCommon.cpp
+++ b/layout/style/AnimationCommon.cpp
@@ -484,43 +484,21 @@ AnimationPlayerCollection::EnsureStyleRu
     mStyleRuleRefreshTime = aRefreshTime;
     return;
   }
 
   // If we're performing animations on the compositor thread, then we can skip
   // most of the work in this method. But even if we are throttled, then we
   // have to do the work if an animation is ending in order to get correct end
   // of animation behaviour (the styles of the animation disappear, or the fill
-  // mode behaviour). This loop checks for any finishing animations and forces
-  // the style recalculation if we find any.
+  // mode behaviour). CanThrottle returns false for any finishing animations
+  // so we can force style recalculation in that case.
   if (aFlags == EnsureStyleRule_IsThrottled) {
     for (size_t playerIdx = mPlayers.Length(); playerIdx-- != 0; ) {
-      AnimationPlayer* player = mPlayers[playerIdx];
-
-      // Skip player with no source content, finished transitions, or animations
-      // whose @keyframes rule is empty.
-      if (!player->GetSource() ||
-          player->GetSource()->IsFinishedTransition() ||
-          player->GetSource()->Properties().IsEmpty()) {
-        continue;
-      }
-
-      // The GetComputedTiming() call here handles pausing.  But:
-      // FIXME: avoid recalculating every time when paused.
-      ComputedTiming computedTiming = player->GetSource()->GetComputedTiming();
-
-      // XXX We shouldn't really be using LastNotification() as a general
-      // indicator that the animation has finished, it should be reserved for
-      // events. If we use it differently in the future this use might need
-      // changing.
-      if (!player->mIsRunningOnCompositor ||
-          (computedTiming.mPhase == ComputedTiming::AnimationPhase_After &&
-           player->GetSource()->LastNotification()
-             != Animation::LAST_NOTIFICATION_END))
-      {
+      if (!mPlayers[playerIdx]->CanThrottle()) {
         aFlags = EnsureStyleRule_IsNotThrottled;
         break;
       }
     }
   }
 
   if (aFlags == EnsureStyleRule_IsThrottled) {
     return;