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
authorHiroyuki Ikezoe <hiikezoe@mozilla-japan.org>
Fri, 14 Oct 2016 07:58:32 +0900
changeset 317814 8a34e43924f5e269579145231f6f3c43a6e812e2
parent 317813 2664c275c9d5e056282c3b7670dc3179c5e71cb6
child 317815 617c4ce41eb287953e9dbf8c073446aafe835dc8
push id30817
push usercbook@mozilla.com
push dateFri, 14 Oct 2016 09:56:08 +0000
treeherdermozilla-central@a71215ad8ab8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersboris
bugs1283754
milestone52.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 1283754 - Part 4: Split test cases for script animations which can be re-written to synchronous test in test_animation_observers.html. r=boris 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>