Bug 1078122 part 3 - Move animation value building down to the Animation objects; r=dholbert
authorBrian Birtles <birtles@gmail.com>
Mon, 20 Oct 2014 13:55:46 +0900
changeset 211088 d54306da529f6918f79a9e43c5e0a61883cee453
parent 211087 8f7dfd335493a888690487b643e7b496ab24a56b
child 211089 93c56f764bc31ea07df24baa8f6fa6e83df79658
push id50643
push userbbirtles@mozilla.com
push dateMon, 20 Oct 2014 04:56:41 +0000
treeherdermozilla-inbound@b3b581cda940 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdholbert
bugs1078122
milestone36.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1078122 part 3 - Move animation value building down to the Animation objects; r=dholbert This patch extracts the logic for calculating animation styles from AnimationPlayerCollection and puts the bulk of it into the Animation objects. Some of the initial logic surrounding the animation player state (e.g. is it paused or not, etc.) is put into AnimationPlayer. In future we may shift this logic even further down to the AnimationEffect objects but currently we don't create such objects unless necessary.
dom/animation/Animation.cpp
dom/animation/Animation.h
dom/animation/AnimationPlayer.cpp
dom/animation/AnimationPlayer.h
layout/style/AnimationCommon.cpp
layout/style/moz.build
--- a/dom/animation/Animation.cpp
+++ b/dom/animation/Animation.cpp
@@ -2,16 +2,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/Animation.h"
 #include "mozilla/dom/AnimationBinding.h"
 #include "mozilla/dom/AnimationEffect.h"
 #include "mozilla/FloatingPoint.h"
+#include "AnimationCommon.h"
+#include "nsCSSPropertySet.h"
 
 namespace mozilla {
 
 void
 ComputedTimingFunction::Init(const nsTimingFunction &aFunction)
 {
   mType = aFunction.mType;
   if (mType == nsTimingFunction::Function) {
@@ -257,10 +259,93 @@ Animation::HasAnimationOfProperty(nsCSSP
        propIdx != propEnd; ++propIdx) {
     if (aProperty == mProperties[propIdx].mProperty) {
       return true;
     }
   }
   return false;
 }
 
+void
+Animation::ComposeStyle(nsRefPtr<css::AnimValuesStyleRule>& aStyleRule,
+                        nsCSSPropertySet& aSetProperties)
+{
+  ComputedTiming computedTiming = GetComputedTiming();
+
+  // If the time fraction is null, we don't have fill data for the current
+  // time so we shouldn't animate.
+  if (computedTiming.mTimeFraction == ComputedTiming::kNullTimeFraction) {
+    return;
+  }
+
+  MOZ_ASSERT(0.0 <= computedTiming.mTimeFraction &&
+             computedTiming.mTimeFraction <= 1.0,
+             "timing fraction should be in [0-1]");
+
+  for (size_t propIdx = 0, propEnd = mProperties.Length();
+       propIdx != propEnd; ++propIdx)
+  {
+    const AnimationProperty& prop = mProperties[propIdx];
+
+    MOZ_ASSERT(prop.mSegments[0].mFromKey == 0.0, "incorrect first from key");
+    MOZ_ASSERT(prop.mSegments[prop.mSegments.Length() - 1].mToKey == 1.0,
+               "incorrect last to key");
+
+    if (aSetProperties.HasProperty(prop.mProperty)) {
+      // Animations are composed by AnimationPlayerCollection by iterating
+      // from the last animation to first. For animations targetting the
+      // same property, the later one wins. So if this property is already set,
+      // we should not override it.
+      return;
+    }
+
+    aSetProperties.AddProperty(prop.mProperty);
+
+    MOZ_ASSERT(prop.mSegments.Length() > 0,
+               "property should not be in animations if it has no segments");
+
+    // FIXME: Maybe cache the current segment?
+    const AnimationPropertySegment *segment = prop.mSegments.Elements(),
+                                *segmentEnd = segment + prop.mSegments.Length();
+    while (segment->mToKey < computedTiming.mTimeFraction) {
+      MOZ_ASSERT(segment->mFromKey < segment->mToKey, "incorrect keys");
+      ++segment;
+      if (segment == segmentEnd) {
+        MOZ_ASSERT_UNREACHABLE("incorrect time fraction");
+        break; // in order to continue in outer loop (just below)
+      }
+      MOZ_ASSERT(segment->mFromKey == (segment-1)->mToKey, "incorrect keys");
+    }
+    if (segment == segmentEnd) {
+      continue;
+    }
+    MOZ_ASSERT(segment->mFromKey < segment->mToKey, "incorrect keys");
+    MOZ_ASSERT(segment >= prop.mSegments.Elements() &&
+               size_t(segment - prop.mSegments.Elements()) <
+                 prop.mSegments.Length(),
+               "out of array bounds");
+
+    if (!aStyleRule) {
+      // Allocate the style rule now that we know we have animation data.
+      aStyleRule = new css::AnimValuesStyleRule();
+    }
+
+    double positionInSegment =
+      (computedTiming.mTimeFraction - segment->mFromKey) /
+      (segment->mToKey - segment->mFromKey);
+    double valuePosition =
+      segment->mTimingFunction.GetValue(positionInSegment);
+
+    StyleAnimationValue *val = aStyleRule->AddEmptyValue(prop.mProperty);
+
+#ifdef DEBUG
+    bool result =
+#endif
+      StyleAnimationValue::Interpolate(prop.mProperty,
+                                       segment->mFromValue,
+                                       segment->mToValue,
+                                       valuePosition, *val);
+    MOZ_ASSERT(result, "interpolate must succeed now");
+  }
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/animation/Animation.h
+++ b/dom/animation/Animation.h
@@ -16,18 +16,22 @@
 #include "mozilla/StyleAnimationValue.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/Nullable.h"
 #include "nsSMILKeySpline.h"
 #include "nsStyleStruct.h" // for nsTimingFunction
 
 struct JSContext;
+class nsCSSPropertySet;
 
 namespace mozilla {
+namespace css {
+class AnimValuesStyleRule;
+} // namespace css
 
 /**
  * Input timing parameters.
  *
  * Eventually this will represent all the input timing parameters specified
  * by content but for now it encapsulates just the subset of those
  * parameters passed to GetPositionInIteration.
  */
@@ -254,16 +258,23 @@ public:
   bool HasAnimationOfProperty(nsCSSProperty aProperty) const;
   const InfallibleTArray<AnimationProperty>& Properties() const {
     return mProperties;
   }
   InfallibleTArray<AnimationProperty>& Properties() {
     return mProperties;
   }
 
+  // Updates |aStyleRule| with the animation values produced by this
+  // Animation for the current time except any properties already contained
+  // in |aSetProperties|.
+  // Any updated properties are added to |aSetProperties|.
+  void ComposeStyle(nsRefPtr<css::AnimValuesStyleRule>& aStyleRule,
+                    nsCSSPropertySet& aSetProperties);
+
 protected:
   virtual ~Animation() { }
 
   // We use a document for a parent object since the other likely candidate,
   // the target element, can be empty.
   nsCOMPtr<nsIDocument> mDocument;
   nsCOMPtr<Element> mTarget;
   Nullable<TimeDuration> mParentTime;
--- a/dom/animation/AnimationPlayer.cpp
+++ b/dom/animation/AnimationPlayer.cpp
@@ -206,16 +206,32 @@ AnimationPlayer::CanThrottle() const
   // 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::ComposeStyle(nsRefPtr<css::AnimValuesStyleRule>& aStyleRule,
+                              nsCSSPropertySet& aSetProperties,
+                              bool& aNeedsRefreshes)
+{
+  if (!mSource || mSource->IsFinishedTransition()) {
+    return;
+  }
+
+  if (PlayState() == AnimationPlayState::Running) {
+    aNeedsRefreshes = true;
+  }
+
+  mSource->ComposeStyle(aStyleRule, aSetProperties);
+}
+
+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
@@ -16,18 +16,22 @@
 #include "nsCSSProperty.h" // for nsCSSProperty
 
 // X11 has a #define for CurrentTime.
 #ifdef CurrentTime
 #undef CurrentTime
 #endif
 
 struct JSContext;
+class nsCSSPropertySet;
 
 namespace mozilla {
+namespace css {
+class AnimValuesStyleRule;
+} // namespace css
 
 class CSSAnimationPlayer;
 
 namespace dom {
 
 class AnimationPlayer : public nsWrapperCache
 {
 protected:
@@ -95,16 +99,27 @@ public:
   void SetIsRunningOnCompositor() { mIsRunningOnCompositor = true; }
   void ClearIsRunningOnCompositor() { mIsRunningOnCompositor = false; }
 
   // 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;
 
+  // Updates |aStyleRule| with the animation values of this player's source
+  // content, if any.
+  // Any properties already contained in |aSetProperties| are not changed. Any
+  // properties that are changed are added to |aSetProperties|.
+  // |aNeedsRefreshes| will be set to true if this player expects to update
+  // the style rule on the next refresh driver tick as well (because it
+  // is running and has source content to sample).
+  void ComposeStyle(nsRefPtr<css::AnimValuesStyleRule>& aStyleRule,
+                    nsCSSPropertySet& aSetProperties,
+                    bool& aNeedsRefreshes);
+
   // The beginning of the delay period.
   Nullable<TimeDuration> mStartTime; // Timeline timescale
 
   nsRefPtr<AnimationTimeline> mTimeline;
   nsRefPtr<Animation> mSource;
 
 protected:
   void FlushStyle() const;
--- a/layout/style/AnimationCommon.cpp
+++ b/layout/style/AnimationCommon.cpp
@@ -507,118 +507,25 @@ AnimationPlayerCollection::EnsureStyleRu
   // mStyleRule may be null and valid, if we have no style to apply.
   if (mStyleRuleRefreshTime.IsNull() ||
       mStyleRuleRefreshTime != aRefreshTime) {
     mStyleRuleRefreshTime = aRefreshTime;
     mStyleRule = nullptr;
     // We'll set mNeedsRefreshes to true below in all cases where we need them.
     mNeedsRefreshes = false;
 
-    // FIXME(spec): assume that properties in higher animations override
-    // those in lower ones.
-    // Therefore, we iterate from last animation to first.
+    // If multiple animations specify behavior for the same property the
+    // animation which occurs last in the value of animation-name wins.
+    // As a result, we iterate from last animation to first and, if a
+    // property has already been set, we don't leave it.
     nsCSSPropertySet properties;
 
     for (size_t playerIdx = mPlayers.Length(); playerIdx-- != 0; ) {
-      AnimationPlayer* player = mPlayers[playerIdx];
-
-      if (!player->GetSource() || player->GetSource()->IsFinishedTransition()) {
-        continue;
-      }
-
-      // The GetComputedTiming() call here handles pausing.  But:
-      // FIXME: avoid recalculating every time when paused.
-      ComputedTiming computedTiming = player->GetSource()->GetComputedTiming();
-
-      if ((computedTiming.mPhase == ComputedTiming::AnimationPhase_Before ||
-           computedTiming.mPhase == ComputedTiming::AnimationPhase_Active) &&
-          !player->IsPaused()) {
-        mNeedsRefreshes = true;
-      }
-
-      // If the time fraction is null, we don't have fill data for the current
-      // time so we shouldn't animate.
-      // Likewise, if the player has no source content.
-      if (computedTiming.mTimeFraction == ComputedTiming::kNullTimeFraction) {
-        continue;
-      }
-
-      NS_ABORT_IF_FALSE(0.0 <= computedTiming.mTimeFraction &&
-                        computedTiming.mTimeFraction <= 1.0,
-                        "timing fraction should be in [0-1]");
-
-      const Animation* anim = player->GetSource();
-      for (size_t propIdx = 0, propEnd = anim->Properties().Length();
-           propIdx != propEnd; ++propIdx)
-      {
-        const AnimationProperty& prop = anim->Properties()[propIdx];
-
-        NS_ABORT_IF_FALSE(prop.mSegments[0].mFromKey == 0.0,
-                          "incorrect first from key");
-        NS_ABORT_IF_FALSE(prop.mSegments[prop.mSegments.Length() - 1].mToKey
-                            == 1.0,
-                          "incorrect last to key");
-
-        if (properties.HasProperty(prop.mProperty)) {
-          // A later animation already set this property.
-          continue;
-        }
-        properties.AddProperty(prop.mProperty);
-
-        NS_ABORT_IF_FALSE(prop.mSegments.Length() > 0,
-                          "property should not be in animations if it "
-                          "has no segments");
-
-        // FIXME: Maybe cache the current segment?
-        const AnimationPropertySegment *segment = prop.mSegments.Elements(),
-                               *segmentEnd = segment + prop.mSegments.Length();
-        while (segment->mToKey < computedTiming.mTimeFraction) {
-          NS_ABORT_IF_FALSE(segment->mFromKey < segment->mToKey,
-                            "incorrect keys");
-          ++segment;
-          if (segment == segmentEnd) {
-            NS_ABORT_IF_FALSE(false, "incorrect time fraction");
-            break; // in order to continue in outer loop (just below)
-          }
-          NS_ABORT_IF_FALSE(segment->mFromKey == (segment-1)->mToKey,
-                            "incorrect keys");
-        }
-        if (segment == segmentEnd) {
-          continue;
-        }
-        NS_ABORT_IF_FALSE(segment->mFromKey < segment->mToKey,
-                          "incorrect keys");
-        NS_ABORT_IF_FALSE(segment >= prop.mSegments.Elements() &&
-                          size_t(segment - prop.mSegments.Elements()) <
-                            prop.mSegments.Length(),
-                          "out of array bounds");
-
-        if (!mStyleRule) {
-          // Allocate the style rule now that we know we have animation data.
-          mStyleRule = new css::AnimValuesStyleRule();
-        }
-
-        double positionInSegment =
-          (computedTiming.mTimeFraction - segment->mFromKey) /
-          (segment->mToKey - segment->mFromKey);
-        double valuePosition =
-          segment->mTimingFunction.GetValue(positionInSegment);
-
-        StyleAnimationValue *val =
-          mStyleRule->AddEmptyValue(prop.mProperty);
-
-#ifdef DEBUG
-        bool result =
-#endif
-          StyleAnimationValue::Interpolate(prop.mProperty,
-                                           segment->mFromValue,
-                                           segment->mToValue,
-                                           valuePosition, *val);
-        NS_ABORT_IF_FALSE(result, "interpolate must succeed now");
-      }
+      mPlayers[playerIdx]->ComposeStyle(mStyleRule, properties,
+                                        mNeedsRefreshes);
     }
   }
 }
 
 
 bool
 AnimationPlayerCollection::CanThrottleTransformChanges(TimeStamp aTime)
 {
--- a/layout/style/moz.build
+++ b/layout/style/moz.build
@@ -16,16 +16,17 @@ EXPORTS += [
     'nsCSSAnonBoxList.h',
     'nsCSSCounterDescList.h',
     'nsCSSFontDescList.h',
     'nsCSSKeywordList.h',
     'nsCSSKeywords.h',
     'nsCSSParser.h',
     'nsCSSPropAliasList.h',
     'nsCSSProperty.h',
+    'nsCSSPropertySet.h',
     'nsCSSPropList.h',
     'nsCSSProps.h',
     'nsCSSPseudoClasses.h',
     'nsCSSPseudoClassList.h',
     'nsCSSPseudoElementList.h',
     'nsCSSPseudoElements.h',
     'nsCSSRuleProcessor.h',
     'nsCSSScanner.h',