| <!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="../../../resources/testharness.js"></script> |
| <script src="../../../resources/testharnessreport.js"></script> |
| <script src="../resources/testcommon.js"></script> |
| </head> |
| <body> |
| <div id="log"></div> |
| <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://drafts.csswg.org/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'); |
| |
| </script> |
| </body> |
| </html> |