diff options
Diffstat (limited to 'dom/animation/test/css-animations')
36 files changed, 4054 insertions, 0 deletions
diff --git a/dom/animation/test/css-animations/file_animation-cancel.html b/dom/animation/test/css-animations/file_animation-cancel.html new file mode 100644 index 000000000..85499addf --- /dev/null +++ b/dom/animation/test/css-animations/file_animation-cancel.html @@ -0,0 +1,154 @@ +<!doctype html> +<meta charset=utf-8> +<script src="../testcommon.js"></script> +<style> +@keyframes translateAnim { + to { transform: translate(100px) } +} +@keyframes marginLeftAnim { + to { margin-left: 100px } +} +@keyframes marginLeftAnim100To200 { + from { margin-left: 100px } + to { margin-left: 200px } +} +</style> +<body> +<script> +'use strict'; + +promise_test(function(t) { + var div = addDiv(t, { style: 'animation: translateAnim 100s' }); + var animation = div.getAnimations()[0]; + + return animation.ready.then(function() { + assert_not_equals(getComputedStyle(div).transform, 'none', + 'transform style is animated before cancelling'); + animation.cancel(); + assert_equals(getComputedStyle(div).transform, 'none', + 'transform style is no longer animated after cancelling'); + }); +}, 'Animated style is cleared after cancelling a running CSS animation'); + +promise_test(function(t) { + var div = addDiv(t, { style: 'animation: translateAnim 100s forwards' }); + var animation = div.getAnimations()[0]; + animation.finish(); + + return animation.ready.then(function() { + assert_not_equals(getComputedStyle(div).transform, 'none', + 'transform style is filling before cancelling'); + animation.cancel(); + assert_equals(getComputedStyle(div).transform, 'none', + 'fill style is cleared after cancelling'); + }); +}, 'Animated style is cleared after cancelling a filling CSS animation'); + +promise_test(function(t) { + var div = addDiv(t, { style: 'animation: translateAnim 100s' }); + var animation = div.getAnimations()[0]; + div.addEventListener('animationend', t.step_func(function() { + assert_unreached('Got unexpected end event on cancelled animation'); + })); + + return animation.ready.then(function() { + // Seek to just before the end then cancel + animation.currentTime = 99.9 * 1000; + animation.cancel(); + + // Then wait a couple of frames and check that no event was dispatched + return waitForAnimationFrames(2); + }); +}, 'Cancelled CSS animations do not dispatch events'); + +test(function(t) { + var div = addDiv(t, { style: 'animation: marginLeftAnim 100s linear' }); + var animation = div.getAnimations()[0]; + animation.cancel(); + + assert_equals(getComputedStyle(div).marginLeft, '0px', + 'margin-left style is not animated after cancelling'); + + animation.currentTime = 50 * 1000; + assert_equals(getComputedStyle(div).marginLeft, '50px', + 'margin-left style is updated when cancelled animation is' + + ' seeked'); +}, 'After cancelling an animation, it can still be seeked'); + +promise_test(function(t) { + var div = + addDiv(t, { style: 'animation: marginLeftAnim100To200 100s linear' }); + var animation = div.getAnimations()[0]; + + return animation.ready.then(function() { + animation.cancel(); + assert_equals(getComputedStyle(div).marginLeft, '0px', + 'margin-left style is not animated after cancelling'); + animation.play(); + assert_equals(getComputedStyle(div).marginLeft, '100px', + 'margin-left style is animated after re-starting animation'); + return animation.ready; + }).then(function() { + assert_equals(animation.playState, 'running', + 'Animation succeeds in running after being re-started'); + }); +}, 'After cancelling an animation, it can still be re-used'); + +test(function(t) { + var div = + addDiv(t, { style: 'animation: marginLeftAnim100To200 100s linear' }); + var animation = div.getAnimations()[0]; + animation.cancel(); + assert_equals(getComputedStyle(div).marginLeft, '0px', + 'margin-left style is not animated after cancelling'); + + // Trigger a change to some animation properties and check that this + // doesn't cause the animation to become live again + div.style.animationDuration = '200s'; + flushComputedStyle(div); + assert_equals(getComputedStyle(div).marginLeft, '0px', + 'margin-left style is still not animated after updating' + + ' animation-duration'); + assert_equals(animation.playState, 'idle', + 'Animation is still idle after updating animation-duration'); +}, 'After cancelling an animation, updating animation properties doesn\'t make' + + ' it live again'); + +test(function(t) { + var div = + addDiv(t, { style: 'animation: marginLeftAnim100To200 100s linear' }); + var animation = div.getAnimations()[0]; + animation.cancel(); + assert_equals(getComputedStyle(div).marginLeft, '0px', + 'margin-left style is not animated after cancelling'); + + // Make some changes to animation-play-state and check that the + // animation doesn't become live again. This is because it should be + // possible to cancel an animation from script such that all future + // changes to style are ignored. + + // Redundant change + div.style.animationPlayState = 'running'; + assert_equals(animation.playState, 'idle', + 'Animation is still idle after a redundant change to' + + ' animation-play-state'); + + // Pause + div.style.animationPlayState = 'paused'; + assert_equals(animation.playState, 'idle', + 'Animation is still idle after setting' + + ' animation-play-state: paused'); + + // Play + div.style.animationPlayState = 'running'; + assert_equals(animation.playState, 'idle', + 'Animation is still idle after re-setting' + + ' animation-play-state: running'); + +}, 'After cancelling an animation, updating animation-play-state doesn\'t' + + ' make it live again'); + +done(); +</script> +</body> +</html> diff --git a/dom/animation/test/css-animations/file_animation-computed-timing.html b/dom/animation/test/css-animations/file_animation-computed-timing.html new file mode 100644 index 000000000..53597a473 --- /dev/null +++ b/dom/animation/test/css-animations/file_animation-computed-timing.html @@ -0,0 +1,566 @@ +<!doctype html> +<meta charset=utf-8> +<script src="../testcommon.js"></script> +<style> +@keyframes moveAnimation { + from { margin-left: 100px } + to { margin-left: 200px } +} +</style> +<body> +<script> + +'use strict'; + +// -------------------- +// delay +// -------------------- +test(function(t) { + var div = addDiv(t, {style: 'animation: moveAnimation 100s'}); + var effect = div.getAnimations()[0].effect; + assert_equals(effect.getComputedTiming().delay, 0, + 'Initial value of delay'); +}, 'delay of a new animation'); + +test(function(t) { + var div = addDiv(t, {style: 'animation: moveAnimation 100s -10s'}); + var effect = div.getAnimations()[0].effect; + assert_equals(effect.getComputedTiming().delay, -10 * MS_PER_SEC, + 'Initial value of delay'); +}, 'Negative delay of a new animation'); + +test(function(t) { + var div = addDiv(t, {style: 'animation: moveAnimation 100s 10s'}); + var effect = div.getAnimations()[0].effect; + assert_equals(effect.getComputedTiming().delay, 10 * MS_PER_SEC, + 'Initial value of delay'); +}, 'Positive delay of a new animation'); + + +// -------------------- +// endDelay +// -------------------- +test(function(t) { + var div = addDiv(t, {style: 'animation: moveAnimation 100s'}); + var effect = div.getAnimations()[0].effect; + assert_equals(effect.getComputedTiming().endDelay, 0, + 'Initial value of endDelay'); +}, 'endDelay of a new animation'); + + +// -------------------- +// fill +// -------------------- +test(function(t) { + var getEffectWithFill = function(fill) { + var div = addDiv(t, {style: 'animation: moveAnimation 100s ' + fill}); + return div.getAnimations()[0].effect; + }; + + var effect = getEffectWithFill(''); + assert_equals(effect.getComputedTiming().fill, 'none', + 'Initial value of fill'); + effect = getEffectWithFill('forwards'); + assert_equals(effect.getComputedTiming().fill, 'forwards', + 'Fill forwards'); + effect = getEffectWithFill('backwards'); + assert_equals(effect.getComputedTiming().fill, 'backwards', + 'Fill backwards'); + effect = getEffectWithFill('both'); + assert_equals(effect.getComputedTiming().fill, 'both', + 'Fill forwards and backwards'); +}, 'fill of a new animation'); + + +// -------------------- +// iterationStart +// -------------------- +test(function(t) { + var div = addDiv(t, {style: 'animation: moveAnimation 100s'}); + var effect = div.getAnimations()[0].effect; + assert_equals(effect.getComputedTiming().iterationStart, 0, + 'Initial value of iterationStart'); +}, 'iterationStart of a new animation'); + + +// -------------------- +// iterations +// -------------------- +test(function(t) { + var div = addDiv(t, {style: 'animation: moveAnimation 100s'}); + var effect = div.getAnimations()[0].effect; + assert_equals(effect.getComputedTiming().iterations, 1, + 'Initial value of iterations'); +}, 'iterations of a new animation'); + +test(function(t) { + var div = addDiv(t, {style: 'animation: moveAnimation 100s 2016.5'}); + var effect = div.getAnimations()[0].effect; + assert_equals(effect.getComputedTiming().iterations, 2016.5, + 'Initial value of iterations'); +}, 'iterations of a finitely repeating animation'); + +test(function(t) { + var div = addDiv(t, {style: 'animation: moveAnimation 100s infinite'}); + var effect = div.getAnimations()[0].effect; + assert_equals(effect.getComputedTiming().iterations, Infinity, + 'Initial value of iterations'); +}, 'iterations of an infinitely repeating animation'); + + +// -------------------- +// duration +// -------------------- +test(function(t) { + var div = addDiv(t, {style: 'animation: moveAnimation 100s -10s infinite'}); + var effect = div.getAnimations()[0].effect; + assert_equals(effect.getComputedTiming().duration, 100 * MS_PER_SEC, + 'Initial value of duration'); +}, 'duration of a new animation'); + + +// -------------------- +// direction +// -------------------- +test(function(t) { + var getEffectWithDir = function(dir) { + var div = addDiv(t, {style: 'animation: moveAnimation 100s ' + dir}); + return div.getAnimations()[0].effect; + }; + + var effect = getEffectWithDir(''); + assert_equals(effect.getComputedTiming().direction, 'normal', + 'Initial value of normal direction'); + effect = getEffectWithDir('reverse'); + assert_equals(effect.getComputedTiming().direction, 'reverse', + 'Initial value of reverse direction'); + effect = getEffectWithDir('alternate'); + assert_equals(effect.getComputedTiming().direction, 'alternate', + 'Initial value of alternate direction'); + effect = getEffectWithDir('alternate-reverse'); + assert_equals(effect.getComputedTiming().direction, 'alternate-reverse', + 'Initial value of alternate-reverse direction'); +}, 'direction of a new animation'); + + +// -------------------- +// easing +// -------------------- +test(function(t) { + var div = addDiv(t, {style: 'animation: moveAnimation 100s'}); + var effect = div.getAnimations()[0].effect; + assert_equals(effect.getComputedTiming().easing, 'linear', + 'Initial value of easing'); +}, 'easing of a new animation'); + + +// ------------------------------ +// endTime +// = max(start delay + active duration + end delay, 0) +// -------------------- +test(function(t) { + var div = addDiv(t, {style: 'animation: moveAnimation 100s'}); + var effect = div.getAnimations()[0].effect; + assert_equals(effect.getComputedTiming().endTime, 100 * MS_PER_SEC, + 'Initial value of endTime'); +}, 'endTime of an new animation'); + +test(function(t) { + var div = addDiv(t, {style: 'animation: moveAnimation 100s -5s'}); + var effect = div.getAnimations()[0].effect; + var answer = (100 - 5) * MS_PER_SEC; + assert_equals(effect.getComputedTiming().endTime, answer, + 'Initial value of endTime'); +}, 'endTime of an animation with a negative delay'); + +test(function(t) { + var div = addDiv(t, {style: 'animation: moveAnimation 10s -100s infinite'}); + var effect = div.getAnimations()[0].effect; + assert_equals(effect.getComputedTiming().endTime, Infinity, + 'Initial value of endTime'); +}, 'endTime of an infinitely repeating animation'); + +test(function(t) { + var div = addDiv(t, {style: 'animation: moveAnimation 0s 100s infinite'}); + var effect = div.getAnimations()[0].effect; + assert_equals(effect.getComputedTiming().endTime, 100 * MS_PER_SEC, + 'Initial value of endTime'); +}, 'endTime of an infinitely repeating zero-duration animation'); + +test(function(t) { + // Fill forwards so div.getAnimations()[0] won't return an + // undefined value. + var div = addDiv(t, {style: 'animation: moveAnimation 10s -100s forwards'}); + var effect = div.getAnimations()[0].effect; + assert_equals(effect.getComputedTiming().endTime, 0, + 'Initial value of endTime'); +}, 'endTime of an animation that finishes before its startTime'); + + +// -------------------- +// activeDuration +// = iteration duration * iteration count +// -------------------- +test(function(t) { + var div = addDiv(t, {style: 'animation: moveAnimation 100s 5'}); + var effect = div.getAnimations()[0].effect; + var answer = 100 * MS_PER_SEC * 5; + assert_equals(effect.getComputedTiming().activeDuration, answer, + 'Initial value of activeDuration'); +}, 'activeDuration of a new animation'); + +test(function(t) { + var div = addDiv(t, {style: 'animation: moveAnimation 100s infinite'}); + var effect = div.getAnimations()[0].effect; + assert_equals(effect.getComputedTiming().activeDuration, Infinity, + 'Initial value of activeDuration'); +}, 'activeDuration of an infinitely repeating animation'); + +test(function(t) { + var div = addDiv(t, {style: 'animation: moveAnimation 0s 1s infinite'}); + var effect = div.getAnimations()[0].effect; + // If either the iteration duration or iteration count are zero, + // the active duration is zero. + assert_equals(effect.getComputedTiming().activeDuration, 0, + 'Initial value of activeDuration'); +}, 'activeDuration of an infinitely repeating zero-duration animation'); + +test(function(t) { + var div = addDiv(t, {style: 'animation: moveAnimation 100s 1s 0'}); + var effect = div.getAnimations()[0].effect; + // If either the iteration duration or iteration count are zero, + // the active duration is zero. + assert_equals(effect.getComputedTiming().activeDuration, 0, + 'Initial value of activeDuration'); +}, 'activeDuration of an animation with zero iterations'); + + +// -------------------- +// localTime +// -------------------- +test(function(t) { + var div = addDiv(t, {style: 'animation: moveAnimation 100s'}); + var effect = div.getAnimations()[0].effect; + assert_equals(effect.getComputedTiming().localTime, 0, + 'Initial value of localTime'); +}, 'localTime of a new animation'); + +test(function(t) { + var div = addDiv(t, {style: 'animation: moveAnimation 100s'}); + var anim = div.getAnimations()[0]; + anim.currentTime = 5 * MS_PER_SEC; + assert_equals(anim.effect.getComputedTiming().localTime, anim.currentTime, + 'current localTime after setting currentTime'); +}, 'localTime of an animation is always equal to currentTime'); + +promise_test(function(t) { + var div = addDiv(t, {style: 'animation: moveAnimation 100s'}); + + var anim = div.getAnimations()[0]; + anim.playbackRate = 2; // 2 times faster + + return anim.ready.then(function() { + assert_equals(anim.effect.getComputedTiming().localTime, anim.currentTime, + 'localTime is equal to currentTime'); + return waitForFrame(); + }).then(function() { + assert_equals(anim.effect.getComputedTiming().localTime, anim.currentTime, + 'localTime is equal to currentTime'); + }); +}, 'localTime reflects playbackRate immediately'); + +test(function(t) { + var div = addDiv(t); + var effect = new KeyframeEffectReadOnly(div, {left: ["0px", "100px"]}); + + assert_equals(effect.getComputedTiming().localTime, null, + 'localTime for orphaned effect'); +}, 'localTime of an AnimationEffect without an Animation'); + + +// -------------------- +// progress +// Note: Default timing function is linear. +// -------------------- +test(function(t) { + [{fill: '', progress: [ null, null ]}, + {fill: 'none', progress: [ null, null ]}, + {fill: 'forwards', progress: [ null, 1.0 ]}, + {fill: 'backwards', progress: [ 0.0, null ]}, + {fill: 'both', progress: [ 0.0, 1.0 ]}] + .forEach(function(test) { + var div = + addDiv(t, {style: 'animation: moveAnimation 100s 10s ' + test.fill}); + var anim = div.getAnimations()[0]; + assert_true(anim.effect.getComputedTiming().progress === test.progress[0], + 'initial progress with "' + test.fill + '" fill'); + anim.finish(); + assert_true(anim.effect.getComputedTiming().progress === test.progress[1], + 'finished progress with "' + test.fill + '" fill'); + }); +}, 'progress of an animation with different fill modes'); + +test(function(t) { + var div = addDiv(t, {style: 'animation: moveAnimation 10s 10 both'}); + var anim = div.getAnimations()[0]; + + assert_equals(anim.effect.getComputedTiming().progress, 0.0, + 'Initial value of progress'); + anim.currentTime += 2.5 * MS_PER_SEC; + assert_equals(anim.effect.getComputedTiming().progress, 0.25, + 'Value of progress'); + anim.currentTime += 5 * MS_PER_SEC; + assert_equals(anim.effect.getComputedTiming().progress, 0.75, + 'Value of progress'); + anim.currentTime += 5 * MS_PER_SEC; + assert_equals(anim.effect.getComputedTiming().progress, 0.25, + 'Value of progress'); + anim.finish() + assert_equals(anim.effect.getComputedTiming().progress, 1.0, + 'Value of progress'); +}, 'progress of an integral repeating animation with normal direction'); + +test(function(t) { + var div = addDiv(t); + // Note: FillMode here is "both" because + // 1. Since this a zero-duration animation, it will already have finished + // so it won't be returned by getAnimations() unless it fills forwards. + // 2. Fill backwards, so the progress before phase wouldn't be + // unresolved (null value). + var div = addDiv(t, {style: 'animation: moveAnimation 0s infinite both'}); + var anim = div.getAnimations()[0]; + + assert_equals(anim.effect.getComputedTiming().progress, 1.0, + 'Initial value of progress in after phase'); + + // Seek backwards + anim.currentTime -= 1 * MS_PER_SEC; + assert_equals(anim.effect.getComputedTiming().progress, 0.0, + 'Value of progress before phase'); +}, 'progress of an infinitely repeating zero-duration animation'); + +test(function(t) { + // Default iterations = 1 + var div = addDiv(t, {style: 'animation: moveAnimation 0s both'}); + var anim = div.getAnimations()[0]; + + assert_equals(anim.effect.getComputedTiming().progress, 1.0, + 'Initial value of progress in after phase'); + + // Seek backwards + anim.currentTime -= 1 * MS_PER_SEC; + assert_equals(anim.effect.getComputedTiming().progress, 0.0, + 'Value of progress before phase'); +}, 'progress of a finitely repeating zero-duration animation'); + +test(function(t) { + var div = addDiv(t, {style: 'animation: moveAnimation 0s 5s 10.25 both'}); + var anim = div.getAnimations()[0]; + + assert_equals(anim.effect.getComputedTiming().progress, 0.0, + 'Initial value of progress (before phase)'); + + // Using iteration duration of 1 now. + // currentIteration now is floor(10.25) = 10, so progress should be 25%. + anim.finish(); + assert_equals(anim.effect.getComputedTiming().progress, 0.25, + 'Value of progress in after phase'); +}, 'progress of a non-integral repeating zero-duration animation'); + +test(function(t) { + var div = addDiv(t, {style: 'animation: moveAnimation 0s 5s 10.25 both reverse'}); + var anim = div.getAnimations()[0]; + + assert_equals(anim.effect.getComputedTiming().progress, 1.0, + 'Initial value of progress (before phase)'); + + // Seek forwards + anim.finish(); + assert_equals(anim.effect.getComputedTiming().progress, 0.75, + 'Value of progress in after phase'); +}, 'Progress of a non-integral repeating zero-duration animation ' + + 'with reversing direction'); + +test(function(t) { + var div = addDiv(t, {style: 'animation: moveAnimation 10s 10.25 both alternate'}); + var anim = div.getAnimations()[0]; + + assert_equals(anim.effect.getComputedTiming().progress, 0.0, + 'Initial value of progress'); + anim.currentTime += 2.5 * MS_PER_SEC; + assert_equals(anim.effect.getComputedTiming().progress, 0.25, + 'Value of progress'); + anim.currentTime += 5 * MS_PER_SEC; + assert_equals(anim.effect.getComputedTiming().progress, 0.75, + 'Value of progress'); + anim.currentTime += 5 * MS_PER_SEC; + assert_equals(anim.effect.getComputedTiming().progress, 0.75, + 'Value of progress'); + anim.finish() + assert_equals(anim.effect.getComputedTiming().progress, 0.25, + 'Value of progress'); +}, 'progress of a non-integral repeating animation ' + + 'with alternate direction'); + +test(function(t) { + var div = addDiv(t, {style: 'animation: moveAnimation 10s 10.25 both alternate-reverse'}); + var anim = div.getAnimations()[0]; + + assert_equals(anim.effect.getComputedTiming().progress, 1.0, + 'Initial value of progress'); + anim.currentTime += 2.5 * MS_PER_SEC; + assert_equals(anim.effect.getComputedTiming().progress, 0.75, + 'Value of progress'); + anim.currentTime += 5 * MS_PER_SEC; + assert_equals(anim.effect.getComputedTiming().progress, 0.25, + 'Value of progress'); + anim.currentTime += 5 * MS_PER_SEC; + assert_equals(anim.effect.getComputedTiming().progress, 0.25, + 'Value of progress'); + anim.finish() + assert_equals(anim.effect.getComputedTiming().progress, 0.75, + 'Value of progress'); +}, 'progress of a non-integral repeating animation ' + + 'with alternate-reversing direction'); + +test(function(t) { + var div = addDiv(t, {style: 'animation: moveAnimation 0s 10.25 both alternate'}); + var anim = div.getAnimations()[0]; + + assert_equals(anim.effect.getComputedTiming().progress, 0.25, + 'Initial value of progress'); + anim.currentTime += 2.5 * MS_PER_SEC; + assert_equals(anim.effect.getComputedTiming().progress, 0.25, + 'Value of progress'); + anim.currentTime -= 5 * MS_PER_SEC; + assert_equals(anim.effect.getComputedTiming().progress, 0.0, + 'Value of progress'); + anim.finish() + assert_equals(anim.effect.getComputedTiming().progress, 0.25, + 'Value of progress'); +}, 'progress of a non-integral repeating zero-duration animation ' + + 'with alternate direction'); + +test(function(t) { + var div = addDiv(t, {style: 'animation: moveAnimation 0s 10.25 both alternate-reverse'}); + var anim = div.getAnimations()[0]; + + assert_equals(anim.effect.getComputedTiming().progress, 0.75, + 'Initial value of progress'); + anim.currentTime += 2.5 * MS_PER_SEC; + assert_equals(anim.effect.getComputedTiming().progress, 0.75, + 'Value of progress'); + anim.currentTime -= 5 * MS_PER_SEC; + assert_equals(anim.effect.getComputedTiming().progress, 1.0, + 'Value of progress'); + anim.finish() + assert_equals(anim.effect.getComputedTiming().progress, 0.75, + 'Value of progress'); +}, 'progress of a non-integral repeating zero-duration animation ' + + 'with alternate-reverse direction'); + + +// -------------------- +// currentIteration +// -------------------- +test(function(t) { + var div = addDiv(t, {style: 'animation: moveAnimation 100s 2s'}); + var effect = div.getAnimations()[0].effect; + assert_equals(effect.getComputedTiming().currentIteration, null, + 'Initial value of currentIteration before phase'); +}, 'currentIteration of a new animation with no backwards fill is unresolved ' + + 'in before phase'); + +test(function(t) { + var div = addDiv(t, {style: 'animation: moveAnimation 100s'}); + var anim = div.getAnimations()[0]; + assert_equals(anim.effect.getComputedTiming().currentIteration, 0, + 'Initial value of currentIteration'); +}, 'currentIteration of a new animation is zero'); + +test(function(t) { + // Note: FillMode here is "both" because + // 1. Since this a zero-duration animation, it will already have finished + // so it won't be returned by getAnimations() unless it fills forwards. + // 2. Fill backwards, so the currentIteration (before phase) wouldn't be + // unresolved (null value). + var div = addDiv(t, {style: 'animation: moveAnimation 0s infinite both'}); + var anim = div.getAnimations()[0]; + + assert_equals(anim.effect.getComputedTiming().currentIteration, Infinity, + 'Initial value of currentIteration in after phase'); + + // Seek backwards + anim.currentTime -= 2 * MS_PER_SEC; + assert_equals(anim.effect.getComputedTiming().currentIteration, 0, + 'Value of currentIteration count during before phase'); +}, 'currentIteration of an infinitely repeating zero-duration animation'); + +test(function(t) { + var div = addDiv(t, {style: 'animation: moveAnimation 0s 10.5 both'}); + var anim = div.getAnimations()[0]; + + // Note: currentIteration = ceil(iteration start + iteration count) - 1 + assert_equals(anim.effect.getComputedTiming().currentIteration, 10, + 'Initial value of currentIteration'); + + // Seek backwards + anim.currentTime -= 2 * MS_PER_SEC; + assert_equals(anim.effect.getComputedTiming().currentIteration, 0, + 'Value of currentIteration count during before phase'); +}, 'currentIteration of a finitely repeating zero-duration animation'); + +test(function(t) { + var div = addDiv(t, {style: 'animation: moveAnimation 100s 5.5 forwards'}); + var anim = div.getAnimations()[0]; + + assert_equals(anim.effect.getComputedTiming().currentIteration, 0, + 'Initial value of currentIteration'); + // The 3rd iteration + // Note: currentIteration = floor(scaled active time / iteration duration) + anim.currentTime = 250 * MS_PER_SEC; + assert_equals(anim.effect.getComputedTiming().currentIteration, 2, + 'Value of currentIteration during the 3rd iteration'); + // Finish + anim.finish(); + assert_equals(anim.effect.getComputedTiming().currentIteration, 5, + 'Value of currentIteration in after phase'); +}, 'currentIteration of an animation with a non-integral iteration count'); + +test(function(t) { + var div = addDiv(t, {style: 'animation: moveAnimation 100s 2 forwards'}); + var anim = div.getAnimations()[0]; + + assert_equals(anim.effect.getComputedTiming().currentIteration, 0, + 'Initial value of currentIteration'); + // Finish + anim.finish(); + assert_equals(anim.effect.getComputedTiming().currentIteration, 1, + 'Value of currentIteration in after phase'); +}, 'currentIteration of an animation with an integral iteration count'); + +test(function(t) { + var div = addDiv(t, {style: 'animation: moveAnimation 100s forwards'}); + var anim = div.getAnimations()[0]; + assert_equals(anim.effect.getComputedTiming().currentIteration, 0, + 'Initial value of currentIteration'); + // Finish + anim.finish(); + assert_equals(anim.effect.getComputedTiming().currentIteration, 0, + 'Value of currentIteration in after phase'); +}, 'currentIteration of an animation with a default iteration count'); + +test(function(t) { + var div = addDiv(t); + var effect = new KeyframeEffectReadOnly(div, {left: ["0px", "100px"]}); + + assert_equals(effect.getComputedTiming().currentIteration, null, + 'currentIteration for orphaned effect'); +}, 'currentIteration of an AnimationEffect without an Animation'); + +// TODO: If iteration duration is Infinity, currentIteration is 0. +// However, we cannot set iteration duration to Infinity in CSS Animation now. + +done(); +</script> +</body> diff --git a/dom/animation/test/css-animations/file_animation-currenttime.html b/dom/animation/test/css-animations/file_animation-currenttime.html new file mode 100644 index 000000000..ec6fb3f1a --- /dev/null +++ b/dom/animation/test/css-animations/file_animation-currenttime.html @@ -0,0 +1,345 @@ +<!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> diff --git a/dom/animation/test/css-animations/file_animation-finish.html b/dom/animation/test/css-animations/file_animation-finish.html new file mode 100644 index 000000000..996cb2ce7 --- /dev/null +++ b/dom/animation/test/css-animations/file_animation-finish.html @@ -0,0 +1,97 @@ +<!doctype html> +<meta charset=utf-8> +<script src="../testcommon.js"></script> +<style> +@keyframes anim { + from { margin-left: 100px; } + to { margin-left: 200px; } +} +</style> +<body> +<script> + +'use strict'; + +const ANIM_PROP_VAL = 'anim 100s'; +const ANIM_DURATION = 100000; // ms + +test(function(t) { + var div = addDiv(t); + div.style.animation = ANIM_PROP_VAL; + div.style.animationIterationCount = 'infinite'; + var animation = div.getAnimations()[0]; + + var threw = false; + try { + animation.finish(); + } catch (e) { + threw = true; + assert_equals(e.name, 'InvalidStateError', + 'Exception should be an InvalidStateError exception when ' + + 'trying to finish an infinite animation'); + } + assert_true(threw, + 'Expect InvalidStateError exception trying to finish an ' + + 'infinite animation'); +}, 'Test exceptions when finishing infinite animation'); + +async_test(function(t) { + var div = addDiv(t); + div.style.animation = ANIM_PROP_VAL + ' paused'; + var animation = div.getAnimations()[0]; + + animation.ready.then(t.step_func(function() { + animation.finish(); + assert_equals(animation.playState, 'finished', + 'The play state of a paused animation should become ' + + '"finished" after finish() is called'); + assert_approx_equals(animation.startTime, + animation.timeline.currentTime - ANIM_DURATION, + 0.0001, + 'The start time of a paused animation should be set ' + + 'after calling finish()'); + t.done(); + })); +}, 'Test finish() while paused'); + +test(function(t) { + var div = addDiv(t); + div.style.animation = ANIM_PROP_VAL + ' paused'; + var animation = div.getAnimations()[0]; + + // Update playbackRate so we can test that the calculated startTime + // respects it + animation.playbackRate = 2; + + // While animation is still pause-pending call finish() + animation.finish(); + + assert_equals(animation.playState, 'finished', + 'The play state of a pause-pending animation should become ' + + '"finished" after finish() is called'); + assert_approx_equals(animation.startTime, + animation.timeline.currentTime - ANIM_DURATION / 2, + 0.0001, + 'The start time of a pause-pending animation should ' + + 'be set after calling finish()'); +}, 'Test finish() while pause-pending with positive playbackRate'); + +test(function(t) { + var div = addDiv(t); + div.style.animation = ANIM_PROP_VAL + ' paused'; + var animation = div.getAnimations()[0]; + + animation.playbackRate = -2; + animation.finish(); + + assert_equals(animation.playState, 'finished', + 'The play state of a pause-pending animation should become ' + + '"finished" after finish() is called'); + assert_equals(animation.startTime, animation.timeline.currentTime, + 'The start time of a pause-pending animation should be ' + + 'set after calling finish()'); +}, 'Test finish() while pause-pending with negative playbackRate'); + +done(); +</script> +</body> diff --git a/dom/animation/test/css-animations/file_animation-finished.html b/dom/animation/test/css-animations/file_animation-finished.html new file mode 100644 index 000000000..c296abb11 --- /dev/null +++ b/dom/animation/test/css-animations/file_animation-finished.html @@ -0,0 +1,93 @@ +<!doctype html> +<meta charset=utf-8> +<script src="../testcommon.js"></script> +<style> +@keyframes abc { + to { transform: translate(10px) } +} +@keyframes def {} +</style> +<body> +<script> +'use strict'; + +const ANIM_PROP_VAL = 'abc 100s'; +const ANIM_DURATION = 100 * MS_PER_SEC; + +promise_test(function(t) { + var div = addDiv(t); + // Set up pending animation + div.style.animation = ANIM_PROP_VAL; + var animation = div.getAnimations()[0]; + var previousFinishedPromise = animation.finished; + // Set up listeners on finished promise + var retPromise = animation.finished.then(function() { + assert_unreached('finished promise is fulfilled'); + }).catch(function(err) { + assert_equals(err.name, 'AbortError', + 'finished promise is rejected with AbortError'); + assert_not_equals(animation.finished, previousFinishedPromise, + 'Finished promise should change after the original is ' + + 'rejected'); + }); + + // Now cancel the animation and flush styles + div.style.animation = ''; + window.getComputedStyle(div).animation; + + return retPromise; +}, 'finished promise is rejected when an animation is cancelled by resetting ' + + 'the animation property'); + +promise_test(function(t) { + var div = addDiv(t); + // As before, but this time instead of removing all animations, simply update + // the list of animations. At least for Firefox, updating is a different + // code path. + + // Set up pending animation + div.style.animation = ANIM_PROP_VAL; + var animation = div.getAnimations()[0]; + var previousFinishedPromise = animation.finished; + + // Set up listeners on finished promise + var retPromise = animation.finished.then(function() { + assert_unreached('finished promise was fulfilled'); + }).catch(function(err) { + assert_equals(err.name, 'AbortError', + 'finished promise is rejected with AbortError'); + assert_not_equals(animation.finished, previousFinishedPromise, + 'Finished promise should change after the original is ' + + 'rejected'); + }); + + // Now update the animation and flush styles + div.style.animation = 'def 100s'; + window.getComputedStyle(div).animation; + + return retPromise; +}, 'finished promise is rejected when an animation is cancelled by changing ' + + 'the animation property'); + +promise_test(function(t) { + var div = addDiv(t); + div.style.animation = ANIM_PROP_VAL; + var animation = div.getAnimations()[0]; + var previousFinishedPromise = animation.finished; + animation.currentTime = ANIM_DURATION; + return animation.finished.then(function() { + div.style.animationPlayState = 'running'; + return waitForAnimationFrames(2); + }).then(function() { + assert_equals(animation.finished, previousFinishedPromise, + 'Should not replay when animation-play-state changes to ' + + '"running" on finished animation'); + assert_equals(animation.currentTime, ANIM_DURATION, + 'currentTime should not change when animation-play-state ' + + 'changes to "running" on finished animation'); + }); +}, 'Test finished promise changes when animationPlayState set to running'); + +done(); +</script> +</body> diff --git a/dom/animation/test/css-animations/file_animation-id.html b/dom/animation/test/css-animations/file_animation-id.html new file mode 100644 index 000000000..dbd5ee0ee --- /dev/null +++ b/dom/animation/test/css-animations/file_animation-id.html @@ -0,0 +1,24 @@ +<!doctype html> +<meta charset=utf-8> +<script src="../testcommon.js"></script> +<style> +@keyframes abc { } +</style> +<body> +<script> +'use strict'; + +test(function(t) { + var div = addDiv(t); + div.style.animation = 'abc 100s'; + var animation = div.getAnimations()[0]; + assert_equals(animation.id, '', 'id for CSS Animation is initially empty'); + animation.id = 'anim' + + assert_equals(animation.id, 'anim', 'animation.id reflects the value set'); +}, 'Animation.id for CSS Animations'); + +done(); +</script> +</body> +</html> diff --git a/dom/animation/test/css-animations/file_animation-pausing.html b/dom/animation/test/css-animations/file_animation-pausing.html new file mode 100644 index 000000000..7176a0c1d --- /dev/null +++ b/dom/animation/test/css-animations/file_animation-pausing.html @@ -0,0 +1,165 @@ +<!doctype html> +<meta charset=utf-8> +<script src="../testcommon.js"></script> +<style> +@keyframes anim { + 0% { margin-left: 0px } + 100% { margin-left: 10000px } +} +</style> +<body> +<script> +'use strict'; + +function getMarginLeft(cs) { + return parseFloat(cs.marginLeft); +} + +promise_test(function(t) { + var div = addDiv(t); + var cs = window.getComputedStyle(div); + div.style.animation = 'anim 1000s paused'; + var animation = div.getAnimations()[0]; + assert_equals(getMarginLeft(cs), 0, + 'Initial value of margin-left is zero'); + animation.play(); + + return animation.ready.then(waitForFrame).then(function() { + assert_true(getMarginLeft(cs) > 0, + 'Playing value of margin-left is greater than zero'); + }); +}, 'play() overrides animation-play-state'); + +promise_test(function(t) { + var div = addDiv(t); + var cs = window.getComputedStyle(div); + div.style.animation = 'anim 1000s paused'; + var animation = div.getAnimations()[0]; + assert_equals(getMarginLeft(cs), 0, + 'Initial value of margin-left is zero'); + + animation.pause(); + div.style.animationPlayState = 'running'; + + return animation.ready.then(waitForFrame).then(function() { + assert_equals(cs.animationPlayState, 'running', + 'animation-play-state is running'); + assert_equals(getMarginLeft(cs), 0, + 'Paused value of margin-left is zero'); + }); +}, 'pause() overrides animation-play-state'); + +promise_test(function(t) { + var div = addDiv(t); + var cs = window.getComputedStyle(div); + div.style.animation = 'anim 1000s paused'; + var animation = div.getAnimations()[0]; + assert_equals(getMarginLeft(cs), 0, + 'Initial value of margin-left is zero'); + animation.play(); + var previousAnimVal; + + return animation.ready.then(function() { + div.style.animationPlayState = 'running'; + cs.animationPlayState; // Trigger style resolution + return waitForFrame(); + }).then(function() { + assert_equals(cs.animationPlayState, 'running', + 'animation-play-state is running'); + div.style.animationPlayState = 'paused'; + return animation.ready; + }).then(function() { + assert_equals(cs.animationPlayState, 'paused', + 'animation-play-state is paused'); + previousAnimVal = getMarginLeft(cs); + return waitForFrame(); + }).then(function() { + assert_equals(getMarginLeft(cs), previousAnimVal, + 'Animated value of margin-left does not change when' + + ' paused by style'); + }); +}, 'play() is overridden by later setting "animation-play-state: paused"'); + +promise_test(function(t) { + var div = addDiv(t); + var cs = window.getComputedStyle(div); + div.style.animation = 'anim 1000s'; + var animation = div.getAnimations()[0]; + assert_equals(getMarginLeft(cs), 0, + 'Initial value of margin-left is zero'); + + // Set the specified style first. If implementations fail to + // apply the style changes first, they will ignore the redundant + // call to play() and fail to correctly override the pause style. + div.style.animationPlayState = 'paused'; + animation.play(); + var previousAnimVal = getMarginLeft(cs); + + return animation.ready.then(waitForFrame).then(function() { + assert_equals(cs.animationPlayState, 'paused', + 'animation-play-state is paused'); + assert_true(getMarginLeft(cs) > previousAnimVal, + 'Playing value of margin-left is increasing'); + }); +}, 'play() flushes pending changes to animation-play-state first'); + +promise_test(function(t) { + var div = addDiv(t); + var cs = window.getComputedStyle(div); + div.style.animation = 'anim 1000s paused'; + var animation = div.getAnimations()[0]; + assert_equals(getMarginLeft(cs), 0, + 'Initial value of margin-left is zero'); + + // Unlike the previous test for play(), since calling pause() is sticky, + // we'll apply it even if the underlying style also says we're paused. + // + // We would like to test that implementations flush styles before running + // pause() but actually there's no style we can currently set that will + // change the behavior of pause(). That may change in the future + // (e.g. if we introduce animation-timeline or animation-playback-rate etc.). + // + // For now this just serves as a sanity check that we do the same thing + // even if we set style before calling the API. + div.style.animationPlayState = 'running'; + animation.pause(); + var previousAnimVal = getMarginLeft(cs); + + return animation.ready.then(waitForFrame).then(function() { + assert_equals(cs.animationPlayState, 'running', + 'animation-play-state is running'); + assert_equals(getMarginLeft(cs), previousAnimVal, + 'Paused value of margin-left does not change'); + }); +}, 'pause() applies pending changes to animation-play-state first'); +// (Note that we can't actually test for this; see comment above, in test-body.) + +promise_test(function(t) { + var div = addDiv(t, { style: 'animation: anim 1000s' }); + var animation = div.getAnimations()[0]; + var readyPromiseRun = false; + + return animation.ready.then(function() { + div.style.animationPlayState = 'paused'; + assert_equals(animation.playState, 'pending', 'Animation is pause pending'); + + // Set current time + animation.currentTime = 5 * MS_PER_SEC; + assert_equals(animation.playState, 'paused', + 'Animation is paused immediately after setting currentTime'); + assert_equals(animation.startTime, null, + 'Animation startTime is unresolved immediately after ' + + 'setting currentTime'); + assert_equals(animation.currentTime, 5 * MS_PER_SEC, + 'Animation currentTime does not change when forcing a ' + + 'pause operation to complete'); + + // The ready promise should now be resolved. If it's not then test will + // probably time out before anything else happens that causes it to resolve. + return animation.ready; + }); +}, 'Setting the current time completes a pending pause'); + +done(); +</script> +</body> diff --git a/dom/animation/test/css-animations/file_animation-playstate.html b/dom/animation/test/css-animations/file_animation-playstate.html new file mode 100644 index 000000000..ce9839f38 --- /dev/null +++ b/dom/animation/test/css-animations/file_animation-playstate.html @@ -0,0 +1,71 @@ +<!doctype html> +<meta charset=utf-8> +<script src="../testcommon.js"></script> +<style> +@keyframes anim { } +</style> +<body> +<script> +'use strict'; + +promise_test(function(t) { + var div = addDiv(t); + var cs = window.getComputedStyle(div); + div.style.animation = 'anim 1000s'; + var animation = div.getAnimations()[0]; + assert_equals(animation.playState, 'pending'); + + return animation.ready.then(function() { + assert_equals(animation.playState, 'running'); + }); +}, 'Animation returns correct playState when running'); + +promise_test(function(t) { + var div = addDiv(t); + var cs = window.getComputedStyle(div); + div.style.animation = 'anim 1000s paused'; + var animation = div.getAnimations()[0]; + assert_equals(animation.playState, 'pending'); + + return animation.ready.then(function() { + assert_equals(animation.playState, 'paused'); + }); +}, 'Animation returns correct playState when paused'); + +promise_test(function(t) { + var div = addDiv(t); + var cs = window.getComputedStyle(div); + div.style.animation = 'anim 1000s'; + var animation = div.getAnimations()[0]; + animation.pause(); + assert_equals(animation.playState, 'pending'); + + return animation.ready.then(function() { + assert_equals(animation.playState, 'paused'); + }); +}, 'Animation.playState updates when paused by script'); + +test(function(t) { + var div = addDiv(t); + var cs = window.getComputedStyle(div); + div.style.animation = 'anim 1000s paused'; + var animation = div.getAnimations()[0]; + div.style.animationPlayState = 'running'; + + // This test also checks that calling playState flushes style + assert_equals(animation.playState, 'pending', + 'Animation.playState reports pending after updating' + + ' animation-play-state (got: ' + animation.playState + ')'); +}, 'Animation.playState updates when resumed by setting style'); + +test(function(t) { + var div = addDiv(t); + div.style.animation = 'anim 1000s'; + var animation = div.getAnimations()[0]; + animation.cancel(); + assert_equals(animation.playState, 'idle'); +}, 'Animation returns correct playState when cancelled'); + +done(); +</script> +</body> diff --git a/dom/animation/test/css-animations/file_animation-ready.html b/dom/animation/test/css-animations/file_animation-ready.html new file mode 100644 index 000000000..9318a1a18 --- /dev/null +++ b/dom/animation/test/css-animations/file_animation-ready.html @@ -0,0 +1,149 @@ +<!doctype html> +<meta charset=utf-8> +<script src="../testcommon.js"></script> +<style> +@keyframes abc { + to { transform: translate(10px) } +} +</style> +<body> +<script> +'use strict'; + +promise_test(function(t) { + var div = addDiv(t); + div.style.animation = 'abc 100s paused'; + var animation = div.getAnimations()[0]; + var originalReadyPromise = animation.ready; + + return animation.ready.then(function() { + div.style.animationPlayState = 'running'; + assert_not_equals(animation.ready, originalReadyPromise, + 'After updating animation-play-state a new ready promise' + + ' object is created'); + }); +}, 'A new ready promise is created when setting animation-play-state: running'); + +promise_test(function(t) { + var div = addDiv(t); + + // Set up pending animation + div.style.animation = 'abc 100s'; + var animation = div.getAnimations()[0]; + assert_equals(animation.playState, 'pending', + 'Animation is initially pending'); + + // Set up listeners on ready promise + var retPromise = animation.ready.then(function() { + assert_unreached('ready promise is fulfilled'); + }).catch(function(err) { + assert_equals(err.name, 'AbortError', + 'ready promise is rejected with AbortError'); + }); + + // Now cancel the animation and flush styles + div.style.animation = ''; + window.getComputedStyle(div).animation; + + return retPromise; +}, 'ready promise is rejected when an animation is cancelled by resetting' + + ' the animation property'); + +promise_test(function(t) { + var div = addDiv(t); + + // As before, but this time instead of removing all animations, simply update + // the list of animations. At least for Firefox, updating is a different + // code path. + + // Set up pending animation + div.style.animation = 'abc 100s'; + var animation = div.getAnimations()[0]; + assert_equals(animation.playState, 'pending', + 'Animation is initially pending'); + + // Set up listeners on ready promise + var retPromise = animation.ready.then(function() { + assert_unreached('ready promise was fulfilled'); + }).catch(function(err) { + assert_equals(err.name, 'AbortError', + 'ready promise is rejected with AbortError'); + }); + + // Now update the animation and flush styles + div.style.animation = 'def 100s'; + window.getComputedStyle(div).animation; + + return retPromise; +}, 'ready promise is rejected when an animation is cancelled by updating' + + ' the animation property'); + +promise_test(function(t) { + var div = addDiv(t, { style: 'animation: abc 100s' }); + var animation = div.getAnimations()[0]; + var originalReadyPromise = animation.ready; + + return animation.ready.then(function() { + div.style.animationPlayState = 'paused'; + assert_not_equals(animation.ready, originalReadyPromise, + 'A new Promise object is generated when setting' + + ' animation-play-state: paused'); + }); +}, 'A new ready promise is created when setting animation-play-state: paused'); + +promise_test(function(t) { + var div = addDiv(t, { style: 'animation: abc 100s' }); + var animation = div.getAnimations()[0]; + + return animation.ready.then(function() { + div.style.animationPlayState = 'paused'; + var firstReadyPromise = animation.ready; + animation.pause(); + assert_equals(animation.ready, firstReadyPromise, + 'Ready promise objects are identical after redundant pause'); + }); +}, 'Pausing twice re-uses the same Promise'); + +promise_test(function(t) { + var div = addDiv(t, { style: 'animation: abc 100s' }); + var animation = div.getAnimations()[0]; + + return animation.ready.then(function() { + div.style.animationPlayState = 'paused'; + + // Flush style and verify we're pending at the same time + assert_equals(animation.playState, 'pending', 'Animation is pending'); + var pauseReadyPromise = animation.ready; + + // Now play again immediately + div.style.animationPlayState = 'running'; + assert_equals(animation.playState, 'pending', 'Animation is still pending'); + assert_equals(animation.ready, pauseReadyPromise, + 'The pause Promise is re-used when playing while waiting' + + ' to pause'); + + return animation.ready; + }).then(function() { + assert_equals(animation.playState, 'running', + 'Animation is running after aborting a pause'); + }); +}, 'If a pause operation is interrupted, the ready promise is reused'); + +promise_test(function(t) { + var div = addDiv(t, { style: 'animation: abc 100s' }); + var animation = div.getAnimations()[0]; + + return animation.ready.then(function() { + div.style.animationPlayState = 'paused'; + return animation.ready; + }).then(function(resolvedAnimation) { + assert_equals(resolvedAnimation, animation, + 'Promise received when ready Promise for a pause operation' + + ' is completed is the animation on which the pause was' + + ' performed'); + }); +}, 'When a pause is complete the Promise callback gets the correct animation'); + +done(); +</script> +</body> diff --git a/dom/animation/test/css-animations/file_animation-reverse.html b/dom/animation/test/css-animations/file_animation-reverse.html new file mode 100644 index 000000000..5060fa55f --- /dev/null +++ b/dom/animation/test/css-animations/file_animation-reverse.html @@ -0,0 +1,29 @@ +<!doctype html> +<meta charset=utf-8> +<script src="../testcommon.js"></script> +<style> +@keyframes anim { + to { transform: translate(100px) } +} +</style> +<body> +<script> +'use strict'; + +test(function(t) { + var div = addDiv(t, { style: 'animation: anim 100s' }); + var animation = div.getAnimations()[0]; + div.style.animation = ""; + flushComputedStyle(div); + + assert_equals(animation.currentTime, null); + animation.reverse(); + + assert_equals(animation.currentTime, 100 * MS_PER_SEC, + 'animation.currentTime should be its effect end'); +}, 'reverse() from idle state starts playing the animation'); + + +done(); +</script> +</body> diff --git a/dom/animation/test/css-animations/file_animation-starttime.html b/dom/animation/test/css-animations/file_animation-starttime.html new file mode 100644 index 000000000..46144464c --- /dev/null +++ b/dom/animation/test/css-animations/file_animation-starttime.html @@ -0,0 +1,383 @@ +<!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> diff --git a/dom/animation/test/css-animations/file_animations-dynamic-changes.html b/dom/animation/test/css-animations/file_animations-dynamic-changes.html new file mode 100644 index 000000000..8f16536ae --- /dev/null +++ b/dom/animation/test/css-animations/file_animations-dynamic-changes.html @@ -0,0 +1,154 @@ +<!doctype html> +<meta charset=utf-8> +<script src="../testcommon.js"></script> +<style> +@keyframes anim1 { + to { left: 100px } +} +@keyframes anim2 { } +</style> +<body> +<script> +'use strict'; + +promise_test(function(t) { + var div = addDiv(t); + div.style.animation = 'anim1 100s'; + var originalAnimation = div.getAnimations()[0]; + var originalStartTime; + var originalCurrentTime; + + // Wait a moment so we can confirm the startTime doesn't change (and doesn't + // simply reflect the current time). + return originalAnimation.ready.then(function() { + originalStartTime = originalAnimation.startTime; + originalCurrentTime = originalAnimation.currentTime; + + // Wait a moment so we can confirm the startTime doesn't change (and + // doesn't simply reflect the current time). + return waitForFrame(); + }).then(function() { + div.style.animationDuration = '200s'; + var animation = div.getAnimations()[0]; + assert_equals(animation, originalAnimation, + 'The same Animation is returned after updating' + + ' animation duration'); + assert_equals(animation.startTime, originalStartTime, + 'Animations returned by getAnimations preserve' + + ' their startTime even when they are updated'); + // Sanity check + assert_not_equals(animation.currentTime, originalCurrentTime, + 'Animation.currentTime has updated in next' + + ' requestAnimationFrame callback'); + }); +}, 'Animations preserve their startTime when changed'); + +test(function(t) { + var div = addDiv(t); + div.style.animation = 'anim1 100s, anim1 100s'; + + // Store original state + var animations = div.getAnimations(); + var animation1 = animations[0]; + var animation2 = animations[1]; + + // Update first in list + div.style.animationDuration = '200s, 100s'; + animations = div.getAnimations(); + assert_equals(animations[0], animation1, + 'First Animation is in same position after update'); + assert_equals(animations[1], animation2, + 'Second Animation is in same position after update'); +}, 'Updated Animations maintain their order in the list'); + +promise_test(function(t) { + var div = addDiv(t); + div.style.animation = 'anim1 200s, anim1 100s'; + + // Store original state + var animations = div.getAnimations(); + var animation1 = animations[0]; + var animation2 = animations[1]; + + // Wait before continuing so we can compare start times (otherwise the + // new Animation objects and existing Animation objects will all have the same + // start time). + return waitForAllAnimations(animations).then(waitForFrame).then(function() { + // Swap duration of first and second in list and prepend animation at the + // same time + div.style.animation = 'anim1 100s, anim1 100s, anim1 200s'; + animations = div.getAnimations(); + assert_true(animations[0] !== animation1 && animations[0] !== animation2, + 'New Animation is prepended to start of list'); + assert_equals(animations[1], animation1, + 'First Animation is in second position after update'); + assert_equals(animations[2], animation2, + 'Second Animation is in third position after update'); + assert_equals(animations[1].startTime, animations[2].startTime, + 'Old Animations have the same start time'); + // TODO: Check that animations[0].startTime === null + return animations[0].ready; + }).then(function() { + assert_true(animations[0].startTime > animations[1].startTime, + 'New Animation has later start time'); + }); +}, 'Only the startTimes of existing animations are preserved'); + +promise_test(function(t) { + var div = addDiv(t); + div.style.animation = 'anim1 100s, anim1 100s'; + var secondAnimation = div.getAnimations()[1]; + + // Wait before continuing so we can compare start times + return secondAnimation.ready.then(waitForFrame).then(function() { + // Trim list of animations + div.style.animationName = 'anim1'; + var animations = div.getAnimations(); + assert_equals(animations.length, 1, 'List of Animations was trimmed'); + assert_equals(animations[0], secondAnimation, + 'Remaining Animation is the second one in the list'); + assert_equals(typeof(animations[0].startTime), 'number', + 'Remaining Animation has resolved startTime'); + assert_true(animations[0].startTime < animations[0].timeline.currentTime, + 'Remaining Animation preserves startTime'); + }); +}, 'Animations are removed from the start of the list while preserving' + + ' the state of existing Animations'); + +promise_test(function(t) { + var div = addDiv(t); + div.style.animation = 'anim1 100s'; + var firstAddedAnimation = div.getAnimations()[0], + secondAddedAnimation, + animations; + + // Wait and add second Animation + return firstAddedAnimation.ready.then(waitForFrame).then(function() { + div.style.animation = 'anim1 100s, anim1 100s'; + secondAddedAnimation = div.getAnimations()[0]; + + // Wait again and add another Animation + return secondAddedAnimation.ready.then(waitForFrame); + }).then(function() { + div.style.animation = 'anim1 100s, anim2 100s, anim1 100s'; + animations = div.getAnimations(); + assert_not_equals(firstAddedAnimation, secondAddedAnimation, + 'New Animations are added to start of the list'); + assert_equals(animations[0], secondAddedAnimation, + 'Second Animation remains in same position after' + + ' interleaving'); + assert_equals(animations[2], firstAddedAnimation, + 'First Animation remains in same position after' + + ' interleaving'); + return animations[1].ready; + }).then(function() { + assert_true(animations[1].startTime > animations[0].startTime, + 'Interleaved animation starts later than existing animations'); + assert_true(animations[0].startTime > animations[2].startTime, + 'Original animations retain their start time'); + }); +}, 'Animation state is preserved when interleaving animations in list'); + +done(); +</script> +</body> diff --git a/dom/animation/test/css-animations/file_cssanimation-animationname.html b/dom/animation/test/css-animations/file_cssanimation-animationname.html new file mode 100644 index 000000000..fd69d8577 --- /dev/null +++ b/dom/animation/test/css-animations/file_cssanimation-animationname.html @@ -0,0 +1,37 @@ +<!doctype html> +<meta charset=utf-8> +<script src="../testcommon.js"></script> +<style> +@keyframes xyz { + to { left: 100px } +} +</style> +<body> +<script> +'use strict'; + +test(function(t) { + var div = addDiv(t); + div.style.animation = 'xyz 100s'; + assert_equals(div.getAnimations()[0].animationName, 'xyz', + 'Animation name matches keyframes rule name'); +}, 'Animation name makes keyframe rule'); + +test(function(t) { + var div = addDiv(t); + div.style.animation = 'x\\yz 100s'; + assert_equals(div.getAnimations()[0].animationName, 'xyz', + 'Escaped animation name matches keyframes rule name'); +}, 'Escaped animation name'); + +test(function(t) { + var div = addDiv(t); + div.style.animation = 'x\\79 z 100s'; + assert_equals(div.getAnimations()[0].animationName, 'xyz', + 'Hex-escaped animation name matches keyframes rule' + + ' name'); +}, 'Animation name with hex-escape'); + +done(); +</script> +</body> diff --git a/dom/animation/test/css-animations/file_document-get-animations.html b/dom/animation/test/css-animations/file_document-get-animations.html new file mode 100644 index 000000000..abe02d7fc --- /dev/null +++ b/dom/animation/test/css-animations/file_document-get-animations.html @@ -0,0 +1,276 @@ +<!doctype html> +<meta charset=utf-8> +<script src="../testcommon.js"></script> +<style> +@keyframes animLeft { + to { left: 100px } +} +@keyframes animTop { + to { top: 100px } +} +@keyframes animBottom { + to { bottom: 100px } +} +@keyframes animRight { + to { right: 100px } +} +</style> +<body> +<script> +'use strict'; + +test(function(t) { + assert_equals(document.getAnimations().length, 0, + 'getAnimations returns an empty sequence for a document' + + ' with no animations'); +}, 'getAnimations for non-animated content'); + +test(function(t) { + var div = addDiv(t); + + // Add an animation + div.style.animation = 'animLeft 100s'; + assert_equals(document.getAnimations().length, 1, + 'getAnimations returns a running CSS Animation'); + + // Add another animation + div.style.animation = 'animLeft 100s, animTop 100s'; + assert_equals(document.getAnimations().length, 2, + 'getAnimations returns two running CSS Animations'); + + // Remove both + div.style.animation = ''; + assert_equals(document.getAnimations().length, 0, + 'getAnimations returns no running CSS Animations'); +}, 'getAnimations for CSS Animations'); + +test(function(t) { + var div = addDiv(t); + div.style.animation = 'animLeft 100s, animTop 100s, animRight 100s, ' + + 'animBottom 100s'; + + var animations = document.getAnimations(); + assert_equals(animations.length, 4, + 'getAnimations returns all running CSS Animations'); + assert_equals(animations[0].animationName, 'animLeft', + 'Order of first animation returned'); + assert_equals(animations[1].animationName, 'animTop', + 'Order of second animation returned'); + assert_equals(animations[2].animationName, 'animRight', + 'Order of third animation returned'); + assert_equals(animations[3].animationName, 'animBottom', + 'Order of fourth animation returned'); +}, 'Order of CSS Animations - within an element'); + +test(function(t) { + var div1 = addDiv(t, { style: 'animation: animLeft 100s' }); + var div2 = addDiv(t, { style: 'animation: animLeft 100s' }); + var div3 = addDiv(t, { style: 'animation: animLeft 100s' }); + var div4 = addDiv(t, { style: 'animation: animLeft 100s' }); + + var animations = document.getAnimations(); + assert_equals(animations.length, 4, + 'getAnimations returns all running CSS Animations'); + assert_equals(animations[0].effect.target, div1, + 'Order of first animation returned'); + assert_equals(animations[1].effect.target, div2, + 'Order of second animation returned'); + assert_equals(animations[2].effect.target, div3, + 'Order of third animation returned'); + assert_equals(animations[3].effect.target, div4, + 'Order of fourth animation returned'); + + // Order should be depth-first pre-order so add some depth as follows: + // + // <parent> + // / | + // 2 3 + // / \ + // 1 4 + // + // Which should give: 2, 1, 4, 3 + div2.appendChild(div1); + div2.appendChild(div4); + animations = document.getAnimations(); + assert_equals(animations[0].effect.target, div2, + 'Order of first animation returned after tree surgery'); + assert_equals(animations[1].effect.target, div1, + 'Order of second animation returned after tree surgery'); + assert_equals(animations[2].effect.target, div4, + 'Order of third animation returned after tree surgery'); + assert_equals(animations[3].effect.target, div3, + 'Order of fourth animation returned after tree surgery'); + +}, 'Order of CSS Animations - across elements'); + +test(function(t) { + var div1 = addDiv(t, { style: 'animation: animLeft 100s, animTop 100s' }); + var div2 = addDiv(t, { style: 'animation: animBottom 100s' }); + + var expectedResults = [ [ div1, 'animLeft' ], + [ div1, 'animTop' ], + [ div2, 'animBottom' ] ]; + var animations = document.getAnimations(); + assert_equals(animations.length, expectedResults.length, + 'getAnimations returns all running CSS Animations'); + animations.forEach(function(anim, i) { + assert_equals(anim.effect.target, expectedResults[i][0], + 'Target of animation in position ' + i); + assert_equals(anim.animationName, expectedResults[i][1], + 'Name of animation in position ' + i); + }); + + // Modify tree structure and animation list + div2.appendChild(div1); + div1.style.animation = 'animLeft 100s, animRight 100s, animTop 100s'; + + expectedResults = [ [ div2, 'animBottom' ], + [ div1, 'animLeft' ], + [ div1, 'animRight' ], + [ div1, 'animTop' ] ]; + animations = document.getAnimations(); + assert_equals(animations.length, expectedResults.length, + 'getAnimations returns all running CSS Animations after ' + + 'making changes'); + animations.forEach(function(anim, i) { + assert_equals(anim.effect.target, expectedResults[i][0], + 'Target of animation in position ' + i + ' after changes'); + assert_equals(anim.animationName, expectedResults[i][1], + 'Name of animation in position ' + i + ' after changes'); + }); +}, 'Order of CSS Animations - across and within elements'); + +test(function(t) { + var div = addDiv(t, { style: 'animation: animLeft 100s, animTop 100s' }); + var animLeft = document.getAnimations()[0]; + assert_equals(animLeft.animationName, 'animLeft', + 'Originally, animLeft animation comes first'); + + // Disassociate animLeft from markup and restart + div.style.animation = 'animTop 100s'; + animLeft.play(); + + var animations = document.getAnimations(); + assert_equals(animations.length, 2, + 'getAnimations returns markup-bound and free animations'); + assert_equals(animations[0].animationName, 'animTop', + 'Markup-bound animations come first'); + assert_equals(animations[1], animLeft, 'Free animations come last'); +}, 'Order of CSS Animations - markup-bound vs free animations'); + +test(function(t) { + var div = addDiv(t, { style: 'animation: animLeft 100s, animTop 100s' }); + var animLeft = document.getAnimations()[0]; + var animTop = document.getAnimations()[1]; + + // Disassociate both animations from markup and restart in opposite order + div.style.animation = ''; + animTop.play(); + animLeft.play(); + + var animations = document.getAnimations(); + assert_equals(animations.length, 2, + 'getAnimations returns free animations'); + assert_equals(animations[0], animTop, + 'Free animations are returned in the order they are started'); + assert_equals(animations[1], animLeft, + 'Animations started later are returned later'); + + // Restarting an animation should have no effect + animTop.cancel(); + animTop.play(); + assert_equals(document.getAnimations()[0], animTop, + 'After restarting, the ordering of free animations' + + ' does not change'); +}, 'Order of CSS Animations - free animations'); + +test(function(t) { + // Add an animation first + var div = addDiv(t, { style: 'animation: animLeft 100s' }); + div.style.top = '0px'; + div.style.transition = 'all 100s'; + flushComputedStyle(div); + + // *Then* add a transition + div.style.top = '100px'; + flushComputedStyle(div); + + // Although the transition was added later, it should come first in the list + var animations = document.getAnimations(); + assert_equals(animations.length, 2, + 'Both CSS animations and transitions are returned'); + assert_class_string(animations[0], 'CSSTransition', 'Transition comes first'); + assert_class_string(animations[1], 'CSSAnimation', 'Animation comes second'); +}, 'Order of CSS Animations and CSS Transitions'); + +test(function(t) { + var div = addDiv(t, { style: 'animation: animLeft 100s forwards' }); + div.getAnimations()[0].finish(); + assert_equals(document.getAnimations().length, 1, + 'Forwards-filling CSS animations are returned'); +}, 'Finished but filling CSS Animations are returned'); + +test(function(t) { + var div = addDiv(t, { style: 'animation: animLeft 100s' }); + div.getAnimations()[0].finish(); + assert_equals(document.getAnimations().length, 0, + 'Non-filling finished CSS animations are not returned'); +}, 'Finished but not filling CSS Animations are not returned'); + +test(function(t) { + var div = addDiv(t, { style: 'animation: animLeft 100s 100s' }); + assert_equals(document.getAnimations().length, 1, + 'Yet-to-start CSS animations are returned'); +}, 'Yet-to-start CSS Animations are returned'); + +test(function(t) { + var div = addDiv(t, { style: 'animation: animLeft 100s' }); + div.getAnimations()[0].cancel(); + assert_equals(document.getAnimations().length, 0, + 'CSS animations cancelled by the API are not returned'); +}, 'CSS Animations cancelled via the API are not returned'); + +test(function(t) { + var div = addDiv(t, { style: 'animation: animLeft 100s' }); + var anim = div.getAnimations()[0]; + anim.cancel(); + anim.play(); + assert_equals(document.getAnimations().length, 1, + 'CSS animations cancelled and restarted by the API are ' + + 'returned'); +}, 'CSS Animations cancelled and restarted via the API are returned'); + +test(function(t) { + addStyle(t, { '#parent::after': 'animation: animLeft 10s;', + '#parent::before': 'animation: animRight 10s;' }); + // create two divs with these arrangement: + // parent + // ::before, + // ::after + // | + // child + var parent = addDiv(t, { 'id': 'parent' }); + var child = addDiv(t); + parent.appendChild(child); + [parent, child].forEach((div) => { + div.setAttribute('style', 'animation: animBottom 10s'); + }); + + var anims = document.getAnimations(); + assert_equals(anims.length, 4, + 'CSS animations on both pseudo-elements and elements ' + + 'are returned'); + assert_equals(anims[0].effect.target, parent, + 'The animation targeting the parent element comes first'); + assert_equals(anims[1].effect.target.type, '::before', + 'The animation targeting the ::before element comes second'); + assert_equals(anims[2].effect.target.type, '::after', + 'The animation targeting the ::after element comes third'); + assert_equals(anims[3].effect.target, child, + 'The animation targeting the child element comes last'); +}, 'CSS Animations targetting (pseudo-)elements should have correct order ' + + 'after sorting'); + +done(); +</script> +</body> diff --git a/dom/animation/test/css-animations/file_effect-target.html b/dom/animation/test/css-animations/file_effect-target.html new file mode 100644 index 000000000..006028e34 --- /dev/null +++ b/dom/animation/test/css-animations/file_effect-target.html @@ -0,0 +1,54 @@ +<!doctype html> +<meta charset=utf-8> +<script src="../testcommon.js"></script> +<style> +@keyframes anim { } +</style> +<body> +<script> +'use strict'; + +test(function(t) { + var div = addDiv(t); + div.style.animation = 'anim 100s'; + var animation = div.getAnimations()[0]; + assert_equals(animation.effect.target, div, + 'Animation.target is the animatable div'); +}, 'Returned CSS animations have the correct effect target'); + +test(function(t) { + addStyle(t, { '.after::after': 'animation: anim 10s, anim 100s;' }); + var div = addDiv(t, { class: 'after' }); + var anims = document.getAnimations(); + assert_equals(anims.length, 2, + 'Got animations running on ::after pseudo element'); + assert_equals(anims[0].effect.target, anims[1].effect.target, + 'Both animations return the same target object'); +}, 'effect.target should return the same CSSPseudoElement object each time'); + +test(function(t) { + addStyle(t, { '.after::after': 'animation: anim 10s;' }); + var div = addDiv(t, { class: 'after' }); + var pseudoTarget = document.getAnimations()[0].effect.target; + var effect = new KeyframeEffectReadOnly(pseudoTarget, + { background: ["blue", "red"] }, + 3 * MS_PER_SEC); + var newAnim = new Animation(effect, document.timeline); + newAnim.play(); + var anims = document.getAnimations(); + assert_equals(anims.length, 2, + 'Got animations running on ::after pseudo element'); + assert_not_equals(anims[0], newAnim, + 'The scriped-generated animation appears last'); + assert_equals(newAnim.effect.target, pseudoTarget, + 'The effect.target of the scripted-generated animation is ' + + 'the same as the one from the argument of ' + + 'KeyframeEffectReadOnly constructor'); + assert_equals(anims[0].effect.target, newAnim.effect.target, + 'Both animations return the same target object'); +}, 'effect.target from the script-generated animation should return the same ' + + 'CSSPseudoElement object as that from the CSS generated animation'); + +done(); +</script> +</body> diff --git a/dom/animation/test/css-animations/file_element-get-animations.html b/dom/animation/test/css-animations/file_element-get-animations.html new file mode 100644 index 000000000..68386c98b --- /dev/null +++ b/dom/animation/test/css-animations/file_element-get-animations.html @@ -0,0 +1,445 @@ +<!doctype html> +<meta charset=utf-8> +<script src="../testcommon.js"></script> +<style> +@keyframes anim1 { + to { left: 100px } +} +@keyframes anim2 { + to { top: 100px } +} +@keyframes multiPropAnim { + to { background: green, opacity: 0.5, left: 100px, top: 100px } +} +@keyframes empty { } +</style> +<body> +<script> +'use strict'; + +test(function(t) { + var div = addDiv(t); + assert_equals(div.getAnimations().length, 0, + 'getAnimations returns an empty sequence for an element' + + ' with no animations'); +}, 'getAnimations for non-animated content'); + +promise_test(function(t) { + var div = addDiv(t); + + // FIXME: This test does too many things. It should be split up. + + // Add an animation + div.style.animation = 'anim1 100s'; + var animations = div.getAnimations(); + assert_equals(animations.length, 1, + 'getAnimations returns an Animation running CSS Animations'); + return animations[0].ready.then(function() { + var startTime = animations[0].startTime; + assert_true(startTime > 0 && startTime <= document.timeline.currentTime, + 'CSS animation has a sensible start time'); + + // Wait a moment then add a second animation. + // + // We wait for the next frame so that we can test that the start times of + // the animations differ. + return waitForFrame(); + }).then(function() { + div.style.animation = 'anim1 100s, anim2 100s'; + animations = div.getAnimations(); + assert_equals(animations.length, 2, + 'getAnimations returns one Animation for each value of' + + ' animation-name'); + // Wait until both Animations are ready + // (We don't make any assumptions about the order of the Animations since + // that is the purpose of the following test.) + return waitForAllAnimations(animations); + }).then(function() { + assert_true(animations[0].startTime < animations[1].startTime, + 'Additional Animations for CSS animations start after the original' + + ' animation and appear later in the list'); + }); +}, 'getAnimations for CSS Animations'); + +test(function(t) { + var div = addDiv(t, { style: 'animation: anim1 100s' }); + assert_class_string(div.getAnimations()[0], 'CSSAnimation', + 'Interface of returned animation is CSSAnimation'); +}, 'getAnimations returns CSSAnimation objects for CSS Animations'); + +test(function(t) { + var div = addDiv(t); + + // Add an animation that targets multiple properties + div.style.animation = 'multiPropAnim 100s'; + assert_equals(div.getAnimations().length, 1, + 'getAnimations returns only one Animation for a CSS Animation' + + ' that targets multiple properties'); +}, 'getAnimations for multi-property animations'); + +promise_test(function(t) { + var div = addDiv(t); + + // Add an animation + div.style.backgroundColor = 'red'; + div.style.animation = 'anim1 100s'; + window.getComputedStyle(div).backgroundColor; + + // Wait until a frame after the animation starts, then add a transition + var animations = div.getAnimations(); + return animations[0].ready.then(waitForFrame).then(function() { + div.style.transition = 'all 100s'; + div.style.backgroundColor = 'green'; + + animations = div.getAnimations(); + assert_equals(animations.length, 2, + 'getAnimations returns Animations for both animations and' + + ' transitions that run simultaneously'); + assert_class_string(animations[0], 'CSSTransition', + 'First-returned animation is the CSS Transition'); + assert_class_string(animations[1], 'CSSAnimation', + 'Second-returned animation is the CSS Animation'); + }); +}, 'getAnimations for both CSS Animations and CSS Transitions at once'); + +async_test(function(t) { + var div = addDiv(t); + + // Set up event listener + div.addEventListener('animationend', t.step_func(function() { + assert_equals(div.getAnimations().length, 0, + 'getAnimations does not return Animations for finished ' + + ' (and non-forwards-filling) CSS Animations'); + t.done(); + })); + + // Add a very short animation + div.style.animation = 'anim1 0.01s'; +}, 'getAnimations for CSS Animations that have finished'); + +async_test(function(t) { + var div = addDiv(t); + + // Set up event listener + div.addEventListener('animationend', t.step_func(function() { + assert_equals(div.getAnimations().length, 1, + 'getAnimations returns Animations for CSS Animations that have' + + ' finished but are filling forwards'); + t.done(); + })); + + // Add a very short animation + div.style.animation = 'anim1 0.01s forwards'; +}, 'getAnimations for CSS Animations that have finished but are' + + ' forwards filling'); + +test(function(t) { + var div = addDiv(t); + div.style.animation = 'none 100s'; + + var animations = div.getAnimations(); + assert_equals(animations.length, 0, + 'getAnimations returns an empty sequence for an element' + + ' with animation-name: none'); + + div.style.animation = 'none 100s, anim1 100s'; + animations = div.getAnimations(); + assert_equals(animations.length, 1, + 'getAnimations returns Animations only for those CSS Animations whose' + + ' animation-name is not none'); +}, 'getAnimations for CSS Animations with animation-name: none'); + +test(function(t) { + var div = addDiv(t); + div.style.animation = 'missing 100s'; + var animations = div.getAnimations(); + assert_equals(animations.length, 0, + 'getAnimations returns an empty sequence for an element' + + ' with animation-name: missing'); + + div.style.animation = 'anim1 100s, missing 100s'; + animations = div.getAnimations(); + assert_equals(animations.length, 1, + 'getAnimations returns Animations only for those CSS Animations whose' + + ' animation-name is found'); +}, 'getAnimations for CSS Animations with animation-name: missing'); + +promise_test(function(t) { + var div = addDiv(t); + div.style.animation = 'anim1 100s, notyet 100s'; + var animations = div.getAnimations(); + assert_equals(animations.length, 1, + 'getAnimations initally only returns Animations for CSS Animations whose' + + ' animation-name is found'); + + return animations[0].ready.then(waitForFrame).then(function() { + var keyframes = '@keyframes notyet { to { left: 100px; } }'; + document.styleSheets[0].insertRule(keyframes, 0); + animations = div.getAnimations(); + assert_equals(animations.length, 2, + 'getAnimations includes Animation when @keyframes rule is added' + + ' later'); + return waitForAllAnimations(animations); + }).then(function() { + assert_true(animations[0].startTime < animations[1].startTime, + 'Newly added animation has a later start time'); + document.styleSheets[0].deleteRule(0); + }); +}, 'getAnimations for CSS Animations where the @keyframes rule is added' + + ' later'); + +test(function(t) { + var div = addDiv(t); + div.style.animation = 'anim1 100s, anim1 100s'; + assert_equals(div.getAnimations().length, 2, + 'getAnimations returns one Animation for each CSS animation-name' + + ' even if the names are duplicated'); +}, 'getAnimations for CSS Animations with duplicated animation-name'); + +test(function(t) { + var div = addDiv(t); + div.style.animation = 'empty 100s'; + assert_equals(div.getAnimations().length, 1, + 'getAnimations returns Animations for CSS animations with an' + + ' empty keyframes rule'); +}, 'getAnimations for CSS Animations with empty keyframes rule'); + +promise_test(function(t) { + var div = addDiv(t); + div.style.animation = 'anim1 100s 100s'; + var animations = div.getAnimations(); + assert_equals(animations.length, 1, + 'getAnimations returns animations for CSS animations whose' + + ' delay makes them start later'); + return animations[0].ready.then(waitForFrame).then(function() { + assert_true(animations[0].startTime <= document.timeline.currentTime, + 'For CSS Animations in delay phase, the start time of the Animation is' + + ' not in the future'); + }); +}, 'getAnimations for CSS animations in delay phase'); + +test(function(t) { + var div = addDiv(t); + div.style.animation = 'anim1 0s 100s'; + assert_equals(div.getAnimations().length, 1, + 'getAnimations returns animations for CSS animations whose' + + ' duration is zero'); + div.remove(); +}, 'getAnimations for zero-duration CSS Animations'); + +test(function(t) { + var div = addDiv(t); + div.style.animation = 'anim1 100s'; + var originalAnimation = div.getAnimations()[0]; + + // Update pause state (an Animation change) + div.style.animationPlayState = 'paused'; + var pendingAnimation = div.getAnimations()[0]; + assert_equals(pendingAnimation.playState, 'pending', + 'animation\'s play state is updated'); + assert_equals(originalAnimation, pendingAnimation, + 'getAnimations returns the same objects even when their' + + ' play state changes'); + + // Update duration (an Animation change) + div.style.animationDuration = '200s'; + var extendedAnimation = div.getAnimations()[0]; + // FIXME: Check extendedAnimation.effect.timing.duration has changed once the + // API is available + assert_equals(originalAnimation, extendedAnimation, + 'getAnimations returns the same objects even when their' + + ' duration changes'); +}, 'getAnimations returns objects with the same identity'); + +test(function(t) { + var div = addDiv(t); + div.style.animation = 'anim1 100s'; + + assert_equals(div.getAnimations().length, 1, + 'getAnimations returns an animation before cancelling'); + + var animation = div.getAnimations()[0]; + + animation.cancel(); + assert_equals(div.getAnimations().length, 0, + 'getAnimations does not return cancelled animations'); + + animation.play(); + assert_equals(div.getAnimations().length, 1, + 'getAnimations returns cancelled animations that have been re-started'); + +}, 'getAnimations for CSS Animations that are cancelled'); + +promise_test(function(t) { + var div = addDiv(t); + div.style.animation = 'anim2 100s'; + + return div.getAnimations()[0].ready.then(function() { + // Prepend to the list and test that even though anim1 was triggered + // *after* anim2, it should come first because it appears first + // in the animation-name property. + div.style.animation = 'anim1 100s, anim2 100s'; + var anims = div.getAnimations(); + assert_equals(anims[0].animationName, 'anim1', + 'animation order after prepending to list'); + assert_equals(anims[1].animationName, 'anim2', + 'animation order after prepending to list'); + + // Normally calling cancel and play would this push anim1 to the top of + // the stack but it shouldn't for CSS animations that map an the + // animation-name property. + var anim1 = anims[0]; + anim1.cancel(); + anim1.play(); + anims = div.getAnimations(); + assert_equals(anims[0].animationName, 'anim1', + 'animation order after cancelling and restarting'); + assert_equals(anims[1].animationName, 'anim2', + 'animation order after cancelling and restarting'); + }); +}, 'getAnimations for CSS Animations follows animation-name order'); + +test(function(t) { + addStyle(t, { '#target::after': 'animation: anim1 10s;', + '#target::before': 'animation: anim1 10s;' }); + var target = addDiv(t, { 'id': 'target' }); + target.style.animation = 'anim1 100s'; + + var animations = target.getAnimations({ subtree: false }); + assert_equals(animations.length, 1, + 'Should find only the element'); + assert_equals(animations[0].effect.target, target, + 'Effect target should be the element'); +}, 'Test AnimationFilter{ subtree: false } with single element'); + +test(function(t) { + addStyle(t, { '#target::after': 'animation: anim1 10s;', + '#target::before': 'animation: anim1 10s;' }); + var target = addDiv(t, { 'id': 'target' }); + target.style.animation = 'anim1 100s'; + + var animations = target.getAnimations({ subtree: true }); + assert_equals(animations.length, 3, + 'getAnimations({ subtree: true }) ' + + 'should return animations on pseudo-elements'); + assert_equals(animations[0].effect.target, target, + 'The animation targeting the parent element ' + + 'should be returned first'); + assert_equals(animations[1].effect.target.type, '::before', + 'The animation targeting the ::before pseudo-element ' + + 'should be returned second'); + assert_equals(animations[2].effect.target.type, '::after', + 'The animation targeting the ::after pesudo-element ' + + 'should be returned last'); +}, 'Test AnimationFilter{ subtree: true } with single element'); + +test(function(t) { + addStyle(t, { '#parent::after': 'animation: anim1 10s;', + '#parent::before': 'animation: anim1 10s;', + '#child::after': 'animation: anim1 10s;', + '#child::before': 'animation: anim1 10s;' }); + var parent = addDiv(t, { 'id': 'parent' }); + parent.style.animation = 'anim1 100s'; + var child = addDiv(t, { 'id': 'child' }); + child.style.animation = 'anim1 100s'; + parent.appendChild(child); + + var animations = parent.getAnimations({ subtree: false }); + assert_equals(animations.length, 1, + 'Should find only the element even if it has a child'); + assert_equals(animations[0].effect.target, parent, + 'Effect target shuld be the element'); +}, 'Test AnimationFilter{ subtree: false } with element that has a child'); + +test(function(t) { + addStyle(t, { '#parent::after': 'animation: anim1 10s;', + '#parent::before': 'animation: anim1 10s;', + '#child::after': 'animation: anim1 10s;', + '#child::before': 'animation: anim1 10s;' }); + var parent = addDiv(t, { 'id': 'parent' }); + var child = addDiv(t, { 'id': 'child' }); + parent.style.animation = 'anim1 100s'; + child.style.animation = 'anim1 100s'; + parent.appendChild(child); + + var animations = parent.getAnimations({ subtree: true }); + assert_equals(animations.length, 6, + 'Should find all elements, pesudo-elements that parent has'); + + assert_equals(animations[0].effect.target, parent, + 'The animation targeting the parent element ' + + 'should be returned first'); + assert_equals(animations[1].effect.target.type, '::before', + 'The animation targeting the ::before pseudo-element ' + + 'should be returned second'); + assert_equals(animations[1].effect.target.parentElement, parent, + 'This ::before element should be child of parent element'); + assert_equals(animations[2].effect.target.type, '::after', + 'The animation targeting the ::after pesudo-element ' + + 'should be returned third'); + assert_equals(animations[2].effect.target.parentElement, parent, + 'This ::after element should be child of parent element'); + + assert_equals(animations[3].effect.target, child, + 'The animation targeting the child element ' + + 'should be returned fourth'); + assert_equals(animations[4].effect.target.type, '::before', + 'The animation targeting the ::before pseudo-element ' + + 'should be returned fifth'); + assert_equals(animations[4].effect.target.parentElement, child, + 'This ::before element should be child of child element'); + assert_equals(animations[5].effect.target.type, '::after', + 'The animation targeting the ::after pesudo-element ' + + 'should be returned last'); + assert_equals(animations[5].effect.target.parentElement, child, + 'This ::after element should be child of child element'); +}, 'Test AnimationFilter{ subtree: true } with element that has a child'); + +test(function(t) { + var parent = addDiv(t, { 'id': 'parent' }); + var child1 = addDiv(t, { 'id': 'child1' }); + var grandchild1 = addDiv(t, { 'id': 'grandchild1' }); + var grandchild2 = addDiv(t, { 'id': 'grandchild2' }); + var child2 = addDiv(t, { 'id': 'child2' }); + + parent.style.animation = 'anim1 100s'; + child1.style.animation = 'anim1 100s'; + grandchild1.style.animation = 'anim1 100s'; + grandchild2.style.animation = 'anim1 100s'; + child2.style.animation = 'anim1 100s'; + + parent.appendChild(child1); + child1.appendChild(grandchild1); + child1.appendChild(grandchild2); + parent.appendChild(child2); + + var animations = parent.getAnimations({ subtree: true }); + assert_equals( + parent.getAnimations({ subtree: true }).length, 5, + 'Should find all descendants of the element'); + + assert_equals(animations[0].effect.target, parent, + 'The animation targeting the parent element ' + + 'should be returned first'); + + assert_equals(animations[1].effect.target, child1, + 'The animation targeting the child1 element ' + + 'should be returned second'); + + assert_equals(animations[2].effect.target, grandchild1, + 'The animation targeting the grandchild1 element ' + + 'should be returned third'); + + assert_equals(animations[3].effect.target, grandchild2, + 'The animation targeting the grandchild2 element ' + + 'should be returned fourth'); + + assert_equals(animations[4].effect.target, child2, + 'The animation targeting the child2 element ' + + 'should be returned last'); + +}, 'Test AnimationFilter{ subtree: true } with element that has many descendant'); + +done(); +</script> +</body> diff --git a/dom/animation/test/css-animations/file_keyframeeffect-getkeyframes.html b/dom/animation/test/css-animations/file_keyframeeffect-getkeyframes.html new file mode 100644 index 000000000..15e2d23f1 --- /dev/null +++ b/dom/animation/test/css-animations/file_keyframeeffect-getkeyframes.html @@ -0,0 +1,672 @@ +<!doctype html> +<meta charset=utf-8> +<script src="../testcommon.js"></script> +<style> +@keyframes anim-empty { } + +@keyframes anim-empty-frames { + from { } + to { } +} + +@keyframes anim-only-timing { + from { animation-timing-function: linear; } + to { } +} + +@keyframes anim-only-non-animatable { + from { animation-duration: 3s; } + to { animation-duration: 5s; } +} + +@keyframes anim-simple { + from { color: black; } + to { color: white; } +} + +@keyframes anim-simple-three { + from { color: black; } + 50% { color: blue; } + to { color: white; } +} + +@keyframes anim-simple-timing { + from { color: black; animation-timing-function: linear; } + 50% { color: blue; animation-timing-function: ease-in-out; } + to { color: white; animation-timing-function: step-end; } +} + +@keyframes anim-simple-timing-some { + from { color: black; animation-timing-function: linear; } + 50% { color: blue; } + to { color: white; } +} + +@keyframes anim-simple-shorthand { + from { margin: 8px; } + to { margin: 16px; } +} + +@keyframes anim-omit-to { + from { color: blue; } +} + +@keyframes anim-omit-from { + to { color: blue; } +} + +@keyframes anim-omit-from-to { + 50% { color: blue; } +} + +@keyframes anim-partially-omit-to { + from { margin-top: 50px; + margin-bottom: 100px; } + to { margin-top: 150px !important; /* ignored */ + margin-bottom: 200px; } +} + +@keyframes anim-different-props { + from { color: black; margin-top: 8px; } + 25% { color: blue; } + 75% { margin-top: 12px; } + to { color: white; margin-top: 16px } +} + +@keyframes anim-different-props-and-easing { + from { color: black; margin-top: 8px; animation-timing-function: linear; } + 25% { color: blue; animation-timing-function: step-end; } + 75% { margin-top: 12px; animation-timing-function: ease-in; } + to { color: white; margin-top: 16px } +} + +@keyframes anim-merge-offset { + from { color: black; } + to { color: white; } + from { margin-top: 8px; } + to { margin-top: 16px; } +} + +@keyframes anim-merge-offset-and-easing { + from { color: black; animation-timing-function: step-end; } + to { color: white; } + from { margin-top: 8px; animation-timing-function: linear; } + to { margin-top: 16px; } + from { font-size: 16px; animation-timing-function: step-end; } + to { font-size: 32px; } + from { padding-left: 2px; animation-timing-function: linear; } + to { padding-left: 4px; } +} + +@keyframes anim-no-merge-equiv-easing { + from { margin-top: 0px; animation-timing-function: steps(1, end); } + from { margin-right: 0px; animation-timing-function: step-end; } + from { margin-bottom: 0px; animation-timing-function: steps(1); } + 50% { margin-top: 10px; animation-timing-function: step-end; } + 50% { margin-right: 10px; animation-timing-function: step-end; } + 50% { margin-bottom: 10px; animation-timing-function: step-end; } + to { margin-top: 20px; margin-right: 20px; margin-bottom: 20px; } +} + +@keyframes anim-overriding { + from { padding-top: 50px } + 50%, from { padding-top: 30px } /* wins: 0% */ + 75%, 85%, 50% { padding-top: 20px } /* wins: 75%, 50% */ + 100%, 85% { padding-top: 70px } /* wins: 100% */ + 85.1% { padding-top: 60px } /* wins: 85.1% */ + 85% { padding-top: 30px } /* wins: 85% */ +} + +@keyframes anim-filter { + to { filter: blur(5px) sepia(60%) saturate(30%); } +} + +@keyframes anim-text-shadow { + to { text-shadow: none; } +} + +@keyframes anim-background-size { + to { background-size: 50%, 6px, contain } +} + +:root { + --var-100px: 100px; +} +@keyframes anim-variables { + to { transform: translate(var(--var-100px), 0) } +} +@keyframes anim-variables-shorthand { + to { margin: var(--var-100px) } +} +</style> +<body> +<script> +"use strict"; + +function getKeyframes(e) { + return e.getAnimations()[0].effect.getKeyframes(); +} + +function assert_frames_equal(a, b, name) { + assert_equals(Object.keys(a).sort().toString(), + Object.keys(b).sort().toString(), + "properties on " + name); + for (var p in a) { + if (p === 'offset' || p === 'computedOffset') { + assert_approx_equals(a[p], b[p], 0.00001, + "value for '" + p + "' on " + name); + } else { + assert_equals(a[p], b[p], "value for '" + p + "' on " + name); + } + } +} + +// animation-timing-function values to test with, where the value +// is exactly the same as its serialization, sorted by the order +// getKeyframes() will group frames with the same easing function +// together (by nsTimingFunction::Compare). +const kTimingFunctionValues = [ + "ease", + "linear", + "ease-in", + "ease-out", + "ease-in-out", + "steps(1, start)", + "steps(2, start)", + "steps(1)", + "steps(2)", + "cubic-bezier(0, 0, 1, 1)", + "cubic-bezier(0, 0.25, 0.75, 1)" +]; + +test(function(t) { + var div = addDiv(t); + + div.style.animation = 'anim-empty 100s'; + assert_equals(getKeyframes(div).length, 0, + "number of frames with empty @keyframes"); + + div.style.animation = 'anim-empty-frames 100s'; + assert_equals(getKeyframes(div).length, 0, + "number of frames when @keyframes has empty keyframes"); + + div.style.animation = 'anim-only-timing 100s'; + assert_equals(getKeyframes(div).length, 0, + "number of frames when @keyframes only has keyframes with " + + "animation-timing-function"); + + div.style.animation = 'anim-only-non-animatable 100s'; + assert_equals(getKeyframes(div).length, 0, + "number of frames when @keyframes only has frames with " + + "non-animatable properties"); +}, 'KeyframeEffectReadOnly.getKeyframes() returns no frames for various kinds' + + ' of empty enimations'); + +test(function(t) { + var div = addDiv(t); + + div.style.animation = 'anim-simple 100s'; + var frames = getKeyframes(div); + + assert_equals(frames.length, 2, "number of frames"); + + var expected = [ + { offset: 0, computedOffset: 0, easing: "ease", + color: "rgb(0, 0, 0)" }, + { offset: 1, computedOffset: 1, easing: "ease", + color: "rgb(255, 255, 255)" }, + ]; + + for (var i = 0; i < frames.length; i++) { + assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i); + } +}, 'KeyframeEffectReadOnly.getKeyframes() returns expected frames for a simple' + + 'animation'); + +test(function(t) { + kTimingFunctionValues.forEach(function(easing) { + var div = addDiv(t); + + div.style.animation = 'anim-simple-three 100s ' + easing; + var frames = getKeyframes(div); + + assert_equals(frames.length, 3, "number of frames"); + + for (var i = 0; i < frames.length; i++) { + assert_equals(frames[i].easing, easing, + "value for 'easing' on ComputedKeyframe #" + i); + } + }); +}, 'KeyframeEffectReadOnly.getKeyframes() returns frames with expected easing' + + ' values, when the easing comes from animation-timing-function on the' + + ' element'); + +test(function(t) { + var div = addDiv(t); + + div.style.animation = 'anim-simple-timing 100s'; + var frames = getKeyframes(div); + + assert_equals(frames.length, 3, "number of frames"); + assert_equals(frames[0].easing, "linear", + "value of 'easing' on ComputedKeyframe #0"); + assert_equals(frames[1].easing, "ease-in-out", + "value of 'easing' on ComputedKeyframe #1"); + assert_equals(frames[2].easing, "steps(1)", + "value of 'easing' on ComputedKeyframe #2"); +}, 'KeyframeEffectReadOnly.getKeyframes() returns frames with expected easing' + + ' values, when the easing is specified on each keyframe'); + +test(function(t) { + var div = addDiv(t); + + div.style.animation = 'anim-simple-timing-some 100s step-start'; + var frames = getKeyframes(div); + + assert_equals(frames.length, 3, "number of frames"); + assert_equals(frames[0].easing, "linear", + "value of 'easing' on ComputedKeyframe #0"); + assert_equals(frames[1].easing, "steps(1, start)", + "value of 'easing' on ComputedKeyframe #1"); + assert_equals(frames[2].easing, "steps(1, start)", + "value of 'easing' on ComputedKeyframe #2"); +}, 'KeyframeEffectReadOnly.getKeyframes() returns frames with expected easing' + + ' values, when the easing is specified on some keyframes'); + +test(function(t) { + var div = addDiv(t); + + div.style.animation = 'anim-simple-shorthand 100s'; + var frames = getKeyframes(div); + + assert_equals(frames.length, 2, "number of frames"); + + var expected = [ + { offset: 0, computedOffset: 0, easing: "ease", + marginBottom: "8px", marginLeft: "8px", + marginRight: "8px", marginTop: "8px" }, + { offset: 1, computedOffset: 1, easing: "ease", + marginBottom: "16px", marginLeft: "16px", + marginRight: "16px", marginTop: "16px" }, + ]; + + for (var i = 0; i < frames.length; i++) { + assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i); + } +}, 'KeyframeEffectReadOnly.getKeyframes() returns expected frames for a simple' + + ' animation that specifies a single shorthand property'); + +test(function(t) { + var div = addDiv(t); + + div.style.animation = 'anim-omit-to 100s'; + div.style.color = 'white'; + var frames = getKeyframes(div); + + assert_equals(frames.length, 2, "number of frames"); + + var expected = [ + { offset: 0, computedOffset: 0, easing: "ease", + color: "rgb(0, 0, 255)" }, + { offset: 1, computedOffset: 1, easing: "ease", + color: "rgb(255, 255, 255)" }, + ]; + + for (var i = 0; i < frames.length; i++) { + assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i); + } +}, 'KeyframeEffectReadOnly.getKeyframes() returns expected frames for an ' + + 'animation with a 0% keyframe and no 100% keyframe'); + +test(function(t) { + var div = addDiv(t); + + div.style.animation = 'anim-omit-from 100s'; + div.style.color = 'white'; + var frames = getKeyframes(div); + + assert_equals(frames.length, 2, "number of frames"); + + var expected = [ + { offset: 0, computedOffset: 0, easing: "ease", + color: "rgb(255, 255, 255)" }, + { offset: 1, computedOffset: 1, easing: "ease", + color: "rgb(0, 0, 255)" }, + ]; + + for (var i = 0; i < frames.length; i++) { + assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i); + } +}, 'KeyframeEffectReadOnly.getKeyframes() returns expected frames for an ' + + 'animation with a 100% keyframe and no 0% keyframe'); + +test(function(t) { + var div = addDiv(t); + + div.style.animation = 'anim-omit-from-to 100s'; + div.style.color = 'white'; + var frames = getKeyframes(div); + + assert_equals(frames.length, 3, "number of frames"); + + var expected = [ + { offset: 0, computedOffset: 0, easing: "ease", + color: "rgb(255, 255, 255)" }, + { offset: 0.5, computedOffset: 0.5, easing: "ease", + color: "rgb(0, 0, 255)" }, + { offset: 1, computedOffset: 1, easing: "ease", + color: "rgb(255, 255, 255)" }, + ]; + + for (var i = 0; i < frames.length; i++) { + assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i); + } +}, 'KeyframeEffectReadOnly.getKeyframes() returns expected frames for an ' + + 'animation with no 0% or 100% keyframe but with a 50% keyframe'); + +test(function(t) { + var div = addDiv(t); + + div.style.animation = 'anim-partially-omit-to 100s'; + div.style.marginTop = '250px'; + var frames = getKeyframes(div); + + assert_equals(frames.length, 2, "number of frames"); + + var expected = [ + { offset: 0, computedOffset: 0, easing: "ease", + marginTop: '50px', marginBottom: '100px' }, + { offset: 1, computedOffset: 1, easing: "ease", + marginTop: '250px', marginBottom: '200px' }, + ]; + + for (var i = 0; i < frames.length; i++) { + assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i); + } +}, 'KeyframeEffectReadOnly.getKeyframes() returns expected frames for an ' + + 'animation with a partially complete 100% keyframe (because the ' + + '!important rule is ignored)'); + +test(function(t) { + var div = addDiv(t); + + div.style.animation = 'anim-different-props 100s'; + var frames = getKeyframes(div); + + assert_equals(frames.length, 4, "number of frames"); + + var expected = [ + { offset: 0, computedOffset: 0, easing: "ease", + color: "rgb(0, 0, 0)", marginTop: "8px" }, + { offset: 0.25, computedOffset: 0.25, easing: "ease", + color: "rgb(0, 0, 255)" }, + { offset: 0.75, computedOffset: 0.75, easing: "ease", + marginTop: "12px" }, + { offset: 1, computedOffset: 1, easing: "ease", + color: "rgb(255, 255, 255)", marginTop: "16px" }, + ]; + + for (var i = 0; i < frames.length; i++) { + assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i); + } +}, 'KeyframeEffectReadOnly.getKeyframes() returns expected frames for an ' + + 'animation with different properties on different keyframes, all ' + + 'with the same easing function'); + +test(function(t) { + var div = addDiv(t); + + div.style.animation = 'anim-different-props-and-easing 100s'; + var frames = getKeyframes(div); + + assert_equals(frames.length, 4, "number of frames"); + + var expected = [ + { offset: 0, computedOffset: 0, easing: "linear", + color: "rgb(0, 0, 0)", marginTop: "8px" }, + { offset: 0.25, computedOffset: 0.25, easing: "steps(1)", + color: "rgb(0, 0, 255)" }, + { offset: 0.75, computedOffset: 0.75, easing: "ease-in", + marginTop: "12px" }, + { offset: 1, computedOffset: 1, easing: "ease", + color: "rgb(255, 255, 255)", marginTop: "16px" }, + ]; + + for (var i = 0; i < frames.length; i++) { + assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i); + } +}, 'KeyframeEffectReadOnly.getKeyframes() returns expected frames for an ' + + 'animation with different properties on different keyframes, with ' + + 'a different easing function on each'); + +test(function(t) { + var div = addDiv(t); + + div.style.animation = 'anim-merge-offset 100s'; + var frames = getKeyframes(div); + + assert_equals(frames.length, 2, "number of frames"); + + var expected = [ + { offset: 0, computedOffset: 0, easing: "ease", + color: "rgb(0, 0, 0)", marginTop: "8px" }, + { offset: 1, computedOffset: 1, easing: "ease", + color: "rgb(255, 255, 255)", marginTop: "16px" }, + ]; + + for (var i = 0; i < frames.length; i++) { + assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i); + } +}, 'KeyframeEffectReadOnly.getKeyframes() returns expected frames for an ' + + 'animation with multiple keyframes for the same time, and all with ' + + 'the same easing function'); + +test(function(t) { + var div = addDiv(t); + + div.style.animation = 'anim-merge-offset-and-easing 100s'; + var frames = getKeyframes(div); + + assert_equals(frames.length, 3, "number of frames"); + + var expected = [ + { offset: 0, computedOffset: 0, easing: "steps(1)", + color: "rgb(0, 0, 0)", fontSize: "16px" }, + { offset: 0, computedOffset: 0, easing: "linear", + marginTop: "8px", paddingLeft: "2px" }, + { offset: 1, computedOffset: 1, easing: "ease", + color: "rgb(255, 255, 255)", fontSize: "32px", marginTop: "16px", + paddingLeft: "4px" }, + ]; + + for (var i = 0; i < frames.length; i++) { + assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i); + } +}, 'KeyframeEffectReadOnly.getKeyframes() returns expected frames for an ' + + 'animation with multiple keyframes for the same time and with ' + + 'different easing functions'); + +test(function(t) { + var div = addDiv(t); + + div.style.animation = 'anim-no-merge-equiv-easing 100s'; + var frames = getKeyframes(div); + + assert_equals(frames.length, 3, "number of frames"); + + var expected = [ + { offset: 0, computedOffset: 0, easing: "steps(1)", + marginTop: "0px", marginRight: "0px", marginBottom: "0px" }, + { offset: 0.5, computedOffset: 0.5, easing: "steps(1)", + marginTop: "10px", marginRight: "10px", marginBottom: "10px" }, + { offset: 1, computedOffset: 1, easing: "ease", + marginTop: "20px", marginRight: "20px", marginBottom: "20px" }, + ]; + + for (var i = 0; i < frames.length; i++) { + assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i); + } +}, 'KeyframeEffectReadOnly.getKeyframes() returns expected frames for an ' + + 'animation with multiple keyframes for the same time and with ' + + 'different but equivalent easing functions'); + +test(function(t) { + var div = addDiv(t); + + div.style.animation = 'anim-overriding 100s'; + var frames = getKeyframes(div); + + assert_equals(frames.length, 6, "number of frames"); + + var expected = [ + { offset: 0, computedOffset: 0, easing: "ease", + paddingTop: "30px" }, + { offset: 0.5, computedOffset: 0.5, easing: "ease", + paddingTop: "20px" }, + { offset: 0.75, computedOffset: 0.75, easing: "ease", + paddingTop: "20px" }, + { offset: 0.85, computedOffset: 0.85, easing: "ease", + paddingTop: "30px" }, + { offset: 0.851, computedOffset: 0.851, easing: "ease", + paddingTop: "60px" }, + { offset: 1, computedOffset: 1, easing: "ease", + paddingTop: "70px" }, + ]; + + for (var i = 0; i < frames.length; i++) { + assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i); + } +}, 'KeyframeEffectReadOnly.getKeyframes() returns expected frames for ' + + 'overlapping keyframes'); + +// Gecko-specific test case: We are specifically concerned here that the +// computed value for filter, "none", is correctly represented. + +test(function(t) { + var div = addDiv(t); + + div.style.animation = 'anim-filter 100s'; + var frames = getKeyframes(div); + + assert_equals(frames.length, 2, "number of frames"); + + var expected = [ + { offset: 0, computedOffset: 0, easing: "ease", + filter: "none" }, + { offset: 1, computedOffset: 1, easing: "ease", + filter: "blur(5px) sepia(60%) saturate(30%)" }, + ]; + + for (var i = 0; i < frames.length; i++) { + assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i); + } +}, 'KeyframeEffectReadOnly.getKeyframes() returns expected values for ' + + 'animations with filter properties and missing keyframes'); + +// Gecko-specific test case: We are specifically concerned here that the +// computed value for text-shadow and a "none" specified on a keyframe +// are correctly represented. + +test(function(t) { + var div = addDiv(t); + + div.style.textShadow = '1px 1px 2px black, 0 0 16px blue, 0 0 3.2px blue'; + div.style.animation = 'anim-text-shadow 100s'; + var frames = getKeyframes(div); + + assert_equals(frames.length, 2, "number of frames"); + + var expected = [ + { offset: 0, computedOffset: 0, easing: "ease", + textShadow: "1px 1px 2px 0px rgb(0, 0, 0)," + + " 0px 0px 16px 0px rgb(0, 0, 255)," + + " 0px 0px 3.2px 0px rgb(0, 0, 255)" }, + { offset: 1, computedOffset: 1, easing: "ease", textShadow: "none" }, + ]; + + for (var i = 0; i < frames.length; i++) { + assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i); + } +}, 'KeyframeEffectReadOnly.getKeyframes() returns expected values for ' + + 'animations with text-shadow properties and missing keyframes'); + +// Gecko-specific test case: We are specifically concerned here that the +// initial value for background-size and the specified list are correctly +// represented. + +test(function(t) { + var div = addDiv(t); + + div.style.animation = 'anim-background-size 100s'; + var frames = getKeyframes(div); + + assert_equals(frames.length, 2, "number of frames"); + + var expected = [ + { offset: 0, computedOffset: 0, easing: "ease", + backgroundSize: "auto auto" }, + { offset: 1, computedOffset: 1, easing: "ease", + backgroundSize: "50% auto, 6px auto, contain" }, + ]; + + for (var i = 0; i < frames.length; i++) { + assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i); + } + + // Test inheriting a background-size value + + expected[0].backgroundSize = div.style.backgroundSize = + "30px auto, 40% auto, auto auto"; + frames = getKeyframes(div); + + for (var i = 0; i < frames.length; i++) { + assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i + + " after updating current style"); + } +}, 'KeyframeEffectReadOnly.getKeyframes() returns expected values for ' + + 'animations with background-size properties and missing keyframes'); + +test(function(t) { + var div = addDiv(t); + div.style.animation = 'anim-variables 100s'; + + var frames = getKeyframes(div); + + var expected = [ + { offset: 0, computedOffset: 0, easing: "ease", + transform: "none" }, + { offset: 1, computedOffset: 1, easing: "ease", + transform: "translate(100px, 0px)" }, + ]; + for (var i = 0; i < frames.length; i++) { + assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i); + } +}, 'KeyframeEffectReadOnly.getKeyframes() returns expected values for ' + + 'animations with CSS variables as keyframe values'); + +test(function(t) { + var div = addDiv(t); + div.style.animation = 'anim-variables-shorthand 100s'; + + var frames = getKeyframes(div); + + var expected = [ + { offset: 0, computedOffset: 0, easing: "ease", + marginBottom: "0px", + marginLeft: "0px", + marginRight: "0px", + marginTop: "0px" }, + { offset: 1, computedOffset: 1, easing: "ease", + marginBottom: "100px", + marginLeft: "100px", + marginRight: "100px", + marginTop: "100px" }, + ]; + for (var i = 0; i < frames.length; i++) { + assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i); + } +}, 'KeyframeEffectReadOnly.getKeyframes() returns expected values for ' + + 'animations with CSS variables as keyframe values in a shorthand property'); +done(); +</script> +</body> diff --git a/dom/animation/test/css-animations/file_pseudoElement-get-animations.html b/dom/animation/test/css-animations/file_pseudoElement-get-animations.html new file mode 100644 index 000000000..bebe14533 --- /dev/null +++ b/dom/animation/test/css-animations/file_pseudoElement-get-animations.html @@ -0,0 +1,70 @@ +<!doctype html> +<meta charset=utf-8> +<script src="../testcommon.js"></script> +<style> +@keyframes anim1 { } +@keyframes anim2 { } +.before::before { + animation: anim1 10s; +} +.after-with-mix-anims-trans::after { + content: ''; + animation: anim1 10s, anim2 10s; + width: 0px; + height: 0px; + transition: all 100s; +} +.after-change::after { + width: 100px; + height: 100px; +} +</style> +<body> +<script> +'use strict'; + +test(function(t) { + var div = addDiv(t, { class: 'before' }); + var pseudoTarget = document.getAnimations()[0].effect.target; + assert_equals(pseudoTarget.getAnimations().length, 1, + 'Expected number of animations are returned'); + assert_equals(pseudoTarget.getAnimations()[0].animationName, 'anim1', + 'CSS animation name matches'); +}, 'getAnimations returns CSSAnimation objects'); + +test(function(t) { + var div = addDiv(t, { class: 'after-with-mix-anims-trans' }); + // Trigger transitions + flushComputedStyle(div); + div.classList.add('after-change'); + + // Create additional animation on the pseudo-element from script + var pseudoTarget = document.getAnimations()[0].effect.target; + var effect = new KeyframeEffectReadOnly(pseudoTarget, + { background: ["blue", "red"] }, + 3 * MS_PER_SEC); + var newAnimation = new Animation(effect, document.timeline); + newAnimation.id = 'scripted-anim'; + newAnimation.play(); + + // Check order - the script-generated animation should appear later + var anims = pseudoTarget.getAnimations(); + assert_equals(anims.length, 5, + 'Got expected number of animations/trnasitions running on ' + + '::after pseudo element'); + assert_equals(anims[0].transitionProperty, 'height', + '1st animation is the 1st transition sorted by name'); + assert_equals(anims[1].transitionProperty, 'width', + '2nd animation is the 2nd transition sorted by name '); + assert_equals(anims[2].animationName, 'anim1', + '3rd animation is the 1st animation in animation-name list'); + assert_equals(anims[3].animationName, 'anim2', + '4rd animation is the 2nd animation in animation-name list'); + assert_equals(anims[4].id, 'scripted-anim', + 'Animation added by script appears last'); +}, 'getAnimations returns css transitions/animations, and script-generated ' + + 'animations in the expected order'); + +done(); +</script> +</body> diff --git a/dom/animation/test/css-animations/test_animation-cancel.html b/dom/animation/test/css-animations/test_animation-cancel.html new file mode 100644 index 000000000..15c9b482f --- /dev/null +++ b/dom/animation/test/css-animations/test_animation-cancel.html @@ -0,0 +1,15 @@ +<!doctype html> +<meta charset=utf-8> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +'use strict'; +setup({explicit_done: true}); +SpecialPowers.pushPrefEnv( + { "set": [["dom.animations-api.core.enabled", true]]}, + function() { + window.open("file_animation-cancel.html"); + }); +</script> +</html> diff --git a/dom/animation/test/css-animations/test_animation-computed-timing.html b/dom/animation/test/css-animations/test_animation-computed-timing.html new file mode 100644 index 000000000..c1b40aaf3 --- /dev/null +++ b/dom/animation/test/css-animations/test_animation-computed-timing.html @@ -0,0 +1,16 @@ +<!doctype html> +<html> +<meta charset=utf-8> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +'use strict'; +setup({explicit_done: true}); +SpecialPowers.pushPrefEnv( + { "set": [["dom.animations-api.core.enabled", true]]}, + function() { + window.open("file_animation-computed-timing.html"); + }); +</script> +</html> diff --git a/dom/animation/test/css-animations/test_animation-currenttime.html b/dom/animation/test/css-animations/test_animation-currenttime.html new file mode 100644 index 000000000..7e3a8d74d --- /dev/null +++ b/dom/animation/test/css-animations/test_animation-currenttime.html @@ -0,0 +1,15 @@ +<!doctype html> +<meta charset=utf-8> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +'use strict'; +setup({explicit_done: true}); +SpecialPowers.pushPrefEnv( + { "set": [["dom.animations-api.core.enabled", true]]}, + function() { + window.open("file_animation-currenttime.html"); + }); +</script> +</html> diff --git a/dom/animation/test/css-animations/test_animation-finish.html b/dom/animation/test/css-animations/test_animation-finish.html new file mode 100644 index 000000000..abbd267d8 --- /dev/null +++ b/dom/animation/test/css-animations/test_animation-finish.html @@ -0,0 +1,15 @@ +<!doctype html> +<meta charset=utf-8> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +'use strict'; +setup({explicit_done: true}); +SpecialPowers.pushPrefEnv( + { "set": [["dom.animations-api.core.enabled", true]]}, + function() { + window.open("file_animation-finish.html"); + }); +</script> +</html> diff --git a/dom/animation/test/css-animations/test_animation-finished.html b/dom/animation/test/css-animations/test_animation-finished.html new file mode 100644 index 000000000..295ffe0af --- /dev/null +++ b/dom/animation/test/css-animations/test_animation-finished.html @@ -0,0 +1,15 @@ +<!doctype html> +<meta charset=utf-8> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +'use strict'; +setup({explicit_done: true}); +SpecialPowers.pushPrefEnv( + { "set": [["dom.animations-api.core.enabled", true]]}, + function() { + window.open("file_animation-finished.html"); + }); +</script> +</html> diff --git a/dom/animation/test/css-animations/test_animation-id.html b/dom/animation/test/css-animations/test_animation-id.html new file mode 100644 index 000000000..c23501b8d --- /dev/null +++ b/dom/animation/test/css-animations/test_animation-id.html @@ -0,0 +1,15 @@ +<!doctype html> +<meta charset=utf-8> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +'use strict'; +setup({explicit_done: true}); +SpecialPowers.pushPrefEnv( + { "set": [["dom.animations-api.core.enabled", true]]}, + function() { + window.open("file_animation-id.html"); + }); +</script> +</html> diff --git a/dom/animation/test/css-animations/test_animation-pausing.html b/dom/animation/test/css-animations/test_animation-pausing.html new file mode 100644 index 000000000..10be1ddf0 --- /dev/null +++ b/dom/animation/test/css-animations/test_animation-pausing.html @@ -0,0 +1,15 @@ +<!doctype html> +<meta charset=utf-8> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +'use strict'; +setup({explicit_done: true}); +SpecialPowers.pushPrefEnv( + { "set": [["dom.animations-api.core.enabled", true]]}, + function() { + window.open("file_animation-pausing.html"); + }); +</script> +</html> diff --git a/dom/animation/test/css-animations/test_animation-playstate.html b/dom/animation/test/css-animations/test_animation-playstate.html new file mode 100644 index 000000000..54c8e1f10 --- /dev/null +++ b/dom/animation/test/css-animations/test_animation-playstate.html @@ -0,0 +1,15 @@ +<!doctype html> +<meta charset=utf-8> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +'use strict'; +setup({explicit_done: true}); +SpecialPowers.pushPrefEnv( + { "set": [["dom.animations-api.core.enabled", true]]}, + function() { + window.open("file_animation-playstate.html"); + }); +</script> +</html> diff --git a/dom/animation/test/css-animations/test_animation-ready.html b/dom/animation/test/css-animations/test_animation-ready.html new file mode 100644 index 000000000..445f751b4 --- /dev/null +++ b/dom/animation/test/css-animations/test_animation-ready.html @@ -0,0 +1,15 @@ +<!doctype html> +<meta charset=utf-8> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +'use strict'; +setup({explicit_done: true}); +SpecialPowers.pushPrefEnv( + { "set": [["dom.animations-api.core.enabled", true]]}, + function() { + window.open("file_animation-ready.html"); + }); +</script> +</html> diff --git a/dom/animation/test/css-animations/test_animation-reverse.html b/dom/animation/test/css-animations/test_animation-reverse.html new file mode 100644 index 000000000..673b1e0d3 --- /dev/null +++ b/dom/animation/test/css-animations/test_animation-reverse.html @@ -0,0 +1,15 @@ +<!doctype html> +<meta charset=utf-8> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +'use strict'; +setup({explicit_done: true}); +SpecialPowers.pushPrefEnv( + { "set": [["dom.animations-api.core.enabled", true]]}, + function() { + window.open("file_animation-reverse.html"); + }); +</script> +</html> diff --git a/dom/animation/test/css-animations/test_animation-starttime.html b/dom/animation/test/css-animations/test_animation-starttime.html new file mode 100644 index 000000000..dfae89ffa --- /dev/null +++ b/dom/animation/test/css-animations/test_animation-starttime.html @@ -0,0 +1,15 @@ +<!doctype html> +<meta charset=utf-8> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +'use strict'; +setup({explicit_done: true}); +SpecialPowers.pushPrefEnv( + { "set": [["dom.animations-api.core.enabled", true]]}, + function() { + window.open("file_animation-starttime.html"); + }); +</script> +</html> diff --git a/dom/animation/test/css-animations/test_animations-dynamic-changes.html b/dom/animation/test/css-animations/test_animations-dynamic-changes.html new file mode 100644 index 000000000..ce4eb378d --- /dev/null +++ b/dom/animation/test/css-animations/test_animations-dynamic-changes.html @@ -0,0 +1,15 @@ +<!doctype html> +<meta charset=utf-8> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +'use strict'; +setup({explicit_done: true}); +SpecialPowers.pushPrefEnv( + { "set": [["dom.animations-api.core.enabled", true]]}, + function() { + window.open("file_animations-dynamic-changes.html"); + }); +</script> +</html> diff --git a/dom/animation/test/css-animations/test_cssanimation-animationname.html b/dom/animation/test/css-animations/test_cssanimation-animationname.html new file mode 100644 index 000000000..ccddecc33 --- /dev/null +++ b/dom/animation/test/css-animations/test_cssanimation-animationname.html @@ -0,0 +1,15 @@ +<!doctype html> +<meta charset=utf-8> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +'use strict'; +setup({explicit_done: true}); +SpecialPowers.pushPrefEnv( + { "set": [["dom.animations-api.core.enabled", true]]}, + function() { + window.open("file_cssanimation-animationname.html"); + }); +</script> +</html> diff --git a/dom/animation/test/css-animations/test_document-get-animations.html b/dom/animation/test/css-animations/test_document-get-animations.html new file mode 100644 index 000000000..dc964e62c --- /dev/null +++ b/dom/animation/test/css-animations/test_document-get-animations.html @@ -0,0 +1,15 @@ +<!doctype html> +<meta charset=utf-8> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +'use strict'; +setup({explicit_done: true}); +SpecialPowers.pushPrefEnv( + { "set": [["dom.animations-api.core.enabled", true]]}, + function() { + window.open("file_document-get-animations.html"); + }); +</script> +</html> diff --git a/dom/animation/test/css-animations/test_effect-target.html b/dom/animation/test/css-animations/test_effect-target.html new file mode 100644 index 000000000..6c230c729 --- /dev/null +++ b/dom/animation/test/css-animations/test_effect-target.html @@ -0,0 +1,15 @@ +<!doctype html> +<meta charset=utf-8> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +'use strict'; +setup({explicit_done: true}); +SpecialPowers.pushPrefEnv( + { "set": [["dom.animations-api.core.enabled", true]]}, + function() { + window.open("file_effect-target.html"); + }); +</script> +</html> diff --git a/dom/animation/test/css-animations/test_element-get-animations.html b/dom/animation/test/css-animations/test_element-get-animations.html new file mode 100644 index 000000000..7b39e65cc --- /dev/null +++ b/dom/animation/test/css-animations/test_element-get-animations.html @@ -0,0 +1,15 @@ +<!doctype html> +<meta charset=utf-8> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +'use strict'; +setup({explicit_done: true}); +SpecialPowers.pushPrefEnv( + { "set": [["dom.animations-api.core.enabled", true]]}, + function() { + window.open("file_element-get-animations.html"); + }); +</script> +</html> diff --git a/dom/animation/test/css-animations/test_keyframeeffect-getkeyframes.html b/dom/animation/test/css-animations/test_keyframeeffect-getkeyframes.html new file mode 100644 index 000000000..3cf227008 --- /dev/null +++ b/dom/animation/test/css-animations/test_keyframeeffect-getkeyframes.html @@ -0,0 +1,15 @@ +<!doctype html> +<meta charset=utf-8> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +'use strict'; +setup({explicit_done: true}); +SpecialPowers.pushPrefEnv( + { "set": [["dom.animations-api.core.enabled", true]]}, + function() { + window.open("file_keyframeeffect-getkeyframes.html"); + }); +</script> +</html> diff --git a/dom/animation/test/css-animations/test_pseudoElement-get-animations.html b/dom/animation/test/css-animations/test_pseudoElement-get-animations.html new file mode 100644 index 000000000..1e0dc5c82 --- /dev/null +++ b/dom/animation/test/css-animations/test_pseudoElement-get-animations.html @@ -0,0 +1,14 @@ +<!doctype html> +<meta charset=utf-8> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +'use strict'; +setup({explicit_done: true}); +SpecialPowers.pushPrefEnv( + { "set": [["dom.animations-api.core.enabled", true]]}, + function() { + window.open("file_pseudoElement-get-animations.html"); + }); +</script> |