Bug 1216843 - Part 2: Implement effect iteration composition. r?birtles, smaug draft
authorHiroyuki Ikezoe <hiikezoe@mozilla-japan.org>
Mon, 12 Sep 2016 07:05:02 +0900
changeset 412496 bb2c59ede587c40f6948f9ec5d2ef00781189edf
parent 412495 c75cbd3c2e142aa6e565f5a7d3571b5108098ef2
child 412497 6870d9c9e45dfa1a10164545ec8820259bc42e89
push id29190
push userbmo:hiikezoe@mozilla-japan.org
push dateMon, 12 Sep 2016 05:22:33 +0000
reviewersbirtles, smaug
bugs1216843
milestone51.0a1
Bug 1216843 - Part 2: Implement effect iteration composition. r?birtles, smaug MozReview-Commit-ID: 6u7WtXwL3y3
dom/animation/KeyframeEffect.cpp
dom/animation/KeyframeEffect.h
dom/animation/KeyframeEffectParams.h
dom/animation/KeyframeEffectReadOnly.cpp
dom/webidl/KeyframeEffect.webidl
gfx/layers/composite/AsyncCompositionManager.cpp
gfx/layers/ipc/LayersMessages.ipdlh
layout/base/nsDisplayList.cpp
layout/style/StyleAnimationValue.cpp
layout/style/StyleAnimationValue.h
testing/web-platform/meta/web-animations/interfaces/KeyframeEffect/iterationComposite.html.ini
--- a/dom/animation/KeyframeEffect.cpp
+++ b/dom/animation/KeyframeEffect.cpp
@@ -122,16 +122,32 @@ KeyframeEffect::SetTarget(const Nullable
     }
   } else if (mEffectOptions.mSpacingMode == SpacingMode::paced) {
     // New target is null, so fall back to distribute spacing.
     KeyframeUtils::ApplyDistributeSpacing(mKeyframes);
   }
 }
 
 void
+KeyframeEffect::SetIterationComposite(
+  const IterationCompositeOperation& aIterationComposite)
+{
+  if (mEffectOptions.mIterationComposite == aIterationComposite) {
+    return;
+  }
+
+  if (mAnimation && mAnimation->IsRelevant()) {
+    nsNodeUtils::AnimationChanged(mAnimation);
+  }
+
+  mEffectOptions.mIterationComposite = aIterationComposite;
+  RequestRestyle(EffectCompositor::RestyleType::Layer);
+}
+
+void
 KeyframeEffect::SetSpacing(JSContext* aCx,
                            const nsAString& aSpacing,
                            ErrorResult& aRv)
 {
   SpacingMode spacingMode = SpacingMode::distribute;
   nsCSSPropertyID pacedProperty = eCSSProperty_UNKNOWN;
   nsAutoString invalidPacedProperty;
   KeyframeEffectParams::ParseSpacing(aSpacing,
--- a/dom/animation/KeyframeEffect.h
+++ b/dom/animation/KeyframeEffect.h
@@ -62,14 +62,16 @@ public:
   // This method calls GetTargetStyleContext which is not safe to use when
   // we are in the middle of updating style. If we need to use this when
   // updating style, we should pass the nsStyleContext into this method and use
   // that to update the properties rather than calling
   // GetStyleContextForElement.
   void SetTarget(const Nullable<ElementOrCSSPseudoElement>& aTarget);
 
   void SetSpacing(JSContext* aCx, const nsAString& aSpacing, ErrorResult& aRv);
+  void SetIterationComposite(
+    const IterationCompositeOperation& aIterationComposite);
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_KeyframeEffect_h
--- a/dom/animation/KeyframeEffectParams.h
+++ b/dom/animation/KeyframeEffectParams.h
@@ -4,16 +4,21 @@
  * 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/. */
 
 #ifndef mozilla_KeyframeEffectParams_h
 #define mozilla_KeyframeEffectParams_h
 
 #include "nsCSSProps.h"
 #include "nsString.h"
+// X11 has a #define for None
+#ifdef None
+#undef None
+#endif
+#include "mozilla/dom/KeyframeEffectBinding.h" // IterationCompositeOperation
 
 namespace mozilla {
 
 class ErrorResult;
 
 enum class SpacingMode
 {
   distribute,
@@ -46,17 +51,18 @@ struct KeyframeEffectParams
    * @param [out] aRv The error result.
    */
   static void ParseSpacing(const nsAString& aSpacing,
                            SpacingMode& aSpacingMode,
                            nsCSSPropertyID& aPacedProperty,
                            nsAString& aInvalidPacedProperty,
                            ErrorResult& aRv);
 
-  // FIXME: Bug 1216843: Add IterationCompositeOperations and
-  //        Bug 1216844: Add CompositeOperation
+  dom::IterationCompositeOperation mIterationComposite =
+    dom::IterationCompositeOperation::Replace;
+  // FIXME: Bug 1216844: Add CompositeOperation
   SpacingMode mSpacingMode = SpacingMode::distribute;
   nsCSSPropertyID mPacedProperty = eCSSProperty_UNKNOWN;
 };
 
 } // namespace mozilla
 
 #endif // mozilla_KeyframeEffectParams_h
--- a/dom/animation/KeyframeEffectReadOnly.cpp
+++ b/dom/animation/KeyframeEffectReadOnly.cpp
@@ -72,17 +72,17 @@ KeyframeEffectReadOnly::WrapObject(JSCon
                                    JS::Handle<JSObject*> aGivenProto)
 {
   return KeyframeEffectReadOnlyBinding::Wrap(aCx, this, aGivenProto);
 }
 
 IterationCompositeOperation
 KeyframeEffectReadOnly::IterationComposite() const
 {
-  return IterationCompositeOperation::Replace;
+  return mEffectOptions.mIterationComposite;
 }
 
 CompositeOperation
 KeyframeEffectReadOnly::Composite() const
 {
   return CompositeOperation::Replace;
 }
 
@@ -368,45 +368,75 @@ KeyframeEffectReadOnly::ComposeStyle(Ref
                  prop.mSegments.Length(),
                "out of array bounds");
 
     if (!aStyleRule) {
       // Allocate the style rule now that we know we have animation data.
       aStyleRule = new AnimValuesStyleRule();
     }
 
+    StyleAnimationValue fromValue = segment->mFromValue;
+    StyleAnimationValue toValue = segment->mToValue;
+    // Iteration composition for accumulate
+    if (mEffectOptions.mIterationComposite ==
+          IterationCompositeOperation::Accumulate &&
+        computedTiming.mCurrentIteration > 0) {
+      const AnimationPropertySegment& lastSegment =
+        prop.mSegments.LastElement();
+      // FIXME: Bug 1293492: Add a utility function to calculate both of
+      // below StyleAnimationValues.
+      DebugOnly<bool> accumulateResult =
+        StyleAnimationValue::Accumulate(prop.mProperty,
+                                        fromValue,
+                                        lastSegment.mToValue,
+                                        computedTiming.mCurrentIteration);
+      // We can't check the accumulation result in case of filter property.
+      // That's because some filter property can't accumulate,
+      // e.g. 'contrast(2) brightness(2)' onto 'brightness(1) contrast(1)'
+      // because of mismatch of the order.
+      MOZ_ASSERT(accumulateResult || prop.mProperty == eCSSProperty_filter,
+                 "could not accumulate value");
+      accumulateResult =
+        StyleAnimationValue::Accumulate(prop.mProperty,
+                                        toValue,
+                                        lastSegment.mToValue,
+                                        computedTiming.mCurrentIteration);
+      MOZ_ASSERT(accumulateResult || prop.mProperty == eCSSProperty_filter,
+                 "could not accumulate value");
+    }
+
     // Special handling for zero-length segments
     if (segment->mToKey == segment->mFromKey) {
       if (computedTiming.mProgress.Value() < 0) {
-        aStyleRule->AddValue(prop.mProperty, segment->mFromValue);
+        aStyleRule->AddValue(prop.mProperty, Move(fromValue));
       } else {
-        aStyleRule->AddValue(prop.mProperty, segment->mToValue);
+        aStyleRule->AddValue(prop.mProperty, Move(toValue));
       }
       continue;
     }
 
     double positionInSegment =
       (computedTiming.mProgress.Value() - segment->mFromKey) /
       (segment->mToKey - segment->mFromKey);
     double valuePosition =
       ComputedTimingFunction::GetPortion(segment->mTimingFunction,
                                          positionInSegment,
                                          computedTiming.mBeforeFlag);
 
     MOZ_ASSERT(IsFinite(valuePosition), "Position value should be finite");
     StyleAnimationValue val;
     if (StyleAnimationValue::Interpolate(prop.mProperty,
-                                         segment->mFromValue,
-                                         segment->mToValue,
+                                         fromValue,
+                                         toValue,
                                          valuePosition, val)) {
       aStyleRule->AddValue(prop.mProperty, Move(val));
     } else if (valuePosition < 0.5) {
-      aStyleRule->AddValue(prop.mProperty, segment->mFromValue);
+      aStyleRule->AddValue(prop.mProperty, Move(fromValue));
     } else {
-      aStyleRule->AddValue(prop.mProperty, segment->mToValue);
+      aStyleRule->AddValue(prop.mProperty, Move(toValue));
     }
   }
 }
 
 bool
 KeyframeEffectReadOnly::IsRunningOnCompositor() const
 {
   // We consider animation is running on compositor if there is at least
@@ -477,16 +507,17 @@ KeyframeEffectParamsFromUnion(const Opti
   if (!aOptions.IsUnrestrictedDouble()) {
     const KeyframeEffectOptions& options =
       KeyframeEffectOptionsFromUnion(aOptions);
     KeyframeEffectParams::ParseSpacing(options.mSpacing,
                                        result.mSpacingMode,
                                        result.mPacedProperty,
                                        aInvalidPacedProperty,
                                        aRv);
+    result.mIterationComposite = options.mIterationComposite;
   }
   return result;
 }
 
 /* static */ Maybe<OwningAnimationTarget>
 KeyframeEffectReadOnly::ConvertTarget(
   const Nullable<ElementOrCSSPseudoElement>& aTarget)
 {
--- a/dom/webidl/KeyframeEffect.webidl
+++ b/dom/webidl/KeyframeEffect.webidl
@@ -64,17 +64,16 @@ partial interface KeyframeEffectReadOnly
 };
 
 [Func="nsDocument::IsWebAnimationsEnabled",
  Constructor ((Element or CSSPseudoElement)? target,
               object? keyframes,
               optional (unrestricted double or KeyframeEffectOptions) options)]
 interface KeyframeEffect : KeyframeEffectReadOnly {
   inherit attribute (Element or CSSPseudoElement)? target;
-  // Bug 1216843 - implement animation composition
-  // inherit attribute IterationCompositeOperation iterationComposite;
+  inherit attribute IterationCompositeOperation    iterationComposite;
   // Bug 1216844 - implement additive animation
   // inherit attribute CompositeOperation          composite;
   [SetterThrows]
   inherit attribute DOMString                   spacing;
   [Throws]
   void setKeyframes (object? keyframes);
 };
--- a/gfx/layers/composite/AsyncCompositionManager.cpp
+++ b/gfx/layers/composite/AsyncCompositionManager.cpp
@@ -11,16 +11,17 @@
 #include "LayerManagerComposite.h"      // for LayerManagerComposite, etc
 #include "Layers.h"                     // for Layer, ContainerLayer, etc
 #include "gfxPoint.h"                   // for gfxPoint, gfxSize
 #include "gfxPrefs.h"                   // for gfxPrefs
 #include "mozilla/StyleAnimationValue.h" // for StyleAnimationValue, etc
 #include "mozilla/WidgetUtils.h"        // for ComputeTransformForRotation
 #include "mozilla/dom/KeyframeEffectReadOnly.h"
 #include "mozilla/dom/AnimationEffectReadOnlyBinding.h" // for dom::FillMode
+#include "mozilla/dom/KeyframeEffectBinding.h" // for dom::IterationComposite
 #include "mozilla/gfx/BaseRect.h"       // for BaseRect
 #include "mozilla/gfx/Point.h"          // for RoundedToInt, PointTyped
 #include "mozilla/gfx/Rect.h"           // for RoundedToInt, RectTyped
 #include "mozilla/gfx/ScaleFactor.h"    // for ScaleFactor
 #include "mozilla/layers/APZUtils.h"    // for CompleteAsyncTransform
 #include "mozilla/layers/Compositor.h"  // for Compositor
 #include "mozilla/layers/CompositorBridgeParent.h" // for CompositorBridgeParent, etc
 #include "mozilla/layers/CompositorThread.h"
@@ -548,28 +549,55 @@ AsyncCompositionManager::AlignFixedAndSt
         //       all of the translation correctly. In such a case,
         //       |a[Previous|Current]TransformForRoot| would need to be adjusted
         //       to reflect only the unconsumed part of the translation.
         return translationConsumed ? TraversalFlag::Skip : TraversalFlag::Continue;
       });
 }
 
 static void
-SampleValue(float aPortion, Animation& aAnimation, StyleAnimationValue& aStart,
-            StyleAnimationValue& aEnd, Animatable* aValue, Layer* aLayer)
+SampleValue(float aPortion, Animation& aAnimation,
+            const StyleAnimationValue& aStart, const StyleAnimationValue& aEnd,
+            const StyleAnimationValue& aLastValue, uint64_t aCurrentIteration,
+            Animatable* aValue, Layer* aLayer)
 {
-  StyleAnimationValue interpolatedValue;
   NS_ASSERTION(aStart.GetUnit() == aEnd.GetUnit() ||
                aStart.GetUnit() == StyleAnimationValue::eUnit_None ||
                aEnd.GetUnit() == StyleAnimationValue::eUnit_None,
                "Must have same unit");
+
+  StyleAnimationValue startValue = aStart;
+  StyleAnimationValue endValue = aEnd;
+  // Iteration composition for accumulate
+  if (static_cast<dom::IterationCompositeOperation>
+        (aAnimation.iterationComposite()) ==
+          dom::IterationCompositeOperation::Accumulate &&
+      aCurrentIteration > 0) {
+    // FIXME: Bug 1293492: Add a utility function to calculate both of
+    // below StyleAnimationValues.
+    DebugOnly<bool> accumulateResult =
+      StyleAnimationValue::Accumulate(aAnimation.property(),
+                                      startValue,
+                                      aLastValue,
+                                      aCurrentIteration);
+    MOZ_ASSERT(accumulateResult, "could not accumulate value");
+    accumulateResult =
+      StyleAnimationValue::Accumulate(aAnimation.property(),
+                                      endValue,
+                                      aLastValue,
+                                      aCurrentIteration);
+    MOZ_ASSERT(accumulateResult, "could not accumulate value");
+  }
+
+  StyleAnimationValue interpolatedValue;
   // This should never fail because we only pass transform and opacity values
   // to the compositor and they should never fail to interpolate.
   DebugOnly<bool> uncomputeResult =
-    StyleAnimationValue::Interpolate(aAnimation.property(), aStart, aEnd,
+    StyleAnimationValue::Interpolate(aAnimation.property(),
+                                     startValue, endValue,
                                      aPortion, interpolatedValue);
   MOZ_ASSERT(uncomputeResult, "could not uncompute value");
 
   if (aAnimation.property() == eCSSProperty_opacity) {
     *aValue = interpolatedValue.GetFloatValue();
     return;
   }
 
@@ -678,18 +706,22 @@ SampleAnimations(Layer* aLayer, TimeStam
 
           double portion =
             ComputedTimingFunction::GetPortion(animData.mFunctions[segmentIndex],
                                                positionInSegment,
                                            computedTiming.mBeforeFlag);
 
           // interpolate the property
           Animatable interpolatedValue;
-          SampleValue(portion, animation, animData.mStartValues[segmentIndex],
-                      animData.mEndValues[segmentIndex], &interpolatedValue, layer);
+          SampleValue(portion, animation,
+                      animData.mStartValues[segmentIndex],
+                      animData.mEndValues[segmentIndex],
+                      animData.mEndValues.LastElement(),
+                      computedTiming.mCurrentIteration,
+                      &interpolatedValue, layer);
           LayerComposite* layerComposite = layer->AsLayerComposite();
           switch (animation.property()) {
           case eCSSProperty_opacity:
           {
             layerComposite->SetShadowOpacity(interpolatedValue.get_float());
             layerComposite->SetShadowOpacitySetByAnimation(true);
             break;
           }
--- a/gfx/layers/ipc/LayersMessages.ipdlh
+++ b/gfx/layers/ipc/LayersMessages.ipdlh
@@ -199,16 +199,17 @@ struct Animation {
   float iterationStart;
   // This uses the NS_STYLE_ANIMATION_DIRECTION_* constants.
   uint8_t direction;
   nsCSSPropertyID property;
   AnimationData data;
   float playbackRate;
   // This is used in the transformed progress calculation.
   TimingFunction easingFunction;
+  uint8_t iterationComposite;
 };
 
 // Change a layer's attributes
 struct CommonLayerAttributes {
   IntRect layerBounds;
   LayerIntRegion visibleRegion;
   EventRegions eventRegions;
   TransformMatrix transform;
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -425,16 +425,19 @@ AddAnimationForProperty(nsIFrame* aFrame
   animation->duration() = computedTiming.mDuration;
   animation->iterations() = computedTiming.mIterations;
   animation->iterationStart() = computedTiming.mIterationStart;
   animation->direction() = static_cast<uint8_t>(timing.mDirection);
   animation->property() = aProperty.mProperty;
   animation->playbackRate() = aAnimation->PlaybackRate();
   animation->data() = aData;
   animation->easingFunction() = ToTimingFunction(timing.mFunction);
+  animation->iterationComposite() =
+    static_cast<uint8_t>(aAnimation->GetEffect()->
+                         AsKeyframeEffect()->IterationComposite());
 
   for (uint32_t segIdx = 0; segIdx < aProperty.mSegments.Length(); segIdx++) {
     const AnimationPropertySegment& segment = aProperty.mSegments[segIdx];
 
     AnimationSegment* animSegment = animation->segments().AppendElement();
     if (aProperty.mProperty == eCSSProperty_transform) {
       animSegment->startState() = InfallibleTArray<TransformFunction>();
       animSegment->endState() = InfallibleTArray<TransformFunction>();
--- a/layout/style/StyleAnimationValue.cpp
+++ b/layout/style/StyleAnimationValue.cpp
@@ -2796,16 +2796,36 @@ StyleAnimationValue::AddWeighted(nsCSSPr
       return true;
     }
   }
 
   MOZ_ASSERT(false, "Can't interpolate using the given common unit");
   return false;
 }
 
+bool
+StyleAnimationValue::Accumulate(nsCSSPropertyID aProperty,
+                                StyleAnimationValue& aDest,
+                                const StyleAnimationValue& aValueToAccumulate,
+                                uint64_t aCount)
+{
+  Unit commonUnit =
+    GetCommonUnit(aProperty, aDest.GetUnit(), aValueToAccumulate.GetUnit());
+  switch (commonUnit) {
+    // FIXME: implement them!
+    //case eUnit_Color:
+    //case eUnit_Shadow:
+    //case eUnit_Filter:
+    default:
+      return Add(aProperty, aDest, aValueToAccumulate, aCount);
+  }
+  MOZ_ASSERT_UNREACHABLE("Can't accumulate using the given common unit");
+  return false;
+}
+
 already_AddRefed<css::StyleRule>
 BuildStyleRule(nsCSSPropertyID aProperty,
                dom::Element* aTargetElement,
                const nsAString& aSpecifiedValue,
                bool aUseSVGMode)
 {
   // Set up an empty CSS Declaration
   RefPtr<css::Declaration> declaration(new css::Declaration());
--- a/layout/style/StyleAnimationValue.h
+++ b/layout/style/StyleAnimationValue.h
@@ -124,16 +124,37 @@ public:
    * positive.
    */
   static MOZ_MUST_USE bool
   AddWeighted(nsCSSPropertyID aProperty,
               double aCoeff1, const StyleAnimationValue& aValue1,
               double aCoeff2, const StyleAnimationValue& aValue2,
               StyleAnimationValue& aResultValue);
 
+  /**
+   * Accumulates |aValueToAccumulate| onto |aDest| |aCount| times.
+   * The result is stored in |aDest| on success.
+   *
+   * @param aDest              The base value to be accumulated.
+   * @param aValueToAccumulate The value to accumulate.
+   * @param aCount             The number of times to accumulate
+   *                           aValueToAccumulate.
+   * @return true on success, false on failure.
+   *
+   * NOTE: This function will work as a wrapper of StyleAnimationValue::Add()
+   * if |aProperty| isn't color or shadow or filter.  For these properties,
+   * this function may return a color value that at least one of its components
+   * has a value which is outside the range [0, 1] so that we can calculate
+   * plausible values as interpolation with the return value.
+   */
+  static MOZ_MUST_USE bool
+  Accumulate(nsCSSPropertyID aProperty, StyleAnimationValue& aDest,
+             const StyleAnimationValue& aValueToAccumulate,
+             uint64_t aCount);
+
   // Type-conversion methods
   // -----------------------
   /**
    * Creates a computed value for the given specified value
    * (property ID + string).  A style context is needed in case the
    * specified value depends on inherited style or on the values of other
    * properties.
    *
--- a/testing/web-platform/meta/web-animations/interfaces/KeyframeEffect/iterationComposite.html.ini
+++ b/testing/web-platform/meta/web-animations/interfaces/KeyframeEffect/iterationComposite.html.ini
@@ -1,74 +1,11 @@
 [iterationComposite.html]
   type: testharness
-  [iterationComposite of <length> type animation]
-    expected: FAIL
-
-  [iterationComposite of <percentage> type animation]
-    expected: FAIL
-
   [iterationComposite of <color> type animation]
     expected: FAIL
 
-  [iterationComposite of <number> type animation]
-    expected: FAIL
-
-  [iterationComposite of <shape> type animation]
-    expected: FAIL
-
-  [iterationComposite of <calc()> value animation]
-    expected: FAIL
-
-  [iterationComposite of opacity animation]
-    expected: FAIL
-
-  [iterationComposite of filter blur animation]
-    expected: FAIL
-
-  [iterationComposite of filter brightness for different unit animation]
-    expected: FAIL
-
   [iterationComposite of filter drop-shadow animation]
     expected: FAIL
 
-  [iterationComposite of same filter list animation]
-    expected: FAIL
-
-  [iterationComposite of discrete filter list because of mismatch of the order]
-    expected: FAIL
-
-  [iterationComposite of different length filter list animation]
-    expected: FAIL
-
-  [iterationComposite of transform: [ scale(1), scale(2) \] animation]
-    expected: FAIL
-
-  [iterationComposite of transform: scale(2) animation]
-    expected: FAIL
-
-  [iterationComposite of transform list animation]
-    expected: FAIL
-
-  [iterationComposite starts with non-zero value animation]
-    expected: FAIL
-
-  [iterationComposite with negative final value animation]
-    expected: FAIL
-
-  [interationComposite changes]
-    expected: FAIL
-
-  [duration changes with iterationComposite(accumulate)]
-    expected: FAIL
-
   [iterationComposite of box-shadow animation]
     expected: FAIL
 
-  [iterationComposite of <color> type animation that green component is decreasing]
-    expected: FAIL
-
-  [iterationComposite of <calc()> value animation that the values can'tbe reduced]
-    expected: FAIL
-
-  [iterationComposite of transform list animation whose order is mismatched]
-    expected: FAIL
-