Bug 1244640 - implement AnimationEffectTiming iterations r=hiro
authorRob McAuley <rmcauley@gmail.com>
Sat, 26 Mar 2016 14:41:37 +0900
changeset 290560 5a2269c1f44800f43e0c876225f11784a480021b
parent 290559 2db5ba70aa1e3ae124506143e60a39f7c9325951
child 290561 a9851bf7034a37f5d8ae2d65db6281735dc9deb2
push id19656
push usergwagner@mozilla.com
push dateMon, 04 Apr 2016 13:43:23 +0000
treeherderb2g-inbound@e99061fde28a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewershiro
bugs1244640
milestone48.0a1
Bug 1244640 - implement AnimationEffectTiming iterations r=hiro MozReview-Commit-ID: Iwq6vleUERo
dom/animation/AnimationEffectTiming.cpp
dom/animation/test/chrome/test_animation_observers.html
layout/style/test/file_animations_effect_timing_iterations.html
layout/style/test/mochitest.ini
layout/style/test/test_animations_effect_timing_iterations.html
testing/web-platform/meta/MANIFEST.json
testing/web-platform/tests/web-animations/animation-effect-timing/getAnimations.html
testing/web-platform/tests/web-animations/animation-effect-timing/getComputedStyle.html
testing/web-platform/tests/web-animations/animation-effect-timing/iterations.html
--- a/dom/animation/AnimationEffectTiming.cpp
+++ b/dom/animation/AnimationEffectTiming.cpp
@@ -68,17 +68,28 @@ AnimationEffectTiming::SetIterationStart
   mTiming.mIterationStart = aIterationStart;
 
   PostSpecifiedTimingUpdated(mEffect);
 }
 
 void
 AnimationEffectTiming::SetIterations(double aIterations, ErrorResult& aRv)
 {
-  // TODO: Bug 1244640 - implement AnimationEffectTiming iterations
+  if (mTiming.mIterations == aIterations) {
+    return;
+  }
+
+  TimingParams::ValidateIterations(aIterations, aRv);
+  if (aRv.Failed()) {
+    return;
+  }
+
+  mTiming.mIterations = aIterations;
+
+  PostSpecifiedTimingUpdated(mEffect);
 }
 
 void
 AnimationEffectTiming::SetDuration(const UnrestrictedDoubleOrString& aDuration,
                                    ErrorResult& aRv)
 {
   Maybe<StickyTimeDuration> newDuration =
     TimingParams::ParseDuration(aDuration, aRv);
--- a/dom/animation/test/chrome/test_animation_observers.html
+++ b/dom/animation/test/chrome/test_animation_observers.html
@@ -1596,16 +1596,49 @@ addAsyncAnimTest("change_enddelay_and_cu
 addAsyncAnimTest("change_enddelay_and_currenttime",
                  { observe: div, subtree: true }, function*() {
   var anim = div.animate({ opacity: [ 0, 1 ] },
                          { duration: 100, endDelay: -100 });
   yield await_frame();
   assert_records([], "records after animation is added");
 });
 
+addAsyncAnimTest("change_iterations",
+                 { observe: div, subtree: true }, function*() {
+  var anim = div.animate({ opacity: [ 0, 1 ] }, 100000);
+
+  yield await_frame();
+  assert_records([{ added: [anim], changed: [], removed: [] }],
+                 "records after animation is added");
+
+  anim.effect.timing.iterations = 2;
+  yield await_frame();
+  assert_records([{ added: [], changed: [anim], removed: [] }],
+                 "records after iterations is changed");
+
+  anim.effect.timing.iterations = 2;
+  yield await_frame();
+  assert_records([], "records after assigning same value");
+
+  anim.effect.timing.iterations = 0;
+  yield await_frame();
+  assert_records([{ added: [], changed: [], removed: [anim] }],
+                 "records after animation end");
+
+  anim.effect.timing.iterations = Infinity;
+  yield await_frame();
+  assert_records([{ added: [anim], changed: [], removed: [] }],
+                 "records after animation restarted");
+
+  anim.cancel();
+  yield await_frame();
+  assert_records([{ added: [], changed: [], removed: [anim] }],
+                 "records after animation end");
+});
+
 addAsyncAnimTest("exclude_animations_targeting_pseudo_elements",
                  { observe: div, subtree: false }, function*() {
   var anim = div.animate({ opacity: [ 0, 1 ] }, { duration: 100000 });
   var pAnim = pseudoTarget.animate({ opacity: [ 0, 1 ] }, { duration: 100000 });
 
   yield await_frame();
   assert_records([{ added: [anim], changed: [], removed: [] }],
                  "records after animation is added");
new file mode 100644
--- /dev/null
+++ b/layout/style/test/file_animations_effect_timing_iterations.html
@@ -0,0 +1,73 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <script type="application/javascript"
+    src="/tests/SimpleTest/paint_listener.js"></script>
+  <script type="application/javascript" src="animation_utils.js"></script>
+  <style type="text/css">
+    @keyframes anim {
+      0% { transform: translate(0px) }
+      100% { transform: translate(100px) }
+    }
+    .target {
+      /* The animation target needs geometry in order to qualify for OMTA */
+      width: 100px;
+      height: 100px;
+      background-color: white;
+    }
+  </style>
+  <script>
+    var ok = opener.ok.bind(opener);
+    var is = opener.is.bind(opener);
+    var todo = opener.todo.bind(opener);
+    function finish() {
+      var o = opener;
+      self.close();
+      o.SimpleTest.finish();
+    }
+  </script>
+</head>
+<body>
+<div id="display"></div>
+<script type="application/javascript">
+"use strict";
+
+runOMTATest(function() {
+  runAllAsyncAnimTests().then(function() {
+    finish();
+  });
+}, finish, opener.SpecialPowers);
+
+addAsyncAnimTest(function *() {
+  var [ div ] = new_div("");
+  var animation = div.animate(
+    [ { transform: 'translate(0px)' },
+      { transform: 'translate(100px)' } ],
+      { duration: 4000,
+        iterations: 2
+      });
+  yield waitForPaints();
+
+  advance_clock(6000);
+  omta_is(div, "transform", { tx: 50 }, RunningOn.Compositor,
+          "Animation is running on compositor");
+  animation.effect.timing.iterations = 1;
+  advance_clock(0);
+
+  yield waitForPaints();
+  omta_is(div, "transform", { tx: 0 }, RunningOn.MainThread,
+          "Animation is on MainThread");
+
+  animation.effect.timing.iterations = 3;
+
+  advance_clock(0);
+  yield waitForPaints();
+  omta_is(div, "transform", { tx: 50 }, RunningOn.Compositor,
+          "Animation is running again on compositor");
+
+  done_div();
+});
+
+</script>
+</body>
+</html>
--- a/layout/style/test/mochitest.ini
+++ b/layout/style/test/mochitest.ini
@@ -44,16 +44,18 @@ support-files = additional_sheets_helper
 skip-if = toolkit == 'android'
 [test_animations_async_tests.html]
 support-files = ../../reftests/fonts/Ahem.ttf file_animations_async_tests.html
 [test_animations_dynamic_changes.html]
 [test_animations_effect_timing_duration.html]
 support-files = file_animations_effect_timing_duration.html
 [test_animations_effect_timing_enddelay.html]
 support-files = file_animations_effect_timing_enddelay.html
+[test_animations_effect_timing_iterations.html]
+support-files = file_animations_effect_timing_iterations.html
 [test_animations_event_order.html]
 [test_animations_iterationstart.html]
 support-files = file_animations_iterationstart.html
 [test_animations_omta.html]
 [test_animations_omta_start.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # bug 1041017
 [test_animations_pausing.html]
 support-files = file_animations_pausing.html
new file mode 100644
--- /dev/null
+++ b/layout/style/test/test_animations_effect_timing_iterations.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for animation.effect.timing.iterations on compositor</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<div id="display"></div>
+<pre id="test">
+<script type="application/javascript">
+"use strict";
+
+SimpleTest.waitForExplicitFinish();
+
+SpecialPowers.pushPrefEnv(
+  { "set": [[ "dom.animations-api.core.enabled", true]] },
+  function() {
+    window.open("file_animations_effect_timing_iterations.html");
+  });
+</script>
+</pre>
+</body>
+</html>
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -28401,16 +28401,20 @@
         "path": "web-animations/animation-effect-timing/getAnimations.html",
         "url": "/web-animations/animation-effect-timing/getAnimations.html"
       },
       {
         "path": "web-animations/animation-effect-timing/getComputedStyle.html",
         "url": "/web-animations/animation-effect-timing/getComputedStyle.html"
       },
       {
+        "path": "web-animations/animation-effect-timing/iterations.html",
+        "url": "/web-animations/animation-effect-timing/iterations.html"
+      },
+      {
         "path": "web-animations/animation-effect-timing/iterationStart.html",
         "url": "/web-animations/animation-effect-timing/iterationStart.html"
       },
       {
         "path": "web-animations/animation-node/animation-node-after.html",
         "url": "/web-animations/animation-node/animation-node-after.html"
       },
       {
--- a/testing/web-platform/tests/web-animations/animation-effect-timing/getAnimations.html
+++ b/testing/web-platform/tests/web-animations/animation-effect-timing/getAnimations.html
@@ -49,16 +49,29 @@ test(function(t) {
     'set currentTime same as endTime when endDelay is negative value');
   anim.currentTime = 1000;
   assert_equals(div.getAnimations().length, 0,
     'set currentTime same as duration when endDelay is negative value');
 }, 'when endDelay is changed');
 
 test(function(t) {
   var div = createDiv(t);
+  var anim = div.animate({ opacity: [ 0, 1 ] }, 2000);
+  anim.finish();
+  assert_equals(div.getAnimations().length, 0, 'animation finished');
+  anim.effect.timing.iterations = 10;
+  assert_equals(div.getAnimations()[0], anim, 'set iterations 10');
+  anim.effect.timing.iterations = 0;
+  assert_equals(div.getAnimations().length, 0, 'set iterations 0');
+  anim.effect.timing.iterations = Infinity;
+  assert_equals(div.getAnimations().length, 1, 'set iterations Infinity');
+}, 'when iterations is changed');
+
+test(function(t) {
+  var div = createDiv(t);
   var anim = div.animate({ opacity: [ 0, 1 ] },
                          { duration: 1000, delay: 500, endDelay: -500 });
   assert_equals(div.getAnimations()[0], anim, 'when currentTime 0');
   anim.currentTime = 500;
   assert_equals(div.getAnimations()[0], anim, 'set currentTime 500');
   anim.currentTime = 1000;
   assert_equals(div.getAnimations().length, 0, 'set currentTime 1000');
 }, 'when currentTime changed in duration:1000, delay: 500, endDelay: -500');
--- a/testing/web-platform/tests/web-animations/animation-effect-timing/getComputedStyle.html
+++ b/testing/web-platform/tests/web-animations/animation-effect-timing/getComputedStyle.html
@@ -22,16 +22,29 @@ test(function(t) {
   anim.effect.timing.duration = 0;
   assert_equals(getComputedStyle(div).opacity, '1', 'set duration 0');
   anim.effect.timing.duration = 'auto';
   assert_equals(getComputedStyle(div).opacity, '1', 'set duration \'auto\'');
 }, 'changed duration immediately updates its computed styles');
 
 test(function(t) {
   var div = createDiv(t);
+  var anim = div.animate({ opacity: [ 0, 1 ] }, 100000);
+  anim.finish();
+  assert_equals(getComputedStyle(div).opacity, '1', 'animation finished');
+  anim.effect.timing.iterations = 2;
+  assert_equals(getComputedStyle(div).opacity, '0', 'set 2 iterations');
+  anim.effect.timing.iterations = 0;
+  assert_equals(getComputedStyle(div).opacity, '1', 'set iterations 0');
+  anim.effect.timing.iterations = Infinity;
+  assert_equals(getComputedStyle(div).opacity, '0', 'set iterations Infinity');
+}, 'changed iterations immediately updates its computed styles');
+
+test(function(t) {
+  var div = createDiv(t);
   var anim = div.animate({ opacity: [ 1, 0 ] },
                          { duration: 10000, endDelay: 1000, fill: 'none' });
 
   anim.currentTime = 9000;
   assert_equals(getComputedStyle(div).opacity, '0.1',
                 'set currentTime during duration');
 
   anim.currentTime = 10900;
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/web-animations/animation-effect-timing/iterations.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>iterations tests</title>
+<link rel="help" href="https://w3c.github.io/web-animations/#dom-animationeffecttiming-iterations">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../testcommon.js"></script>
+<link rel="stylesheet" href="/resources/testharness.css">
+<body>
+<div id="log"></div>
+<script>
+'use strict';
+
+test(function(t) {
+  var div = createDiv(t);
+  var anim = div.animate({ opacity: [ 0, 1 ] }, 2000);
+  anim.effect.timing.iterations = 2;
+  assert_equals(anim.effect.timing.iterations, 2, 'set duration 2');
+  assert_equals(anim.effect.getComputedTiming().iterations, 2,
+                       'getComputedTiming() after set iterations 2');
+}, 'set iterations 2');
+
+test(function(t) {
+  var div = createDiv(t);
+  var anim = div.animate({ opacity: [ 0, 1 ] }, 2000);
+  anim.effect.timing.iterations = Infinity;
+  assert_equals(anim.effect.timing.iterations, Infinity, 'set duration Infinity');
+  assert_equals(anim.effect.getComputedTiming().iterations, Infinity,
+                       'getComputedTiming() after set iterations Infinity');
+}, 'set iterations Infinity');
+
+test(function(t) {
+  var div = createDiv(t);
+  var anim = div.animate({ opacity: [ 0, 1 ] }, 2000);
+  assert_throws({ name: 'TypeError' }, function() {
+    anim.effect.timing.iterations = -1;
+  });
+}, 'set negative iterations');
+
+test(function(t) {
+  var div = createDiv(t);
+  var anim = div.animate({ opacity: [ 0, 1 ] }, 2000);
+  assert_throws({ name: 'TypeError' }, function() {
+    anim.effect.timing.iterations = -Infinity;
+  });
+}, 'set negative infinity iterations ');
+
+test(function(t) {
+  var div = createDiv(t);
+  var anim = div.animate({ opacity: [ 0, 1 ] }, 2000);
+  assert_throws({ name: 'TypeError' }, function() {
+    anim.effect.timing.iterations = NaN;
+  });
+}, 'set NaN iterations');
+
+</script>
+</body>