blob: fd8719299dbc0d2233f8f16bc08cfa3c20684746 [file] [log] [blame]
<!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>