mirror of
https://github.com/servo/servo.git
synced 2025-08-08 06:55:31 +01:00
Update web-platform-tests to revision dc60bfc45b49e3a5e653320e65b0fd447676b836
This commit is contained in:
parent
652a177ff5
commit
0bc549be55
690 changed files with 6588 additions and 1564 deletions
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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();
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue