Bug 1216844 - Implement KeyframeEffect::SetComposite(). r?boris,smaug draft
authorHiroyuki Ikezoe <hiikezoe@mozilla-japan.org>
Wed, 14 Dec 2016 08:51:44 +0900
changeset 449350 bf23681025904f882dad84ea04ff55d87aa3547a
parent 449349 aad04cbf4c7aaf742ac4c853af3cc949e2e452b4
child 539478 c919b11ec9a261ab3b7c9713d183d503873b37e8
push id38547
push userhiikezoe@mozilla-japan.org
push dateWed, 14 Dec 2016 01:11:26 +0000
reviewersboris, smaug
bugs1216844
milestone53.0a1
Bug 1216844 - Implement KeyframeEffect::SetComposite(). r?boris,smaug MozReview-Commit-ID: C9wHsriHgZ9
dom/animation/KeyframeEffect.cpp
dom/animation/KeyframeEffect.h
dom/animation/test/chrome/test_observers_for_sync_api.html
dom/animation/test/style/file_composite.html
dom/webidl/KeyframeEffect.webidl
testing/web-platform/meta/MANIFEST.json
testing/web-platform/tests/web-animations/animation-model/combining-effects/effect-composition.html
testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/composite.html
--- a/dom/animation/KeyframeEffect.cpp
+++ b/dom/animation/KeyframeEffect.cpp
@@ -150,16 +150,37 @@ KeyframeEffect::SetIterationComposite(
     nsNodeUtils::AnimationChanged(mAnimation);
   }
 
   mEffectOptions.mIterationComposite = aIterationComposite;
   RequestRestyle(EffectCompositor::RestyleType::Layer);
 }
 
 void
+KeyframeEffect::SetComposite(const CompositeOperation& aComposite)
+{
+  if (mEffectOptions.mComposite == aComposite) {
+    return;
+  }
+
+  mEffectOptions.mComposite = aComposite;
+
+  if (mAnimation && mAnimation->IsRelevant()) {
+    nsNodeUtils::AnimationChanged(mAnimation);
+  }
+
+  if (mTarget) {
+    RefPtr<nsStyleContext> styleContext = GetTargetStyleContext();
+    if (styleContext) {
+      UpdateProperties(styleContext);
+    }
+  }
+}
+
+void
 KeyframeEffect::SetSpacing(JSContext* aCx,
                            const nsAString& aSpacing,
                            CallerType aCallerType,
                            ErrorResult& aRv)
 {
   SpacingMode spacingMode = SpacingMode::distribute;
   nsCSSPropertyID pacedProperty = eCSSProperty_UNKNOWN;
   nsAutoString invalidPacedProperty;
--- a/dom/animation/KeyframeEffect.h
+++ b/dom/animation/KeyframeEffect.h
@@ -80,14 +80,15 @@ public:
                   CallerType aCallerType, ErrorResult& aRv);
   IterationCompositeOperation IterationComposite(CallerType aCallerType)
   {
     return KeyframeEffectReadOnly::IterationComposite();
   }
   void SetIterationComposite(
     const IterationCompositeOperation& aIterationComposite,
     CallerType aCallerType);
+  void SetComposite(const CompositeOperation& aComposite);
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_KeyframeEffect_h
--- a/dom/animation/test/chrome/test_observers_for_sync_api.html
+++ b/dom/animation/test/chrome/test_observers_for_sync_api.html
@@ -439,16 +439,39 @@ function createPseudo(test, element, typ
       [{ added: [anim], changed: [], removed: [] }],
       "records after animation is added");
 
     anim.effect.spacing = "paced(margin-left)";
     assert_equals_records(observer.takeRecords(),
       [], "no record after setting the same spacing");
   }, "set_the_same_spacing");
 
+  test(t => {
+    var div = addDiv(t);
+    var observer =
+      setupSynchronousObserver(t,
+                               aOptions.subtree ? div.parentNode : div,
+                               aOptions.subtree);
+
+    var anim = div.animate({ }, 100 * MS_PER_SEC);
+    assert_equals_records(observer.takeRecords(),
+      [{ added: [anim], changed: [], removed: [] }],
+      "records after animation is added");
+
+    anim.effect.composite = "add";
+    assert_equals_records(observer.takeRecords(),
+      [{ added: [], changed: [anim], removed: [] }],
+      "records after composite is changed");
+
+    anim.effect.composite = "add";
+    assert_equals_records(observer.takeRecords(),
+      [], "no record after setting the same composite");
+
+  }, "set_composite");
+
   // Test that starting a single animation that is cancelled by calling
   // cancel() dispatches an added notification and then a removed
   // notification.
   test(t => {
     var div = addDiv(t, { style: "animation: anim 100s forwards" });
     var observer =
       setupSynchronousObserver(t,
                                aOptions.subtree ? div.parentNode : div,
--- a/dom/animation/test/style/file_composite.html
+++ b/dom/animation/test/style/file_composite.html
@@ -1,17 +1,17 @@
 <!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;
+  width: 20px;
+  height: 20px;
   background-color: white;
 }
 </style>
 <body>
 <script>
 'use strict';
 
 if (!SpecialPowers.DOMWindowUtils.layerManagerRemote ||
@@ -99,11 +99,37 @@ promise_test(t => {
     var transform =
       SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'transform');
     assert_matrix_equals(transform, 'matrix(1, 0, 0, 1, 250, 0)',
       'Transform value at 50%');
   });
 }, 'Composite specified on a keyframe overrides the composite mode of the ' +
    'effect');
 
+promise_test(t => {
+  useTestRefreshMode(t);
+
+  var div = addDiv(t);
+  div.animate({ transform: [ 'scale(2)', 'scale(2)' ] }, 100 * MS_PER_SEC);
+  var anim = div.animate({ transform: [ 'scale(4)', 'scale(4)' ] },
+                         { duration: 100 * MS_PER_SEC, composite: 'add' });
+
+  return waitForPaintsFlushed().then(() => {
+    var transform =
+      SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'transform');
+    assert_matrix_equals(transform, 'matrix(8, 0, 0, 8, 0, 0)',
+      'The additive scale value should be scale(8)'); // scale(2) scale(4)
+
+    anim.effect.composite = 'accumulate';
+    return waitForPaintsFlushed();
+  }).then(() => {
+    SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(1);
+    var transform =
+      SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'transform');
+    assert_matrix_equals(transform, 'matrix(5, 0, 0, 5, 0, 0)',
+      // (scale(2 - 1) + scale(4 - 1) + scale(1))
+      'The accumulate scale value should be scale(5)');
+  });
+}, 'Composite operation change');
+
 done();
 </script>
 </body>
--- a/dom/webidl/KeyframeEffect.webidl
+++ b/dom/webidl/KeyframeEffect.webidl
@@ -65,15 +65,14 @@ partial interface KeyframeEffectReadOnly
  Constructor ((Element or CSSPseudoElement)? target,
               object? keyframes,
               optional (unrestricted double or KeyframeEffectOptions) options),
  Constructor (KeyframeEffectReadOnly source)]
 interface KeyframeEffect : KeyframeEffectReadOnly {
   inherit attribute (Element or CSSPseudoElement)? target;
   [NeedsCallerType]
   inherit attribute IterationCompositeOperation    iterationComposite;
-  // Bug 1216844 - implement additive animation
-  // inherit attribute CompositeOperation          composite;
+  inherit attribute CompositeOperation          composite;
   [SetterThrows, NeedsCallerType]
   inherit attribute DOMString                   spacing;
   [Throws]
   void setKeyframes (object? keyframes);
 };
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -39592,16 +39592,22 @@
           }
         ],
         "web-animations/animation-model/combining-effects/effect-composition.html": [
           {
             "path": "web-animations/animation-model/combining-effects/effect-composition.html",
             "url": "/web-animations/animation-model/combining-effects/effect-composition.html"
           }
         ],
+        "web-animations/interfaces/KeyframeEffect/composite.html": [
+          {
+            "path": "web-animations/interfaces/KeyframeEffect/composite.html",
+            "url": "/web-animations/interfaces/KeyframeEffect/composite.html"
+          }
+        ],
         "web-animations/interfaces/KeyframeEffect/copy-contructor.html": [
           {
             "path": "web-animations/interfaces/KeyframeEffect/copy-contructor.html",
             "url": "/web-animations/interfaces/KeyframeEffect/copy-contructor.html"
           }
         ],
         "web-animations/interfaces/KeyframeEffectReadOnly/copy-contructor.html": [
           {
--- a/testing/web-platform/tests/web-animations/animation-model/combining-effects/effect-composition.html
+++ b/testing/web-platform/tests/web-animations/animation-model/combining-effects/effect-composition.html
@@ -60,11 +60,26 @@
                    { marginLeft: '20px' }],
                   { duration: 100 , composite: composite });
 
     anim.currentTime = 50;
     assert_equals(getComputedStyle(div).marginLeft, '20px',
       'Animated style at 50%');
   }, composite + ' specified on a keyframe overrides the composite mode of ' +
      'the effect');
+
+  test(function(t) {
+    var div = createDiv(t);
+    div.style.marginLeft = '10px';
+    var anim =
+      div.animate([{ marginLeft: '10px', composite: 'replace' },
+                   { marginLeft: '20px' }],
+                  100);
+
+    anim.effect.composite = composite;
+    anim.currentTime = 50;                       // (10 + (10 + 20)) * 0.5
+    assert_equals(getComputedStyle(div).marginLeft, '20px',
+      'Animated style at 50%');
+  }, 'unspecified composite mode on a keyframe is overriden by setting ' +
+     composite + ' of the effect');
 });
 
 </script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/composite.html
@@ -0,0 +1,47 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>KeyframeEffect.composite tests</title>
+<link rel="help"
+      href="https://w3c.github.io/web-animations/#dom-keyframeeffect-composite">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../testcommon.js"></script>
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+test(function(t) {
+  var anim = createDiv(t).animate(null);
+  assert_equals(anim.effect.composite, 'replace',
+                'The default value should be replace');
+}, 'Default value');
+
+test(function(t) {
+  var anim = createDiv(t).animate(null);
+  anim.effect.composite = 'add';
+  assert_equals(anim.effect.composite, 'add',
+                'The effect composite value should be replaced');
+}, 'Change composite value');
+
+test(function(t) {
+  var anim = createDiv(t).animate({ left: '10px' });
+
+  anim.effect.composite = 'add';
+  var keyframes = anim.effect.getKeyframes();
+  assert_equals(keyframes[0].composite, undefined,
+                'unspecified keyframe composite value should be absent even ' +
+                'if effect composite is set');
+}, 'Unspecified keyframe composite value when setting effect composite');
+
+test(function(t) {
+  var anim = createDiv(t).animate({ left: '10px', composite: 'replace' });
+
+  anim.effect.composite = 'add';
+  var keyframes = anim.effect.getKeyframes();
+  assert_equals(keyframes[0].composite, 'replace',
+                'specified keyframe composite value should not be overridden ' +
+                'by setting effect composite');
+}, 'Specified keyframe composite value when setting effect composite');
+
+</script>