Bug 1291468 - Part 2: Implement keyframe composite(accumulate). r=birtles
authorHiroyuki Ikezoe <hiikezoe@mozilla-japan.org>
Sun, 04 Dec 2016 08:07:41 +0900
changeset 372481 d6c7a5bc54525014ae4b812bf2e29a6f789a1aa8
parent 372480 be8333efe3623aec192916f058e80bed3872feac
child 372482 60f747f5ce44e6d9aa9c2ffdf004e0187b447f2b
push id1419
push userjlund@mozilla.com
push dateMon, 10 Apr 2017 20:44:07 +0000
treeherdermozilla-release@5e6801b73ef6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbirtles
bugs1291468
milestone53.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 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