<!doctype html> <html> <head> <meta charset=utf-8> <title>Tests for the effect of setting a CSS animation's Animation.startTime</title> <style> .animated-div { margin-left: 10px; /* Make it easier to calculate expected values: */ animation-timing-function: linear ! important; } @keyframes anim { from { margin-left: 100px; } to { margin-left: 200px; } } </style> <script src="../testcommon.js"></script> </head> <body> <script type="text/javascript"> 'use strict'; // 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']; 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 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 ' + 'the time when it was started'); var startTimeBeforePausing = animation.startTime; div.style.animationPlayState = 'paused'; // Flush styles just in case querying animation.startTime doesn't flush // styles (which would be a bug in of itself and could mask a further bug // by causing startTime to appear to not change). getComputedStyle(div).animationPlayState; assert_equals(animation.startTime, startTimeBeforePausing, 'The startTime does not change when pausing-pending'); return animation.ready; }).then(function() { assert_equals(animation.startTime, null, '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 100s 100s'; var animation = div.getAnimations()[0]; var currentTime = animation.timeline.currentTime; animation.startTime = currentTime; 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 100s 100s'; var animation = div.getAnimations()[0]; return animation.ready.then(function() { 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 = animation.timeline.currentTime - 100 * MS_PER_SEC; return eventWatcher.wait_for('animationstart'); }).then(function() { animation.startTime = animation.timeline.currentTime - 200 * MS_PER_SEC; return eventWatcher.wait_for('animationend'); }); }, '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 100s 100s'; var animation = div.getAnimations()[0]; animation.startTime = animation.timeline.currentTime - 200 * MS_PER_SEC; var previousTimelineTime = animation.timeline.currentTime; return eventWatcher.wait_for(['animationstart', 'animationend']).then(function() { assert_true(document.timeline.currentTime - previousTimelineTime < 100 * MS_PER_SEC, 'Sanity check that seeking worked rather than the events ' + 'firing after normal playback through the very long ' + 'animation duration'); 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. return eventWatcher.wait_for('animationstart'); }).then(function() { 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() { 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 100s 100s"; var animation = div.getAnimations()[0]; 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 100s 100s"; var animation = div.getAnimations()[0]; 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 100s 100s"; var animation = div.getAnimations()[0]; var retPromise = eventWatcher.wait_for('animationstart').then(function() { 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 = 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 100s 100s"; var animation = div.getAnimations()[0]; var retPromise = eventWatcher.wait_for('animationstart').then(function() { 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 = 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 100s 100s"; var animation = div.getAnimations()[0]; var retPromise = eventWatcher.wait_for(['animationstart', 'animationend']).then(function() { 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 = 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 100s 100s"; var animation = div.getAnimations()[0]; var retPromise = eventWatcher.wait_for(['animationstart', 'animationend']).then(function() { 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 = 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 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'); animation.pause(); return animation.ready; }).then(function() { assert_equals(animation.startTime, null, 'Animation.startTime is null after paused'); 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]; 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(); </script> </body> </html>