355 lines
12 KiB
HTML
355 lines
12 KiB
HTML
<!DOCTYPE html>
|
|
<meta charset=utf-8>
|
|
<title>Animatable.getAnimations</title>
|
|
<link rel="help" href="https://drafts.csswg.org/web-animations/#dom-animatable-getanimations">
|
|
<script src="/resources/testharness.js"></script>
|
|
<script src="/resources/testharnessreport.js"></script>
|
|
<script src="../../testcommon.js"></script>
|
|
<body>
|
|
<script>
|
|
'use strict';
|
|
|
|
test(t => {
|
|
const div = createDiv(t);
|
|
assert_array_equals(div.getAnimations(), []);
|
|
}, 'Returns an empty array for an element with no animations');
|
|
|
|
test(t => {
|
|
const div = createDiv(t);
|
|
const animationA = div.animate(null, 100 * MS_PER_SEC);
|
|
const animationB = div.animate(null, 100 * MS_PER_SEC);
|
|
assert_array_equals(div.getAnimations(), [animationA, animationB]);
|
|
}, 'Returns both animations for an element with two animations');
|
|
|
|
test(t => {
|
|
const divA = createDiv(t);
|
|
const divB = createDiv(t);
|
|
const animationA = divA.animate(null, 100 * MS_PER_SEC);
|
|
const animationB = divB.animate(null, 100 * MS_PER_SEC);
|
|
assert_array_equals(divA.getAnimations(), [animationA], 'divA');
|
|
assert_array_equals(divB.getAnimations(), [animationB], 'divB');
|
|
}, 'Returns only the animations specific to each sibling element');
|
|
|
|
test(t => {
|
|
const divParent = createDiv(t);
|
|
const divChild = createDiv(t);
|
|
divParent.appendChild(divChild);
|
|
const animationParent = divParent.animate(null, 100 * MS_PER_SEC);
|
|
const animationChild = divChild.animate(null, 100 * MS_PER_SEC);
|
|
assert_array_equals(divParent.getAnimations(), [animationParent],
|
|
'divParent');
|
|
assert_array_equals(divChild.getAnimations(), [animationChild], 'divChild');
|
|
}, 'Returns only the animations specific to each parent/child element');
|
|
|
|
test(t => {
|
|
const divParent = createDiv(t);
|
|
const divChild = createDiv(t);
|
|
divParent.appendChild(divChild);
|
|
const divGrandChildA = createDiv(t);
|
|
const divGrandChildB = createDiv(t);
|
|
divChild.appendChild(divGrandChildA);
|
|
divChild.appendChild(divGrandChildB);
|
|
|
|
// Trigger the animations in a somewhat random order
|
|
const animGrandChildB = divGrandChildB.animate(null, 100 * MS_PER_SEC);
|
|
const animChild = divChild.animate(null, 100 * MS_PER_SEC);
|
|
const animGrandChildA = divGrandChildA.animate(null, 100 * MS_PER_SEC);
|
|
|
|
assert_array_equals(
|
|
divParent.getAnimations({ subtree: true }),
|
|
[animGrandChildB, animChild, animGrandChildA],
|
|
'Returns expected animations from parent'
|
|
);
|
|
assert_array_equals(
|
|
divChild.getAnimations({ subtree: true }),
|
|
[animGrandChildB, animChild, animGrandChildA],
|
|
'Returns expected animations from child'
|
|
);
|
|
assert_array_equals(
|
|
divGrandChildA.getAnimations({ subtree: true }),
|
|
[animGrandChildA],
|
|
'Returns expected animations from grandchild A'
|
|
);
|
|
}, 'Returns animations on descendants when subtree: true is specified');
|
|
|
|
test(t => {
|
|
createStyle(t, {
|
|
'@keyframes anim': '',
|
|
[`.pseudo::before`]: 'animation: anim 100s; ' + "content: '';",
|
|
});
|
|
const div = createDiv(t);
|
|
div.classList.add('pseudo');
|
|
|
|
assert_equals(
|
|
div.getAnimations().length,
|
|
0,
|
|
'Returns no animations when subtree is false'
|
|
);
|
|
assert_equals(
|
|
div.getAnimations({ subtree: true }).length,
|
|
1,
|
|
'Returns one animation when subtree is true'
|
|
);
|
|
}, 'Returns animations on pseudo-elements when subtree: true is specified');
|
|
|
|
test(t => {
|
|
const host = createDiv(t);
|
|
const shadow = host.attachShadow({ mode: 'open' });
|
|
|
|
const elem = createDiv(t);
|
|
shadow.appendChild(elem);
|
|
|
|
const elemChild = createDiv(t);
|
|
elem.appendChild(elemChild);
|
|
|
|
elemChild.animate(null, 100 * MS_PER_SEC);
|
|
|
|
assert_equals(
|
|
host.getAnimations({ subtree: true }).length,
|
|
0,
|
|
'Returns no animations with subtree:true when called on the host'
|
|
);
|
|
assert_equals(
|
|
elem.getAnimations({ subtree: true }).length,
|
|
1,
|
|
'Returns one animation when called on a parent in the shadow tree'
|
|
);
|
|
}, 'Does NOT cross shadow-tree boundaries when subtree: true is specified');
|
|
|
|
test(t => {
|
|
const foreignElement
|
|
= document.createElementNS('http://example.org/test', 'test');
|
|
document.body.appendChild(foreignElement);
|
|
t.add_cleanup(() => {
|
|
foreignElement.remove();
|
|
});
|
|
|
|
const animation = foreignElement.animate(null, 100 * MS_PER_SEC);
|
|
assert_array_equals(foreignElement.getAnimations(), [animation]);
|
|
}, 'Returns animations for a foreign element');
|
|
|
|
test(t => {
|
|
const div = createDiv(t);
|
|
const animation = div.animate(null, 100 * MS_PER_SEC);
|
|
animation.finish();
|
|
assert_array_equals(div.getAnimations(), []);
|
|
}, 'Does not return finished animations that do not fill forwards');
|
|
|
|
test(t => {
|
|
const div = createDiv(t);
|
|
const animation = div.animate(null, {
|
|
duration: 100 * MS_PER_SEC,
|
|
fill: 'forwards',
|
|
});
|
|
animation.finish();
|
|
assert_array_equals(div.getAnimations(), [animation]);
|
|
}, 'Returns finished animations that fill forwards');
|
|
|
|
test(t => {
|
|
const div = createDiv(t);
|
|
const animation = div.animate(null, {
|
|
duration: 100 * MS_PER_SEC,
|
|
delay: 100 * MS_PER_SEC,
|
|
});
|
|
assert_array_equals(div.getAnimations(), [animation]);
|
|
}, 'Returns animations yet to reach their active phase');
|
|
|
|
test(t => {
|
|
const div = createDiv(t);
|
|
const animation = div.animate(null, 100 * MS_PER_SEC);
|
|
animation.playbackRate = -1;
|
|
assert_array_equals(div.getAnimations(), []);
|
|
}, 'Does not return reversed finished animations that do not fill backwards');
|
|
|
|
test(t => {
|
|
const div = createDiv(t);
|
|
const animation = div.animate(null, {
|
|
duration: 100 * MS_PER_SEC,
|
|
fill: 'backwards',
|
|
});
|
|
animation.playbackRate = -1;
|
|
assert_array_equals(div.getAnimations(), [animation]);
|
|
}, 'Returns reversed finished animations that fill backwards');
|
|
|
|
test(t => {
|
|
const div = createDiv(t);
|
|
const animation = div.animate(null, 100 * MS_PER_SEC);
|
|
animation.playbackRate = -1;
|
|
animation.currentTime = 200 * MS_PER_SEC;
|
|
assert_array_equals(div.getAnimations(), [animation]);
|
|
}, 'Returns reversed animations yet to reach their active phase');
|
|
|
|
test(t => {
|
|
const div = createDiv(t);
|
|
const animation = div.animate(null, {
|
|
duration: 100 * MS_PER_SEC,
|
|
delay: 100 * MS_PER_SEC,
|
|
});
|
|
animation.playbackRate = 0;
|
|
assert_array_equals(div.getAnimations(), []);
|
|
}, 'Does not return animations with zero playback rate in before phase');
|
|
|
|
test(t => {
|
|
const div = createDiv(t);
|
|
const animation = div.animate(null, 100 * MS_PER_SEC);
|
|
animation.finish();
|
|
animation.playbackRate = 0;
|
|
animation.currentTime = 200 * MS_PER_SEC;
|
|
assert_array_equals(div.getAnimations(), []);
|
|
}, 'Does not return animations with zero playback rate in after phase');
|
|
|
|
test(t => {
|
|
const div = createDiv(t);
|
|
const effect = new KeyframeEffect(div, {}, 225);
|
|
const animation = new Animation(effect, new DocumentTimeline());
|
|
animation.reverse();
|
|
animation.pause();
|
|
animation.playbackRate = -1;;
|
|
animation.updatePlaybackRate(1);
|
|
assert_array_equals(div.getAnimations(), []);
|
|
}, 'Does not return an animation that has recently been made not current by setting the playback rate');
|
|
|
|
test(t => {
|
|
const div = createDiv(t);
|
|
const animation = div.animate(null, 100 * MS_PER_SEC);
|
|
|
|
animation.finish();
|
|
assert_array_equals(div.getAnimations(), [],
|
|
'Animation should not be returned when it is finished');
|
|
|
|
animation.effect.updateTiming({
|
|
duration: animation.effect.getTiming().duration + 100 * MS_PER_SEC,
|
|
});
|
|
assert_array_equals(div.getAnimations(), [animation],
|
|
'Animation should be returned after extending the'
|
|
+ ' duration');
|
|
|
|
animation.effect.updateTiming({ duration: 0 });
|
|
assert_array_equals(div.getAnimations(), [],
|
|
'Animation should not be returned after setting the'
|
|
+ ' duration to zero');
|
|
}, 'Returns animations based on dynamic changes to individual'
|
|
+ ' animations\' duration');
|
|
|
|
test(t => {
|
|
const div = createDiv(t);
|
|
const animation = div.animate(null, 100 * MS_PER_SEC);
|
|
|
|
animation.effect.updateTiming({ endDelay: -200 * MS_PER_SEC });
|
|
assert_array_equals(div.getAnimations(), [],
|
|
'Animation should not be returned after setting a'
|
|
+ ' negative end delay such that the end time is less'
|
|
+ ' than the current time');
|
|
|
|
animation.effect.updateTiming({ endDelay: 100 * MS_PER_SEC });
|
|
assert_array_equals(div.getAnimations(), [animation],
|
|
'Animation should be returned after setting a positive'
|
|
+ ' end delay such that the end time is more than the'
|
|
+ ' current time');
|
|
}, 'Returns animations based on dynamic changes to individual'
|
|
+ ' animations\' end delay');
|
|
|
|
test(t => {
|
|
const div = createDiv(t);
|
|
const animation = div.animate(null, 100 * MS_PER_SEC);
|
|
|
|
animation.finish();
|
|
assert_array_equals(div.getAnimations(), [],
|
|
'Animation should not be returned when it is finished');
|
|
|
|
animation.effect.updateTiming({ iterations: 10 });
|
|
assert_array_equals(div.getAnimations(), [animation],
|
|
'Animation should be returned after inreasing the'
|
|
+ ' number of iterations');
|
|
|
|
animation.effect.updateTiming({ iterations: 0 });
|
|
assert_array_equals(div.getAnimations(), [],
|
|
'Animations should not be returned after setting the'
|
|
+ ' iteration count to zero');
|
|
|
|
animation.effect.updateTiming({ iterations: Infinity });
|
|
assert_array_equals(div.getAnimations(), [animation],
|
|
'Animation should be returned after inreasing the'
|
|
+ ' number of iterations to infinity');
|
|
}, 'Returns animations based on dynamic changes to individual'
|
|
+ ' animations\' iteration count');
|
|
|
|
test(t => {
|
|
const div = createDiv(t);
|
|
const animation = div.animate(null,
|
|
{ duration: 100 * MS_PER_SEC,
|
|
delay: 50 * MS_PER_SEC,
|
|
endDelay: -50 * MS_PER_SEC });
|
|
|
|
assert_array_equals(div.getAnimations(), [animation],
|
|
'Animation should be returned at during delay phase');
|
|
|
|
animation.currentTime = 50 * MS_PER_SEC;
|
|
assert_array_equals(div.getAnimations(), [animation],
|
|
'Animation should be returned after seeking to the start'
|
|
+ ' of the active interval');
|
|
|
|
animation.currentTime = 100 * MS_PER_SEC;
|
|
assert_array_equals(div.getAnimations(), [],
|
|
'Animation should not be returned after seeking to the'
|
|
+ ' clipped end of the active interval');
|
|
}, 'Returns animations based on dynamic changes to individual'
|
|
+ ' animations\' current time');
|
|
|
|
promise_test(async t => {
|
|
const div = createDiv(t);
|
|
|
|
const animA = div.animate({ opacity: 1 }, { duration: 1, fill: 'forwards' });
|
|
const animB = div.animate({ opacity: 1 }, { duration: 1, fill: 'forwards' });
|
|
await animA.finished;
|
|
// It is not guaranteed that the mircrotask PerformCheckpoint() happens before
|
|
// the animation finish promised got resolved, because the microtask
|
|
// checkpoint could also be triggered from other source such as the event_loop
|
|
// Thus we wait for one animation frame to make sure the finished animation is
|
|
// properly removed.
|
|
await waitForNextFrame(1);
|
|
assert_array_equals(div.getAnimations(), [animB]);
|
|
}, 'Does not return an animation that has been removed');
|
|
|
|
promise_test(async t => {
|
|
const div = createDiv(t);
|
|
|
|
const animA = div.animate({ opacity: 1 }, { duration: 1, fill: 'forwards' });
|
|
const animB = div.animate({ opacity: 1 }, { duration: 1, fill: 'forwards' });
|
|
await animA.finished;
|
|
|
|
animA.persist();
|
|
|
|
assert_array_equals(div.getAnimations(), [animA, animB]);
|
|
}, 'Returns an animation that has been persisted');
|
|
|
|
promise_test(async t => {
|
|
const div = createDiv(t);
|
|
const watcher = EventWatcher(t, div, 'transitionrun');
|
|
|
|
// Create a covering animation to prevent transitions from firing after
|
|
// calling getAnimations().
|
|
const coveringAnimation = new Animation(
|
|
new KeyframeEffect(div, { opacity: [0, 1] }, 100 * MS_PER_SEC)
|
|
);
|
|
|
|
// Setup transition start point.
|
|
div.style.transition = 'opacity 100s';
|
|
getComputedStyle(div).opacity;
|
|
|
|
// Update specified style but don't flush style.
|
|
div.style.opacity = '0.5';
|
|
|
|
// Fetch animations
|
|
div.getAnimations();
|
|
|
|
// Play the covering animation to ensure that only the call to
|
|
// getAnimations() has a chance to trigger transitions.
|
|
coveringAnimation.play();
|
|
|
|
// If getAnimations() flushed style, we should get a transitionrun event.
|
|
await watcher.wait_for('transitionrun');
|
|
}, 'Triggers a style change event');
|
|
|
|
</script>
|
|
</body>
|