summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/web-animations/interfaces/Animatable
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/web-animations/interfaces/Animatable')
-rw-r--r--testing/web-platform/tests/web-animations/interfaces/Animatable/animate-no-browsing-context.html107
-rw-r--r--testing/web-platform/tests/web-animations/interfaces/Animatable/animate.html346
-rw-r--r--testing/web-platform/tests/web-animations/interfaces/Animatable/getAnimations-iframe.html51
-rw-r--r--testing/web-platform/tests/web-animations/interfaces/Animatable/getAnimations.html355
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>