Update web-platform-tests to revision dc60bfc45b49e3a5e653320e65b0fd447676b836

This commit is contained in:
WPT Sync Bot 2018-06-04 21:06:39 -04:00
parent 652a177ff5
commit 0bc549be55
690 changed files with 6588 additions and 1564 deletions

View file

@ -0,0 +1,42 @@
<!doctype html>
<meta charset=utf-8>
<title>CSSAnimation.animationName</title>
<link rel="help"
href="https://drafts.csswg.org/css-animations-2/#dom-cssanimation-animationname">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="support/testcommon.js"></script>
<style>
@keyframes xyz {
to { left: 100px }
}
</style>
<body>
<div id="log"></div>
<script>
'use strict';
test(t => {
const 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(t => {
const 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(t => {
const 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');
</script>
</body>

View file

@ -0,0 +1,199 @@
<!doctype html>
<meta charset=utf-8>
<title>Canceling a CSS animation</title>
<!-- TODO: Add a more specific link for this once it is specified. -->
<link rel="help" href="https://drafts.csswg.org/css-animations-2/">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="support/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>
<div id="log"></div>
<script>
'use strict';
promise_test(async t => {
const div = addDiv(t, { style: 'animation: translateAnim 100s' });
const animation = div.getAnimations()[0];
await animation.ready;
assert_not_equals(getComputedStyle(div).transform, 'none',
'transform style is animated before canceling');
animation.cancel();
assert_equals(getComputedStyle(div).transform, 'none',
'transform style is no longer animated after canceling');
}, 'Animated style is cleared after canceling a running CSS animation');
promise_test(async t => {
const div = addDiv(t, { style: 'animation: translateAnim 100s forwards' });
const animation = div.getAnimations()[0];
animation.finish();
await animation.ready;
assert_not_equals(getComputedStyle(div).transform, 'none',
'transform style is filling before canceling');
animation.cancel();
assert_equals(getComputedStyle(div).transform, 'none',
'fill style is cleared after canceling');
}, 'Animated style is cleared after canceling a filling CSS animation');
test(t => {
const div = addDiv(t, { style: 'animation: marginLeftAnim 100s linear' });
const animation = div.getAnimations()[0];
animation.cancel();
assert_equals(getComputedStyle(div).marginLeft, '0px',
'margin-left style is not animated after canceling');
animation.currentTime = 50 * 1000;
assert_equals(getComputedStyle(div).marginLeft, '50px',
'margin-left style is updated when canceled animation is'
+ ' seeked');
}, 'After canceling an animation, it can still be seeked');
promise_test(async t => {
const div =
addDiv(t, { style: 'animation: marginLeftAnim100To200 100s linear' });
const animation = div.getAnimations()[0];
await animation.ready;
animation.cancel();
assert_equals(getComputedStyle(div).marginLeft, '0px',
'margin-left style is not animated after canceling');
animation.play();
assert_equals(getComputedStyle(div).marginLeft, '100px',
'margin-left style is animated after re-starting animation');
await animation.ready;
assert_equals(animation.playState, 'running',
'Animation succeeds in running after being re-started');
}, 'After canceling an animation, it can still be re-used');
test(t => {
const div =
addDiv(t, { style: 'animation: marginLeftAnim100To200 100s linear' });
const animation = div.getAnimations()[0];
animation.cancel();
assert_equals(getComputedStyle(div).marginLeft, '0px',
'margin-left style is not animated after canceling');
// Trigger a change to some animation properties and check that this
// doesn't cause the animation to become live again
div.style.animationDuration = '200s';
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 canceling an animation, updating animation properties doesn\'t make'
+ ' it live again');
test(t => {
const div =
addDiv(t, { style: 'animation: marginLeftAnim100To200 100s linear' });
const animation = div.getAnimations()[0];
animation.cancel();
assert_equals(getComputedStyle(div).marginLeft, '0px',
'margin-left style is not animated after canceling');
// 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 canceling an animation, updating animation-play-state doesn\'t'
+ ' make it live again');
promise_test(async t => {
const div = addDiv(t, { style: 'animation: translateAnim 10s both' });
div.style.marginLeft = '0px';
const animation = div.getAnimations()[0];
await animation.ready;
assert_equals(animation.playState, 'running');
div.style.animationName = 'none';
flushComputedStyle(div);
await waitForFrame();
assert_equals(animation.playState, 'idle');
assert_equals(getComputedStyle(div).marginLeft, '0px');
}, 'Setting animation-name to \'none\' cancels the animation');
promise_test(async t => {
const div = addDiv(t, { style: 'animation: translateAnim 10s both' });
const animation = div.getAnimations()[0];
await animation.ready;
assert_equals(animation.playState, 'running');
div.style.display = 'none';
await waitForFrame();
assert_equals(animation.playState, 'idle');
assert_equals(getComputedStyle(div).marginLeft, '0px');
}, 'Setting display:none on an element cancel its animations');
promise_test(async t => {
const parentDiv = addDiv(t);
const childDiv = document.createElement('div');
parentDiv.appendChild(childDiv);
childDiv.setAttribute('style', 'animation: translateAnim 10s both');
flushComputedStyle(childDiv);
const animation = childDiv.getAnimations()[0];
await animation.ready;
assert_equals(animation.playState, 'running');
parentDiv.style.display = 'none';
await waitForFrame();
assert_equals(animation.playState, 'idle');
assert_equals(getComputedStyle(childDiv).marginLeft, '0px');
}, 'Setting display:none on an ancestor element cancels animations on ' +
'descendants');
</script>
</body>
</html>

View file

@ -0,0 +1,132 @@
<!doctype html>
<meta charset=utf-8>
<title>CSSAnimation.effect</title>
<!-- TODO: Add a more specific link for this once it is specified. -->
<link rel="help" href="https://drafts.csswg.org/css-animations-2/#cssanimation">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="support/testcommon.js"></script>
<style>
@keyframes anim {
from {
margin-left: 0px;
}
to {
margin-left: 100px;
}
}
</style>
<body>
<div id="log"></div>
<script>
'use strict';
promise_test(async t => {
const div = addDiv(t);
div.style.animation = 'anim 100s';
const watcher = new EventWatcher(t, div, [ 'animationend',
'animationcancel' ]);
const animation = div.getAnimations()[0];
await animation.ready;
animation.currentTime = 50 * MS_PER_SEC;
animation.effect = null;
assert_equals(animation.playState, 'finished');
assert_equals(getComputedStyle(div).marginLeft, '0px');
await watcher.wait_for('animationend');
}, 'Setting a null effect on a running animation fires an animationend event');
promise_test(async t => {
const div = addDiv(t);
div.style.animation = 'anim 100s';
const animation = div.getAnimations()[0];
await animation.ready;
animation.currentTime = 50 * MS_PER_SEC;
animation.effect = new KeyframeEffect(div,
{ left: [ '0px' , '100px'] },
100 * MS_PER_SEC);
assert_equals(getComputedStyle(div).marginLeft, '0px');
assert_equals(getComputedStyle(div).left, '50px');
}, 'Replacing an animation\'s effect with an effect that targets a different ' +
'property should update both properties');
promise_test(async t => {
const div = addDiv(t);
div.style.animation = 'anim 100s';
const animation = div.getAnimations()[0];
await animation.ready;
animation.currentTime = 50 * MS_PER_SEC;
animation.effect = new KeyframeEffect(div,
{ left: [ '0px' , '100px'] },
20 * MS_PER_SEC);
assert_equals(animation.playState, 'finished');
}, 'Replacing an animation\'s effect with a shorter one that should have ' +
'already finished, the animation finishes immediately');
promise_test(async t => {
const div = addDiv(t);
div.style.animation = 'anim 100s';
const animation = div.getAnimations()[0];
assert_true(animation.pending);
animation.effect = new KeyframeEffect(div,
{ left: [ '0px' , '100px'] },
100 * MS_PER_SEC);
assert_true(animation.pending);
await animation.ready;
assert_false(animation.pending);
}, 'A play-pending animation\'s effect whose effect is replaced still exits ' +
'the pending state');
promise_test(async t => {
const div1 = addDiv(t);
const div2 = addDiv(t);
const watcher1 = new EventWatcher(t, div1, 'animationstart');
// Watch |div2| as well to ensure it does *not* get events.
const watcher2 = new EventWatcher(t, div2, 'animationstart');
div1.style.animation = 'anim 100s';
const animation = div1.getAnimations()[0];
animation.effect = new KeyframeEffect(div2,
{ left: [ '0px', '100px' ] },
100 * MS_PER_SEC);
await watcher1.wait_for('animationstart');
assert_equals(animation.effect.target, div2);
// Then wait a couple of frames and check that no event was dispatched.
await waitForAnimationFrames(2);
}, 'CSS animation events are dispatched at the original element even after'
+ ' setting an effect with a different target element');
promise_test(async t => {
const div = addDiv(t);
const watcher = new EventWatcher(t, div, [ 'animationstart',
'animationend',
'animationcancel' ]);
div.style.animation = 'anim 100s';
const animation = div.getAnimations()[0];
animation.finish();
await watcher.wait_for([ 'animationstart', 'animationend' ]);
// Set a longer effect
animation.effect = new KeyframeEffect(div,
{ left: [ '0px', '100px' ] },
200 * MS_PER_SEC);
await watcher.wait_for('animationstart');
}, 'After replacing a finished animation\'s effect with a longer one ' +
'it fires an animationstart event');
</script>
</body>

View file

@ -0,0 +1,87 @@
<!doctype html>
<meta charset=utf-8>
<title>CSSAnimation.finished</title>
<!-- TODO: Add a more specific link for this once it is specified. -->
<link rel="help" href="https://drafts.csswg.org/css-animations-2/#cssanimation">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="support/testcommon.js"></script>
<style>
@keyframes abc {
to { transform: translate(10px) }
}
@keyframes def {}
</style>
<body>
<div id="log"></div>
<script>
'use strict';
const ANIM_PROP_VAL = 'abc 100s';
const ANIM_DURATION = 100 * MS_PER_SEC;
promise_test(async t => {
const div = addDiv(t);
// Set up pending animation
div.style.animation = ANIM_PROP_VAL;
const animation = div.getAnimations()[0];
const originalFinishedPromise = animation.finished;
// Cancel the animation and flush styles
div.style.animation = '';
getComputedStyle(div).animation;
await promise_rejects(t, 'AbortError', originalFinishedPromise,
'finished promise is rejected with AbortError');
assert_not_equals(animation.finished, originalFinishedPromise,
'Finished promise should change after the original is ' +
'rejected');
}, 'finished promise is rejected when an animation is canceled by resetting ' +
'the animation property');
promise_test(async t => {
const 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;
const animation = div.getAnimations()[0];
const originalFinishedPromise = animation.finished;
// Update the animation and flush styles
div.style.animation = 'def 100s';
getComputedStyle(div).animation;
await promise_rejects(t, 'AbortError', originalFinishedPromise,
'finished promise is rejected with AbortError');
assert_not_equals(animation.finished, originalFinishedPromise,
'Finished promise should change after the original is ' +
'rejected');
}, 'finished promise is rejected when an animation is canceled by changing ' +
'the animation property');
promise_test(async t => {
const div = addDiv(t);
div.style.animation = ANIM_PROP_VAL;
const animation = div.getAnimations()[0];
const originalFinishedPromise = animation.finished;
animation.currentTime = ANIM_DURATION;
await animation.finished;
div.style.animationPlayState = 'running';
await waitForAnimationFrames(2);
assert_equals(animation.finished, originalFinishedPromise,
'The finished promise should NOT be reset');
assert_equals(animation.currentTime, ANIM_DURATION,
'Sanity check: the current time should not change');
}, 'finished promise is not reset when animationPlayState is set to running');
</script>
</body>

View file

@ -0,0 +1,609 @@
<!doctype html>
<meta charset=utf-8>
<title>CSSAnimation.getComputedTiming()</title>
<!-- TODO: Add a more specific link for this once it is specified. -->
<link rel="help" href="https://drafts.csswg.org/css-animations-2/#cssanimation">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="support/testcommon.js"></script>
<style>
@keyframes moveAnimation {
from { margin-left: 100px }
to { margin-left: 200px }
}
</style>
<body>
<div id="log"></div>
<script>
'use strict';
// --------------------
// delay
// --------------------
test(t => {
const div = addDiv(t, { style: 'animation: moveAnimation 100s' });
const effect = div.getAnimations()[0].effect;
assert_equals(effect.getComputedTiming().delay, 0, 'Initial value of delay');
}, 'delay of a new animation');
test(t => {
const div = addDiv(t, { style: 'animation: moveAnimation 100s -10s' });
const 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(t => {
const div = addDiv(t, { style: 'animation: moveAnimation 100s 10s' });
const 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(t => {
const div = addDiv(t, { style: 'animation: moveAnimation 100s' });
const effect = div.getAnimations()[0].effect;
assert_equals(effect.getComputedTiming().endDelay, 0,
'Initial value of endDelay');
}, 'endDelay of a new animation');
// --------------------
// fill
// --------------------
test(t => {
const getEffectWithFill = fill => {
const div = addDiv(t, { style: 'animation: moveAnimation 100s ' + fill });
return div.getAnimations()[0].effect;
};
let 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(t => {
const div = addDiv(t, { style: 'animation: moveAnimation 100s' });
const effect = div.getAnimations()[0].effect;
assert_equals(effect.getComputedTiming().iterationStart, 0,
'Initial value of iterationStart');
}, 'iterationStart of a new animation');
// --------------------
// iterations
// --------------------
test(t => {
const div = addDiv(t, { style: 'animation: moveAnimation 100s' });
const effect = div.getAnimations()[0].effect;
assert_equals(effect.getComputedTiming().iterations, 1,
'Initial value of iterations');
}, 'iterations of a new animation');
test(t => {
const div = addDiv(t, { style: 'animation: moveAnimation 100s 2016.5' });
const effect = div.getAnimations()[0].effect;
assert_equals(effect.getComputedTiming().iterations, 2016.5,
'Initial value of iterations');
}, 'iterations of a finitely repeating animation');
test(t => {
const div = addDiv(t, { style: 'animation: moveAnimation 100s infinite' });
const effect = div.getAnimations()[0].effect;
assert_equals(effect.getComputedTiming().iterations, Infinity,
'Initial value of iterations');
}, 'iterations of an infinitely repeating animation');
// --------------------
// duration
// --------------------
test(t => {
const div = addDiv(t, {
style: 'animation: moveAnimation 100s -10s infinite'
});
const 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(t => {
const getEffectWithDir = dir => {
const div = addDiv(t, { style: 'animation: moveAnimation 100s ' + dir });
return div.getAnimations()[0].effect;
};
let 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(t => {
const div = addDiv(t, { style: 'animation: moveAnimation 100s' });
const 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(t => {
const div = addDiv(t, { style: 'animation: moveAnimation 100s' });
const effect = div.getAnimations()[0].effect;
assert_equals(effect.getComputedTiming().endTime, 100 * MS_PER_SEC,
'Initial value of endTime');
}, 'endTime of an new animation');
test(t => {
const div = addDiv(t, { style: 'animation: moveAnimation 100s -5s' });
const effect = div.getAnimations()[0].effect;
const 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(t => {
const div = addDiv(t, {
style: 'animation: moveAnimation 10s -100s infinite'
});
const effect = div.getAnimations()[0].effect;
assert_equals(effect.getComputedTiming().endTime, Infinity,
'Initial value of endTime');
}, 'endTime of an infinitely repeating animation');
test(t => {
const div = addDiv(t, { style: 'animation: moveAnimation 0s 100s infinite' });
const 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(t => {
// Fill forwards so div.getAnimations()[0] won't return an
// undefined value.
const div = addDiv(t, {
style: 'animation: moveAnimation 10s -100s forwards'
});
const 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(t => {
const div = addDiv(t, { style: 'animation: moveAnimation 100s 5' });
const effect = div.getAnimations()[0].effect;
const answer = 100 * MS_PER_SEC * 5;
assert_equals(effect.getComputedTiming().activeDuration, answer,
'Initial value of activeDuration');
}, 'activeDuration of a new animation');
test(t => {
const div = addDiv(t, { style: 'animation: moveAnimation 100s infinite' });
const effect = div.getAnimations()[0].effect;
assert_equals(effect.getComputedTiming().activeDuration, Infinity,
'Initial value of activeDuration');
}, 'activeDuration of an infinitely repeating animation');
test(t => {
const div = addDiv(t, { style: 'animation: moveAnimation 0s 1s infinite' });
const 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(t => {
const div = addDiv(t, { style: 'animation: moveAnimation 100s 1s 0' });
const 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(t => {
const div = addDiv(t, { style: 'animation: moveAnimation 100s' });
const effect = div.getAnimations()[0].effect;
assert_equals(effect.getComputedTiming().localTime, 0,
'Initial value of localTime');
}, 'localTime of a new animation');
test(t => {
const div = addDiv(t, { style: 'animation: moveAnimation 100s' });
const 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(async t => {
const div = addDiv(t, { style: 'animation: moveAnimation 100s' });
const anim = div.getAnimations()[0];
anim.playbackRate = 2; // 2 times faster
await anim.ready;
assert_equals(anim.effect.getComputedTiming().localTime, anim.currentTime,
'localTime is equal to currentTime');
await waitForFrame();
assert_equals(anim.effect.getComputedTiming().localTime, anim.currentTime,
'localTime is equal to currentTime');
}, 'localTime reflects playbackRate immediately');
test(t => {
const div = addDiv(t);
const effect = new KeyframeEffect(div, {left: ["0px", "100px"]});
assert_equals(effect.getComputedTiming().localTime, null,
'localTime for orphaned effect');
}, 'localTime of an AnimationEffect without an Animation');
// --------------------
// progress
//
// Note: Even though CSS animations have a default animation-timing-function of
// "ease", this only applies between keyframes (often referred to as the
// keyframe-level easing). The progress value returned by getComputedTiming(),
// however, only reflects effect-level easing and this defaults to "linear",
// even for CSS animations.
// --------------------
test(t => {
const tests = [
{ 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] },
];
for (const test of tests) {
const div = addDiv(t, {
style: 'animation: moveAnimation 100s 10s ' + test.fill
});
const 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],
`Initial progress with "${test.fill}" fill`);
}
}, 'progress of an animation with different fill modes');
test(t => {
const div = addDiv(t, { style: 'animation: moveAnimation 10s 10 both' });
const 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(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).
const div = addDiv(t, { style: 'animation: moveAnimation 0s infinite both' });
const 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(t => {
// Default iterations = 1
const div = addDiv(t, { style: 'animation: moveAnimation 0s both' });
const 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(t => {
const div = addDiv(t, { style: 'animation: moveAnimation 0s 5s 10.25 both' });
const 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(t => {
const div = addDiv(t, {
style: 'animation: moveAnimation 0s 5s 10.25 both reverse',
});
const 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(t => {
const div = addDiv(t, {
style: 'animation: moveAnimation 10s 10.25 both alternate',
});
const 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(t => {
const div = addDiv(t, {
style: 'animation: moveAnimation 10s 10.25 both alternate-reverse',
});
const 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(t => {
const div = addDiv(t, {
style: 'animation: moveAnimation 0s 10.25 both alternate',
});
const 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(t => {
const div = addDiv(t, {
style: 'animation: moveAnimation 0s 10.25 both alternate-reverse',
});
const 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(t => {
const div = addDiv(t, { style: 'animation: moveAnimation 100s 2s' });
const 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(t => {
const div = addDiv(t, { style: 'animation: moveAnimation 100s' });
const anim = div.getAnimations()[0];
assert_equals(anim.effect.getComputedTiming().currentIteration, 0,
'Initial value of currentIteration');
}, 'currentIteration of a new animation is zero');
test(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).
const div = addDiv(t, { style: 'animation: moveAnimation 0s infinite both' });
const 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(t => {
const div = addDiv(t, { style: 'animation: moveAnimation 0s 10.5 both' });
const 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(t => {
const div = addDiv(t, {
style: 'animation: moveAnimation 100s 5.5 forwards'
});
const 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(t => {
const div = addDiv(t, { style: 'animation: moveAnimation 100s 2 forwards' });
const 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(t => {
const div = addDiv(t, { style: 'animation: moveAnimation 100s forwards' });
const 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(t => {
const div = addDiv(t);
const effect = new KeyframeEffect(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.
</script>
</body>

View file

@ -0,0 +1,74 @@
<!doctype html>
<html>
<head>
<meta charset=utf-8>
<title>CSSAnimation.currentTime</title>
<!-- TODO: Add a more specific link for this once it is specified. -->
<link rel="help" href="https://drafts.csswg.org/css-animations-2/#cssanimation">
<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="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="support/testcommon.js"></script>
</head>
<body>
<div id="log"></div>
<script type="text/javascript">
'use strict';
promise_test(async t => {
const div = addDiv(t, { class: 'animated-div' });
div.style.animation = 'anim 100s';
const animation = div.getAnimations()[0];
assert_equals(
animation.currentTime,
0,
'Animation.currentTime should be zero when an animation ' +
'is initially created'
);
await animation.ready;
animation.currentTime = 50 * MS_PER_SEC;
assert_time_equals_literal(
animation.currentTime,
50 * MS_PER_SEC,
'Check setting of currentTime actually works'
);
assert_equals(getComputedStyle(div).marginLeft, '150px');
}, 'currentTime can be used to seek a CSS animation');
promise_test(async t => {
const div = addDiv(t, { class: 'animated-div' });
div.style.animation = 'anim 100s';
const animation = div.getAnimations()[0];
await animation.ready;
assert_throws(
new TypeError(),
() => {
animation.currentTime = null;
},
'Expect TypeError exception on trying to set Animation.currentTime to null'
);
}, 'Setting currentTime to null on a CSS animation throws');
</script>
</body>
</html>

View file

@ -0,0 +1,29 @@
<!doctype html>
<meta charset=utf-8>
<title>CSSAnimation.id</title>
<!-- TODO: Add a more specific link for this once it is specified. -->
<link rel="help" href="https://drafts.csswg.org/css-animations-2/#cssanimation">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="support/testcommon.js"></script>
<style>
@keyframes abc { }
</style>
<body>
<div id="log"></div>
<script>
'use strict';
test(t => {
const div = addDiv(t);
div.style.animation = 'abc 100s';
const 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');
</script>
</body>
</html>

View file

@ -0,0 +1,172 @@
<!doctype html>
<meta charset=utf-8>
<title>Pausing a CSSAnimation</title>
<link rel="help"
href="https://drafts.csswg.org/css-animations-2/#animation-play-state">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="support/testcommon.js"></script>
<style>
@keyframes anim {
0% { margin-left: 0px }
100% { margin-left: 10000px }
}
</style>
<body>
<div id="log"></div>
<script>
'use strict';
const getMarginLeft = cs => parseFloat(cs.marginLeft);
promise_test(async t => {
const div = addDiv(t);
const cs = getComputedStyle(div);
div.style.animation = 'anim 1000s paused';
const animation = div.getAnimations()[0];
assert_equals(getMarginLeft(cs), 0,
'Initial value of margin-left is zero');
animation.play();
await animation.ready;
await waitForNextFrame();
assert_greater_than(getMarginLeft(cs), 0,
'Playing value of margin-left is greater than zero');
}, 'play() overrides animation-play-state');
promise_test(async t => {
const div = addDiv(t);
const cs = getComputedStyle(div);
div.style.animation = 'anim 1000s paused';
const animation = div.getAnimations()[0];
assert_equals(getMarginLeft(cs), 0,
'Initial value of margin-left is zero');
animation.pause();
div.style.animationPlayState = 'running';
await animation.ready;
await waitForNextFrame();
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(async t => {
const div = addDiv(t);
const cs = getComputedStyle(div);
div.style.animation = 'anim 1000s paused';
const animation = div.getAnimations()[0];
assert_equals(getMarginLeft(cs), 0,
'Initial value of margin-left is zero');
animation.play();
await animation.ready;
div.style.animationPlayState = 'running';
cs.animationPlayState; // Trigger style resolution
await waitForNextFrame();
assert_equals(cs.animationPlayState, 'running',
'animation-play-state is running');
div.style.animationPlayState = 'paused';
await animation.ready;
assert_equals(cs.animationPlayState, 'paused',
'animation-play-state is paused');
const previousAnimVal = getMarginLeft(cs);
await waitForNextFrame();
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(async t => {
const div = addDiv(t);
const cs = getComputedStyle(div);
div.style.animation = 'anim 1000s';
const 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();
const previousAnimVal = getMarginLeft(cs);
await animation.ready;
await waitForNextFrame();
assert_equals(cs.animationPlayState, 'paused',
'animation-play-state is paused');
assert_greater_than(getMarginLeft(cs), previousAnimVal,
'Playing value of margin-left is increasing');
}, 'play() flushes pending changes to animation-play-state first');
promise_test(async t => {
const div = addDiv(t);
const cs = getComputedStyle(div);
div.style.animation = 'anim 1000s paused';
const 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();
const previousAnimVal = getMarginLeft(cs);
await animation.ready;
await waitForNextFrame();
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(async t => {
const div = addDiv(t, { style: 'animation: anim 1000s' });
const animation = div.getAnimations()[0];
let readyPromiseRun = false;
await animation.ready;
div.style.animationPlayState = 'paused';
assert_true(animation.pending && animation.playState === 'paused',
'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.
await animation.ready;
}, 'Setting the current time completes a pending pause');
</script>
</body>

View file

@ -0,0 +1,57 @@
<!doctype html>
<meta charset=utf-8>
<title>CSSAnimation.playState</title>
<!-- TODO: Add a more specific link for this once it is specified. -->
<link rel="help" href="https://drafts.csswg.org/css-animations-2/#cssanimation">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="support/testcommon.js"></script>
<style>
@keyframes anim { }
</style>
<body>
<div id="log"></div>
<script>
'use strict';
test(t => {
const div = addDiv(t, { 'style': 'animation: anim 100s' });
const animation = div.getAnimations()[0];
assert_true(animation.pending);
assert_equals(animation.playState, 'running');
assert_equals(animation.startTime, null);
}, 'A new CSS animation is initially play-pending');
test(t => {
const div = addDiv(t, { 'style': 'animation: anim 1000s paused' });
const animation = div.getAnimations()[0];
assert_equals(animation.playState, 'paused');
}, 'Animation returns correct playState when paused');
test(t => {
const div = addDiv(t, { 'style': 'animation: anim 1000s' });
const animation = div.getAnimations()[0];
animation.pause();
assert_equals(animation.playState, 'paused');
}, 'Animation.playState updates when paused by script');
test(t => {
const div = addDiv(t, { 'style': 'animation: anim 1000s paused' });
const animation = div.getAnimations()[0];
div.style.animationPlayState = 'running';
// This test also checks that calling playState flushes style
assert_equals(animation.playState, 'running',
'Animation.playState reports running after updating'
+ ' animation-play-state (got: ' + animation.playState + ')');
}, 'Animation.playState updates when resumed by setting style');
test(t => {
const div = addDiv(t, { 'style': 'animation: anim 1000s' });
const animation = div.getAnimations()[0];
animation.cancel();
assert_equals(animation.playState, 'idle');
}, 'Animation returns correct playState when canceled');
</script>
</body>

View file

@ -0,0 +1,100 @@
<!doctype html>
<meta charset=utf-8>
<title>CSSAnimation.ready</title>
<!-- TODO: Add a more specific link for this once it is specified. -->
<link rel="help" href="https://drafts.csswg.org/css-animations-2/#cssanimation">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="support/testcommon.js"></script>
<style>
@keyframes abc {
to { transform: translate(10px) }
}
</style>
<body>
<div id="log"></div>
<script>
'use strict';
promise_test(async t => {
const div = addDiv(t);
div.style.animation = 'abc 100s paused';
const animation = div.getAnimations()[0];
const originalReadyPromise = animation.ready;
await animation.ready;
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(async t => {
const div = addDiv(t);
// Set up pending animation
div.style.animation = 'abc 100s';
const animation = div.getAnimations()[0];
assert_true(animation.pending, 'Animation is initially pending');
const readyPromise = animation.ready;
// Cancel the animation and flush styles
div.style.animation = '';
getComputedStyle(div).animation;
await promise_rejects(t, 'AbortError', readyPromise,
'ready promise is rejected with AbortError');
}, 'ready promise is rejected when an animation is canceled by resetting'
+ ' the animation property');
promise_test(async t => {
const 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';
const animation = div.getAnimations()[0];
assert_true(animation.pending, 'Animation is initially pending');
const readyPromise = animation.ready;
// Update the animation and flush styles
div.style.animation = 'def 100s';
getComputedStyle(div).animation;
await promise_rejects(t, 'AbortError', readyPromise,
'ready promise is rejected with AbortError');
}, 'ready promise is rejected when an animation is canceled by updating'
+ ' the animation property');
promise_test(async t => {
const div = addDiv(t, { style: 'animation: abc 100s' });
const animation = div.getAnimations()[0];
const originalReadyPromise = animation.ready;
await animation.ready;
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(async t => {
const div = addDiv(t, { style: 'animation: abc 100s' });
const animation = div.getAnimations()[0];
await animation.ready;
div.style.animationPlayState = 'paused';
const 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');
</script>
</body>

View file

@ -0,0 +1,75 @@
<!doctype html>
<html>
<head>
<meta charset=utf-8>
<title>CSSAnimation.startTime</title>
<!-- TODO: Add a more specific link for this once it is specified. -->
<link rel="help" href="https://drafts.csswg.org/css-animations-2/#cssanimation">
<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="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="support/testcommon.js"></script>
</head>
<body>
<div id="log"></div>
<script type="text/javascript">
'use strict';
test(t => {
const div = addDiv(t, { 'class': 'animated-div' });
div.style.animation = 'anim 100s 100s';
const animation = div.getAnimations()[0];
const timelineTime = animation.timeline.currentTime;
animation.startTime = timelineTime;
assert_times_equal(animation.startTime, timelineTime,
'Check setting of startTime actually works');
}, 'The start time of a CSS animation can be set');
promise_test(async t => {
const div = addDiv(t, { 'class': 'animated-div' });
div.style.animation = 'anim 100s 100s';
const animation = div.getAnimations()[0];
// Seek to the half-way point
animation.startTime = animation.timeline.currentTime - 150 * MS_PER_SEC;
assert_equals(getComputedStyle(div).marginLeft, '150px');
}, 'The start time can be set to seek a CSS animation');
promise_test(async t => {
const div = addDiv(t, { class: 'animated-div' });
const eventWatcher = new EventWatcher(t, div, [
'animationstart',
'animationend',
]);
div.style.animation = 'anim 100s 100s';
const animation = div.getAnimations()[0];
await animation.ready;
animation.startTime = animation.timeline.currentTime - 100 * MS_PER_SEC;
await eventWatcher.wait_for('animationstart');
animation.startTime = animation.timeline.currentTime - 200 * MS_PER_SEC;
await eventWatcher.wait_for('animationend');
}, 'Seeking a CSS animation using the start time dispatches animation events');
</script>
</body>
</html>

View file

@ -0,0 +1,77 @@
<!doctype html>
<meta charset=utf-8>
<title>CSSPseudoElement.getAnimations() for CSS animations</title>
<!-- TODO: Add a more specific link for this once it is specified. -->
<link rel="help" href="https://drafts.csswg.org/css-animations-2/">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="support/testcommon.js"></script>
<style>
@keyframes anim1 { }
@keyframes anim2 { }
.before::before {
animation: anim1 10s;
content: '';
}
.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;
content: '';
}
</style>
<body>
<div id="log"></div>
<script>
'use strict';
test(t => {
const div = addDiv(t, { class: 'before' });
const 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(t => {
const 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
const pseudoTarget = document.getAnimations()[0].effect.target;
const effect = new KeyframeEffect(pseudoTarget,
{ background: ["blue", "red"] },
3 * MS_PER_SEC);
const newAnimation = new Animation(effect, document.timeline);
newAnimation.id = 'scripted-anim';
newAnimation.play();
// Check order - the script-generated animation should appear later
const 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');
</script>
</body>

View file

@ -0,0 +1,286 @@
<!doctype html>
<meta charset=utf-8>
<title>Document.getAnimations() for CSS animations</title>
<link rel="help" href="https://drafts.csswg.org/css-animations-2/#animation-composite-order">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="support/testcommon.js"></script>
<style>
@keyframes animLeft {
to { left: 100px }
}
@keyframes animTop {
to { top: 100px }
}
@keyframes animBottom {
to { bottom: 100px }
}
@keyframes animRight {
to { right: 100px }
}
::before {
content: ''
}
::after {
content: ''
}
</style>
<body>
<div id="log"></div>
<script>
'use strict';
test(t => {
assert_equals(document.getAnimations().length, 0,
'getAnimations returns an empty sequence for a document'
+ ' with no animations');
}, 'getAnimations for non-animated content');
test(t => {
const 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(t => {
const div = addDiv(t);
div.style.animation = 'animLeft 100s, animTop 100s, animRight 100s, ' +
'animBottom 100s';
const 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(t => {
const div1 = addDiv(t, { style: 'animation: animLeft 100s' });
const div2 = addDiv(t, { style: 'animation: animLeft 100s' });
const div3 = addDiv(t, { style: 'animation: animLeft 100s' });
const div4 = addDiv(t, { style: 'animation: animLeft 100s' });
let 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(t => {
const div1 = addDiv(t, { style: 'animation: animLeft 100s, animTop 100s' });
const div2 = addDiv(t, { style: 'animation: animBottom 100s' });
let expectedResults = [ [ div1, 'animLeft' ],
[ div1, 'animTop' ],
[ div2, 'animBottom' ] ];
let animations = document.getAnimations();
assert_equals(animations.length, expectedResults.length,
'getAnimations returns all running CSS Animations');
animations.forEach((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((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(t => {
const div = addDiv(t, { style: 'animation: animLeft 100s, animTop 100s' });
const 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();
const 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(t => {
const div = addDiv(t, { style: 'animation: animLeft 100s, animTop 100s' });
const animLeft = document.getAnimations()[0];
const animTop = document.getAnimations()[1];
// Disassociate both animations from markup and restart in opposite order
div.style.animation = '';
animTop.play();
animLeft.play();
const 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(t => {
// Add an animation first
const 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
const 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(t => {
const 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(t => {
const 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(t => {
const 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(t => {
const div = addDiv(t, { style: 'animation: animLeft 100s' });
div.getAnimations()[0].cancel();
assert_equals(document.getAnimations().length, 0,
'CSS animations canceled by the API are not returned');
}, 'CSS Animations canceled via the API are not returned');
test(t => {
const div = addDiv(t, { style: 'animation: animLeft 100s' });
const anim = div.getAnimations()[0];
anim.cancel();
anim.play();
assert_equals(document.getAnimations().length, 1,
'CSS animations canceled and restarted by the API are ' +
'returned');
}, 'CSS Animations canceled and restarted via the API are returned');
test(t => {
addStyle(t, { '#parent::after': 'animation: animLeft 10s;',
'#parent::before': 'animation: animRight 10s;' });
// create two divs with these arrangement:
// parent
// ::before,
// ::after
// |
// child
const parent = addDiv(t, { 'id': 'parent' });
const child = addDiv(t);
parent.appendChild(child);
for (const div of [parent, child]) {
div.setAttribute('style', 'animation: animBottom 10s');
}
const 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');
</script>
</body>

View file

@ -0,0 +1,165 @@
<!doctype html>
<meta charset=utf-8>
<title>
Element.getAnimations() - Dynamic changes to the list of CSS animations
</title>
<!-- TODO: Add a more specific link for this once it is specified. -->
<link rel="help" href="https://drafts.csswg.org/css-animations-2/">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="support/testcommon.js"></script>
<style>
@keyframes anim1 {
to { left: 100px }
}
@keyframes anim2 { }
</style>
<body>
<div id="log"></div>
<script>
'use strict';
promise_test(async t => {
const div = addDiv(t);
div.style.animation = 'anim1 100s';
const originalAnimation = div.getAnimations()[0];
// Wait a moment so we can confirm the startTime doesn't change (and doesn't
// simply reflect the current time).
await originalAnimation.ready;
const originalStartTime = originalAnimation.startTime;
const originalCurrentTime = originalAnimation.currentTime;
// Wait a moment so we can confirm the startTime doesn't change (and
// doesn't simply reflect the current time).
await waitForNextFrame();
div.style.animationDuration = '200s';
const 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(t => {
const div = addDiv(t);
div.style.animation = 'anim1 100s, anim1 100s';
// Store original state
let animations = div.getAnimations();
const animation1 = animations[0];
const 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(async t => {
const div = addDiv(t);
div.style.animation = 'anim1 200s, anim1 100s';
// Store original state
let animations = div.getAnimations();
const animation1 = animations[0];
const 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).
await waitForAllAnimations(animations);
await waitForFrame();
// 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');
assert_equals(animations[0].startTime, null,
'New animation has a null start time');
await animations[0].ready;
assert_greater_than(animations[0].startTime, animations[1].startTime,
'New animation has later start time');
}, 'Only the startTimes of existing animations are preserved');
promise_test(async t => {
const div = addDiv(t);
div.style.animation = 'anim1 100s, anim1 100s';
const secondAnimation = div.getAnimations()[1];
// Wait before continuing so we can compare start times
await secondAnimation.ready;
await waitForNextFrame();
// Trim list of animations
div.style.animationName = 'anim1';
const 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_less_than(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(async t => {
const div = addDiv(t);
div.style.animation = 'anim1 100s';
const firstAddedAnimation = div.getAnimations()[0];
// Wait and add second Animation
await firstAddedAnimation.ready;
await waitForFrame();
div.style.animation = 'anim1 100s, anim1 100s';
const secondAddedAnimation = div.getAnimations()[0];
// Wait again and add another Animation
await secondAddedAnimation.ready;
await waitForFrame();
div.style.animation = 'anim1 100s, anim2 100s, anim1 100s';
const 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');
await animations[1].ready;
assert_greater_than(animations[1].startTime, animations[0].startTime,
'Interleaved animation starts later than existing ' +
'animations');
assert_greater_than(animations[0].startTime, animations[2].startTime,
'Original animations retain their start time');
}, 'Animation state is preserved when interleaving animations in list');
</script>
</body>

View file

@ -0,0 +1,449 @@
<!doctype html>
<meta charset=utf-8>
<title>Element.getAnimations() for CSS animations</title>
<!-- TODO: Add a more specific link for this once it is specified. -->
<link rel="help" href="https://drafts.csswg.org/css-animations-2/">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="support/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 }
}
::before {
content: ''
}
::after {
content: ''
}
@keyframes empty { }
</style>
<body>
<div id="log"></div>
<script>
'use strict';
test(t => {
const 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(async t => {
const div = addDiv(t);
// Add an animation
div.style.animation = 'anim1 100s';
let animations = div.getAnimations();
assert_equals(animations.length, 1,
'getAnimations returns an Animation running CSS Animations');
await animations[0].ready;
// Add a second animation
div.style.animation = 'anim1 100s, anim2 100s';
animations = div.getAnimations();
assert_equals(animations.length, 2,
'getAnimations returns one CSSAnimation for each value of animation-name');
// (We don't check the order of the Animations since that is covered by tests
// later in this file.)
}, 'getAnimations for CSS Animations');
test(t => {
const 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(t => {
const 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(async t => {
const div = addDiv(t);
// Add an animation
div.style.backgroundColor = 'red';
div.style.animation = 'anim1 100s';
getComputedStyle(div).backgroundColor;
// Wait until a frame after the animation starts, then add a transition
let animations = div.getAnimations();
await animations[0].ready;
await waitForFrame();
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(t => {
const div = addDiv(t);
// Set up event listener
div.addEventListener('animationend', t.step_func(() => {
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(t => {
const div = addDiv(t);
// Set up event listener
div.addEventListener('animationend', t.step_func(() => {
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(t => {
const div = addDiv(t);
div.style.animation = 'none 100s';
let 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(t => {
const div = addDiv(t);
div.style.animation = 'missing 100s';
let 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(async t => {
const div = addDiv(t);
div.style.animation = 'anim1 100s, notyet 100s';
let animations = div.getAnimations();
assert_equals(animations.length, 1,
'getAnimations initally only returns Animations for CSS Animations whose'
+ ' animation-name is found');
await animations[0].ready;
await waitForFrame();
const 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');
await waitForAllAnimations(animations);
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(t => {
const 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(t => {
const 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(async t => {
const div = addDiv(t);
div.style.animation = 'anim1 100s 100s';
const animations = div.getAnimations();
assert_equals(animations.length, 1,
'getAnimations returns animations for CSS animations whose'
+ ' delay makes them start later');
await animations[0].ready;
await waitForFrame();
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(t => {
const 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(t => {
const div = addDiv(t);
div.style.animation = 'anim1 100s';
const originalAnimation = div.getAnimations()[0];
// Update pause state (an Animation change)
div.style.animationPlayState = 'paused';
const pendingAnimation = div.getAnimations()[0];
assert_equals(pendingAnimation.playState, 'paused',
'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';
const extendedAnimation = div.getAnimations()[0];
assert_equals(
extendedAnimation.effect.getTiming().duration,
200 * MS_PER_SEC,
'animation\'s duration has been updated'
);
assert_equals(originalAnimation, extendedAnimation,
'getAnimations returns the same objects even when their'
+ ' duration changes');
}, 'getAnimations returns objects with the same identity');
test(t => {
const div = addDiv(t);
div.style.animation = 'anim1 100s';
assert_equals(div.getAnimations().length, 1,
'getAnimations returns an animation before canceling');
const animation = div.getAnimations()[0];
animation.cancel();
assert_equals(div.getAnimations().length, 0,
'getAnimations does not return canceled animations');
animation.play();
assert_equals(div.getAnimations().length, 1,
'getAnimations returns canceled animations that have been re-started');
}, 'getAnimations for CSS Animations that are canceled');
promise_test(async t => {
const div = addDiv(t);
div.style.animation = 'anim2 100s';
await div.getAnimations()[0].ready;
// 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';
let 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.
const anim1 = anims[0];
anim1.cancel();
anim1.play();
anims = div.getAnimations();
assert_equals(anims[0].animationName, 'anim1',
'animation order after canceling and restarting');
assert_equals(anims[1].animationName, 'anim2',
'animation order after canceling and restarting');
}, 'getAnimations for CSS Animations follows animation-name order');
test(t => {
addStyle(t, { '#target::after': 'animation: anim1 10s;',
'#target::before': 'animation: anim1 10s;' });
const target = addDiv(t, { 'id': 'target' });
target.style.animation = 'anim1 100s';
const 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');
}, '{ subtree: false } on a leaf element returns the element\'s animations'
+ ' and ignore pseudo-elements');
test(t => {
addStyle(t, { '#target::after': 'animation: anim1 10s;',
'#target::before': 'animation: anim1 10s;' });
const target = addDiv(t, { 'id': 'target' });
target.style.animation = 'anim1 100s';
const 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');
}, '{ subtree: true } on a leaf element returns the element\'s animations'
+ ' and its pseudo-elements\' animations');
test(t => {
addStyle(t, { '#parent::after': 'animation: anim1 10s;',
'#parent::before': 'animation: anim1 10s;',
'#child::after': 'animation: anim1 10s;',
'#child::before': 'animation: anim1 10s;' });
const parent = addDiv(t, { 'id': 'parent' });
parent.style.animation = 'anim1 100s';
const child = addDiv(t, { 'id': 'child' });
child.style.animation = 'anim1 100s';
parent.appendChild(child);
const 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');
}, '{ subtree: false } on an element with a child returns only the element\'s'
+ ' animations');
test(t => {
addStyle(t, { '#parent::after': 'animation: anim1 10s;',
'#parent::before': 'animation: anim1 10s;',
'#child::after': 'animation: anim1 10s;',
'#child::before': 'animation: anim1 10s;' });
const parent = addDiv(t, { 'id': 'parent' });
const child = addDiv(t, { 'id': 'child' });
parent.style.animation = 'anim1 100s';
child.style.animation = 'anim1 100s';
parent.appendChild(child);
const 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');
}, '{ subtree: true } on an element with a child returns animations from the'
+ ' element, its pseudo-elements, its child and its child pseudo-elements');
test(t => {
const parent = addDiv(t, { 'id': 'parent' });
const child1 = addDiv(t, { 'id': 'child1' });
const grandchild1 = addDiv(t, { 'id': 'grandchild1' });
const grandchild2 = addDiv(t, { 'id': 'grandchild2' });
const 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);
const 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');
}, '{ subtree: true } on an element with many descendants returns animations'
+ ' from all the descendants');
</script>
</body>

View file

@ -0,0 +1,757 @@
<!doctype html>
<meta charset=utf-8>
<title>KeyframeEffect.getKeyframes() for CSS animations</title>
<!-- TODO: Add a more specific link for this once it is specified. -->
<link rel="help" href="https://drafts.csswg.org/css-animations-2/">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="support/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: rgb(0, 0, 0); }
to { color: rgb(255, 255, 255); }
}
@keyframes anim-simple-three {
from { color: rgb(0, 0, 0); }
50% { color: rgb(0, 0, 255); }
to { color: rgb(255, 255, 255); }
}
@keyframes anim-simple-timing {
from { color: rgb(0, 0, 0); animation-timing-function: linear; }
50% { color: rgb(0, 0, 255); animation-timing-function: ease-in-out; }
to { color: rgb(255, 255, 255); animation-timing-function: step-end; }
}
@keyframes anim-simple-timing-some {
from { color: rgb(0, 0, 0); animation-timing-function: linear; }
50% { color: rgb(0, 0, 255); }
to { color: rgb(255, 255, 255); }
}
@keyframes anim-simple-shorthand {
from { margin: 8px; }
to { margin: 16px; }
}
@keyframes anim-omit-to {
from { color: rgb(0, 0, 255); }
}
@keyframes anim-omit-from {
to { color: rgb(0, 0, 255); }
}
@keyframes anim-omit-from-to {
50% { color: rgb(0, 0, 255); }
}
@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: rgb(0, 0, 0); margin-top: 8px; }
25% { color: rgb(0, 0, 255); }
75% { margin-top: 12px; }
to { color: rgb(255, 255, 255); margin-top: 16px }
}
@keyframes anim-different-props-and-easing {
from { color: rgb(0, 0, 0); margin-top: 8px; animation-timing-function: linear; }
25% { color: rgb(0, 0, 255); animation-timing-function: step-end; }
75% { margin-top: 12px; animation-timing-function: ease-in; }
to { color: rgb(255, 255, 255); margin-top: 16px }
}
@keyframes anim-merge-offset {
from { color: rgb(0, 0, 0); }
to { color: rgb(255, 255, 255); }
from { margin-top: 8px; }
to { margin-top: 16px; }
}
@keyframes anim-merge-offset-and-easing {
from { color: rgb(0, 0, 0); animation-timing-function: step-end; }
to { color: rgb(255, 255, 255); }
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-filter-drop-shadow {
from { filter: drop-shadow(10px 10px 10px rgb(0, 255, 0)); }
to { filter: drop-shadow(50px 30px 10px rgb(255, 0, 0)); }
}
@keyframes anim-text-shadow {
to { text-shadow: none; }
}
@keyframes anim-background-size {
to { background-size: 50%, 6px, contain }
}
:root {
--var-100px: 100px;
--end-color: rgb(255, 0, 0);
}
@keyframes anim-variables {
to { transform: translate(var(--var-100px), 0) }
}
@keyframes anim-variables-shorthand {
to { margin: var(--var-100px) }
}
@keyframes anim-custom-property-in-keyframe {
to { --end-color: rgb(0, 255, 0); color: var(--end-color) }
}
@keyframes anim-only-custom-property-in-keyframe {
from { transform: translate(100px, 0) }
to { --not-used: 200px }
}
</style>
<body>
<div id="log"></div>
<script>
"use strict";
const getKeyframes = elem => elem.getAnimations()[0].effect.getKeyframes();
const assert_frames_equal = (a, b, name) => {
assert_equals(Object.keys(a).sort().toString(),
Object.keys(b).sort().toString(),
"properties on " + name);
for (const 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(t => {
const 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");
}, 'KeyframeEffect.getKeyframes() returns no frames for various kinds'
+ ' of empty enimations');
test(t => {
const div = addDiv(t);
div.style.animation = 'anim-simple 100s';
const frames = getKeyframes(div);
assert_equals(frames.length, 2, "number of frames");
const expected = [
{ offset: 0, computedOffset: 0, easing: "ease",
color: "rgb(0, 0, 0)", composite: null },
{ offset: 1, computedOffset: 1, easing: "ease",
color: "rgb(255, 255, 255)", composite: null },
];
for (let i = 0; i < frames.length; i++) {
assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i);
}
}, 'KeyframeEffect.getKeyframes() returns expected frames for a simple'
+ ' animation');
test(t => {
for (const easing of kTimingFunctionValues) {
const div = addDiv(t);
div.style.animation = 'anim-simple-three 100s ' + easing;
const frames = getKeyframes(div);
assert_equals(frames.length, 3, "number of frames");
for (let i = 0; i < frames.length; i++) {
assert_equals(frames[i].easing, easing,
"value for 'easing' on ComputedKeyframe #" + i);
}
}
}, 'KeyframeEffect.getKeyframes() returns frames with expected easing'
+ ' values, when the easing comes from animation-timing-function on the'
+ ' element');
test(t => {
const div = addDiv(t);
div.style.animation = 'anim-simple-timing 100s';
const 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");
}, 'KeyframeEffect.getKeyframes() returns frames with expected easing'
+ ' values, when the easing is specified on each keyframe');
test(t => {
const div = addDiv(t);
div.style.animation = 'anim-simple-timing-some 100s step-start';
const 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");
}, 'KeyframeEffect.getKeyframes() returns frames with expected easing'
+ ' values, when the easing is specified on some keyframes');
test(t => {
const div = addDiv(t);
div.style.animation = 'anim-simple-shorthand 100s';
const frames = getKeyframes(div);
assert_equals(frames.length, 2, "number of frames");
const expected = [
{ offset: 0, computedOffset: 0, easing: "ease", composite: null,
marginBottom: "8px", marginLeft: "8px",
marginRight: "8px", marginTop: "8px" },
{ offset: 1, computedOffset: 1, easing: "ease", composite: null,
marginBottom: "16px", marginLeft: "16px",
marginRight: "16px", marginTop: "16px" },
];
for (let i = 0; i < frames.length; i++) {
assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i);
}
}, 'KeyframeEffect.getKeyframes() returns expected frames for a simple'
+ ' animation that specifies a single shorthand property');
test(t => {
const div = addDiv(t);
div.style.animation = 'anim-omit-to 100s';
div.style.color = 'rgb(255, 255, 255)';
const frames = getKeyframes(div);
assert_equals(frames.length, 2, "number of frames");
const expected = [
{ offset: 0, computedOffset: 0, easing: "ease", composite: null,
color: "rgb(0, 0, 255)" },
{ offset: 1, computedOffset: 1, easing: "ease", composite: null,
color: "rgb(255, 255, 255)" },
];
for (let i = 0; i < frames.length; i++) {
assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i);
}
}, 'KeyframeEffect.getKeyframes() returns expected frames for an ' +
'animation with a 0% keyframe and no 100% keyframe');
test(t => {
const div = addDiv(t);
div.style.animation = 'anim-omit-from 100s';
div.style.color = 'rgb(255, 255, 255)';
const frames = getKeyframes(div);
assert_equals(frames.length, 2, "number of frames");
const expected = [
{ offset: 0, computedOffset: 0, easing: "ease", composite: null,
color: "rgb(255, 255, 255)" },
{ offset: 1, computedOffset: 1, easing: "ease", composite: null,
color: "rgb(0, 0, 255)" },
];
for (let i = 0; i < frames.length; i++) {
assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i);
}
}, 'KeyframeEffect.getKeyframes() returns expected frames for an ' +
'animation with a 100% keyframe and no 0% keyframe');
test(t => {
const div = addDiv(t);
div.style.animation = 'anim-omit-from-to 100s';
div.style.color = 'rgb(255, 255, 255)';
const frames = getKeyframes(div);
assert_equals(frames.length, 3, "number of frames");
const expected = [
{ offset: 0, computedOffset: 0, easing: "ease", composite: null,
color: "rgb(255, 255, 255)" },
{ offset: 0.5, computedOffset: 0.5, easing: "ease", composite: null,
color: "rgb(0, 0, 255)" },
{ offset: 1, computedOffset: 1, easing: "ease", composite: null,
color: "rgb(255, 255, 255)" },
];
for (let i = 0; i < frames.length; i++) {
assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i);
}
}, 'KeyframeEffect.getKeyframes() returns expected frames for an ' +
'animation with no 0% or 100% keyframe but with a 50% keyframe');
test(t => {
const div = addDiv(t);
div.style.animation = 'anim-partially-omit-to 100s';
div.style.marginTop = '250px';
const frames = getKeyframes(div);
assert_equals(frames.length, 2, "number of frames");
const expected = [
{ offset: 0, computedOffset: 0, easing: "ease", composite: null,
marginTop: '50px', marginBottom: '100px' },
{ offset: 1, computedOffset: 1, easing: "ease", composite: null,
marginTop: '250px', marginBottom: '200px' },
];
for (let i = 0; i < frames.length; i++) {
assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i);
}
}, 'KeyframeEffect.getKeyframes() returns expected frames for an ' +
'animation with a partially complete 100% keyframe (because the ' +
'!important rule is ignored)');
test(t => {
const div = addDiv(t);
div.style.animation = 'anim-different-props 100s';
const frames = getKeyframes(div);
assert_equals(frames.length, 4, "number of frames");
const expected = [
{ offset: 0, computedOffset: 0, easing: "ease", composite: null,
color: "rgb(0, 0, 0)", marginTop: "8px" },
{ offset: 0.25, computedOffset: 0.25, easing: "ease", composite: null,
color: "rgb(0, 0, 255)" },
{ offset: 0.75, computedOffset: 0.75, easing: "ease", composite: null,
marginTop: "12px" },
{ offset: 1, computedOffset: 1, easing: "ease", composite: null,
color: "rgb(255, 255, 255)", marginTop: "16px" },
];
for (let i = 0; i < frames.length; i++) {
assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i);
}
}, 'KeyframeEffect.getKeyframes() returns expected frames for an ' +
'animation with different properties on different keyframes, all ' +
'with the same easing function');
test(t => {
const div = addDiv(t);
div.style.animation = 'anim-different-props-and-easing 100s';
const frames = getKeyframes(div);
assert_equals(frames.length, 4, "number of frames");
const expected = [
{ offset: 0, computedOffset: 0, easing: "linear", composite: null,
color: "rgb(0, 0, 0)", marginTop: "8px" },
{ offset: 0.25, computedOffset: 0.25, easing: "steps(1)", composite: null,
color: "rgb(0, 0, 255)" },
{ offset: 0.75, computedOffset: 0.75, easing: "ease-in", composite: null,
marginTop: "12px" },
{ offset: 1, computedOffset: 1, easing: "ease", composite: null,
color: "rgb(255, 255, 255)", marginTop: "16px" },
];
for (let i = 0; i < frames.length; i++) {
assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i);
}
}, 'KeyframeEffect.getKeyframes() returns expected frames for an ' +
'animation with different properties on different keyframes, with ' +
'a different easing function on each');
test(t => {
const div = addDiv(t);
div.style.animation = 'anim-merge-offset 100s';
const frames = getKeyframes(div);
assert_equals(frames.length, 2, "number of frames");
const expected = [
{ offset: 0, computedOffset: 0, easing: "ease", composite: null,
color: "rgb(0, 0, 0)", marginTop: "8px" },
{ offset: 1, computedOffset: 1, easing: "ease", composite: null,
color: "rgb(255, 255, 255)", marginTop: "16px" },
];
for (let i = 0; i < frames.length; i++) {
assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i);
}
}, 'KeyframeEffect.getKeyframes() returns expected frames for an ' +
'animation with multiple keyframes for the same time, and all with ' +
'the same easing function');
test(t => {
const div = addDiv(t);
div.style.animation = 'anim-merge-offset-and-easing 100s';
const frames = getKeyframes(div);
assert_equals(frames.length, 3, "number of frames");
const expected = [
{ offset: 0, computedOffset: 0, easing: "steps(1)", composite: null,
color: "rgb(0, 0, 0)", fontSize: "16px" },
{ offset: 0, computedOffset: 0, easing: "linear", composite: null,
marginTop: "8px", paddingLeft: "2px" },
{ offset: 1, computedOffset: 1, easing: "ease", composite: null,
color: "rgb(255, 255, 255)", fontSize: "32px", marginTop: "16px",
paddingLeft: "4px" },
];
for (let i = 0; i < frames.length; i++) {
assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i);
}
}, 'KeyframeEffect.getKeyframes() returns expected frames for an ' +
'animation with multiple keyframes for the same time and with ' +
'different easing functions');
test(t => {
const div = addDiv(t);
div.style.animation = 'anim-no-merge-equiv-easing 100s';
const frames = getKeyframes(div);
assert_equals(frames.length, 3, "number of frames");
const expected = [
{ offset: 0, computedOffset: 0, easing: "steps(1)", composite: null,
marginTop: "0px", marginRight: "0px", marginBottom: "0px" },
{ offset: 0.5, computedOffset: 0.5, easing: "steps(1)", composite: null,
marginTop: "10px", marginRight: "10px", marginBottom: "10px" },
{ offset: 1, computedOffset: 1, easing: "ease", composite: null,
marginTop: "20px", marginRight: "20px", marginBottom: "20px" },
];
for (let i = 0; i < frames.length; i++) {
assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i);
}
}, 'KeyframeEffect.getKeyframes() returns expected frames for an ' +
'animation with multiple keyframes for the same time and with ' +
'different but equivalent easing functions');
test(t => {
const div = addDiv(t);
div.style.animation = 'anim-overriding 100s';
const frames = getKeyframes(div);
assert_equals(frames.length, 6, "number of frames");
const expected = [
{ offset: 0, computedOffset: 0, easing: "ease", composite: null,
paddingTop: "30px" },
{ offset: 0.5, computedOffset: 0.5, easing: "ease", composite: null,
paddingTop: "20px" },
{ offset: 0.75, computedOffset: 0.75, easing: "ease", composite: null,
paddingTop: "20px" },
{ offset: 0.85, computedOffset: 0.85, easing: "ease", composite: null,
paddingTop: "30px" },
{ offset: 0.851, computedOffset: 0.851, easing: "ease", composite: null,
paddingTop: "60px" },
{ offset: 1, computedOffset: 1, easing: "ease", composite: null,
paddingTop: "70px" },
];
for (let i = 0; i < frames.length; i++) {
assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i);
}
}, 'KeyframeEffect.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(t => {
const div = addDiv(t);
div.style.animation = 'anim-filter 100s';
const frames = getKeyframes(div);
assert_equals(frames.length, 2, "number of frames");
const expected = [
{ offset: 0, computedOffset: 0, easing: "ease", composite: null,
filter: "none" },
{ offset: 1, computedOffset: 1, easing: "ease", composite: null,
filter: "blur(5px) sepia(60%) saturate(30%)" },
];
for (let i = 0; i < frames.length; i++) {
assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i);
}
}, 'KeyframeEffect.getKeyframes() returns expected values for ' +
'animations with filter properties and missing keyframes');
test(t => {
const div = addDiv(t);
div.style.animation = 'anim-filter-drop-shadow 100s';
const frames = getKeyframes(div);
assert_equals(frames.length, 2, "number of frames");
const expected = [
{ offset: 0, computedOffset: 0, easing: "ease", composite: null,
filter: "drop-shadow(rgb(0, 255, 0) 10px 10px 10px)" },
{ offset: 1, computedOffset: 1, easing: "ease", composite: null,
filter: "drop-shadow(rgb(255, 0, 0) 50px 30px 10px)" },
];
for (let i = 0; i < frames.length; i++) {
assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i);
}
}, 'KeyframeEffect.getKeyframes() returns expected values for ' +
'animation with drop-shadow of filter property');
// 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(t => {
const div = addDiv(t);
div.style.textShadow = '1px 1px 2px rgb(0, 0, 0), ' +
'0 0 16px rgb(0, 0, 255), ' +
'0 0 3.2px rgb(0, 0, 255)';
div.style.animation = 'anim-text-shadow 100s';
const frames = getKeyframes(div);
assert_equals(frames.length, 2, "number of frames");
const expected = [
{ offset: 0, computedOffset: 0, easing: "ease", composite: null,
textShadow: "rgb(0, 0, 0) 1px 1px 2px,"
+ " rgb(0, 0, 255) 0px 0px 16px,"
+ " rgb(0, 0, 255) 0px 0px 3.2px" },
{ offset: 1, computedOffset: 1, easing: "ease", composite: null,
textShadow: "none" },
];
for (let i = 0; i < frames.length; i++) {
assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i);
}
}, 'KeyframeEffect.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(t => {
const div = addDiv(t);
div.style.animation = 'anim-background-size 100s';
let frames = getKeyframes(div);
assert_equals(frames.length, 2, "number of frames");
const expected = [
{ offset: 0, computedOffset: 0, easing: "ease", composite: null,
backgroundSize: "auto auto" },
{ offset: 1, computedOffset: 1, easing: "ease", composite: null,
backgroundSize: "50% auto, 6px auto, contain" },
];
for (let 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 (let i = 0; i < frames.length; i++) {
assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i
+ " after updating current style");
}
}, 'KeyframeEffect.getKeyframes() returns expected values for ' +
'animations with background-size properties and missing keyframes');
test(t => {
const div = addDiv(t);
div.style.animation = 'anim-variables 100s';
const frames = getKeyframes(div);
assert_equals(frames.length, 2, "number of frames");
const expected = [
{ offset: 0, computedOffset: 0, easing: "ease", composite: null,
transform: "none" },
{ offset: 1, computedOffset: 1, easing: "ease", composite: null,
transform: "translate(100px, 0px)" },
];
for (let i = 0; i < frames.length; i++) {
assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i);
}
}, 'KeyframeEffect.getKeyframes() returns expected values for ' +
'animations with CSS variables as keyframe values');
test(t => {
const div = addDiv(t);
div.style.animation = 'anim-variables-shorthand 100s';
const frames = getKeyframes(div);
assert_equals(frames.length, 2, "number of frames");
const expected = [
{ offset: 0, computedOffset: 0, easing: "ease", composite: null,
marginBottom: "0px",
marginLeft: "0px",
marginRight: "0px",
marginTop: "0px" },
{ offset: 1, computedOffset: 1, easing: "ease", composite: null,
marginBottom: "100px",
marginLeft: "100px",
marginRight: "100px",
marginTop: "100px" },
];
for (let i = 0; i < frames.length; i++) {
assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i);
}
}, 'KeyframeEffect.getKeyframes() returns expected values for ' +
'animations with CSS variables as keyframe values in a shorthand property');
test(t => {
const div = addDiv(t);
div.style.animation = 'anim-custom-property-in-keyframe 100s';
const frames = getKeyframes(div);
assert_equals(frames.length, 2, "number of frames");
const expected = [
{ offset: 0, computedOffset: 0, easing: "ease", composite: null,
color: "rgb(0, 0, 0)" },
{ offset: 1, computedOffset: 1, easing: "ease", composite: null,
color: "rgb(0, 255, 0)" },
];
for (let i = 0; i < frames.length; i++) {
assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i);
}
}, 'KeyframeEffect.getKeyframes() returns expected values for ' +
'animations with a CSS variable which is overriden by the value in keyframe');
test(t => {
const div = addDiv(t);
div.style.animation = 'anim-only-custom-property-in-keyframe 100s';
const frames = getKeyframes(div);
assert_equals(frames.length, 2, "number of frames");
const expected = [
{ offset: 0, computedOffset: 0, easing: "ease", composite: null,
transform: "translate(100px, 0px)" },
{ offset: 1, computedOffset: 1, easing: "ease", composite: null,
transform: "none" },
];
for (let i = 0; i < frames.length; i++) {
assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i);
}
}, 'KeyframeEffect.getKeyframes() returns expected values for ' +
'animations with only custom property in a keyframe');
</script>
</body>

View file

@ -0,0 +1,65 @@
<!doctype html>
<meta charset=utf-8>
<title>CSSAnimation.effect.target</title>
<!-- TODO: Add a more specific link for this once it is specified. -->
<link rel="help" href="https://drafts.csswg.org/css-animations-2/">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="support/testcommon.js"></script>
<style>
@keyframes anim { }
::before {
content: ''
}
::after {
content: ''
}
</style>
<body>
<div id="log"></div>
<script>
'use strict';
test(t => {
const div = addDiv(t);
div.style.animation = 'anim 100s';
const 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(t => {
addStyle(t, { '.after::after': 'animation: anim 10s, anim 100s;' });
const div = addDiv(t, { class: 'after' });
const 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(t => {
addStyle(t, { '.after::after': 'animation: anim 10s;' });
const div = addDiv(t, { class: 'after' });
const pseudoTarget = document.getAnimations()[0].effect.target;
const effect = new KeyframeEffect(pseudoTarget,
{ background: ["blue", "red"] },
3 * MS_PER_SEC);
const newAnim = new Animation(effect, document.timeline);
newAnim.play();
const 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 ' +
'KeyframeEffect 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');
</script>
</body>

View file

@ -0,0 +1,423 @@
<!doctype html>
<meta charset=utf-8>
<title>Tests for CSS animation event dispatch</title>
<link rel="help" href="https://drafts.csswg.org/css-animations-2/#event-dispatch"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="support/testcommon.js"></script>
<style>
@keyframes anim {
from { margin-left: 0px; }
to { margin-left: 100px; }
}
</style>
<body>
<div id="log"></div>
<script>
'use strict';
const setupAnimation = (t, animationStyle) => {
const div = addDiv(t, { style: 'animation: ' + animationStyle });
const watcher = new EventWatcher(t, div, [ 'animationstart',
'animationiteration',
'animationend',
'animationcancel' ]);
const animation = div.getAnimations()[0];
return { animation, watcher, div };
};
promise_test(async t => {
// Add 1ms delay to ensure that the delay is not included in the elapsedTime.
const { animation, watcher } = setupAnimation(t, 'anim 100s 1ms');
const evt = await watcher.wait_for('animationstart');
assert_equals(evt.elapsedTime, 0.0);
}, 'Idle -> Active');
promise_test(async t => {
const { animation, watcher } = setupAnimation(t, 'anim 100s');
// Seek to After phase.
animation.finish();
const events = await watcher.wait_for(['animationstart', 'animationend'], {
record: 'all',
});
assert_equals(events[0].elapsedTime, 0.0);
assert_equals(events[1].elapsedTime, 100);
}, 'Idle -> After');
promise_test(async t => {
const { animation, watcher } = setupAnimation(t, 'anim 100s 100s paused');
await animation.ready;
// Seek to Active phase.
animation.currentTime = 100 * MS_PER_SEC;
const evt = await watcher.wait_for('animationstart');
assert_equals(evt.elapsedTime, 0.0);
}, 'Before -> Active');
promise_test(async t => {
const { animation, watcher } = setupAnimation(t, 'anim 100s 100s paused');
const allEvents = watcher.wait_for(['animationstart', 'animationend'], {
record: 'all',
});
await animation.ready;
// Seek to After phase.
animation.finish();
const events = await allEvents;
assert_equals(events[0].elapsedTime, 0.0);
assert_equals(events[1].elapsedTime, 100.0);
}, 'Before -> After');
promise_test(async t => {
const { animation, watcher, div } = setupAnimation(t, 'anim 100s paused');
await watcher.wait_for('animationstart');
// Make idle
div.style.display = 'none';
const evt = await watcher.wait_for('animationcancel');
assert_equals(evt.elapsedTime, 0.0);
}, 'Active -> Idle, display: none');
promise_test(async t => {
const { animation, watcher } = setupAnimation(t, 'anim 100s');
await watcher.wait_for('animationstart');
animation.currentTime = 100.0;
// Make idle
animation.timeline = null;
const evt = await watcher.wait_for('animationcancel');
assert_time_equals_literal(evt.elapsedTime, 0.1);
}, 'Active -> Idle, setting Animation.timeline = null');
promise_test(async t => {
// We should NOT pause animation since calling cancel synchronously.
const { animation, watcher } = setupAnimation(t, 'anim 100s');
await watcher.wait_for('animationstart');
animation.currentTime = 50.0;
animation.cancel();
const evt = await watcher.wait_for('animationcancel');
assert_time_equals_literal(evt.elapsedTime, 0.05);
}, 'Active -> Idle, calling Animation.cancel()');
promise_test(async t => {
const { animation, watcher } = setupAnimation(t, 'anim 100s 100s paused');
// Seek to Active phase.
animation.currentTime = 100 * MS_PER_SEC;
await watcher.wait_for('animationstart');
// Seek to Before phase.
animation.currentTime = 0;
const evt = await watcher.wait_for('animationend');
assert_equals(evt.elapsedTime, 0.0);
}, 'Active -> Before');
promise_test(async t => {
const { animation, watcher } = setupAnimation(t, 'anim 100s paused');
await watcher.wait_for('animationstart');
// Seek to After phase.
animation.finish();
const evt = await watcher.wait_for('animationend');
assert_equals(evt.elapsedTime, 100.0);
}, 'Active -> After');
promise_test(async t => {
const { animation, watcher } = setupAnimation(t, 'anim 100s 100s paused');
// Seek to After phase.
animation.finish();
await watcher.wait_for([ 'animationstart', 'animationend' ]);
// Seek to Before phase.
animation.currentTime = 0;
const events = await watcher.wait_for(['animationstart', 'animationend'], {
record: 'all',
});
assert_equals(events[0].elapsedTime, 100.0);
assert_equals(events[1].elapsedTime, 0.0);
}, 'After -> Before');
promise_test(async t => {
const { animation, watcher } = setupAnimation(t, 'anim 100s 100s paused');
// Seek to After phase.
animation.finish();
await watcher.wait_for([ 'animationstart', 'animationend' ]);
// Seek to Active phase.
animation.currentTime = 100 * MS_PER_SEC;
const evt = await watcher.wait_for('animationstart');
assert_equals(evt.elapsedTime, 100.0);
}, 'After -> Active');
promise_test(async t => {
const { animation, watcher } = setupAnimation(t, 'anim 100s 100s 3 paused');
await animation.ready;
// Seek to iteration 0 (no animationiteration event should be dispatched)
animation.currentTime = 100 * MS_PER_SEC;
await watcher.wait_for('animationstart');
// Seek to iteration 2
animation.currentTime = 300 * MS_PER_SEC;
let evt = await watcher.wait_for('animationiteration');
assert_equals(evt.elapsedTime, 200);
// Seek to After phase (no animationiteration event should be dispatched)
animation.currentTime = 400 * MS_PER_SEC;
evt = await watcher.wait_for('animationend');
assert_equals(evt.elapsedTime, 300);
}, 'Active -> Active (forwards)');
promise_test(async t => {
const { animation, watcher } = setupAnimation(t, 'anim 100s 100s 3');
// Seek to After phase.
animation.finish();
await watcher.wait_for([ 'animationstart', 'animationend' ]);
// Seek to iteration 2 (no animationiteration event should be dispatched)
animation.pause();
animation.currentTime = 300 * MS_PER_SEC;
await watcher.wait_for('animationstart');
// Seek to mid of iteration 0 phase.
animation.currentTime = 200 * MS_PER_SEC;
const evt = await watcher.wait_for('animationiteration');
assert_equals(evt.elapsedTime, 200.0);
// Seek to before phase (no animationiteration event should be dispatched)
animation.currentTime = 0;
await watcher.wait_for('animationend');
}, 'Active -> Active (backwards)');
promise_test(async t => {
const { animation, watcher, div } = setupAnimation(t, 'anim 100s paused');
await watcher.wait_for('animationstart');
// Seek to Idle phase.
div.style.display = 'none';
flushComputedStyle(div);
await watcher.wait_for('animationcancel');
// Restart this animation.
div.style.display = '';
await watcher.wait_for('animationstart');
}, 'Active -> Idle -> Active: animationstart is fired by restarting animation');
promise_test(async t => {
const { animation, watcher } = setupAnimation(t, 'anim 100s 100s 2 paused');
// Make After.
animation.finish();
await watcher.wait_for([ 'animationstart', 'animationend' ]);
animation.playbackRate = -1;
let evt = await watcher.wait_for('animationstart');
assert_equals(evt.elapsedTime, 200);
// Seek to 1st iteration
animation.currentTime = 200 * MS_PER_SEC - 1;
evt = await watcher.wait_for('animationiteration');
assert_equals(evt.elapsedTime, 100);
// Seek to before
animation.currentTime = 100 * MS_PER_SEC - 1;
evt = await watcher.wait_for('animationend');
assert_equals(evt.elapsedTime, 0);
assert_equals(animation.playState, 'running'); // delay
}, 'Negative playbackRate sanity test(Before -> Active -> Before)');
promise_test(async t => {
const { animation, watcher } = setupAnimation(t, 'anim 100s 100s');
animation.currentTime = 150 * MS_PER_SEC;
animation.currentTime = 50 * MS_PER_SEC;
// Then wait a couple of frames and check that no event was dispatched.
await waitForAnimationFrames(2);
}, 'Redundant change, before -> active, then back');
promise_test(async t => {
const { animation, watcher } = setupAnimation(t, 'anim 100s 100s');
animation.currentTime = 250 * MS_PER_SEC;
animation.currentTime = 50 * MS_PER_SEC;
// Then wait a couple of frames and check that no event was dispatched.
await waitForAnimationFrames(2);
}, 'Redundant change, before -> after, then back');
promise_test(async t => {
const { animation, watcher } = setupAnimation(t, 'anim 100s 100s');
// Get us into the initial state:
animation.currentTime = 150 * MS_PER_SEC;
await watcher.wait_for('animationstart');
animation.currentTime = 50 * MS_PER_SEC;
animation.currentTime = 150 * MS_PER_SEC;
// Then wait a couple of frames and check that no event was dispatched.
await waitForAnimationFrames(2);
}, 'Redundant change, active -> before, then back');
promise_test(async t => {
const { animation, watcher } = setupAnimation(t, 'anim 100s 100s');
// Get us into the initial state:
animation.currentTime = 150 * MS_PER_SEC;
await watcher.wait_for('animationstart');
animation.currentTime = 250 * MS_PER_SEC;
animation.currentTime = 150 * MS_PER_SEC;
// Then wait a couple of frames and check that no event was dispatched.
await waitForAnimationFrames(2);
}, 'Redundant change, active -> after, then back');
promise_test(async t => {
const { animation, watcher } = setupAnimation(t, 'anim 100s 100s');
// Get us into the initial state:
animation.currentTime = 250 * MS_PER_SEC;
await watcher.wait_for(['animationstart', 'animationend']);
animation.currentTime = 50 * MS_PER_SEC;
animation.currentTime = 250 * MS_PER_SEC;
// Then wait a couple of frames and check that no event was dispatched.
await waitForAnimationFrames(2);
}, 'Redundant change, after -> before, then back');
promise_test(async t => {
const { animation, watcher } = setupAnimation(t, 'anim 100s 100s');
// Get us into the initial state:
animation.currentTime = 250 * MS_PER_SEC;
await watcher.wait_for(['animationstart', 'animationend']);
animation.currentTime = 150 * MS_PER_SEC;
animation.currentTime = 250 * MS_PER_SEC;
// Then wait a couple of frames and check that no event was dispatched.
await waitForAnimationFrames(2);
}, 'Redundant change, after -> active, then back');
promise_test(async t => {
const { animation, watcher } = setupAnimation(t, 'anim 100s');
await watcher.wait_for('animationstart');
// Make idle
animation.cancel();
await watcher.wait_for('animationcancel');
animation.cancel();
// Then wait a couple of frames and check that no event was dispatched.
await waitForAnimationFrames(2);
}, 'Call Animation.cancel after canceling animation.');
promise_test(async t => {
const { animation, watcher } = setupAnimation(t, 'anim 100s');
await watcher.wait_for('animationstart');
// Make idle
animation.cancel();
animation.play();
await watcher.wait_for([ 'animationcancel', 'animationstart' ]);
}, 'Restart animation after canceling animation immediately.');
promise_test(async t => {
const { animation, watcher } = setupAnimation(t, 'anim 100s');
await watcher.wait_for('animationstart');
// Make idle
animation.cancel();
animation.play();
animation.cancel();
await watcher.wait_for('animationcancel');
// Then wait a couple of frames and check that no event was dispatched.
await waitForAnimationFrames(2);
}, 'Call Animation.cancel after restarting animation immediately.');
promise_test(async t => {
const { animation, watcher } = setupAnimation(t, 'anim 100s');
await watcher.wait_for('animationstart');
// Make idle
animation.timeline = null;
await watcher.wait_for('animationcancel');
animation.timeline = document.timeline;
animation.play();
await watcher.wait_for('animationstart');
}, 'Set timeline and play transition after clearing the timeline.');
promise_test(async t => {
const { animation, watcher } = setupAnimation(t, 'anim 100s');
await watcher.wait_for('animationstart');
// Make idle
animation.cancel();
await watcher.wait_for('animationcancel');
animation.effect = null;
// Then wait a couple of frames and check that no event was dispatched.
await waitForAnimationFrames(2);
}, 'Set null target effect after canceling the animation.');
promise_test(async t => {
const { animation, watcher } = setupAnimation(t, 'anim 100s');
await watcher.wait_for('animationstart');
animation.effect = null;
await watcher.wait_for('animationend');
animation.cancel();
// Then wait a couple of frames and check that no event was dispatched.
await waitForAnimationFrames(2);
}, 'Cancel the animation after clearing the target effect.');
</script>
</body>
</html>

View file

@ -0,0 +1,164 @@
<!doctype html>
<meta charset=utf-8>
<title>Tests for CSS animation event order</title>
<link rel="help" href="https://drafts.csswg.org/css-animations-2/#event-dispatch"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="support/testcommon.js"></script>
<style>
@keyframes anim {
from { margin-left: 0px; }
to { margin-left: 100px; }
}
</style>
<body>
<div id="log"></div>
<script type='text/javascript'>
'use strict';
/**
* Asserts that the set of actual and received events match.
* @param actualEvents An array of the received AnimationEvent objects.
* @param expectedEvents A series of array objects representing the expected
* events, each having the form:
* [ event type, target element, elapsed time ]
*/
const checkEvents = (actualEvents, ...expectedEvents) => {
assert_equals(actualEvents.length, expectedEvents.length,
`Number of actual events (${actualEvents.length}: \
${actualEvents.map(event => event.type).join(', ')}) should match expected \
events (${expectedEvents.map(event => event.type).join(', ')})`);
actualEvents.forEach((actualEvent, i) => {
assert_equals(expectedEvents[i][0], actualEvent.type,
'Event type should match');
assert_equals(expectedEvents[i][1], actualEvent.target,
'Event target should match');
assert_equals(expectedEvents[i][2], actualEvent.elapsedTime,
'Event\'s elapsed time should match');
});
};
const setupAnimation = (t, animationStyle, receiveEvents) => {
const div = addDiv(t, { style: "animation: " + animationStyle });
for (const name of ['start', 'iteration', 'end']) {
div['onanimation' + name] = evt => {
receiveEvents.push({ type: evt.type,
target: evt.target,
elapsedTime: evt.elapsedTime });
};
}
const watcher = new EventWatcher(t, div, [ 'animationstart',
'animationiteration',
'animationend' ]);
const animation = div.getAnimations()[0];
return [animation, watcher, div];
};
promise_test(async t => {
let events = [];
const [animation1, watcher1, div1] =
setupAnimation(t, 'anim 100s 2 paused', events);
const [animation2, watcher2, div2] =
setupAnimation(t, 'anim 100s 2 paused', events);
await Promise.all([ watcher1.wait_for('animationstart'),
watcher2.wait_for('animationstart') ]);
checkEvents(events, ['animationstart', div1, 0],
['animationstart', div2, 0]);
events.length = 0; // Clear received event array
animation1.currentTime = 100 * MS_PER_SEC;
animation2.currentTime = 100 * MS_PER_SEC;
await Promise.all([ watcher1.wait_for('animationiteration'),
watcher2.wait_for('animationiteration') ]);
checkEvents(events, ['animationiteration', div1, 100],
['animationiteration', div2, 100]);
events.length = 0; // Clear received event array
animation1.finish();
animation2.finish();
await Promise.all([ watcher1.wait_for('animationend'),
watcher2.wait_for('animationend') ]);
checkEvents(events, ['animationend', div1, 200],
['animationend', div2, 200]);
}, 'Test same events are ordered by elements.');
promise_test(async t => {
let events = [];
const [animation1, watcher1, div1] =
setupAnimation(t, 'anim 200s 400s', events);
const [animation2, watcher2, div2] =
setupAnimation(t, 'anim 300s 2', events);
await watcher2.wait_for('animationstart');
animation1.currentTime = 400 * MS_PER_SEC;
animation2.currentTime = 400 * MS_PER_SEC;
events.length = 0; // Clear received event array
await Promise.all([ watcher1.wait_for('animationstart'),
watcher2.wait_for('animationiteration') ]);
checkEvents(events, ['animationiteration', div2, 300],
['animationstart', div1, 0]);
}, 'Test start and iteration events are ordered by time.');
promise_test(async t => {
let events = [];
const [animation1, watcher1, div1] =
setupAnimation(t, 'anim 150s', events);
const [animation2, watcher2, div2] =
setupAnimation(t, 'anim 100s 2', events);
await Promise.all([ watcher1.wait_for('animationstart'),
watcher2.wait_for('animationstart') ]);
animation1.currentTime = 150 * MS_PER_SEC;
animation2.currentTime = 150 * MS_PER_SEC;
events.length = 0; // Clear received event array
await Promise.all([ watcher1.wait_for('animationend'),
watcher2.wait_for('animationiteration') ]);
checkEvents(events, ['animationiteration', div2, 100],
['animationend', div1, 150]);
}, 'Test iteration and end events are ordered by time.');
promise_test(async t => {
let events = [];
const [animation1, watcher1, div1] =
setupAnimation(t, 'anim 100s 100s', events);
const [animation2, watcher2, div2] =
setupAnimation(t, 'anim 100s 2', events);
animation1.finish();
animation2.finish();
await Promise.all([ watcher1.wait_for([ 'animationstart',
'animationend' ]),
watcher2.wait_for([ 'animationstart',
'animationend' ]) ]);
checkEvents(events, ['animationstart', div2, 0],
['animationstart', div1, 0],
['animationend', div1, 100],
['animationend', div2, 200]);
}, 'Test start and end events are sorted correctly when fired simultaneously');
</script>
</body>
</html>

View file

@ -0,0 +1,177 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Use this variable if you specify duration or some other properties
* for script animation.
* E.g., div.animate({ opacity: [0, 1] }, 100 * MS_PER_SEC);
*
* NOTE: Creating animations with short duration may cause intermittent
* failures in asynchronous test. For example, the short duration animation
* might be finished when animation.ready has been fulfilled because of slow
* platforms or busyness of the main thread.
* Setting short duration to cancel its animation does not matter but
* if you don't want to cancel the animation, consider using longer duration.
*/
const MS_PER_SEC = 1000;
/* The recommended minimum precision to use for time values[1].
*
* [1] https://drafts.csswg.org/web-animations/#precision-of-time-values
*/
var TIME_PRECISION = 0.0005; // ms
/*
* Allow implementations to substitute an alternative method for comparing
* times based on their precision requirements.
*/
function assert_times_equal(actual, expected, description) {
assert_approx_equals(actual, expected, TIME_PRECISION * 2, description);
}
/*
* Compare a time value based on its precision requirements with a fixed value.
*/
function assert_time_equals_literal(actual, expected, description) {
assert_approx_equals(actual, expected, TIME_PRECISION, description);
}
/**
* Appends a div to the document body.
*
* @param t The testharness.js Test object. If provided, this will be used
* to register a cleanup callback to remove the div when the test
* finishes.
*
* @param attrs A dictionary object with attribute names and values to set on
* the div.
*/
function addDiv(t, attrs) {
var div = document.createElement('div');
if (attrs) {
for (var attrName in attrs) {
div.setAttribute(attrName, attrs[attrName]);
}
}
document.body.appendChild(div);
if (t && typeof t.add_cleanup === 'function') {
t.add_cleanup(function() {
if (div.parentNode) {
div.remove();
}
});
}
return div;
}
/**
* Appends a style div to the document head.
*
* @param t The testharness.js Test object. If provided, this will be used
* to register a cleanup callback to remove the style element
* when the test finishes.
*
* @param rules A dictionary object with selector names and rules to set on
* the style sheet.
*/
function addStyle(t, rules) {
var extraStyle = document.createElement('style');
document.head.appendChild(extraStyle);
if (rules) {
var sheet = extraStyle.sheet;
for (var selector in rules) {
sheet.insertRule(selector + '{' + rules[selector] + '}',
sheet.cssRules.length);
}
}
if (t && typeof t.add_cleanup === 'function') {
t.add_cleanup(function() {
extraStyle.remove();
});
}
}
/**
* Promise wrapper for requestAnimationFrame.
*/
function waitForFrame() {
return new Promise(function(resolve, reject) {
window.requestAnimationFrame(resolve);
});
}
/**
* Waits for a requestAnimationFrame callback in the next refresh driver tick.
* Note that 'dom.animations-api.core.enabled' pref should be true to use this
* function.
*/
function waitForNextFrame() {
const timeAtStart = document.timeline.currentTime;
return new Promise(resolve => {
window.requestAnimationFrame(() => {
if (timeAtStart === document.timeline.currentTime) {
window.requestAnimationFrame(resolve);
} else {
resolve();
}
});
});
}
/**
* Returns a Promise that is resolved after the given number of consecutive
* animation frames have occured (using requestAnimationFrame callbacks).
*
* @param frameCount The number of animation frames.
* @param onFrame An optional function to be processed in each animation frame.
*/
function waitForAnimationFrames(frameCount, onFrame) {
const timeAtStart = document.timeline.currentTime;
return new Promise(function(resolve, reject) {
function handleFrame() {
if (onFrame && typeof onFrame === 'function') {
onFrame();
}
if (timeAtStart != document.timeline.currentTime &&
--frameCount <= 0) {
resolve();
} else {
window.requestAnimationFrame(handleFrame); // wait another frame
}
}
window.requestAnimationFrame(handleFrame);
});
}
/**
* Wrapper that takes a sequence of N animations and returns:
*
* Promise.all([animations[0].ready, animations[1].ready, ... animations[N-1].ready]);
*/
function waitForAllAnimations(animations) {
return Promise.all(animations.map(animation => animation.ready));
}
/**
* Flush the computed style for the given element. This is useful, for example,
* when we are testing a transition and need the initial value of a property
* to be computed so that when we synchronouslyet set it to a different value
* we actually get a transition instead of that being the initial value.
*/
function flushComputedStyle(elem) {
var cs = getComputedStyle(elem);
cs.marginLeft;
}
// Waits for a given animation being ready to restyle.
async function waitForAnimationReadyToRestyle(aAnimation) {
await aAnimation.ready;
// If |aAnimation| begins at the current timeline time, we will not process
// restyling in the initial frame because of aligning with the refresh driver,
// the animation frame in which the ready promise is resolved happens to
// coincide perfectly with the start time of the animation. In this case no
// restyling is needed in the frame so we have to wait one more frame.
if (animationStartsRightNow(aAnimation)) {
await waitForNextFrame();
}
}