summaryrefslogtreecommitdiffstats
path: root/dom/animation/test/css-animations
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /dom/animation/test/css-animations
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'dom/animation/test/css-animations')
-rw-r--r--dom/animation/test/css-animations/file_animation-cancel.html154
-rw-r--r--dom/animation/test/css-animations/file_animation-computed-timing.html566
-rw-r--r--dom/animation/test/css-animations/file_animation-currenttime.html345
-rw-r--r--dom/animation/test/css-animations/file_animation-finish.html97
-rw-r--r--dom/animation/test/css-animations/file_animation-finished.html93
-rw-r--r--dom/animation/test/css-animations/file_animation-id.html24
-rw-r--r--dom/animation/test/css-animations/file_animation-pausing.html165
-rw-r--r--dom/animation/test/css-animations/file_animation-playstate.html71
-rw-r--r--dom/animation/test/css-animations/file_animation-ready.html149
-rw-r--r--dom/animation/test/css-animations/file_animation-reverse.html29
-rw-r--r--dom/animation/test/css-animations/file_animation-starttime.html383
-rw-r--r--dom/animation/test/css-animations/file_animations-dynamic-changes.html154
-rw-r--r--dom/animation/test/css-animations/file_cssanimation-animationname.html37
-rw-r--r--dom/animation/test/css-animations/file_document-get-animations.html276
-rw-r--r--dom/animation/test/css-animations/file_effect-target.html54
-rw-r--r--dom/animation/test/css-animations/file_element-get-animations.html445
-rw-r--r--dom/animation/test/css-animations/file_keyframeeffect-getkeyframes.html672
-rw-r--r--dom/animation/test/css-animations/file_pseudoElement-get-animations.html70
-rw-r--r--dom/animation/test/css-animations/test_animation-cancel.html15
-rw-r--r--dom/animation/test/css-animations/test_animation-computed-timing.html16
-rw-r--r--dom/animation/test/css-animations/test_animation-currenttime.html15
-rw-r--r--dom/animation/test/css-animations/test_animation-finish.html15
-rw-r--r--dom/animation/test/css-animations/test_animation-finished.html15
-rw-r--r--dom/animation/test/css-animations/test_animation-id.html15
-rw-r--r--dom/animation/test/css-animations/test_animation-pausing.html15
-rw-r--r--dom/animation/test/css-animations/test_animation-playstate.html15
-rw-r--r--dom/animation/test/css-animations/test_animation-ready.html15
-rw-r--r--dom/animation/test/css-animations/test_animation-reverse.html15
-rw-r--r--dom/animation/test/css-animations/test_animation-starttime.html15
-rw-r--r--dom/animation/test/css-animations/test_animations-dynamic-changes.html15
-rw-r--r--dom/animation/test/css-animations/test_cssanimation-animationname.html15
-rw-r--r--dom/animation/test/css-animations/test_document-get-animations.html15
-rw-r--r--dom/animation/test/css-animations/test_effect-target.html15
-rw-r--r--dom/animation/test/css-animations/test_element-get-animations.html15
-rw-r--r--dom/animation/test/css-animations/test_keyframeeffect-getkeyframes.html15
-rw-r--r--dom/animation/test/css-animations/test_pseudoElement-get-animations.html14
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>