Bug 1245748 - Add KeyframeEffectReadOnly::SetFrames; r?heycam draft
authorBrian Birtles <birtles@gmail.com>
Wed, 30 Mar 2016 08:59:08 +0900
changeset 345702 5088be096e15841b0c936d5a4c02c82ca581fad9
parent 345701 e16f5047b85a705f948312ae83da074e08b9258d
child 345703 a3264ea12e2830535bea4dd7f78a42a81c4532b7
push id14148
push userbbirtles@mozilla.com
push dateWed, 30 Mar 2016 04:59:38 +0000
reviewersheycam
bugs1245748, 1235002
milestone48.0a1
Bug 1245748 - 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
@@ -444,16 +444,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) {
+    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) {
@@ -519,16 +559,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.
@@ -707,42 +805,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.