Bug 1283754 - Part 4: Split test cases for script animations which can be re-written to synchronous test in test_animation_observers.html. r=boris, a=test-only
authorHiroyuki Ikezoe <hiikezoe@mozilla-japan.org>
Fri, 14 Oct 2016 07:58:32 +0900
changeset 358465 3c14278bdb4a4c7979be29709410599776cfcaef
parent 358464 be7380e4282cc819afca0b870b11e7df6726453f
child 358466 9ae26225490b71243491572e6b709cfeb2705ba2
push id1324
push usermtabara@mozilla.com
push dateMon, 16 Jan 2017 13:07:44 +0000
treeherdermozilla-release@a01c49833940 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersboris, test-only
bugs1283754
milestone51.0a2
Bug 1283754 - Part 4: Split test cases for script animations which can be re-written to synchronous test in test_animation_observers.html. r=boris, a=test-only All of the split out test cases were re-written with synchronous API. Also, cleanup codes in the end of each test cases, e.g. anim.cancel(), were dropped because we now create a new MutationObserver and a new element for each test respectively. MozReview-Commit-ID: 9Bx0OUOU9On
dom/animation/test/chrome.ini
dom/animation/test/chrome/test_animation_observers.html
dom/animation/test/chrome/test_observers_for_script_animation.html
--- a/dom/animation/test/chrome.ini
+++ b/dom/animation/test/chrome.ini
@@ -7,11 +7,12 @@ support-files =
 
 [chrome/test_animate_xrays.html]
 # file_animate_xrays.html needs to go in mochitest.ini since it is served
 # over HTTP
 [chrome/test_animation_observers.html]
 [chrome/test_animation_performance_warning.html]
 [chrome/test_animation_properties.html]
 [chrome/test_generated_content_getAnimations.html]
+[chrome/test_observers_for_script_animation.html]
 [chrome/test_restyles.html]
 [chrome/test_running_on_compositor.html]
 skip-if = buildapp == 'b2g'
--- a/dom/animation/test/chrome/test_animation_observers.html
+++ b/dom/animation/test/chrome/test_animation_observers.html
@@ -1517,193 +1517,55 @@ addAsyncAnimTest("tree_ordering", { obse
   div.classList.remove("before");
   div.classList.remove("after");
   div.style = "";
   childA.remove();
   childB.remove();
   extraStyle.remove();
 });
 
-[ div, pseudoTarget ].forEach(function(target) {
-  addAsyncAnimTest("change_duration_and_currenttime",
-                   { observe: div, subtree: true }, function*() {
-    var anim = target.animate({ opacity: [ 0, 1 ] }, 200 * MS_PER_SEC);
-
-    yield waitForFrame();
-    assert_records([{ added: [anim], changed: [], removed: [] }],
-                   "records after animation is added");
-
-    anim.effect.timing.duration = 100 * MS_PER_SEC;
-    yield waitForFrame();
-    assert_records([{ added: [], changed: [anim], removed: [] }],
-                   "records after duration is changed");
-
-    anim.effect.timing.duration = 100 * MS_PER_SEC;
-    yield waitForFrame();
-    assert_records([], "records after assigning same value");
-
-    anim.currentTime = anim.effect.timing.duration * 2;
-    anim.finish();
-    yield waitForFrame();
-    assert_records([{ added: [], changed: [], removed: [anim] }],
-                   "records after animation end");
-
-    anim.effect.timing.duration = anim.effect.timing.duration * 3;
-    yield waitForFrame();
-    assert_records([{ added: [anim], changed: [], removed: [] }],
-                   "records after animation restarted");
-
-    anim.effect.timing.duration = "auto";
-    yield waitForFrame();
-    assert_records([{ added: [], changed: [], removed: [anim] }],
-                   "records after duration set \"auto\"");
-
-    anim.effect.timing.duration = "auto";
-    yield waitForFrame();
-    assert_records([], "records after assigning same value \"auto\"");
-
-    anim.cancel();
-    yield waitForFrame();
-  });
-});
-
-addAsyncAnimTest("change_enddelay_and_currenttime",
+addAsyncAnimTest("change_duration_and_currenttime",
                  { observe: div, subtree: true }, function*() {
-  var anim = div.animate({ opacity: [ 0, 1 ] }, { duration: 100 * MS_PER_SEC });
+  var anim = pseudoTarget.animate({ opacity: [ 0, 1 ] }, 200 * MS_PER_SEC);
 
   yield waitForFrame();
   assert_records([{ added: [anim], changed: [], removed: [] }],
                  "records after animation is added");
 
-  anim.effect.timing.endDelay = 10 * MS_PER_SEC;
+  anim.effect.timing.duration = 100 * MS_PER_SEC;
   yield waitForFrame();
   assert_records([{ added: [], changed: [anim], removed: [] }],
-                 "records after endDelay is changed");
+                 "records after duration is changed");
 
-  anim.effect.timing.endDelay = 10 * MS_PER_SEC;
+  anim.effect.timing.duration = 100 * MS_PER_SEC;
   yield waitForFrame();
   assert_records([], "records after assigning same value");
 
-  anim.currentTime = 109 * MS_PER_SEC;
-  yield waitForFrame();
-  assert_records([{ added: [], changed: [], removed: [anim] }],
-                 "records after currentTime during endDelay");
-
-  anim.effect.timing.endDelay = -110 * MS_PER_SEC;
-  yield waitForFrame();
-  assert_records([], "records after assigning negative value");
-
-  anim.cancel();
-  yield waitForFrame();
-});
-
-addAsyncAnimTest("change_enddelay_and_currenttime",
-                 { observe: div, subtree: true }, function*() {
-  var anim = div.animate({ opacity: [ 0, 1 ] },
-                         { duration: 100 * MS_PER_SEC,
-                           endDelay: -100 * MS_PER_SEC });
-  yield waitForFrame();
-  assert_records([], "records after animation is added");
-});
-
-addAsyncAnimTest("change_iterations",
-                 { observe: div, subtree: true }, function*() {
-  var anim = div.animate({ opacity: [ 0, 1 ] }, 100 * MS_PER_SEC);
-
-  yield waitForFrame();
-  assert_records([{ added: [anim], changed: [], removed: [] }],
-                 "records after animation is added");
-
-  anim.effect.timing.iterations = 2;
-  yield waitForFrame();
-  assert_records([{ added: [], changed: [anim], removed: [] }],
-                 "records after iterations is changed");
-
-  anim.effect.timing.iterations = 2;
-  yield waitForFrame();
-  assert_records([], "records after assigning same value");
-
-  anim.effect.timing.iterations = 0;
+  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.finish();
   yield waitForFrame();
   assert_records([{ added: [], changed: [], removed: [anim] }],
                  "records after animation end");
 
-  anim.effect.timing.iterations = Infinity;
+  anim.effect.timing.duration = anim.effect.timing.duration * 3;
   yield waitForFrame();
   assert_records([{ added: [anim], changed: [], removed: [] }],
                  "records after animation restarted");
 
-  anim.cancel();
+  anim.effect.timing.duration = "auto";
   yield waitForFrame();
   assert_records([{ added: [], changed: [], removed: [anim] }],
-                 "records after animation end");
-});
+                 "records after duration set \"auto\"");
 
-addAsyncAnimTest("change_delay",
-                 { observe: div, subtree: true }, function*() {
-  var anim = div.animate({ opacity: [ 0, 1 ] }, 100000);
-
-  yield waitForFrame();
-  assert_records([{ added: [anim], changed: [], removed: [] }],
-                 "records after animation is added");
-
-  anim.effect.timing.delay = 100;
+  anim.effect.timing.duration = "auto";
   yield waitForFrame();
-  assert_records([{ added: [], changed: [anim], removed: [] }],
-                 "records after delay is changed");
-
-  anim.effect.timing.delay = 100;
-  yield waitForFrame();
-  assert_records([], "records after assigning same value");
-
-  anim.effect.timing.delay = -100000;
-  yield waitForFrame();
-  assert_records([{ added: [], changed: [], removed: [anim] }],
-                 "records after animation end");
-
-  anim.effect.timing.delay = 0;
-  yield waitForFrame();
-  assert_records([{ added: [anim], changed: [], removed: [] }],
-                 "records after animation restarted");
+  assert_records([], "records after assigning same value \"auto\"");
 
   anim.cancel();
   yield waitForFrame();
-  assert_records([{ added: [], changed: [], removed: [anim] }],
-                 "records after animation end");
-});
-
-addAsyncAnimTest("change_easing",
-                 { observe: div, subtree: true }, function*() {
-  var anim = div.animate({ opacity: [ 0, 1 ] },
-                         { duration: 100 * MS_PER_SEC,
-                           easing: "steps(2, start)" });
-
-  yield waitForFrame();
-  assert_records([{ added: [anim], changed: [], removed: [] }],
-                 "records after animation is added");
-
-  anim.effect.timing.easing = "steps(2, end)";
-  yield waitForFrame();
-  assert_records([{ added: [], changed: [anim], removed: [] }],
-                 "records after easing is changed");
-
-  anim.effect.timing.easing = "steps(2, end)";
-  yield waitForFrame();
-  assert_records([], "records after assigning same value");
-
-  anim.cancel();
-  yield waitForFrame();
-});
-
-addAsyncAnimTest("negative_delay_in_constructor",
-                 { observe: div, subtree: true }, function*() {
-  var anim = div.animate({ opacity: [ 0, 1 ] },
-                         { duration: 100, delay: -100 });
-  yield waitForFrame();
-  assert_records([], "records after assigning negative value");
 });
 
 addAsyncAnimTest("exclude_animations_targeting_pseudo_elements",
                  { observe: div, subtree: false }, function*() {
   var anim = div.animate({ opacity: [ 0, 1 ] },
                          { duration: 100 * MS_PER_SEC });
   var pAnim = pseudoTarget.animate({ opacity: [ 0, 1 ] },
                                    { duration: 100 * MS_PER_SEC });
@@ -1714,260 +1576,16 @@ addAsyncAnimTest("exclude_animations_tar
 
   anim.finish();
   pAnim.finish();
   yield waitForFrame();
   assert_records([{ added: [], changed: [], removed: [anim] }],
                  "records after animation is finished");
 });
 
-addAsyncAnimTest("create_animation_without_target",
-                 { observe: document, subtree: true }, function*() {
-  var effect = new KeyframeEffectReadOnly(null,
-                                          { opacity: [ 0, 1 ] },
-                                          { duration: 10000 });
-  var anim = new Animation(effect, document.timeline);
-  anim.play();
-  yield waitForFrame();
-  assert_records([], "no records after animation is added");
-
-  anim.cancel();
-  yield waitForFrame();
-  assert_records([], "no records after animation is removed");
-});
-
-addAsyncAnimTest("set_animation_target",
-                 { observe: document, subtree: true }, function*() {
-  var anim = div.animate({ opacity: [ 0, 1 ] },
-                         { duration: 100 * MS_PER_SEC });
-
-  yield waitForFrame();
-  assert_records([{ added: [anim], changed: [], removed: [] }],
-                 "records after animation is added");
-
-  anim.effect.target = null;
-  yield waitForFrame();
-  assert_records([{ added: [], changed: [], removed: [anim] }],
-                 "records after setting null");
-
-  anim.effect.target = div;
-  yield waitForFrame();
-  assert_records([{ added: [anim], changed: [], removed: [] }],
-                 "records after setting a target");
-
-  var newTarget = document.createElement("div");
-  document.body.appendChild(newTarget);
-  anim.effect.target = newTarget;
-  yield waitForFrame();
-  assert_records([{ added: [], changed: [], removed: [anim] },
-                  { added: [anim], changed: [], removed: [] }],
-                 "records after setting a different target");
-
-  anim.cancel();
-  yield waitForFrame();
-  assert_records([{ added: [], changed: [], removed: [anim] }],
-                 "records after animation ends");
-
-  newTarget.remove();
-});
-
-addAsyncAnimTest("set_redundant_animation_target",
-                 { observe: div, subtree: true }, function*() {
-  var anim = div.animate({ opacity: [ 0, 1 ] },
-                         { duration: 100 * MS_PER_SEC });
-  yield waitForFrame();
-  assert_records([{ added: [anim], changed: [], removed: [] }],
-                 "records after animation is added");
-
-  anim.effect.target = div;
-  yield waitForFrame();
-  assert_records([], "no records after setting the same target");
-
-  anim.effect.target = null;
-  yield waitForFrame();
-  assert_records([{ added: [], changed: [], removed: [anim] }],
-                 "records after setting null");
-
-  anim.effect.target = null;
-  yield waitForFrame();
-  assert_records([], "records after setting redundant null");
-
-  anim.cancel();
-  yield waitForFrame();
-});
-
-addAsyncAnimTest("set_null_animation_effect",
-                 { observe: div, subtree: true }, function*() {
-  var anim = div.animate({ opacity: [ 0, 1 ] },
-                         { duration: 100 * MS_PER_SEC });
-  yield waitForFrame();
-  assert_records([{ added: [anim], changed: [], removed: [] }],
-                 "records after animation is added");
-
-  anim.effect = null;
-  yield waitForFrame();
-  assert_records([{ added: [], changed: [], removed: [anim] }],
-                 "records after animation is removed");
-
-  anim.cancel();
-  yield waitForFrame();
-});
-
-addAsyncAnimTest("set_effect_on_null_effect_animation",
-                 { observe: div, subtree: true }, function*() {
-  var anim = new Animation();
-  anim.play();
-  anim.effect = new KeyframeEffect(div, { opacity: [ 0, 1 ] },
-                                   100 * MS_PER_SEC);
-  yield waitForFrame();
-  assert_records([{ added: [anim], changed: [], removed: [] }],
-                 "records after animation is added");
-
-  anim.cancel();
-  yield waitForFrame();
-});
-
-addAsyncAnimTest("replace_effect_targeting_on_the_same_element",
-                 { observe: div, subtree: true }, function*() {
-  var anim = div.animate({ marginLeft: [ "0px", "100px" ] },
-                         100 * MS_PER_SEC);
-  yield waitForFrame();
-  assert_records([{ added: [anim], changed: [], removed: [] }],
-                 "records after animation is added");
-
-  anim.effect = new KeyframeEffect(div, { opacity: [ 0, 1 ] },
-                                   100 * MS_PER_SEC);
-  yield waitForFrame();
-  assert_records([{ added: [], changed: [anim], removed: [] }],
-                 "records after replace effects");
-
-  anim.cancel();
-  yield waitForFrame();
-});
-
-addAsyncAnimTest("replace_effect_targeting_on_the_same_element_not_in_effect",
-                 { observe: div, subtree: true }, function*() {
-  var anim = div.animate({ marginLeft: [ "0px", "100px" ] },
-                         100 * MS_PER_SEC);
-  yield waitForFrame();
-  assert_records([{ added: [anim], changed: [], removed: [] }],
-                 "records after animation is added");
-
-  anim.currentTime = 60 * MS_PER_SEC;
-  yield waitForFrame();
-  assert_records([{ added: [], changed: [anim], removed: [] }],
-                 "records after animation is changed");
-
-  anim.effect = new KeyframeEffect(div, { opacity: [ 0, 1 ] },
-                                   50 * MS_PER_SEC);
-  yield waitForFrame();
-  assert_records([{ added: [], changed: [], removed: [anim] }],
-                 "records after replacing effects");
-
-  anim.cancel();
-  yield waitForFrame();
-});
-
-addAsyncAnimTest("set_effect_with_previous_animation",
-                 { observe: div, subtree: true }, function*() {
-  var child = document.createElement("div");
-  div.appendChild(child);
-
-  var anim1 = div.animate({ marginLeft: [ "0px", "50px" ] },
-                          100 * MS_PER_SEC);
-  var anim2 = child.animate({ marginLeft: [ "0px", "100px" ] },
-                              50 * MS_PER_SEC);
-  yield waitForFrame();
-  assert_records([{ added: [anim1], changed: [], removed: [] },
-                  { added: [anim2], changed: [], removed: [] }],
-                 "records after animation is added");
-
-  // After setting a new effect, we remove the current animation, anim1, because
-  // it is no longer attached to |div|, and then remove the previous animation,
-  // anim2. Finally, add back the anim1 which is in effect on |child| now.
-  // In addition, we sort them by tree order and they are batched.
-  anim1.effect = anim2.effect;
-  yield waitForFrame();
-  assert_records([{ added: [], changed: [], removed: [anim1] },       // div
-                  { added: [anim1], changed: [], removed: [anim2] }], // child
-                 "records after animation effects are changed");
-
-  anim1.cancel();
-  anim2.cancel();
-  child.remove();
-  yield waitForFrame();
-});
-
-addAsyncAnimTest("set_spacing",
-                 { observe: div, subtree: true }, function*() {
-  var anim = div.animate([ { marginLeft: "0px" },
-                           { marginLeft: "-20px" },
-                           { marginLeft: "100px" },
-                           { marginLeft: "50px" } ],
-                         { duration: 100 * MS_PER_SEC });
-  yield waitForFrame();
-  assert_records([{ added: [anim], changed: [], removed: [] }],
-                 "records after animation is added");
-
-  anim.effect.spacing = "paced(margin-left)";
-  yield waitForFrame();
-  assert_records([{ added: [], changed: [anim], removed: [] }],
-                 "records after animation is changed");
-
-  anim.cancel();
-  yield waitForFrame();
-  assert_records([{ added: [], changed: [], removed: [anim] }],
-                 "records after animation end");
-});
-
-addAsyncAnimTest("set_spacing_on_a_non-animatable_property",
-                 { observe: div, subtree: true }, function*() {
-  var anim = div.animate([ { marginLeft: "0px" },
-                           { marginLeft: "-20px" },
-                           { marginLeft: "100px" },
-                           { marginLeft: "50px" } ],
-                         { duration: 100 * MS_PER_SEC,
-                           spacing: "paced(margin-left)" });
-  yield waitForFrame();
-  assert_records([{ added: [anim], changed: [], removed: [] }],
-                 "records after animation is added");
-
-  anim.effect.spacing = "paced(animation-duration)";
-  yield waitForFrame();
-  assert_records([{ added: [], changed: [anim], removed: [] }],
-                 "records after setting a non-animatable paced property");
-
-  anim.cancel();
-  yield waitForFrame();
-  assert_records([{ added: [], changed: [], removed: [anim] }],
-                 "records after animation end");
-});
-
-addAsyncAnimTest("set_the_same_spacing",
-                 { observe: div, subtree: true }, function*() {
-  var anim = div.animate([ { marginLeft: "0px" },
-                           { marginLeft: "-20px" },
-                           { marginLeft: "100px" },
-                           { marginLeft: "50px" } ],
-                         { duration: 100 * MS_PER_SEC,
-                           spacing: "paced(margin-left)" });
-  yield waitForFrame();
-  assert_records([{ added: [anim], changed: [], removed: [] }],
-                 "records after animation is added");
-
-  anim.effect.spacing = "paced(margin-left)";
-  yield waitForFrame();
-  assert_records([], "no record after setting the same spacing");
-
-  anim.cancel();
-  yield waitForFrame();
-  assert_records([{ added: [], changed: [], removed: [anim] }],
-                 "records after animation end");
-});
-
 // Run the tests.
 SimpleTest.requestLongerTimeout(2);
 SimpleTest.waitForExplicitFinish();
 
 runAllAsyncTests().then(function() {
   SimpleTest.finish();
 }, function(aError) {
   ok(false, "Something failed: " + aError);
new file mode 100644
--- /dev/null
+++ b/dom/animation/test/chrome/test_observers_for_script_animation.html
@@ -0,0 +1,433 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>
+Test chrome-only MutationObserver animation notifications for script
+animations
+</title>
+<script type="application/javascript" src="../testharness.js"></script>
+<script type="application/javascript" src="../testharnessreport.js"></script>
+<script type="application/javascript" src="../testcommon.js"></script>
+<div id="log"></div>
+<script>
+
+function assert_record_list(actual, expected, desc, index, listName) {
+  assert_equals(actual.length, expected.length,
+                `${desc} - record[${index}].${listName} length`);
+  if (actual.length != expected.length) {
+    return;
+  }
+  for (var i = 0; i < actual.length; i++) {
+    assert_not_equals(actual.indexOf(expected[i]), -1,
+       `${desc} - record[${index}].${listName} contains expected Animation`);
+  }
+}
+
+function assert_equals_records(actual, expected, desc) {
+  assert_equals(actual.length, expected.length, `${desc} - number of records`);
+  if (actual.length != expected.length) {
+    return;
+  }
+  for (var i = 0; i < actual.length; i++) {
+    assert_record_list(actual[i].addedAnimations,
+                       expected[i].added, desc, i, "addedAnimations");
+    assert_record_list(actual[i].changedAnimations,
+                       expected[i].changed, desc, i, "changedAnimations");
+    assert_record_list(actual[i].removedAnimations,
+                       expected[i].removed, desc, i, "removedAnimations");
+  }
+}
+
+test(t => {
+  var div = addDiv(t);
+  var observer = setupSynchronousObserver(t, div, false);
+
+  var anim = div.animate({ opacity: [ 0, 1 ] }, 200 * MS_PER_SEC);
+
+  assert_equals_records(observer.takeRecords(),
+    [{ added: [anim], changed: [], removed: [] }],
+    "records after animation is added");
+
+  anim.effect.timing.duration = 100 * MS_PER_SEC;
+  assert_equals_records(observer.takeRecords(),
+    [{ added: [], changed: [anim], removed: [] }],
+    "records after duration is changed");
+
+  anim.effect.timing.duration = 100 * MS_PER_SEC;
+  assert_equals_records(observer.takeRecords(),
+    [], "records after assigning same value");
+
+  anim.currentTime = anim.effect.timing.duration * 2;
+  anim.finish();
+  assert_equals_records(observer.takeRecords(),
+    [{ added: [], changed: [], removed: [anim] }],
+    "records after animation end");
+
+  anim.effect.timing.duration = anim.effect.timing.duration * 3;
+  assert_equals_records(observer.takeRecords(),
+    [{ added: [anim], changed: [], removed: [] }],
+    "records after animation restarted");
+
+  anim.effect.timing.duration = "auto";
+  assert_equals_records(observer.takeRecords(),
+    [{ added: [], changed: [], removed: [anim] }],
+    "records after duration set \"auto\"");
+
+  anim.effect.timing.duration = "auto";
+  assert_equals_records(observer.takeRecords(),
+    [], "records after assigning same value \"auto\"");
+}, "change_duration_and_currenttime");
+
+test(t => {
+  var div = addDiv(t);
+  var observer = setupSynchronousObserver(t, div, false);
+
+  var anim = div.animate({ opacity: [ 0, 1 ] }, 100 * MS_PER_SEC);
+
+  assert_equals_records(observer.takeRecords(),
+    [{ added: [anim], changed: [], removed: [] }],
+    "records after animation is added");
+
+  anim.effect.timing.endDelay = 10 * MS_PER_SEC;
+  assert_equals_records(observer.takeRecords(),
+    [{ added: [], changed: [anim], removed: [] }],
+    "records after endDelay is changed");
+
+  anim.effect.timing.endDelay = 10 * MS_PER_SEC;
+  assert_equals_records(observer.takeRecords(),
+    [], "records after assigning same value");
+
+  anim.currentTime = 109 * MS_PER_SEC;
+  assert_equals_records(observer.takeRecords(),
+    [{ added: [], changed: [], removed: [anim] }],
+    "records after currentTime during endDelay");
+
+  anim.effect.timing.endDelay = -110 * MS_PER_SEC;
+  assert_equals_records(observer.takeRecords(),
+    [], "records after assigning negative value");
+}, "change_enddelay_and_currenttime");
+
+test(t => {
+  var div = addDiv(t);
+  var observer = setupSynchronousObserver(t, div, false);
+
+  var anim = div.animate({ opacity: [ 0, 1 ] },
+                         { duration: 100 * MS_PER_SEC,
+                           endDelay: -100 * MS_PER_SEC });
+  assert_equals_records(observer.takeRecords(),
+    [], "records after animation is added");
+}, "zero_end_time");
+
+test(t => {
+  var div = addDiv(t);
+  var observer = setupSynchronousObserver(t, div, false);
+
+  var anim = div.animate({ opacity: [ 0, 1 ] }, 100 * MS_PER_SEC);
+
+  assert_equals_records(observer.takeRecords(),
+    [{ added: [anim], changed: [], removed: [] }],
+    "records after animation is added");
+
+  anim.effect.timing.iterations = 2;
+  assert_equals_records(observer.takeRecords(),
+    [{ added: [], changed: [anim], removed: [] }],
+    "records after iterations is changed");
+
+  anim.effect.timing.iterations = 2;
+  assert_equals_records(observer.takeRecords(),
+    [], "records after assigning same value");
+
+  anim.effect.timing.iterations = 0;
+  assert_equals_records(observer.takeRecords(),
+    [{ added: [], changed: [], removed: [anim] }],
+    "records after animation end");
+
+  anim.effect.timing.iterations = Infinity;
+  assert_equals_records(observer.takeRecords(),
+    [{ added: [anim], changed: [], removed: [] }],
+    "records after animation restarted");
+}, "change_iterations");
+
+test(t => {
+  var div = addDiv(t);
+  var observer = setupSynchronousObserver(t, div, false);
+
+  var anim = div.animate({ opacity: [ 0, 1 ] }, 100 * MS_PER_SEC);
+
+  assert_equals_records(observer.takeRecords(),
+    [{ added: [anim], changed: [], removed: [] }],
+    "records after animation is added");
+
+  anim.effect.timing.delay = 100;
+  assert_equals_records(observer.takeRecords(),
+    [{ added: [], changed: [anim], removed: [] }],
+    "records after delay is changed");
+
+  anim.effect.timing.delay = 100;
+  assert_equals_records(observer.takeRecords(),
+    [], "records after assigning same value");
+
+  anim.effect.timing.delay = -100 * MS_PER_SEC;
+  assert_equals_records(observer.takeRecords(),
+    [{ added: [], changed: [], removed: [anim] }],
+    "records after animation end");
+
+  anim.effect.timing.delay = 0;
+  assert_equals_records(observer.takeRecords(),
+    [{ added: [anim], changed: [], removed: [] }],
+    "records after animation restarted");
+}, "change_delay");
+
+test(t => {
+  var div = addDiv(t);
+  var observer = setupSynchronousObserver(t, div, false);
+
+  var anim = div.animate({ opacity: [ 0, 1 ] },
+                         { duration: 100 * MS_PER_SEC,
+                           easing: "steps(2, start)" });
+
+  assert_equals_records(observer.takeRecords(),
+    [{ added: [anim], changed: [], removed: [] }],
+    "records after animation is added");
+
+  anim.effect.timing.easing = "steps(2, end)";
+  assert_equals_records(observer.takeRecords(),
+    [{ added: [], changed: [anim], removed: [] }],
+    "records after easing is changed");
+
+  anim.effect.timing.easing = "steps(2, end)";
+  assert_equals_records(observer.takeRecords(),
+    [], "records after assigning same value");
+}, "change_easing");
+
+test(t => {
+  var div = addDiv(t);
+  var observer = setupSynchronousObserver(t, div, false);
+
+  var anim = div.animate({ opacity: [ 0, 1 ] },
+                         { duration: 100, delay: -100 });
+  assert_equals_records(observer.takeRecords(),
+    [], "records after assigning negative value");
+}, "negative_delay_in_constructor");
+
+test(t => {
+  var div = addDiv(t);
+  var observer = setupSynchronousObserver(t, div, false);
+
+  var effect = new KeyframeEffectReadOnly(null,
+                                          { opacity: [ 0, 1 ] },
+                                          { duration: 100 * MS_PER_SEC });
+  var anim = new Animation(effect, document.timeline);
+  anim.play();
+  assert_equals_records(observer.takeRecords(),
+    [], "no records after animation is added");
+}, "create_animation_without_target");
+
+test(t => {
+  var div = addDiv(t);
+  var observer = setupSynchronousObserver(t, document, true);
+
+  var anim = div.animate({ opacity: [ 0, 1 ] },
+                         { duration: 100 * MS_PER_SEC });
+
+  var newTarget = document.createElement("div");
+
+  assert_equals_records(observer.takeRecords(),
+    [{ added: [anim], changed: [], removed: [] }],
+    "records after animation is added");
+
+  anim.effect.target = null;
+  assert_equals_records(observer.takeRecords(),
+    [{ added: [], changed: [], removed: [anim] }],
+    "records after setting null");
+
+  anim.effect.target = div;
+  assert_equals_records(observer.takeRecords(),
+    [{ added: [anim], changed: [], removed: [] }],
+    "records after setting a target");
+
+  anim.effect.target = addDiv(t);
+  assert_equals_records(observer.takeRecords(),
+    [{ added: [], changed: [], removed: [anim] },
+     { added: [anim], changed: [], removed: [] }],
+    "records after setting a different target");
+}, "set_animation_target");
+
+test(t => {
+  var div = addDiv(t);
+  var observer = setupSynchronousObserver(t, div, false);
+
+  var anim = div.animate({ opacity: [ 0, 1 ] },
+                         { duration: 100 * MS_PER_SEC });
+  assert_equals_records(observer.takeRecords(),
+    [{ added: [anim], changed: [], removed: [] }],
+    "records after animation is added");
+
+  anim.effect.target = div;
+  assert_equals_records(observer.takeRecords(),
+    [], "no records after setting the same target");
+
+  anim.effect.target = null;
+  assert_equals_records(observer.takeRecords(),
+    [{ added: [], changed: [], removed: [anim] }],
+    "records after setting null");
+
+  anim.effect.target = null;
+  assert_equals_records(observer.takeRecords(),
+    [], "records after setting redundant null");
+}, "set_redundant_animation_target");
+
+test(t => {
+  var div = addDiv(t);
+  var observer = setupSynchronousObserver(t, div, false);
+
+  var anim = div.animate({ opacity: [ 0, 1 ] },
+                         { duration: 100 * MS_PER_SEC });
+  assert_equals_records(observer.takeRecords(),
+    [{ added: [anim], changed: [], removed: [] }],
+    "records after animation is added");
+
+  anim.effect = null;
+  assert_equals_records(observer.takeRecords(),
+    [{ added: [], changed: [], removed: [anim] }],
+    "records after animation is removed");
+}, "set_null_animation_effect");
+
+test(t => {
+  var div = addDiv(t);
+  var observer = setupSynchronousObserver(t, div, false);
+
+  var anim = new Animation();
+  anim.play();
+  anim.effect = new KeyframeEffect(div, { opacity: [ 0, 1 ] },
+                                   100 * MS_PER_SEC);
+  assert_equals_records(observer.takeRecords(),
+    [{ added: [anim], changed: [], removed: [] }],
+    "records after animation is added");
+}, "set_effect_on_null_effect_animation");
+
+test(t => {
+  var div = addDiv(t);
+  var observer = setupSynchronousObserver(t, div, false);
+
+  var anim = div.animate({ marginLeft: [ "0px", "100px" ] },
+                         100 * MS_PER_SEC);
+  assert_equals_records(observer.takeRecords(),
+    [{ added: [anim], changed: [], removed: [] }],
+    "records after animation is added");
+
+  anim.effect = new KeyframeEffect(div, { opacity: [ 0, 1 ] },
+                                   100 * MS_PER_SEC);
+  assert_equals_records(observer.takeRecords(),
+    [{ added: [], changed: [anim], removed: [] }],
+    "records after replace effects");
+}, "replace_effect_targeting_on_the_same_element");
+
+test(t => {
+  var div = addDiv(t);
+  var observer = setupSynchronousObserver(t, div, false);
+
+  var anim = div.animate({ marginLeft: [ "0px", "100px" ] },
+                         100 * MS_PER_SEC);
+  assert_equals_records(observer.takeRecords(),
+    [{ added: [anim], changed: [], removed: [] }],
+    "records after animation is added");
+
+  anim.currentTime = 60 * MS_PER_SEC;
+  assert_equals_records(observer.takeRecords(),
+    [{ added: [], changed: [anim], removed: [] }],
+    "records after animation is changed");
+
+  anim.effect = new KeyframeEffect(div, { opacity: [ 0, 1 ] },
+                                   50 * MS_PER_SEC);
+  assert_equals_records(observer.takeRecords(),
+    [{ added: [], changed: [], removed: [anim] }],
+    "records after replacing effects");
+}, "replace_effect_targeting_on_the_same_element_not_in_effect");
+
+test(t => {
+  var div = addDiv(t);
+  var observer = setupSynchronousObserver(t, div, true);
+
+  var child = document.createElement("div");
+  div.appendChild(child);
+
+  var anim1 = div.animate({ marginLeft: [ "0px", "50px" ] },
+                          100 * MS_PER_SEC);
+  var anim2 = child.animate({ marginLeft: [ "0px", "100px" ] },
+                              50 * MS_PER_SEC);
+  assert_equals_records(observer.takeRecords(),
+    [{ added: [anim1], changed: [], removed: [] },
+     { added: [anim2], changed: [], removed: [] }],
+    "records after animation is added");
+
+  // After setting a new effect, we remove the current animation, anim1,
+  // because it is no longer attached to |div|, and then remove the previous
+  // animation, anim2. Finally, add back the anim1 which is in effect on
+  // |child| now. In addition, we sort them by tree order and they are
+  // batched.
+  anim1.effect = anim2.effect;
+  assert_equals_records(observer.takeRecords(),
+    [{ added: [], changed: [], removed: [anim1] },       // div
+     { added: [anim1], changed: [], removed: [anim2] }], // child
+    "records after animation effects are changed");
+}, "set_effect_with_previous_animation");
+
+test(t => {
+  var div = addDiv(t);
+  var observer = setupSynchronousObserver(t, div, false);
+
+  var anim = div.animate([ { marginLeft: "0px" },
+                           { marginLeft: "-20px" },
+                           { marginLeft: "100px" },
+                           { marginLeft: "50px" } ],
+                         { duration: 100 * MS_PER_SEC });
+  assert_equals_records(observer.takeRecords(),
+    [{ added: [anim], changed: [], removed: [] }],
+    "records after animation is added");
+
+  anim.effect.spacing = "paced(margin-left)";
+  assert_equals_records(observer.takeRecords(),
+    [{ added: [], changed: [anim], removed: [] }],
+    "records after animation is changed");
+}, "set_spacing");
+
+test(t => {
+  var div = addDiv(t);
+  var observer = setupSynchronousObserver(t, div, false);
+
+  var anim = div.animate([ { marginLeft: "0px" },
+                           { marginLeft: "-20px" },
+                           { marginLeft: "100px" },
+                           { marginLeft: "50px" } ],
+                         { duration: 100 * MS_PER_SEC,
+                           spacing: "paced(margin-left)" });
+  assert_equals_records(observer.takeRecords(),
+    [{ added: [anim], changed: [], removed: [] }],
+    "records after animation is added");
+
+  anim.effect.spacing = "paced(animation-duration)";
+  assert_equals_records(observer.takeRecords(),
+    [{ added: [], changed: [anim], removed: [] }],
+    "records after setting a non-animatable paced property");
+}, "set_spacing_on_a_non-animatable_property");
+
+test(t => {
+  var div = addDiv(t);
+  var observer = setupSynchronousObserver(t, div, false);
+
+  var anim = div.animate([ { marginLeft: "0px" },
+                           { marginLeft: "-20px" },
+                           { marginLeft: "100px" },
+                           { marginLeft: "50px" } ],
+                         { duration: 100 * MS_PER_SEC,
+                           spacing: "paced(margin-left)" });
+  assert_equals_records(observer.takeRecords(),
+    [{ 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");
+
+</script>