Bug 1260655 - Add KeyframeEffectReadOnly::SetFrames; r=heycam
authorBrian Birtles <birtles@gmail.com>
Wed, 30 Mar 2016 08:59:08 +0900
changeset 292569 5db982657c9b2606d9572fced222741cffb136c1
parent 292557 3baed5c339e31188949641cb7bc7178a71070718
child 292570 8272d22371b731282138b3f4862e641ab8d4a6e7
push id30164
push userkwierso@gmail.com
push dateMon, 11 Apr 2016 23:02:30 +0000
treeherdermozilla-central@21bf1af375c1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersheycam
bugs1260655, 1235002
milestone48.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 1260655 - Add KeyframeEffectReadOnly::SetFrames; r=heycam Earlier in this patch series we divided keyframe processing into two stages: (1) Turning javascript objects into an array of Keyframe objects (2) Calculating AnimationProperty arrays from the Keyframe objects This patch creates a SetFrames method so that CSS animations and CSS transitions can skip (1) and pass the frames constructed from CSS syntax into (2). It also adds the following additional processing: a. Notifying animation mutation observers when the set of frames has changed. This is currently performed by nsAnimationManager but ultimately we should encapsulate this logic inside the effect itself. Furthermore, it will be needed when we implement effect.setFrames() (i.e. the Javascript-facing wrapper for this method). b. Preserving the mWinsInCascade and mIsRunningOnCompositor state on properties when updating them. This is currently performed by: bool KeyframeEffectReadOnly::UpdateProperties( const InfallibleTArray<AnimationProperty>& aProperties) which is what nsAnimationManager currently uses. We will ultimately remove the above method and here we are just moving this code to the new version of UpdateProperties. c. Requesting a restyle when the set of AnimationProperty objects has changed. Again, this is currently performed by the existing UpdateProperties method so we are just moving it here. This behavior will also be required when we implement effect.setFrames() and when we call UpdateProperties from elsewhere in restyling code. This is bug 1235002 but we fix it here and leave that bug to just do further cleanup work (e.g. re-instating the check for an empty property set before requesting a restyle in NotifyAnimationTimingUpdated). d. Marking the cascade as needing an update when the set of AnimationProperty objects has changed. This is in preparation for calling UpdateProperties from elsewhere in restyling (e.g. when the nsStyleContext is updated). MozReview-Commit-ID: 2ll26lsWZTm
dom/animation/KeyframeEffect.cpp
dom/animation/KeyframeEffect.h
dom/animation/KeyframeUtils.cpp
--- a/dom/animation/KeyframeEffect.cpp
+++ b/dom/animation/KeyframeEffect.cpp
@@ -452,16 +452,56 @@ KeyframeEffectReadOnly::IsInEffect() con
 
 void
 KeyframeEffectReadOnly::SetAnimation(Animation* aAnimation)
 {
   mAnimation = aAnimation;
   NotifyAnimationTimingUpdated();
 }
 
+static bool
+KeyframesEqualIgnoringComputedOffsets(const nsTArray<Keyframe>& aLhs,
+                                      const nsTArray<Keyframe>& aRhs)
+{
+  if (aLhs.Length() != aRhs.Length()) {
+    return false;
+  }
+
+  for (size_t i = 0, len = aLhs.Length(); i < len; ++i) {
+    const Keyframe& a = aLhs[i];
+    const Keyframe& b = aRhs[i];
+    if (a.mOffset != b.mOffset ||
+        a.mTimingFunction != b.mTimingFunction ||
+        a.mPropertyValues != b.mPropertyValues) {
+      return false;
+    }
+  }
+  return true;
+}
+
+void
+KeyframeEffectReadOnly::SetFrames(nsTArray<Keyframe>&& aFrames,
+                                  nsStyleContext* aStyleContext)
+{
+  if (KeyframesEqualIgnoringComputedOffsets(aFrames, mFrames)) {
+    return;
+  }
+
+  mFrames = Move(aFrames);
+  KeyframeUtils::ApplyDistributeSpacing(mFrames);
+
+  if (mAnimation && mAnimation->IsRelevant()) {
+    nsNodeUtils::AnimationChanged(mAnimation);
+  }
+
+  if (aStyleContext) {
+    UpdateProperties(aStyleContext);
+  }
+}
+
 const AnimationProperty*
 KeyframeEffectReadOnly::GetAnimationOfProperty(nsCSSProperty aProperty) const
 {
   for (size_t propIdx = 0, propEnd = mProperties.Length();
        propIdx != propEnd; ++propIdx) {
     if (aProperty == mProperties[propIdx].mProperty) {
       const AnimationProperty* result = &mProperties[propIdx];
       if (!result->mWinsInCascade) {
@@ -527,16 +567,74 @@ KeyframeEffectReadOnly::UpdateProperties
                        mAnimation->CascadeLevel());
     }
   }
 
   return true;
 }
 
 void
+KeyframeEffectReadOnly::UpdateProperties(nsStyleContext* aStyleContext)
+{
+  MOZ_ASSERT(aStyleContext);
+
+  nsTArray<AnimationProperty> properties;
+  if (mTarget) {
+    properties =
+      KeyframeUtils::GetAnimationPropertiesFromKeyframes(aStyleContext,
+                                                         mTarget,
+                                                         mPseudoType,
+                                                         mFrames);
+  }
+
+  if (mProperties == properties) {
+    return;
+  }
+
+  // Preserve the state of mWinsInCascade and mIsRunningOnCompositor flags.
+  nsCSSPropertySet winningInCascadeProperties;
+  nsCSSPropertySet runningOnCompositorProperties;
+
+  for (const AnimationProperty& property : mProperties) {
+    if (property.mWinsInCascade) {
+      winningInCascadeProperties.AddProperty(property.mProperty);
+    }
+    if (property.mIsRunningOnCompositor) {
+      runningOnCompositorProperties.AddProperty(property.mProperty);
+    }
+  }
+
+  mProperties = Move(properties);
+
+  for (AnimationProperty& property : mProperties) {
+    property.mWinsInCascade =
+      winningInCascadeProperties.HasProperty(property.mProperty);
+    property.mIsRunningOnCompositor =
+      runningOnCompositorProperties.HasProperty(property.mProperty);
+  }
+
+  if (mTarget) {
+    EffectSet* effectSet = EffectSet::GetEffectSet(mTarget, mPseudoType);
+    if (effectSet) {
+      effectSet->MarkCascadeNeedsUpdate();
+    }
+  }
+
+  if (mAnimation) {
+    nsPresContext* presContext = GetPresContext();
+    if (presContext) {
+      presContext->EffectCompositor()->
+        RequestRestyle(mTarget, mPseudoType,
+                       EffectCompositor::RestyleType::Layer,
+                       mAnimation->CascadeLevel());
+    }
+  }
+}
+
+void
 KeyframeEffectReadOnly::ComposeStyle(RefPtr<AnimValuesStyleRule>& aStyleRule,
                                      nsCSSPropertySet& aSetProperties)
 {
   ComputedTiming computedTiming = GetComputedTiming();
   mProgressOnLastCompose = computedTiming.mProgress;
 
   // If the progress is null, we don't have fill data for the current
   // time so we shouldn't animate.
@@ -716,42 +814,33 @@ KeyframeEffectReadOnly::ConstructKeyfram
     return nullptr;
   }
 
   nsTArray<Keyframe> keyframes =
     KeyframeUtils::GetKeyframesFromObject(aGlobal.Context(), aFrames, aRv);
   if (aRv.Failed()) {
     return nullptr;
   }
-  KeyframeUtils::ApplyDistributeSpacing(keyframes);
+
+  RefPtr<KeyframeEffectType> effect =
+    new KeyframeEffectType(targetElement->OwnerDoc(), targetElement,
+                           pseudoType, timingParams);
 
   RefPtr<nsStyleContext> styleContext;
   nsIPresShell* shell = doc->GetShell();
   if (shell && targetElement) {
     nsIAtom* pseudo =
       pseudoType < CSSPseudoElementType::Count ?
       nsCSSPseudoElements::GetPseudoAtom(pseudoType) : nullptr;
     styleContext =
       nsComputedDOMStyle::GetStyleContextForElement(targetElement, pseudo,
                                                     shell);
   }
+  effect->SetFrames(Move(keyframes), styleContext);
 
-  nsTArray<AnimationProperty> animationProperties;
-  if (styleContext) {
-    animationProperties =
-      KeyframeUtils::GetAnimationPropertiesFromKeyframes(styleContext,
-                                                         targetElement,
-                                                         pseudoType,
-                                                         keyframes);
-  }
-
-  RefPtr<KeyframeEffectType> effect =
-    new KeyframeEffectType(targetElement->OwnerDoc(), targetElement,
-                           pseudoType, timingParams);
-  effect->mProperties = Move(animationProperties);
   return effect.forget();
 }
 
 void
 KeyframeEffectReadOnly::ResetIsRunningOnCompositor()
 {
   for (AnimationProperty& property : mProperties) {
     property.mIsRunningOnCompositor = false;
--- a/dom/animation/KeyframeEffect.h
+++ b/dom/animation/KeyframeEffect.h
@@ -58,16 +58,21 @@ struct AnimationPropertyDetails;
  */
 struct PropertyValuePair
 {
   nsCSSProperty mProperty;
   // The specified value for the property. For shorthand properties or invalid
   // property values, we store the specified property value as a token stream
   // (string).
   nsCSSValue    mValue;
+
+  bool operator==(const PropertyValuePair& aOther) const {
+    return mProperty == aOther.mProperty &&
+           mValue == aOther.mValue;
+  }
 };
 
 /**
  * A single keyframe.
  *
  * This is the canonical form in which keyframe effects are stored and
  * corresponds closely to the type of objects returned via the getFrames() API.
  *
@@ -269,16 +274,17 @@ public:
 
   bool IsInPlay() const;
   bool IsCurrent() const;
   bool IsInEffect() const;
 
   void SetAnimation(Animation* aAnimation);
   Animation* GetAnimation() const { return mAnimation; }
 
+  void SetFrames(nsTArray<Keyframe>&& aFrames, nsStyleContext* aStyleContext);
   const AnimationProperty*
   GetAnimationOfProperty(nsCSSProperty aProperty) const;
   bool HasAnimationOfProperty(nsCSSProperty aProperty) const {
     return GetAnimationOfProperty(aProperty) != nullptr;
   }
   bool HasAnimationOfProperties(const nsCSSProperty* aProperties,
                                 size_t aPropertyCount) const;
   const InfallibleTArray<AnimationProperty>& Properties() const {
@@ -289,16 +295,20 @@ public:
   }
   // Updates the set of properties using the supplied list whilst preserving
   // the mWinsInCascade and mIsRunningOnCompositor state of any matching
   // properties.
   // Returns true if we updated anything in the properties.
   bool UpdateProperties(
     const InfallibleTArray<AnimationProperty>& aProperties);
 
+  // Update |mProperties| by recalculating from |mFrames| using |aStyleContext|
+  // to resolve specified values.
+  void UpdateProperties(nsStyleContext* aStyleContext);
+
   // Updates |aStyleRule| with the animation values produced by this
   // AnimationEffect for the current time except any properties already
   // contained in |aSetProperties|.
   // Any updated properties are added to |aSetProperties|.
   void ComposeStyle(RefPtr<AnimValuesStyleRule>& aStyleRule,
                     nsCSSPropertySet& aSetProperties);
   // Returns true if at least one property is being animated on compositor.
   bool IsRunningOnCompositor() const;
@@ -358,17 +368,21 @@ protected:
   void UpdateTargetRegistration();
 
   nsCOMPtr<Element> mTarget;
   RefPtr<Animation> mAnimation;
 
   OwningNonNull<AnimationEffectTimingReadOnly> mTiming;
   CSSPseudoElementType mPseudoType;
 
-  InfallibleTArray<AnimationProperty> mProperties;
+  // The specified keyframes.
+  nsTArray<Keyframe>          mFrames;
+
+  // A set of per-property value arrays, derived from |mFrames|.
+  nsTArray<AnimationProperty> mProperties;
 
   // The computed progress last time we composed the style rule. This is
   // used to detect when the progress is not changing (e.g. due to a step
   // timing function) so we can avoid unnecessary style updates.
   Nullable<double> mProgressOnLastCompose;
 
   // We need to track when we go to or from being "in effect" since
   // we need to re-evaluate the cascade of animations when that changes.
--- a/dom/animation/KeyframeUtils.cpp
+++ b/dom/animation/KeyframeUtils.cpp
@@ -462,16 +462,19 @@ KeyframeUtils::ApplyDistributeSpacing(ns
 
 /* static */ nsTArray<AnimationProperty>
 KeyframeUtils::GetAnimationPropertiesFromKeyframes(
     nsStyleContext* aStyleContext,
     dom::Element* aElement,
     CSSPseudoElementType aPseudoType,
     const nsTArray<Keyframe>& aFrames)
 {
+  MOZ_ASSERT(aStyleContext);
+  MOZ_ASSERT(aElement);
+
   nsTArray<KeyframeValueEntry> entries;
 
   for (const Keyframe& frame : aFrames) {
     nsCSSPropertySet propertiesOnThisKeyframe;
     for (const PropertyValuePair& pair :
            PropertyPriorityIterator(frame.mPropertyValues)) {
       // We currently store invalid longhand values on keyframes as a token
       // stream so if we see one of them, just keep moving.