Bug 1134163 - Part3. Modify the currentTime and startTime tests. r?birtles draft
authorMantaroh Yoshinaga <mantaroh@gmail.com>
Wed, 18 May 2016 13:01:31 +0900
changeset 368159 6dd6193a5db47b3bb4535093b0e1642004ecc794
parent 367997 f3f2fa1d7eed5a8262f6401ef18ff8117a3ce43e
child 368160 45c98573fbe7435a89a71c78b605b07d096419f4
child 368161 c150f66e7a79bcd267d716c02f03d976ba1f4df7
child 368163 c43e3a58bc1af5e0cf49490f7f6de604e0615423
push id18440
push usermantaroh@gmail.com
push dateWed, 18 May 2016 04:59:26 +0000
reviewersbirtles
bugs1134163
milestone49.0a1
Bug 1134163 - Part3. Modify the currentTime and startTime tests. r?birtles MozReview-Commit-ID: xalzt833He
dom/animation/test/css-animations/file_animation-currenttime.html
dom/animation/test/css-animations/file_animation-starttime.html
dom/animation/test/css-transitions/file_animation-currenttime.html
dom/animation/test/css-transitions/file_animation-starttime.html
dom/animation/test/testcommon.js
--- a/dom/animation/test/css-animations/file_animation-currenttime.html
+++ b/dom/animation/test/css-animations/file_animation-currenttime.html
@@ -20,436 +20,225 @@
     </style>
     <script src="../testcommon.js"></script>
   </head>
   <body>
     <script type="text/javascript">
 
 'use strict';
 
-// TODO: add equivalent tests without an animation-delay, but first we need to
-// change the timing of animationstart dispatch. (Right now the animationstart
-// event will fire before the ready Promise is resolved if there is no
-// animation-delay.)
-// See https://bugzilla.mozilla.org/show_bug.cgi?id=1134163
+// TODO: We should separate this test(Testing for CSS Animation events /
+// Testing for currentTime of Web Animation).
+// e.g:
+//  CSS Animation events test :
+//    - check the firing an event using Animation.currentTime
+//  The current Time of Web Animation test :
+//    - check an current time value on several situation(init / processing..)
+//    - Based on W3C Spec, check the behavior of setting current time.
 
 // TODO: Once the computedTiming property is implemented, add checks to the
 // checker helpers to ensure that computedTiming's properties are updated as
 // expected.
 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1108055
 
-
 const CSS_ANIM_EVENTS =
   ['animationstart', 'animationiteration', 'animationend'];
-const ANIM_DELAY_MS = 1000 * MS_PER_SEC;
-const ANIM_DUR_MS = 1000 * MS_PER_SEC;
-const ANIM_PROPERTY_VAL = 'anim ' + ANIM_DUR_MS + 'ms ' + ANIM_DELAY_MS + 'ms';
-
-/**
- * These helpers get the value that the currentTime needs to be set to, to put
- * an animation that uses the above ANIM_DELAY_MS and ANIM_DUR_MS values into
- * the middle of various phases or points through the active duration.
- */
-function currentTimeForBeforePhase(timeline) {
-  return ANIM_DELAY_MS / 2;
-}
-function currentTimeForActivePhase(timeline) {
-  return ANIM_DELAY_MS + ANIM_DUR_MS / 2;
-}
-function currentTimeForAfterPhase(timeline) {
-  return ANIM_DELAY_MS + ANIM_DUR_MS + ANIM_DELAY_MS / 2;
-}
-function currentTimeForStartOfActiveInterval(timeline) {
-  return ANIM_DELAY_MS;
-}
-function currentTimeForFiftyPercentThroughActiveInterval(timeline) {
-  return ANIM_DELAY_MS + ANIM_DUR_MS * 0.5;
-}
-function currentTimeForEndOfActiveInterval(timeline) {
-  return ANIM_DELAY_MS + ANIM_DUR_MS;
-}
-
-
-// Expected computed 'margin-left' values at points during the active interval:
-// When we assert_between_inclusive using these values we could in theory cause
-// intermittent failure due to very long delays between paints, but since the
-// active duration is 1000s long, a delay would need to be around 100s to cause
-// that. If that's happening then there are likely other issues that should be
-// fixed, so a failure to make us look into that seems like a good thing.
-const UNANIMATED_POSITION = 10;
-const INITIAL_POSITION = 100;
-const TEN_PCT_POSITION = 110;
-const FIFTY_PCT_POSITION = 150;
-const END_POSITION = 200;
-
-// The terms used for the naming of the following helper functions refer to
-// terms used in the Web Animations specification for specific phases of an
-// animation. The terms can be found here:
-//
-//   https://w3c.github.io/web-animations/#animation-effect-phases-and-states
-//
-// Note the distinction between the "animation start time" which occurs before
-// the start delay and the start of the active interval which occurs after it.
-
-// Called when currentTime is set to zero (the beginning of the start delay).
-function checkStateOnSettingCurrentTimeToZero(animation)
-{
-  // We don't test animation.currentTime since our caller just set it.
-
-  assert_equals(animation.playState, 'running',
-    'Animation.playState should be "running" at the start of ' +
-    'the start delay');
-
-  assert_equals(animation.effect.target.style.animationPlayState, 'running',
-    'Animation.effect.target.style.animationPlayState should be ' +
-    '"running" at the start of the start delay');
-
-  var div = animation.effect.target;
-  var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
-  assert_equals(marginLeft, UNANIMATED_POSITION,
-                'the computed value of margin-left should be unaffected ' +
-                'at the beginning of the start delay');
-}
-
-// Called when the ready Promise's callbacks should happen
-function checkStateOnReadyPromiseResolved(animation)
-{
-  // the 0.0001 here is for rounding error
-  assert_less_than_equal(animation.currentTime,
-    animation.timeline.currentTime - animation.startTime + 0.0001,
-    'Animation.currentTime should be less than the local time ' +
-    'equivalent of the timeline\'s currentTime on the first paint tick ' +
-    'after animation creation');
-
-  assert_equals(animation.playState, 'running',
-    'Animation.playState should be "running" on the first paint ' +
-    'tick after animation creation');
-
-  assert_equals(animation.effect.target.style.animationPlayState, 'running',
-    'Animation.effect.target.style.animationPlayState should be ' +
-    '"running" on the first paint tick after animation creation');
-
-  var div = animation.effect.target;
-  var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
-  assert_equals(marginLeft, UNANIMATED_POSITION,
-                'the computed value of margin-left should be unaffected ' +
-                'by an animation with a delay on ready Promise resolve');
-}
-
-// Called when currentTime is set to the time the active interval starts.
-function checkStateAtActiveIntervalStartTime(animation)
-{
-  // We don't test animation.currentTime since our caller just set it.
-
-  assert_equals(animation.playState, 'running',
-    'Animation.playState should be "running" at the start of ' +
-    'the active interval');
-
-  assert_equals(animation.effect.target.style.animationPlayState, 'running',
-    'Animation.effect.target.style.animationPlayState should be ' +
-    '"running" at the start of the active interval');
-
-  var div = animation.effect.target;
-  var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
-  assert_between_inclusive(marginLeft, INITIAL_POSITION, TEN_PCT_POSITION,
-    'the computed value of margin-left should be close to the value at the ' +
-    'beginning of the animation');
-}
-
-function checkStateAtFiftyPctOfActiveInterval(animation)
-{
-  // We don't test animation.currentTime since our caller just set it.
-
-  var div = animation.effect.target;
-  var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
-  assert_equals(marginLeft, FIFTY_PCT_POSITION,
-    'the computed value of margin-left should be half way through the ' +
-    'animation at the midpoint of the active interval');
-}
-
-// Called when currentTime is set to the time the active interval ends.
-function checkStateAtActiveIntervalEndTime(animation)
-{
-  // We don't test animation.currentTime since our caller just set it.
-
-  assert_equals(animation.playState, 'finished',
-    'Animation.playState should be "finished" at the end of ' +
-    'the active interval');
-
-  assert_equals(animation.effect.target.style.animationPlayState, "running",
-    'Animation.effect.target.style.animationPlayState should be ' +
-    '"finished" at the end of the active interval');
-
-  var div = animation.effect.target;
-  var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
-  assert_equals(marginLeft, UNANIMATED_POSITION,
-    'the computed value of margin-left should be unaffected ' +
-    'by the animation at the end of the active duration when the ' +
-    'animation-fill-mode is none');
-}
 
 test(function(t)
 {
   var div = addDiv(t, {'class': 'animated-div'});
-
-  div.style.animation = ANIM_PROPERTY_VAL;
-
+  div.style.animation = "anim 100s";
   var animation = div.getAnimations()[0];
 
   // Animations shouldn't start until the next paint tick, so:
   assert_equals(animation.currentTime, 0,
     'Animation.currentTime should be zero when an animation ' +
     'is initially created');
 
-  assert_equals(animation.playState, "pending",
-    'Animation.playState should be "pending" when an animation ' +
-    'is initially created');
-
-  assert_equals(animation.effect.target.style.animationPlayState, 'running',
-    'Animation.effect.target.style.animationPlayState should be ' +
-    '"running" when an animation is initially created');
-
-  // XXX Ideally we would have a test to check the ready Promise is initially
-  // unresolved, but currently there is no Web API to do that. Waiting for the
-  // ready Promise with a timeout doesn't work because the resolved callback
-  // will be called (async) regardless of whether the Promise was resolved in
-  // the past or is resolved in the future.
-
-  // So that animation is running instead of paused when we set currentTime:
+  // Make sure the animation is running before we set the current time.
   animation.startTime = animation.timeline.currentTime;
 
-  assert_approx_equals(animation.currentTime, 0, 0.0001, // rounding error
+  animation.currentTime = 50 * MS_PER_SEC;
+  assert_times_equal(animation.currentTime, 50 * MS_PER_SEC,
     'Check setting of currentTime actually works');
-
-  checkStateOnSettingCurrentTimeToZero(animation);
 }, 'Sanity test to check round-tripping assigning to new animation\'s ' +
    'currentTime');
 
 promise_test(function(t) {
   var div = addDiv(t, {'class': 'animated-div'});
   var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
-
-  div.style.animation = ANIM_PROPERTY_VAL;
-
+  div.style.animation = "anim 100s 100s";
   var animation = div.getAnimations()[0];
 
   return animation.ready.then(function() {
-    checkStateOnReadyPromiseResolved(animation);
+    // the 0.0001 here is for rounding error
+    assert_less_than_equal(animation.currentTime,
+      animation.timeline.currentTime - animation.startTime + 0.0001,
+      'Animation.currentTime should be less than the local time ' +
+      'equivalent of the timeline\'s currentTime on the first paint tick ' +
+      'after animation creation');
 
-    animation.currentTime =
-      currentTimeForStartOfActiveInterval(animation.timeline);
+    animation.currentTime = 100 * MS_PER_SEC;
     return eventWatcher.wait_for('animationstart');
   }).then(function() {
-    checkStateAtActiveIntervalStartTime(animation);
-
-    animation.currentTime =
-      currentTimeForFiftyPercentThroughActiveInterval(animation.timeline);
-    checkStateAtFiftyPctOfActiveInterval(animation);
-
-    animation.currentTime =
-      currentTimeForEndOfActiveInterval(animation.timeline);
+    animation.currentTime = 200 * MS_PER_SEC;
     return eventWatcher.wait_for('animationend');
-  }).then(function() {
-    checkStateAtActiveIntervalEndTime(animation);
   });
 }, 'Skipping forward through animation');
 
-
 promise_test(function(t) {
   var div = addDiv(t, {'class': 'animated-div'});
   var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
-
-  div.style.animation = ANIM_PROPERTY_VAL;
-
+  div.style.animation = "anim 100s 100s";
   var animation = div.getAnimations()[0];
-
-  // So that animation is running instead of paused when we set currentTime:
-  animation.startTime = animation.timeline.currentTime;
-
-  animation.currentTime = currentTimeForEndOfActiveInterval(animation.timeline);
-
+  animation.currentTime = 200 * MS_PER_SEC;
   var previousTimelineTime = animation.timeline.currentTime;
 
-  // Skipping over the active interval will dispatch an 'animationstart' then
-  // an 'animationend' event. We need to wait for these events before we start
-  // testing going backwards since EventWatcher will fail the test if it gets
-  // an event that we haven't told it about.
-  var retPromise =  eventWatcher.wait_for(['animationstart',
-                                           'animationend']).then(function() {
+  return eventWatcher.wait_for(['animationstart',
+                                'animationend']).then(function() {
     assert_true(document.timeline.currentTime - previousTimelineTime <
-                  ANIM_DUR_MS,
+                100 * MS_PER_SEC,
                 'Sanity check that seeking worked rather than the events ' +
                 'firing after normal playback through the very long ' +
                 'animation duration');
 
-    // Now we can start the tests for skipping backwards, but first we check
-    // that after the events we're still in the same end time state:
-    checkStateAtActiveIntervalEndTime(animation);
-
-    animation.currentTime =
-      currentTimeForFiftyPercentThroughActiveInterval(animation.timeline);
-
-    // Despite going backwards from after the end of the animation (to being
-    // in the active interval), we now expect an 'animationstart' event
-    // because the animation should go from being inactive to active.
-    //
-    // Calling checkStateAtFiftyPctOfActiveInterval will check computed style,
-    // causing computed style to be updated and the 'animationstart' event to
-    // be dispatched synchronously. We need to call wait_for first
-    // otherwise eventWatcher will assert that the event was unexpected.
-    var promise = eventWatcher.wait_for('animationstart');
-    checkStateAtFiftyPctOfActiveInterval(animation);
-    return promise;
+    animation.currentTime = 150 * MS_PER_SEC;
+    return eventWatcher.wait_for('animationstart');
   }).then(function() {
-    animation.currentTime =
-      currentTimeForStartOfActiveInterval(animation.timeline);
-    checkStateAtActiveIntervalStartTime(animation);
-
     animation.currentTime = 0;
-    // Despite going backwards from just after the active interval starts to
-    // the animation start time, we now expect an animationend event
-    // because we went from inside to outside the active interval.
     return eventWatcher.wait_for('animationend');
-  }).then(function() {
-    checkStateOnReadyPromiseResolved(animation);
   });
-
-  // This must come after we've set up the Promise chain, since requesting
-  // computed style will force events to be dispatched.
-  checkStateAtActiveIntervalEndTime(animation);
-
-  return retPromise;
 }, 'Skipping backwards through animation');
 
 // Next we have multiple tests to check that redundant currentTime changes do
 // NOT dispatch events. It's impossible to distinguish between events not being
 // dispatched and events just taking an incredibly long time to dispatch
 // without waiting an infinitely long time. Obviously we don't want to do that
 // (block this test from finishing forever), so instead we just listen for
 // events until two animation frames (i.e. requestAnimationFrame callbacks)
 // have happened, then assume that no events will ever be dispatched for the
 // redundant changes if no events were detected in that time.
 
 promise_test(function(t) {
   var div = addDiv(t, {'class': 'animated-div'});
   var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
-  div.style.animation = ANIM_PROPERTY_VAL;
+  div.style.animation = "anim 100s 100s";
   var animation = div.getAnimations()[0];
 
-  animation.currentTime = currentTimeForActivePhase(animation.timeline);
-  animation.currentTime = currentTimeForBeforePhase(animation.timeline);
+  animation.currentTime = 150 * MS_PER_SEC;
+  animation.currentTime = 50 * MS_PER_SEC;
 
   return waitForAnimationFrames(2);
 }, 'Redundant change, before -> active, then back');
 
 promise_test(function(t) {
   var div = addDiv(t, {'class': 'animated-div'});
   var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
-  div.style.animation = ANIM_PROPERTY_VAL;
+  div.style.animation = "anim 100s 100s";
   var animation = div.getAnimations()[0];
 
-  animation.currentTime = currentTimeForAfterPhase(animation.timeline);
-  animation.currentTime = currentTimeForBeforePhase(animation.timeline);
+  animation.currentTime = 250 * MS_PER_SEC;
+  animation.currentTime = 50 * MS_PER_SEC;
 
   return waitForAnimationFrames(2);
 }, 'Redundant change, before -> after, then back');
 
 promise_test(function(t) {
   var div = addDiv(t, {'class': 'animated-div'});
   var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
-  div.style.animation = ANIM_PROPERTY_VAL;
+  div.style.animation = "anim 100s 100s";
   var animation = div.getAnimations()[0];
 
   var retPromise = eventWatcher.wait_for('animationstart').then(function() {
-    animation.currentTime = currentTimeForBeforePhase(animation.timeline);
-    animation.currentTime = currentTimeForActivePhase(animation.timeline);
+    animation.currentTime = 50 * MS_PER_SEC;
+    animation.currentTime = 150 * MS_PER_SEC;
 
     return waitForAnimationFrames(2);
   });
   // get us into the initial state:
-  animation.currentTime = currentTimeForActivePhase(animation.timeline);
+  animation.currentTime = 150 * MS_PER_SEC;
 
   return retPromise;
 }, 'Redundant change, active -> before, then back');
 
 promise_test(function(t) {
   var div = addDiv(t, {'class': 'animated-div'});
   var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
-  div.style.animation = ANIM_PROPERTY_VAL;
+  div.style.animation = "anim 100s 100s";
   var animation = div.getAnimations()[0];
 
-  var retPromise =  eventWatcher.wait_for('animationstart').then(function() {
-    animation.currentTime = currentTimeForAfterPhase(animation.timeline);
-    animation.currentTime = currentTimeForActivePhase(animation.timeline);
+  var retPromise = eventWatcher.wait_for('animationstart').then(function() {
+    animation.currentTime = 250 * MS_PER_SEC;
+    animation.currentTime = 150 * MS_PER_SEC;
 
     return waitForAnimationFrames(2);
   });
   // get us into the initial state:
-  animation.currentTime = currentTimeForActivePhase(animation.timeline);
+  animation.currentTime = 150 * MS_PER_SEC;
 
   return retPromise;
 }, 'Redundant change, active -> after, then back');
 
 promise_test(function(t) {
   var div = addDiv(t, {'class': 'animated-div'});
   var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
-  div.style.animation = ANIM_PROPERTY_VAL;
+  div.style.animation = "anim 100s 100s";
   var animation = div.getAnimations()[0];
 
   var retPromise =  eventWatcher.wait_for(['animationstart',
                                            'animationend']).then(function() {
-    animation.currentTime = currentTimeForBeforePhase(animation.timeline);
-    animation.currentTime = currentTimeForAfterPhase(animation.timeline);
+    animation.currentTime = 50 * MS_PER_SEC;
+    animation.currentTime = 250 * MS_PER_SEC;
 
     return waitForAnimationFrames(2);
   });
   // get us into the initial state:
-  animation.currentTime = currentTimeForAfterPhase(animation.timeline);
+  animation.currentTime = 250 * MS_PER_SEC;
 
   return retPromise;
 }, 'Redundant change, after -> before, then back');
 
 promise_test(function(t) {
   var div = addDiv(t, {'class': 'animated-div'});
   var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
-  div.style.animation = ANIM_PROPERTY_VAL;
+  div.style.animation = "anim 100s 100s";
   var animation = div.getAnimations()[0];
 
   var retPromise =  eventWatcher.wait_for(['animationstart',
                                            'animationend']).then(function() {
-    animation.currentTime = currentTimeForActivePhase(animation.timeline);
-    animation.currentTime = currentTimeForAfterPhase(animation.timeline);
+    animation.currentTime = 150 * MS_PER_SEC;
+    animation.currentTime = 250 * MS_PER_SEC;
 
     return waitForAnimationFrames(2);
   });
   // get us into the initial state:
-  animation.currentTime = currentTimeForAfterPhase(animation.timeline);
+  animation.currentTime = 250 * MS_PER_SEC;
 
   return retPromise;
 }, 'Redundant change, after -> active, then back');
 
 promise_test(function(t) {
   var div = addDiv(t, {'class': 'animated-div'});
   var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
-  div.style.animation = ANIM_PROPERTY_VAL;
+  div.style.animation = "anim 100s"
   var animation = div.getAnimations()[0];
 
   animation.pause();
-  animation.currentTime = currentTimeForAfterPhase(animation.timeline);
+  animation.currentTime = 150 * MS_PER_SEC;
 
   return eventWatcher.wait_for(['animationstart',
                                 'animationend']).then(function() {
-    animation.currentTime = currentTimeForActivePhase(animation.timeline);
+    animation.currentTime = 50 * MS_PER_SEC;
     return eventWatcher.wait_for('animationstart');
   });
 }, 'Seeking finished -> paused dispatches animationstart');
 
 promise_test(function(t) {
   var div = addDiv(t, {'class': 'animated-div'});
-  div.style.animation = ANIM_PROPERTY_VAL;
+  div.style.animation = "anim 100s";
 
   var animation = div.getAnimations()[0];
 
   return animation.ready.then(function() {
     var exception;
     try {
       animation.currentTime = null;
     } catch (e) {
@@ -479,36 +268,33 @@ promise_test(function(t) {
   }).then(function() {
     assert_equals(animation.currentTime, pauseTime,
       'Animation.currentTime is unchanged after pausing');
   });
 }, 'Animation.currentTime after pausing');
 
 promise_test(function(t) {
   var div = addDiv(t, {'class': 'animated-div'});
-  div.style.animation = ANIM_PROPERTY_VAL;
-
+  div.style.animation = "anim 100s";
   var animation = div.getAnimations()[0];
 
   return animation.ready.then(function() {
     // just before animation ends:
-    animation.currentTime = ANIM_DELAY_MS + ANIM_DUR_MS - 1;
-
+    animation.currentTime = 100 * MS_PER_SEC - 1;
     return waitForAnimationFrames(2);
   }).then(function() {
-    assert_equals(animation.currentTime, ANIM_DELAY_MS + ANIM_DUR_MS,
+    assert_equals(animation.currentTime, 100 * MS_PER_SEC,
       'Animation.currentTime should not continue to increase after the ' +
       'animation has finished');
   });
 }, 'Animation.currentTime clamping');
 
 promise_test(function(t) {
   var div = addDiv(t, {'class': 'animated-div'});
-  div.style.animation = ANIM_PROPERTY_VAL;
-
+  div.style.animation = "anim 100s";
   var animation = div.getAnimations()[0];
 
   return animation.ready.then(function() {
     // play backwards:
     animation.playbackRate = -1;
 
     // just before animation ends (at the "start"):
     animation.currentTime = 1;
@@ -519,28 +305,28 @@ promise_test(function(t) {
       'Animation.currentTime should not continue to decrease after an ' +
       'animation running in reverse has finished and currentTime is zero');
   });
 }, 'Animation.currentTime clamping for reversed animation');
 
 test(function(t) {
   var div = addDiv(t, {'class': 'animated-div'});
   div.style.animation = 'anim 100s';
-
   var animation = div.getAnimations()[0];
   animation.cancel();
+
   assert_equals(animation.currentTime, null,
                 'The currentTime of a cancelled animation should be null');
 }, 'Animation.currentTime after cancelling');
 
 promise_test(function(t) {
   var div = addDiv(t, {'class': 'animated-div'});
   div.style.animation = 'anim 100s';
+  var animation = div.getAnimations()[0];
 
-  var animation = div.getAnimations()[0];
   return animation.ready.then(function() {
     animation.finish();
 
     // Initiate a pause then abort it
     animation.pause();
     animation.play();
 
     // Wait to return to running state
--- a/dom/animation/test/css-animations/file_animation-starttime.html
+++ b/dom/animation/test/css-animations/file_animation-starttime.html
@@ -20,228 +20,116 @@
     </style>
     <script src="../testcommon.js"></script>
   </head>
   <body>
     <script type="text/javascript">
 
 'use strict';
 
-// TODO: add equivalent tests without an animation-delay, but first we need to
-// change the timing of animationstart dispatch. (Right now the animationstart
-// event will fire before the ready Promise is resolved if there is no
-// animation-delay.)
-// See https://bugzilla.mozilla.org/show_bug.cgi?id=1134163
+// TODO: We should separate this test(Testing for CSS Animation events /
+// Testing for start time of Web Animation).
+// e.g:
+//  CSS Animation events test:
+//    - check the firing an event after setting an Animation.startTime
+//  The start time of Web Animation test:
+//    - check an start time value on several situation(init / processing..)
+//    - Based on W3C Spec, check the behavior of setting current time.
 
 // TODO: Once the computedTiming property is implemented, add checks to the
 // checker helpers to ensure that computedTiming's properties are updated as
 // expected.
 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1108055
 
-
 const CSS_ANIM_EVENTS =
   ['animationstart', 'animationiteration', 'animationend'];
-const ANIM_DELAY_MS = 1000 * MS_PER_SEC; // 1000s
-const ANIM_DUR_MS = 1000 * MS_PER_SEC; // 1000s
-const ANIM_PROPERTY_VAL = 'anim ' + ANIM_DUR_MS + 'ms ' + ANIM_DELAY_MS + 'ms';
-
-/**
- * These helpers get the value that the startTime needs to be set to, to put an
- * animation that uses the above ANIM_DELAY_MS and ANIM_DUR_MS values into the
- * middle of various phases or points through the active duration.
- */
-function startTimeForBeforePhase(timeline) {
-  return timeline.currentTime - ANIM_DELAY_MS / 2;
-}
-function startTimeForActivePhase(timeline) {
-  return timeline.currentTime - ANIM_DELAY_MS - ANIM_DUR_MS / 2;
-}
-function startTimeForAfterPhase(timeline) {
-  return timeline.currentTime - ANIM_DELAY_MS - ANIM_DUR_MS - ANIM_DELAY_MS / 2;
-}
-function startTimeForStartOfActiveInterval(timeline) {
-  return timeline.currentTime - ANIM_DELAY_MS;
-}
-function startTimeForFiftyPercentThroughActiveInterval(timeline) {
-  return timeline.currentTime - ANIM_DELAY_MS - ANIM_DUR_MS * 0.5;
-}
-function startTimeForEndOfActiveInterval(timeline) {
-  return timeline.currentTime - ANIM_DELAY_MS - ANIM_DUR_MS;
-}
-
-
-// Expected computed 'margin-left' values at points during the active interval:
-// When we assert_between_inclusive using these values we could in theory cause
-// intermittent failure due to very long delays between paints, but since the
-// active duration is 1000s long, a delay would need to be around 100s to cause
-// that. If that's happening then there are likely other issues that should be
-// fixed, so a failure to make us look into that seems like a good thing.
-const UNANIMATED_POSITION = 10;
-const INITIAL_POSITION = 100;
-const TEN_PCT_POSITION = 110;
-const FIFTY_PCT_POSITION = 150;
-const END_POSITION = 200;
-
-// The terms used for the naming of the following helper functions refer to
-// terms used in the Web Animations specification for specific phases of an
-// animation. The terms can be found here:
-//
-//   https://w3c.github.io/web-animations/#animation-effect-phases-and-states
-//
-// Note the distinction between the "animation start time" which occurs before
-// the start delay and the start of the active interval which occurs after it.
-
-// Called when the ready Promise's callbacks should happen
-function checkStateOnReadyPromiseResolved(animation)
-{
-  assert_less_than_equal(animation.startTime, animation.timeline.currentTime,
-    'Animation.startTime should be less than the timeline\'s ' +
-    'currentTime on the first paint tick after animation creation');
-
-  assert_equals(animation.playState, 'running',
-    'Animation.playState should be "running" on the first paint ' +
-    'tick after animation creation');
-
-  assert_equals(animation.effect.target.style.animationPlayState, 'running',
-    'Animation.effect.target.style.animationPlayState should be ' +
-    '"running" on the first paint tick after animation creation');
-
-  var div = animation.effect.target;
-  var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
-  assert_equals(marginLeft, UNANIMATED_POSITION,
-                'the computed value of margin-left should be unaffected ' +
-                'by an animation with a delay on ready Promise resolve');
-}
-
-// Called when startTime is set to the time the active interval starts.
-function checkStateAtActiveIntervalStartTime(animation)
-{
-  // We don't test animation.startTime since our caller just set it.
-
-  assert_equals(animation.playState, 'running',
-    'Animation.playState should be "running" at the start of ' +
-    'the active interval');
-
-  assert_equals(animation.effect.target.style.animationPlayState, 'running',
-    'Animation.effect.target.style.animationPlayState should be ' +
-    '"running" at the start of the active interval');
-
-  var div = animation.effect.target;
-  var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
-  assert_between_inclusive(marginLeft, INITIAL_POSITION, TEN_PCT_POSITION,
-    'the computed value of margin-left should be close to the value at the ' +
-    'beginning of the animation');
-}
-
-function checkStateAtFiftyPctOfActiveInterval(animation)
-{
-  // We don't test animation.startTime since our caller just set it.
-
-  var div = animation.effect.target;
-  var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
-  assert_equals(marginLeft, FIFTY_PCT_POSITION,
-    'the computed value of margin-left should be half way through the ' +
-    'animation at the midpoint of the active interval');
-}
-
-// Called when startTime is set to the time the active interval ends.
-function checkStateAtActiveIntervalEndTime(animation)
-{
-  // We don't test animation.startTime since our caller just set it.
-
-  assert_equals(animation.playState, 'finished',
-    'Animation.playState should be "finished" at the end of ' +
-    'the active interval');
-
-  assert_equals(animation.effect.target.style.animationPlayState, "running",
-    'Animation.effect.target.style.animationPlayState should be ' +
-    '"finished" at the end of the active interval');
-
-  var div = animation.effect.target;
-  var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
-  assert_equals(marginLeft, UNANIMATED_POSITION,
-    'the computed value of margin-left should be unaffected ' +
-    'by the animation at the end of the active duration when the ' +
-    'animation-fill-mode is none');
-}
 
 test(function(t)
 {
   var div = addDiv(t, { 'style': 'animation: anim 100s' });
   var animation = div.getAnimations()[0];
+
   assert_equals(animation.startTime, null, 'startTime is unresolved');
 }, 'startTime of a newly created (play-pending) animation is unresolved');
 
 test(function(t)
 {
   var div = addDiv(t, { 'style': 'animation: anim 100s paused' });
   var animation = div.getAnimations()[0];
   assert_equals(animation.startTime, null, 'startTime is unresolved');
 }, 'startTime of a newly created (pause-pending) animation is unresolved');
 
 promise_test(function(t)
 {
   var div = addDiv(t, { 'style': 'animation: anim 100s' });
   var animation = div.getAnimations()[0];
+
   return animation.ready.then(function() {
     assert_true(animation.startTime > 0,
                 'startTime is resolved when running');
   });
 }, 'startTime is resolved when running');
 
 promise_test(function(t)
 {
   var div = addDiv(t, { 'style': 'animation: anim 100s paused' });
   var animation = div.getAnimations()[0];
+
   return animation.ready.then(function() {
     assert_equals(animation.startTime, null,
                   'startTime is unresolved when paused');
   });
 }, 'startTime is unresolved when paused');
 
 promise_test(function(t)
 {
   var div = addDiv(t, { 'style': 'animation: anim 100s' });
   var animation = div.getAnimations()[0];
+
   return animation.ready.then(function() {
     div.style.animationPlayState = 'paused';
     getComputedStyle(div).animationPlayState;
+
     assert_not_equals(animation.startTime, null,
                       'startTime is resolved when pause-pending');
 
     div.style.animationPlayState = 'running';
     getComputedStyle(div).animationPlayState;
+
     assert_not_equals(animation.startTime, null,
                       'startTime is preserved when a pause is aborted');
   });
 }, 'startTime while pause-pending and play-pending');
 
 promise_test(function(t) {
   var div = addDiv(t, { 'style': 'animation: anim 100s' });
   var animation = div.getAnimations()[0];
   // Seek to end to put us in the finished state
   animation.currentTime = 100 * MS_PER_SEC;
+
   return animation.ready.then(function() {
     // Call play() which puts us back in the running state
     animation.play();
+
     assert_equals(animation.startTime, null, 'startTime is unresolved');
   });
 }, 'startTime while play-pending from finished state');
 
 test(function(t) {
   var div = addDiv(t, { 'style': 'animation: anim 100s' });
   var animation = div.getAnimations()[0];
   animation.finish();
   // Call play() which puts us back in the running state
   animation.play();
+
   assert_equals(animation.startTime, null, 'startTime is unresolved');
 }, 'startTime while play-pending from finished state using finish()');
 
 promise_test(function(t) {
-  var div = addDiv(t, { style: 'animation: anim 1000s' });
+  var div = addDiv(t, { style: 'animation: anim 100s' });
   var animation = div.getAnimations()[0];
 
   assert_equals(animation.startTime, null, 'The initial startTime is null');
   var initialTimelineTime = document.timeline.currentTime;
 
   return animation.ready.then(function() {
     assert_true(animation.startTime > initialTimelineTime,
                 'After the animation has started, startTime is greater than ' +
@@ -262,243 +150,208 @@ promise_test(function(t) {
                   'After actually pausing, the startTime of an animation ' +
                   'is null');
   });
 }, 'Pausing should make the startTime become null');
 
 test(function(t)
 {
   var div = addDiv(t, {'class': 'animated-div'});
-  div.style.animation = ANIM_PROPERTY_VAL;
-
+  div.style.animation = 'anim 100s 100s';
   var animation = div.getAnimations()[0];
   var currentTime = animation.timeline.currentTime;
   animation.startTime = currentTime;
-  assert_approx_equals(animation.startTime, currentTime, 0.0001, // rounding error
+
+  assert_times_equal(animation.startTime, currentTime,
     'Check setting of startTime actually works');
 }, 'Sanity test to check round-tripping assigning to a new animation\'s ' +
    'startTime');
 
 promise_test(function(t) {
   var div = addDiv(t, {'class': 'animated-div'});
   var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
-
-  div.style.animation = ANIM_PROPERTY_VAL;
-
+  div.style.animation = 'anim 100s 100s';
   var animation = div.getAnimations()[0];
 
   return animation.ready.then(function() {
-    checkStateOnReadyPromiseResolved(animation);
+    assert_less_than_equal(animation.startTime, animation.timeline.currentTime,
+      'Animation.startTime should be less than the timeline\'s ' +
+      'currentTime on the first paint tick after animation creation');
 
-    animation.startTime = startTimeForStartOfActiveInterval(animation.timeline);
+    animation.startTime = animation.timeline.currentTime - 100 * MS_PER_SEC;
     return eventWatcher.wait_for('animationstart');
   }).then(function() {
-    checkStateAtActiveIntervalStartTime(animation);
-
-    animation.startTime =
-      startTimeForFiftyPercentThroughActiveInterval(animation.timeline);
-    checkStateAtFiftyPctOfActiveInterval(animation);
-
-    animation.startTime = startTimeForEndOfActiveInterval(animation.timeline);
+    animation.startTime = animation.timeline.currentTime - 200 * MS_PER_SEC;
     return eventWatcher.wait_for('animationend');
-  }).then(function() {
-    checkStateAtActiveIntervalEndTime(animation);
   });
 }, 'Skipping forward through animation');
 
 promise_test(function(t) {
   var div = addDiv(t, {'class': 'animated-div'});
   var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
-
-  div.style.animation = ANIM_PROPERTY_VAL;
-
+  div.style.animation = 'anim 100s 100s';
   var animation = div.getAnimations()[0];
-
-  animation.startTime = startTimeForEndOfActiveInterval(animation.timeline);
-
+  animation.startTime = animation.timeline.currentTime - 200 * MS_PER_SEC;
   var previousTimelineTime = animation.timeline.currentTime;
 
-  // Skipping over the active interval will dispatch an 'animationstart' then
-  // an 'animationend' event. We need to wait for these events before we start
-  // testing going backwards since EventWatcher will fail the test if it gets
-  // an event that we haven't told it about.
   return eventWatcher.wait_for(['animationstart',
                                 'animationend']).then(function() {
     assert_true(document.timeline.currentTime - previousTimelineTime <
-                  ANIM_DUR_MS,
+                  100 * MS_PER_SEC,
                 'Sanity check that seeking worked rather than the events ' +
                 'firing after normal playback through the very long ' +
                 'animation duration');
 
-    // Now we can start the tests for skipping backwards, but first we check
-    // that after the events we're still in the same end time state:
-    checkStateAtActiveIntervalEndTime(animation);
-
-    animation.startTime =
-      startTimeForFiftyPercentThroughActiveInterval(animation.timeline);
+    animation.startTime = animation.timeline.currentTime - 150 * MS_PER_SEC;
 
     // Despite going backwards from after the end of the animation (to being
     // in the active interval), we now expect an 'animationstart' event
     // because the animation should go from being inactive to active.
-    //
-    // Calling checkStateAtFiftyPctOfActiveInterval will check computed style,
-    // causing computed style to be updated and the 'animationstart' event to
-    // be dispatched synchronously. We need to call wait_for first
-    // otherwise eventWatcher will assert that the event was unexpected.
-    var promise = eventWatcher.wait_for('animationstart');
-    checkStateAtFiftyPctOfActiveInterval(animation);
-    return promise;
+    return eventWatcher.wait_for('animationstart');
   }).then(function() {
-    animation.startTime = startTimeForStartOfActiveInterval(animation.timeline);
-    checkStateAtActiveIntervalStartTime(animation);
+    animation.startTime = animation.timeline.currentTime;
 
-    animation.startTime = animation.timeline.currentTime;
     // Despite going backwards from just after the active interval starts to
     // the animation start time, we now expect an animationend event
     // because we went from inside to outside the active interval.
     return eventWatcher.wait_for('animationend');
   }).then(function() {
-    checkStateOnReadyPromiseResolved(animation);
-  })
-
-  // This must come after we've set up the Promise chain, since requesting
-  // computed style will force events to be dispatched.
-  checkStateAtActiveIntervalEndTime(animation);
+    assert_less_than_equal(animation.startTime, animation.timeline.currentTime,
+      'Animation.startTime should be less than the timeline\'s ' +
+      'currentTime on the first paint tick after animation creation');
+  });
 }, 'Skipping backwards through animation');
 
-
 // Next we have multiple tests to check that redundant startTime changes do NOT
 // dispatch events. It's impossible to distinguish between events not being
 // dispatched and events just taking an incredibly long time to dispatch
 // without waiting an infinitely long time. Obviously we don't want to do that
 // (block this test from finishing forever), so instead we just listen for
 // events until two animation frames (i.e. requestAnimationFrame callbacks)
 // have happened, then assume that no events will ever be dispatched for the
 // redundant changes if no events were detected in that time.
 
 promise_test(function(t) {
   var div = addDiv(t, {'class': 'animated-div'});
   var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
-  div.style.animation = ANIM_PROPERTY_VAL;
+  div.style.animation = "anim 100s 100s";
   var animation = div.getAnimations()[0];
 
-  animation.startTime = startTimeForActivePhase(animation.timeline);
-  animation.startTime = startTimeForBeforePhase(animation.timeline);
+  animation.startTime = animation.timeline.currentTime - 150 * MS_PER_SEC;
+  animation.startTime = animation.timeline.currentTime - 50 * MS_PER_SEC;
 
   return waitForAnimationFrames(2);
 }, 'Redundant change, before -> active, then back');
 
 promise_test(function(t) {
   var div = addDiv(t, {'class': 'animated-div'});
   var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
-  div.style.animation = ANIM_PROPERTY_VAL;
+  div.style.animation = "anim 100s 100s";
   var animation = div.getAnimations()[0];
 
-  animation.startTime = startTimeForAfterPhase(animation.timeline);
-  animation.startTime = startTimeForBeforePhase(animation.timeline);
+  animation.startTime = animation.timeline.currentTime - 250 * MS_PER_SEC;
+  animation.startTime = animation.timeline.currentTime - 50 * MS_PER_SEC;
 
   return waitForAnimationFrames(2);
 }, 'Redundant change, before -> after, then back');
 
 promise_test(function(t) {
   var div = addDiv(t, {'class': 'animated-div'});
   var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
-  div.style.animation = ANIM_PROPERTY_VAL;
+  div.style.animation = "anim 100s 100s";
   var animation = div.getAnimations()[0];
 
   var retPromise =  eventWatcher.wait_for('animationstart').then(function() {
-    animation.startTime = startTimeForBeforePhase(animation.timeline);
-    animation.startTime = startTimeForActivePhase(animation.timeline);
+    animation.startTime = animation.timeline.currentTime - 50 * MS_PER_SEC;
+    animation.startTime = animation.timeline.currentTime - 150 * MS_PER_SEC;
 
     return waitForAnimationFrames(2);
   });
   // get us into the initial state:
-  animation.startTime = startTimeForActivePhase(animation.timeline);
+  animation.startTime = animation.timeline.currentTime - 150 * MS_PER_SEC;
 
   return retPromise;
 }, 'Redundant change, active -> before, then back');
 
 promise_test(function(t) {
   var div = addDiv(t, {'class': 'animated-div'});
   var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
-  div.style.animation = ANIM_PROPERTY_VAL;
+  div.style.animation = "anim 100s 100s";
   var animation = div.getAnimations()[0];
 
   var retPromise = eventWatcher.wait_for('animationstart').then(function() {
-    animation.startTime = startTimeForAfterPhase(animation.timeline);
-    animation.startTime = startTimeForActivePhase(animation.timeline);
+    animation.startTime = animation.timeline.currentTime - 250 * MS_PER_SEC;
+    animation.startTime = animation.timeline.currentTime - 150 * MS_PER_SEC;
 
     return waitForAnimationFrames(2);
   });
   // get us into the initial state:
-  animation.startTime = startTimeForActivePhase(animation.timeline);
+  animation.startTime = animation.timeline.currentTime - 150 * MS_PER_SEC;
 
   return retPromise;
 }, 'Redundant change, active -> after, then back');
 
 promise_test(function(t) {
   var div = addDiv(t, {'class': 'animated-div'});
   var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
-  div.style.animation = ANIM_PROPERTY_VAL;
+  div.style.animation = "anim 100s 100s";
   var animation = div.getAnimations()[0];
 
   var retPromise = eventWatcher.wait_for(['animationstart',
                                           'animationend']).then(function() {
-    animation.startTime = startTimeForBeforePhase(animation.timeline);
-    animation.startTime = startTimeForAfterPhase(animation.timeline);
+    animation.startTime = animation.timeline.currentTime - 50 * MS_PER_SEC;
+    animation.startTime = animation.timeline.currentTime - 250 * MS_PER_SEC;
 
     return waitForAnimationFrames(2);
   });
   // get us into the initial state:
-  animation.startTime = startTimeForAfterPhase(animation.timeline);
+  animation.startTime = animation.timeline.currentTime - 250 * MS_PER_SEC;
 
   return retPromise;
 }, 'Redundant change, after -> before, then back');
 
 promise_test(function(t) {
   var div = addDiv(t, {'class': 'animated-div'});
   var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
-  div.style.animation = ANIM_PROPERTY_VAL;
+  div.style.animation = "anim 100s 100s";
   var animation = div.getAnimations()[0];
 
   var retPromise = eventWatcher.wait_for(['animationstart',
                                           'animationend']).then(function() {
-    animation.startTime = startTimeForActivePhase(animation.timeline);
-    animation.startTime = startTimeForAfterPhase(animation.timeline);
+    animation.startTime = animation.timeline.currentTime - 150 * MS_PER_SEC;
+    animation.startTime = animation.timeline.currentTime - 250 * MS_PER_SEC;
 
     return waitForAnimationFrames(2);
+
   });
   // get us into the initial state:
-  animation.startTime = startTimeForAfterPhase(animation.timeline);
+  animation.startTime = animation.timeline.currentTime - 250 * MS_PER_SEC;
 
   return retPromise;
 }, 'Redundant change, after -> active, then back');
 
 promise_test(function(t) {
   var div = addDiv(t, {'class': 'animated-div'});
-  div.style.animation = ANIM_PROPERTY_VAL;
-
+  div.style.animation = 'anim 100s 100s';
   var animation = div.getAnimations()[0];
-
   var storedCurrentTime;
 
   return animation.ready.then(function() {
     storedCurrentTime = animation.currentTime;
     animation.startTime = null;
     return animation.ready;
   }).then(function() {
     assert_equals(animation.currentTime, storedCurrentTime,
       'Test that hold time is correct');
   });
 }, 'Setting startTime to null');
 
 promise_test(function(t) {
   var div = addDiv(t, {'class': 'animated-div'});
   div.style.animation = 'anim 100s';
-
   var animation = div.getAnimations()[0];
 
   return animation.ready.then(function() {
     var savedStartTime = animation.startTime;
 
     assert_not_equals(animation.startTime, null,
       'Animation.startTime not null on ready Promise resolve');
 
@@ -510,18 +363,18 @@ promise_test(function(t) {
     assert_equals(animation.playState, 'paused',
       'Animation.playState is "paused" after pause() call');
   });
 }, 'Animation.startTime after pausing');
 
 promise_test(function(t) {
   var div = addDiv(t, {'class': 'animated-div'});
   div.style.animation = 'anim 100s';
+  var animation = div.getAnimations()[0];
 
-  var animation = div.getAnimations()[0];
   return animation.ready.then(function() {
     animation.cancel();
     assert_equals(animation.startTime, null,
                   'The startTime of a cancelled animation should be null');
   });
 }, 'Animation.startTime after cancelling');
 
 done();
--- a/dom/animation/test/css-transitions/file_animation-currenttime.html
+++ b/dom/animation/test/css-transitions/file_animation-currenttime.html
@@ -14,22 +14,16 @@
     </style>
     <script src="../testcommon.js"></script>
   </head>
   <body>
     <script type="text/javascript">
 
 'use strict';
 
-// TODO: add equivalent tests without an animation-delay, but first we need to
-// change the timing of animationstart dispatch. (Right now the animationstart
-// event will fire before the ready Promise is resolved if there is no
-// animation-delay.)
-// See https://bugzilla.mozilla.org/show_bug.cgi?id=1134163
-
 // TODO: Once the computedTiming property is implemented, add checks to the
 // checker helpers to ensure that computedTiming's properties are updated as
 // expected.
 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1108055
 
 
 const ANIM_DELAY_MS = 1000000; // 1000s
 const ANIM_DUR_MS = 1000000; // 1000s
--- a/dom/animation/test/css-transitions/file_animation-starttime.html
+++ b/dom/animation/test/css-transitions/file_animation-starttime.html
@@ -14,22 +14,16 @@
     </style>
     <script src="../testcommon.js"></script>
   </head>
   <body>
     <script type="text/javascript">
 
 'use strict';
 
-// TODO: add equivalent tests without an animation-delay, but first we need to
-// change the timing of animationstart dispatch. (Right now the animationstart
-// event will fire before the ready Promise is resolved if there is no
-// animation-delay.)
-// See https://bugzilla.mozilla.org/show_bug.cgi?id=1134163
-
 // TODO: Once the computedTiming property is implemented, add checks to the
 // checker helpers to ensure that computedTiming's properties are updated as
 // expected.
 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1108055
 
 
 const ANIM_DELAY_MS = 1000000; // 1000s
 const ANIM_DUR_MS = 1000000; // 1000s
--- a/dom/animation/test/testcommon.js
+++ b/dom/animation/test/testcommon.js
@@ -10,16 +10,30 @@
  * failures in asynchronous test. For example, the short duration animation
  * might be finished when animation.ready has been fulfilled because of slow
  * platforms or busyness of the main thread.
  * Setting short duration to cancel its animation does not matter but
  * if you don't want to cancel the animation, consider using longer duration.
  */
 const MS_PER_SEC = 1000;
 
+/* The recommended minimum precision to use for time values[1].
+ *
+ * [1] https://w3c.github.io/web-animations/#precision-of-time-values
+ */
+var TIME_PRECISION = 0.0005; // ms
+
+/*
+ * Allow implementations to substitute an alternative method for comparing
+ * times based on their precision requirements.
+ */
+function assert_times_equal(actual, expected, description) {
+  assert_approx_equals(actual, expected, TIME_PRECISION, description);
+}
+
 /**
  * Appends a div to the document body and creates an animation on the div.
  * NOTE: This function asserts when trying to create animations with durations
  * shorter than 100s because the shorter duration may cause intermittent
  * failures.  If you are not sure how long it is suitable, use 100s; it's
  * long enough but shorter than our test framework timeout (330s).
  * If you really need to use shorter durations, use animate() function directly.
  *