| <!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> |