diff options
Diffstat (limited to 'testing/web-platform/tests/web-animations/interfaces/Animatable')
4 files changed, 859 insertions, 0 deletions
diff --git a/testing/web-platform/tests/web-animations/interfaces/Animatable/animate-no-browsing-context.html b/testing/web-platform/tests/web-animations/interfaces/Animatable/animate-no-browsing-context.html new file mode 100644 index 0000000000..61a7502a98 --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/Animatable/animate-no-browsing-context.html @@ -0,0 +1,107 @@ +<!doctype html> +<meta charset=utf-8> +<title>Animatable.animate in combination with elements in documents + without a browsing context</title> +<link rel="help" href="https://drafts.csswg.org/web-animations/#dom-animatable-animate"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<div id="log"></div> +<script> +'use strict'; + +// +// The following tests relate to animations on elements in documents without +// a browsing context. This is NOT the same as documents that are not bound to +// a document tree. +// + +function getXHRDoc(t) { + return new Promise(resolve => { + const xhr = new XMLHttpRequest(); + xhr.open('GET', '../../resources/xhr-doc.py'); + xhr.responseType = 'document'; + xhr.onload = t.step_func(() => { + assert_equals(xhr.readyState, xhr.DONE, + 'Request should complete successfully'); + assert_equals(xhr.status, 200, + 'Response should be OK'); + resolve(xhr.responseXML); + }); + xhr.send(); + }); +} + +promise_test(t => { + return getXHRDoc(t).then(xhrdoc => { + const div = xhrdoc.getElementById('test'); + const anim = div.animate(null); + assert_class_string(anim.timeline, 'DocumentTimeline', + 'Animation should have a timeline'); + assert_equals(anim.timeline, xhrdoc.timeline, + 'Animation timeline should be the default document timeline' + + ' of the XHR doc'); + assert_not_equals(anim.timeline, document.timeline, + 'Animation timeline should NOT be the same timeline as' + + ' the default document timeline for the current' + + ' document'); + + }); +}, 'Element.animate() creates an animation with the correct timeline' + + ' when called on an element in a document without a browsing context'); + +// +// The following tests are cross-cutting tests that are not specific to the +// Animatable.animate() interface. Instead, they draw on assertions from +// various parts of the spec. These assertions are tested in other tests +// but are repeated here to confirm that user agents are not doing anything +// different in the particular case of document without a browsing context. +// + +promise_test(t => { + return getXHRDoc(t).then(xhrdoc => { + const div = xhrdoc.getElementById('test'); + const anim = div.animate(null); + // Since a document from XHR will not be active by itself, its document + // timeline will also be inactive. + assert_equals(anim.timeline.currentTime, null, + 'Document timeline time should be null'); + }); +}, 'The timeline associated with an animation trigger on an element in' + + ' a document without a browsing context is inactive'); + +promise_test(t => { + let anim; + return getXHRDoc(t).then(xhrdoc => { + const div = xhrdoc.getElementById('test'); + anim = div.animate(null); + anim.timeline = document.timeline; + assert_true(anim.pending, 'The animation should be initially pending'); + return waitForAnimationFrames(2); + }).then(() => { + // Because the element is in a document without a browsing context, it will + // not be rendered and hence the user agent will never deem it ready to + // animate. + assert_true(anim.pending, + 'The animation should still be pending after replacing' + + ' the document timeline'); + }); +}, 'Replacing the timeline of an animation targetting an element in a' + + ' document without a browsing context leaves it in the pending state'); + +promise_test(t => { + let anim; + return getXHRDoc(t).then(xhrdoc => { + const div = xhrdoc.getElementById('test'); + anim = div.animate({ opacity: [ 0, 1 ] }, 1000); + anim.timeline = document.timeline; + document.body.appendChild(div); + assert_equals(getComputedStyle(div).opacity, '0', + 'Style should be updated'); + }); +}, 'Replacing the timeline of an animation targetting an element in a' + + ' document without a browsing context and then adopting that element' + + ' causes it to start updating style'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/Animatable/animate.html b/testing/web-platform/tests/web-animations/interfaces/Animatable/animate.html new file mode 100644 index 0000000000..dad633ba9a --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/Animatable/animate.html @@ -0,0 +1,346 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Animatable.animate</title> +<link rel="help" href="https://drafts.csswg.org/web-animations/#dom-animatable-animate"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<script src="../../resources/easing-tests.js"></script> +<script src="../../resources/keyframe-utils.js"></script> +<script src="../../resources/keyframe-tests.js"></script> +<script src="../../resources/timing-utils.js"></script> +<script src="../../resources/timing-tests.js"></script> +<style> +.pseudo::before {content: '';} +.pseudo::after {content: '';} +.pseudo::marker {content: '';} +</style> +<body> +<div id="log"></div> +<iframe width="10" height="10" id="iframe"></iframe> +<script> +'use strict'; + +// Tests on Element + +test(t => { + const anim = createDiv(t).animate(null); + assert_class_string(anim, 'Animation', 'Returned object is an Animation'); +}, 'Element.animate() creates an Animation object'); + +test(t => { + const iframe = window.frames[0]; + const div = createDiv(t, iframe.document); + const anim = Element.prototype.animate.call(div, null); + assert_equals(Object.getPrototypeOf(anim), iframe.Animation.prototype, + 'The prototype of the created Animation is that defined on' + + ' the relevant global for the target element'); + assert_not_equals(Object.getPrototypeOf(anim), Animation.prototype, + 'The prototype of the created Animation is NOT that of' + + ' the current global'); +}, 'Element.animate() creates an Animation object in the relevant realm of' + + ' the target element'); + +test(t => { + const div = createDiv(t); + const anim = Element.prototype.animate.call(div, null); + assert_class_string(anim.effect, 'KeyframeEffect', + 'Returned Animation has a KeyframeEffect'); +}, 'Element.animate() creates an Animation object with a KeyframeEffect'); + +test(t => { + const iframe = window.frames[0]; + const div = createDiv(t, iframe.document); + const anim = Element.prototype.animate.call(div, null); + assert_equals(Object.getPrototypeOf(anim.effect), + iframe.KeyframeEffect.prototype, + 'The prototype of the created KeyframeEffect is that defined on' + + ' the relevant global for the target element'); + assert_not_equals(Object.getPrototypeOf(anim.effect), + KeyframeEffect.prototype, + 'The prototype of the created KeyframeEffect is NOT that of' + + ' the current global'); +}, 'Element.animate() creates an Animation object with a KeyframeEffect' + + ' that is created in the relevant realm of the target element'); + +for (const subtest of gEmptyKeyframeListTests) { + test(t => { + const anim = createDiv(t).animate(subtest, 2000); + assert_not_equals(anim, null); + }, 'Element.animate() accepts empty keyframe lists ' + + `(input: ${JSON.stringify(subtest)})`); +} + +for (const subtest of gKeyframesTests) { + test(t => { + const anim = createDiv(t).animate(subtest.input, 2000); + assert_frame_lists_equal(anim.effect.getKeyframes(), subtest.output); + }, `Element.animate() accepts ${subtest.desc}`); +} + +for (const subtest of gInvalidKeyframesTests) { + test(t => { + const div = createDiv(t); + assert_throws_js(TypeError, () => { + div.animate(subtest.input, 2000); + }); + }, `Element.animate() does not accept ${subtest.desc}`); +} + +test(t => { + const anim = createDiv(t).animate(null, 2000); + assert_equals(anim.effect.getTiming().duration, 2000); + assert_default_timing_except(anim.effect, ['duration']); +}, 'Element.animate() accepts a double as an options argument'); + +test(t => { + const anim = createDiv(t).animate(null, + { duration: Infinity, fill: 'forwards' }); + assert_equals(anim.effect.getTiming().duration, Infinity); + assert_equals(anim.effect.getTiming().fill, 'forwards'); + assert_default_timing_except(anim.effect, ['duration', 'fill']); +}, 'Element.animate() accepts a KeyframeAnimationOptions argument'); + +test(t => { + const anim = createDiv(t).animate(null); + assert_default_timing_except(anim.effect, []); +}, 'Element.animate() accepts an absent options argument'); + +for (const invalid of gBadDelayValues) { + test(t => { + assert_throws_js(TypeError, () => { + createDiv(t).animate(null, { delay: invalid }); + }); + }, `Element.animate() does not accept invalid delay value: ${invalid}`); +} + +test(t => { + const anim = createDiv(t).animate(null, { duration: 'auto' }); + assert_equals(anim.effect.getTiming().duration, 'auto', 'set duration \'auto\''); + assert_equals(anim.effect.getComputedTiming().duration, 0, + 'getComputedTiming() after set duration \'auto\''); +}, 'Element.animate() accepts a duration of \'auto\' using a dictionary' + + ' object'); + +for (const invalid of gBadDurationValues) { + if (typeof invalid === 'string' && !isNaN(parseFloat(invalid))) { + continue; + } + test(t => { + assert_throws_js(TypeError, () => { + createDiv(t).animate(null, invalid); + }); + }, 'Element.animate() does not accept invalid duration value: ' + + (typeof invalid === 'string' ? `"${invalid}"` : invalid)); +} + +for (const invalid of gBadDurationValues) { + test(t => { + assert_throws_js(TypeError, () => { + createDiv(t).animate(null, { duration: invalid }); + }); + }, 'Element.animate() does not accept invalid duration value: ' + + (typeof invalid === 'string' ? `"${invalid}"` : invalid) + + ' using a dictionary object'); +} + +for (const invalidEasing of gInvalidEasings) { + test(t => { + assert_throws_js(TypeError, () => { + createDiv(t).animate({ easing: invalidEasing }, 2000); + }); + }, `Element.animate() does not accept invalid easing: '${invalidEasing}'`); +} + +for (const invalid of gBadIterationStartValues) { + test(t => { + assert_throws_js(TypeError, () => { + createDiv(t).animate(null, { iterationStart: invalid }); + }); + }, 'Element.animate() does not accept invalid iterationStart value: ' + + invalid); +} + +for (const invalid of gBadIterationsValues) { + test(t => { + assert_throws_js(TypeError, () => { + createDiv(t).animate(null, { iterations: invalid }); + }); + }, 'Element.animate() does not accept invalid iterations value: ' + + invalid); +} + +test(t => { + const anim = createDiv(t).animate(null, 2000); + assert_equals(anim.id, ''); +}, 'Element.animate() correctly sets the id attribute when no id is specified'); + +test(t => { + const anim = createDiv(t).animate(null, { id: 'test' }); + assert_equals(anim.id, 'test'); +}, 'Element.animate() correctly sets the id attribute'); + +test(t => { + const anim = createDiv(t).animate(null, 2000); + assert_equals(anim.timeline, document.timeline); +}, 'Element.animate() correctly sets the Animation\'s timeline'); + +async_test(t => { + const iframe = document.createElement('iframe'); + iframe.width = 10; + iframe.height = 10; + + iframe.addEventListener('load', t.step_func(() => { + const div = createDiv(t, iframe.contentDocument); + const anim = div.animate(null, 2000); + assert_equals(anim.timeline, iframe.contentDocument.timeline); + iframe.remove(); + t.done(); + })); + + document.body.appendChild(iframe); +}, 'Element.animate() correctly sets the Animation\'s timeline when ' + + 'triggered on an element in a different document'); + +for (const subtest of gAnimationTimelineTests) { + test(t => { + const anim = createDiv(t).animate(null, { timeline: subtest.timeline }); + assert_not_equals(anim, null, + 'An animation sohuld be created'); + assert_equals(anim.timeline, subtest.expectedTimeline, + 'Animation timeline should be '+ + subtest.expectedTimelineDescription); + }, 'Element.animate() correctly sets the Animation\'s timeline ' + + subtest.description + ' in KeyframeAnimationOptions.'); +} + +test(t => { + const anim = createDiv(t).animate(null, 2000); + assert_equals(anim.playState, 'running'); +}, 'Element.animate() calls play on the Animation'); + +promise_test(async t => { + const div = createDiv(t); + + let gotTransition = false; + div.addEventListener('transitionrun', () => { + gotTransition = true; + }); + + // 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'; + + // Trigger a new animation at the same time. + const anim = div.animate({ opacity: [0, 1] }, 100 * MS_PER_SEC); + + // If Element.animate() produces a style change event it will have triggered + // a transition. + // + // If it does NOT produce a style change event, the animation will override + // the before-change style and after-change style such that a transition is + // never triggered. + + // Wait for the animation to start and then for one more animation + // frame to give the transitionrun event a chance to be dispatched. + await anim.ready; + await waitForAnimationFrames(1); + + assert_false(gotTransition, 'A transition should NOT have been triggered'); +}, 'Element.animate() does NOT trigger a style change event'); + +// Tests on pseudo-elements +// Some tests occur twice (on pseudo-elements with and without content) +// in order to test both code paths for tree-abiding pseudo-elements in blink. + +test(t => { + const div = createDiv(t); + div.classList.add('pseudo'); + const anim = div.animate(null, {pseudoElement: '::before'}); + assert_class_string(anim, 'Animation', 'The returned object is an Animation'); +}, 'animate() with pseudoElement parameter creates an Animation object'); + +test(t => { + const div = createDiv(t); + const anim = div.animate(null, {pseudoElement: '::before'}); + assert_class_string(anim, 'Animation', 'The returned object is an Animation'); +}, 'animate() with pseudoElement parameter without content creates an Animation object'); + +test(t => { + const div = createDiv(t); + div.classList.add('pseudo'); + div.style.display = 'list-item'; + const anim = div.animate(null, {pseudoElement: '::marker'}); + assert_class_string(anim, 'Animation', 'The returned object is an Animation for ::marker'); +}, 'animate() with pseudoElement parameter creates an Animation object for ::marker'); + +test(t => { + const div = createDiv(t); + div.classList.add('pseudo'); + div.textContent = 'foo'; + const anim = div.animate(null, {pseudoElement: '::first-line'}); + assert_class_string(anim, 'Animation', 'The returned object is an Animation for ::first-line'); +}, 'animate() with pseudoElement parameter creates an Animation object for ::first-line'); + +test(t => { + const div = createDiv(t); + div.classList.add('pseudo'); + const anim = div.animate(null, {pseudoElement: '::before'}); + assert_equals(anim.effect.target, div, 'The returned element has the correct target element'); + assert_equals(anim.effect.pseudoElement, '::before', + 'The returned Animation targets the correct selector'); +}, 'animate() with pseudoElement an Animation object targeting ' + + 'the correct pseudo-element'); + +test(t => { + const div = createDiv(t); + const anim = div.animate(null, {pseudoElement: '::before'}); + assert_equals(anim.effect.target, div, 'The returned element has the correct target element'); + assert_equals(anim.effect.pseudoElement, '::before', + 'The returned Animation targets the correct selector'); +}, 'animate() with pseudoElement without content creates an Animation object targeting ' + + 'the correct pseudo-element'); + +test(t => { + const div = createDiv(t); + div.classList.add('pseudo'); + div.style.display = 'list-item'; + const anim = div.animate(null, {pseudoElement: '::marker'}); + assert_equals(anim.effect.target, div, 'The returned element has the correct target element'); + assert_equals(anim.effect.pseudoElement, '::marker', + 'The returned Animation targets the correct selector'); +}, 'animate() with pseudoElement an Animation object targeting ' + + 'the correct pseudo-element for ::marker'); + +test(t => { + const div = createDiv(t); + div.classList.add('pseudo'); + div.textContent = 'foo'; + const anim = div.animate(null, {pseudoElement: '::first-line'}); + assert_equals(anim.effect.target, div, 'The returned element has the correct target element'); + assert_equals(anim.effect.pseudoElement, '::first-line', + 'The returned Animation targets the correct selector'); +}, 'animate() with pseudoElement an Animation object targeting ' + + 'the correct pseudo-element for ::first-line'); + +for (const pseudo of [ + '', + 'before', + ':abc', + '::abc', + '::placeholder', +]) { + test(t => { + const div = createDiv(t); + assert_throws_dom("SyntaxError", () => { + div.animate(null, {pseudoElement: pseudo}); + }); + }, `animate() with a non-null invalid pseudoElement '${pseudo}' throws a ` + + `SyntaxError`); +} + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/Animatable/getAnimations-iframe.html b/testing/web-platform/tests/web-animations/interfaces/Animatable/getAnimations-iframe.html new file mode 100644 index 0000000000..1851878c41 --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/Animatable/getAnimations-iframe.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<title>getAnimations in dirty iframe</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<style> + iframe { + width: 200px; + height: 40px; + } +</style> +<body> +<script> + + const createFrame = async test => { + const iframe = createElement(test, "iframe"); + const contents = "" + + "<style>" + + " div { color: red; }" + + " @keyframes test {" + + " from { color: green; }" + + " to { color: green; }" + + " }" + + " @media (min-width: 300px) {" + + " div { animation: test 1s linear forwards; }" + + " }" + + "</style>" + + "<div id=div>Green</div>"; + iframe.setAttribute("srcdoc", contents); + await new Promise(resolve => iframe.addEventListener("load", resolve)); + return iframe; + }; + + const iframeTest = (getAnimations, interfaceName) => { + promise_test(async test => { + const frame = await createFrame(test); + const inner_div = frame.contentDocument.getElementById('div'); + assert_equals(getComputedStyle(inner_div).color, 'rgb(255, 0, 0)'); + + frame.style.width = '400px'; + const animations = getAnimations(inner_div); + assert_equals(animations.length, 1); + assert_equals(getComputedStyle(inner_div).color, 'rgb(0, 128, 0)'); + }, `Calling ${interfaceName}.getAnimations updates layout of parent frame if needed`); + } + + iframeTest(element => element.getAnimations(), 'Element'); + iframeTest(element => element.ownerDocument.getAnimations(), 'Document'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/Animatable/getAnimations.html b/testing/web-platform/tests/web-animations/interfaces/Animatable/getAnimations.html new file mode 100644 index 0000000000..fd8719299d --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/Animatable/getAnimations.html @@ -0,0 +1,355 @@ +<!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> |