Bug 1291468 - Part 2: Implement keyframe composite(accumulate). r?birtles draft
authorHiroyuki Ikezoe <hiikezoe@mozilla-japan.org>
Sun, 04 Dec 2016 08:07:41 +0900
changeset 447352 b99216593c82efc6a013590a63dc9c95f66c4478
parent 447351 0a3738e29592deb02dc05de1799c2317648590f3
child 447353 6172988fc6448a7d5a87d6cf38857a70d317fce1
push id38040
push userhiikezoe@mozilla-japan.org
push dateSat, 03 Dec 2016 23:30:08 +0000
reviewersbirtles
bugs1291468
milestone53.0a1
Bug 1291468 - Part 2: Implement keyframe composite(accumulate). r?birtles Test cases in file_composite.html are matching pair of tests in web-platform-tests. MozReview-Commit-ID: ApuvVCHKQ8Y
dom/animation/KeyframeEffectReadOnly.cpp
dom/animation/KeyframeUtils.cpp
dom/animation/test/mochitest.ini
dom/animation/test/style/file_composite.html
dom/animation/test/style/test_composite.html
testing/web-platform/meta/web-animations/animation-model/combining-effects/effect-composition.html.ini
--- a/dom/animation/KeyframeEffectReadOnly.cpp
+++ b/dom/animation/KeyframeEffectReadOnly.cpp
@@ -317,20 +317,22 @@ KeyframeEffectReadOnly::CompositeValue(
       return aValueToComposite;
     case dom::CompositeOperation::Add:
       // So far nothing to do since we come to here only in case of missing
       // keyframe, that means we have only to use the base value or the
       // underlying value as the composited value.
       // FIXME: Bug 1311620: Once we implement additive operation, we need to
       // calculate it here.
       return aUnderlyingValue;
-    case dom::CompositeOperation::Accumulate:
-      // FIXME: Bug 1291468: Implement accumulate operation.
-      MOZ_ASSERT_UNREACHABLE("Not implemented yet");
-      break;
+    case dom::CompositeOperation::Accumulate: {
+      StyleAnimationValue result(aValueToComposite);
+      return StyleAnimationValue::Accumulate(aProperty,
+                                             aUnderlyingValue,
+                                             Move(result));
+    }
     default:
       MOZ_ASSERT_UNREACHABLE("Unknown compisite operation type");
       break;
   }
   return StyleAnimationValue();
 }
 
 StyleAnimationValue
@@ -996,16 +998,20 @@ KeyframeEffectReadOnly::GetKeyframes(JSC
     MOZ_ASSERT(keyframe.mComputedOffset != Keyframe::kComputedOffsetNotSet,
                "Invalid computed offset");
     keyframeDict.mComputedOffset.Construct(keyframe.mComputedOffset);
     if (keyframe.mTimingFunction) {
       keyframeDict.mEasing.Truncate();
       keyframe.mTimingFunction.ref().AppendToString(keyframeDict.mEasing);
     } // else if null, leave easing as its default "linear".
 
+    if (keyframe.mComposite) {
+      keyframeDict.mComposite.Construct(keyframe.mComposite.value());
+    }
+
     JS::Rooted<JS::Value> keyframeJSValue(aCx);
     if (!ToJSValue(aCx, keyframeDict, &keyframeJSValue)) {
       aRv.Throw(NS_ERROR_FAILURE);
       return;
     }
 
     JS::Rooted<JSObject*> keyframeObject(aCx, &keyframeJSValue.toObject());
     for (const PropertyValuePair& propertyValue : keyframe.mPropertyValues) {
--- a/dom/animation/KeyframeUtils.cpp
+++ b/dom/animation/KeyframeUtils.cpp
@@ -808,16 +808,23 @@ ConvertKeyframeSequence(JSContext* aCx,
     Keyframe* keyframe = aResult.AppendElement(fallible);
     if (!keyframe) {
       return false;
     }
     if (!keyframeDict.mOffset.IsNull()) {
       keyframe->mOffset.emplace(keyframeDict.mOffset.Value());
     }
 
+    if (keyframeDict.mComposite.WasPassed()) {
+      // FIXME: Bug 1311620: We don't support additive animation yet.
+      if (keyframeDict.mComposite.Value() != dom::CompositeOperation::Add) {
+        keyframe->mComposite.emplace(keyframeDict.mComposite.Value());
+      }
+    }
+
     ErrorResult rv;
     keyframe->mTimingFunction =
       TimingParams::ParseEasing(keyframeDict.mEasing, aDocument, rv);
     if (rv.MaybeSetPendingException(aCx)) {
       return false;
     }
 
     // Look for additional property-values pairs on the object.
@@ -1367,16 +1374,18 @@ BuildSegmentsFromValueEntries(nsTArray<K
     // Now generate the segment.
     AnimationPropertySegment* segment =
       animationProperty->mSegments.AppendElement();
     segment->mFromKey   = aEntries[i].mOffset;
     segment->mToKey     = aEntries[j].mOffset;
     segment->mFromValue = aEntries[i].mValue;
     segment->mToValue   = aEntries[j].mValue;
     segment->mTimingFunction = aEntries[i].mTimingFunction;
+    segment->mFromComposite = aEntries[i].mComposite;
+    segment->mToComposite = aEntries[j].mComposite;
 
     i = j;
   }
 }
 
 /**
  * Converts a JS object representing a property-indexed keyframe into
  * an array of Keyframe objects.
@@ -1403,16 +1412,24 @@ GetKeyframeListFromPropertyIndexedKeyfra
   // get its explicit dictionary members.
   dom::binding_detail::FastBasePropertyIndexedKeyframe keyframeDict;
   if (!keyframeDict.Init(aCx, aValue, "BasePropertyIndexedKeyframe argument",
                          false)) {
     aRv.Throw(NS_ERROR_FAILURE);
     return;
   }
 
+  Maybe<dom::CompositeOperation> composite;
+  if (keyframeDict.mComposite.WasPassed()) {
+    // FIXME: Bug 1311620: We don't support additive animation yet.
+    if (keyframeDict.mComposite.Value() != dom::CompositeOperation::Add) {
+      composite.emplace(keyframeDict.mComposite.Value());
+    }
+  }
+
   Maybe<ComputedTimingFunction> easing =
     TimingParams::ParseEasing(keyframeDict.mEasing, aDocument, aRv);
   if (aRv.Failed()) {
     return;
   }
 
   // Get all the property--value-list pairs off the object.
   JS::Rooted<JSObject*> object(aCx, &aValue.toObject());
@@ -1448,16 +1465,17 @@ GetKeyframeListFromPropertyIndexedKeyfra
     size_t n = pair.mValues.Length() - 1;
     size_t i = 0;
 
     for (const nsString& stringValue : pair.mValues) {
       double offset = i++ / double(n);
       Keyframe* keyframe = processedKeyframes.LookupOrAdd(offset);
       if (keyframe->mPropertyValues.IsEmpty()) {
         keyframe->mTimingFunction = easing;
+        keyframe->mComposite = composite;
         keyframe->mComputedOffset = offset;
       }
       keyframe->mPropertyValues.AppendElement(
         MakePropertyValuePair(pair.mProperty, stringValue, parser, aDocument));
     }
   }
 
   aResult.SetCapacity(processedKeyframes.Count());
--- a/dom/animation/test/mochitest.ini
+++ b/dom/animation/test/mochitest.ini
@@ -48,16 +48,17 @@ support-files =
   mozilla/file_transform_limits.html
   mozilla/file_transition_finish_on_compositor.html
   mozilla/file_underlying-discrete-value.html
   mozilla/file_set-easing.html
   style/file_animation-seeking-with-current-time.html
   style/file_animation-seeking-with-start-time.html
   style/file_animation-setting-effect.html
   style/file_animation-setting-spacing.html
+  style/file_composite.html
   style/file_missing-keyframe.html
   style/file_missing-keyframe-on-compositor.html
   testcommon.js
 
 [css-animations/test_animations-dynamic-changes.html]
 [css-animations/test_animation-cancel.html]
 [css-animations/test_animation-computed-timing.html]
 [css-animations/test_animation-currenttime.html]
@@ -104,10 +105,11 @@ support-files =
 [mozilla/test_transform_limits.html]
 [mozilla/test_transition_finish_on_compositor.html]
 skip-if = toolkit == 'android'
 [mozilla/test_underlying-discrete-value.html]
 [style/test_animation-seeking-with-current-time.html]
 [style/test_animation-seeking-with-start-time.html]
 [style/test_animation-setting-effect.html]
 [style/test_animation-setting-spacing.html]
+[style/test_composite.html]
 [style/test_missing-keyframe.html]
 [style/test_missing-keyframe-on-compositor.html]
new file mode 100644
--- /dev/null
+++ b/dom/animation/test/style/file_composite.html
@@ -0,0 +1,91 @@
+<!doctype html>
+<meta charset=utf-8>
+<script src="../testcommon.js"></script>
+<script src="/tests/SimpleTest/paint_listener.js"></script>
+<style>
+div {
+  /* Element needs geometry to be eligible for layerization */
+  width: 100px;
+  height: 100px;
+  background-color: white;
+}
+</style>
+<body>
+<script>
+'use strict';
+
+if (!SpecialPowers.DOMWindowUtils.layerManagerRemote ||
+    !SpecialPowers.getBoolPref(
+      'layers.offmainthreadcomposition.async-animations')) {
+  // If OMTA is disabled, nothing to run.
+  done();
+}
+
+function waitForPaintsFlushed() {
+  return new Promise(function(resolve, reject) {
+    waitForAllPaintsFlushed(resolve);
+  });
+}
+
+promise_test(t => {
+  // Without this, the first test case fails on Android.
+  return waitForDocumentLoad();
+}, 'Ensure document has been loaded');
+
+promise_test(t => {
+  useTestRefreshMode(t);
+
+  var div = addDiv(t, { style: 'transform: translateX(100px)' });
+  div.animate({ transform: ['translateX(0px)', 'translateX(200px)'],
+                composite: 'accumulate' },
+              100 * MS_PER_SEC);
+
+  return waitForPaintsFlushed().then(() => {
+    SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(50 * MS_PER_SEC);
+    var transform =
+      SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'transform');
+    assert_matrix_equals(transform, 'matrix(1, 0, 0, 1, 200, 0)',
+      'Transform value at 50%');
+  });
+}, 'Accumulate onto the base value');
+
+promise_test(t => {
+  useTestRefreshMode(t);
+
+  var div = addDiv(t);
+  div.animate({ transform: ['translateX(100px)', 'translateX(200px)'],
+                composite: 'replace' },
+              100 * MS_PER_SEC);
+  div.animate({ transform: ['translateX(0px)', 'translateX(100px)'],
+                composite: 'accumulate' },
+              100 * MS_PER_SEC);
+
+  return waitForPaintsFlushed().then(() => {
+    SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(50 * MS_PER_SEC);
+    var transform =
+      SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'transform');
+    assert_matrix_equals(transform, 'matrix(1, 0, 0, 1, 200, 0)',
+      'Transform value at 50%');
+  });
+}, 'Accumulate onto an underlying animation value');
+
+promise_test(t => {
+  useTestRefreshMode(t);
+
+  var div = addDiv(t, { style: 'transform: translateX(100px)' });
+  div.animate([{ transform: 'translateX(100px)', composite: 'accumulate' },
+               { transform: 'translateX(300px)', composite: 'replace' }],
+              100 * MS_PER_SEC);
+
+  return waitForPaintsFlushed().then(() => {
+    SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(50 * MS_PER_SEC);
+    var transform =
+      SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'transform');
+    assert_matrix_equals(transform, 'matrix(1, 0, 0, 1, 250, 0)',
+      'Transform value at 50s');
+  });
+}, 'Composite when mixing accumulate and replace');
+
+done();
+</script>
+</body>
new file mode 100644
--- /dev/null
+++ b/dom/animation/test/style/test_composite.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<meta charset=utf-8>
+<script src='/resources/testharness.js'></script>
+<script src='/resources/testharnessreport.js'></script>
+<div id='log'></div>
+<script>
+'use strict';
+setup({explicit_done: true});
+SpecialPowers.pushPrefEnv(
+  { 'set': [['dom.animations-api.core.enabled', true]] },
+  function() {
+    window.open('file_composite.html');
+  });
+</script>
+</html>
--- a/testing/web-platform/meta/web-animations/animation-model/combining-effects/effect-composition.html.ini
+++ b/testing/web-platform/meta/web-animations/animation-model/combining-effects/effect-composition.html.ini
@@ -1,13 +1,4 @@
 [composite.html]
-  [Accumulate onto the base value]
-    expected: FAIL
-
-  [Accumulate onto an underlying animation value]
-    expected: FAIL
-
-  [Composite when mixing accumulate and replace]
-    expected: FAIL
-
   [Composite specified on a keyframe overrides the composite mode of the effect]
     expected: FAIL