<!doctype html> <html> <head> <meta charset=utf-8> <title>Tests for the effect of setting a CSS animation's Animation.currentTime</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 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']; test(function(t) { var div = addDiv(t, {'class': 'animated-div'}); 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'); // Make sure the animation is running before we set the current time. animation.startTime = animation.timeline.currentTime; animation.currentTime = 50 * MS_PER_SEC; assert_times_equal(animation.currentTime, 50 * MS_PER_SEC, 'Check setting of currentTime actually works'); }, '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 100s 100s"; var animation = div.getAnimations()[0]; return animation.ready.then(function() { // 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 = 100 * MS_PER_SEC; return eventWatcher.wait_for('animationstart'); }).then(function() { animation.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.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.currentTime = 150 * MS_PER_SEC; return eventWatcher.wait_for('animationstart'); }).then(function() { animation.currentTime = 0; return eventWatcher.wait_for('animationend'); }); }, '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 100s 100s"; var animation = div.getAnimations()[0]; 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 100s 100s"; var animation = div.getAnimations()[0]; 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 100s 100s"; var animation = div.getAnimations()[0]; var retPromise = eventWatcher.wait_for('animationstart').then(function() { animation.currentTime = 50 * MS_PER_SEC; animation.currentTime = 150 * MS_PER_SEC; return waitForAnimationFrames(2); }); // get us into the initial state: 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 100s 100s"; var animation = div.getAnimations()[0]; 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 = 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.currentTime = 50 * MS_PER_SEC; animation.currentTime = 250 * MS_PER_SEC; return waitForAnimationFrames(2); }); // get us into the initial state: 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 100s 100s"; var animation = div.getAnimations()[0]; var retPromise = eventWatcher.wait_for(['animationstart', 'animationend']).then(function() { animation.currentTime = 150 * MS_PER_SEC; animation.currentTime = 250 * MS_PER_SEC; return waitForAnimationFrames(2); }); // get us into the initial state: 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 100s" var animation = div.getAnimations()[0]; animation.pause(); animation.currentTime = 150 * MS_PER_SEC; return eventWatcher.wait_for(['animationstart', 'animationend']).then(function() { 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 100s"; var animation = div.getAnimations()[0]; return animation.ready.then(function() { var exception; try { animation.currentTime = null; } catch (e) { exception = e; } assert_equals(exception.name, 'TypeError', 'Expect TypeError exception on trying to set ' + 'Animation.currentTime to null'); }); }, 'Setting currentTime to null'); promise_test(function(t) { var div = addDiv(t, {'class': 'animated-div'}); div.style.animation = 'anim 100s'; var animation = div.getAnimations()[0]; var pauseTime; return animation.ready.then(function() { assert_not_equals(animation.currentTime, null, 'Animation.currentTime not null on ready Promise resolve'); animation.pause(); return animation.ready; }).then(function() { pauseTime = animation.currentTime; return waitForFrame(); }).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 100s"; var animation = div.getAnimations()[0]; return animation.ready.then(function() { // just before animation ends: animation.currentTime = 100 * MS_PER_SEC - 1; return waitForAnimationFrames(2); }).then(function() { 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 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; return waitForAnimationFrames(2); }).then(function() { assert_equals(animation.currentTime, 0, '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]; return animation.ready.then(function() { animation.finish(); // Initiate a pause then abort it animation.pause(); animation.play(); // Wait to return to running state return animation.ready; }).then(function() { assert_true(animation.currentTime < 100 * 1000, 'After aborting a pause when finished, the currentTime should' + ' jump back towards the start of the animation'); }); }, 'After aborting a pause when finished, the call to play() should rewind' + ' the current time'); done(); </script> </body> </html>