<!doctype html> <html> <head> <meta charset=utf-8> <title>Tests for the effect of setting a CSS transition's Animation.currentTime</title> <style> .animated-div { margin-left: 100px; transition: margin-left 1000s linear 1000s; } </style> <script src="../testcommon.js"></script> </head> <body> <script type="text/javascript"> 'use strict'; // 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 /** * 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() { return ANIM_DELAY_MS / 2; } function currentTimeForActivePhase() { return ANIM_DELAY_MS + ANIM_DUR_MS / 2; } function currentTimeForAfterPhase() { return ANIM_DELAY_MS + ANIM_DUR_MS + ANIM_DELAY_MS / 2; } function currentTimeForStartOfActiveInterval() { return ANIM_DELAY_MS; } function currentTimeForFiftyPercentThroughActiveInterval() { return ANIM_DELAY_MS + ANIM_DUR_MS * 0.5; } function currentTimeForEndOfActiveInterval() { 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 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: // // http://w3c.github.io/web-animations/#animation-effect-phases-and-states // 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'); var div = animation.effect.target; var marginLeft = parseFloat(getComputedStyle(div).marginLeft); assert_equals(marginLeft, INITIAL_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'); 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'); var div = animation.effect.target; var marginLeft = parseFloat(getComputedStyle(div).marginLeft); assert_equals(marginLeft, END_POSITION, 'the computed value of margin-left should be the final transitioned-to ' + 'value at the end of the active duration'); } test(function(t) { var div = addDiv(t, {'class': 'animated-div'}); flushComputedStyle(div); div.style.marginLeft = '200px'; // initiate transition var animation = div.getAnimations()[0]; assert_equals(animation.currentTime, 0, 'currentTime should be zero'); }, 'currentTime of a newly created transition is zero'); test(function(t) { var div = addDiv(t, {'class': 'animated-div'}); flushComputedStyle(div); div.style.marginLeft = '200px'; // initiate transition var animation = div.getAnimations()[0]; // So that animation is running instead of paused when we set currentTime: animation.startTime = animation.timeline.currentTime; animation.currentTime = 10; assert_equals(animation.currentTime, 10, 'Check setting of currentTime actually works'); }, 'Sanity test to check round-tripping assigning to new animation\'s ' + 'currentTime'); async_test(function(t) { var div = addDiv(t, {'class': 'animated-div'}); var eventWatcher = new EventWatcher(t, div, 'transitionend'); flushComputedStyle(div); div.style.marginLeft = '200px'; // initiate transition var animation = div.getAnimations()[0]; animation.ready.then(t.step_func(function() { checkStateOnReadyPromiseResolved(animation); animation.currentTime = currentTimeForStartOfActiveInterval(); checkStateAtActiveIntervalStartTime(animation); animation.currentTime = currentTimeForFiftyPercentThroughActiveInterval(); checkStateAtFiftyPctOfActiveInterval(animation); animation.currentTime = currentTimeForEndOfActiveInterval(); return eventWatcher.wait_for('transitionend'); })).then(t.step_func(function() { checkStateAtActiveIntervalEndTime(animation); })).catch(t.step_func(function(reason) { assert_unreached(reason); })).then(function() { t.done(); }); }, 'Skipping forward through transition'); test(function(t) { var div = addDiv(t, {'class': 'animated-div'}); var eventWatcher = new EventWatcher(t, div, 'transitionend'); flushComputedStyle(div); div.style.marginLeft = '200px'; // initiate transition var animation = div.getAnimations()[0]; // Unlike in the case of CSS animations, we cannot skip to the end and skip // backwards since when we reach the end the transition effect is removed and // changes to the Animation object no longer affect the element. For // this reason we only skip forwards as far as the 50% through point. animation.ready.then(t.step_func(function() { animation.currentTime = currentTimeForFiftyPercentThroughActiveInterval(); checkStateAtFiftyPctOfActiveInterval(animation); animation.currentTime = currentTimeForStartOfActiveInterval(); // Despite going backwards from being in the active interval to being // before it, we now expect a 'transitionend' event because the transition // should go from being active to inactive. // // Calling checkStateAtActiveIntervalStartTime will check computed style, // causing computed style to be updated and the 'transitionend' event to // be dispatched synchronously. We need to call wait_for first // otherwise eventWatcher will assert that the event was unexpected. eventWatcher.wait_for('transitionend').then(function() { t.done(); }); checkStateAtActiveIntervalStartTime(animation); })); }, 'Skipping backwards through transition'); async_test(function(t) { var div = addDiv(t, {'class': 'animated-div'}); flushComputedStyle(div); div.style.marginLeft = '200px'; // initiate transition var animation = div.getAnimations()[0]; animation.ready.then(t.step_func(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'); })).catch(t.step_func(function(reason) { assert_unreached(reason); })).then(function() { t.done(); }); }, 'Setting currentTime to null'); async_test(function(t) { var div = addDiv(t, {'class': 'animated-div'}); flushComputedStyle(div); div.style.marginLeft = '200px'; // initiate transition var animation = div.getAnimations()[0]; var pauseTime; animation.ready.then(t.step_func(function() { assert_not_equals(animation.currentTime, null, 'Animation.currentTime not null on ready Promise resolve'); animation.pause(); return animation.ready; })).then(t.step_func(function() { pauseTime = animation.currentTime; return waitForFrame(); })).then(t.step_func(function() { assert_equals(animation.currentTime, pauseTime, 'Animation.currentTime is unchanged after pausing'); })).catch(t.step_func(function(reason) { assert_unreached(reason); })).then(function() { t.done(); }); }, 'Animation.currentTime after pausing'); done(); </script> </body> </html>