Bug 1641870 [wpt PR 23847] - [ScrollTimeline] Add timeline to Element.animate(),...
[gecko.git] / testing / web-platform / tests / web-animations / interfaces / Animatable / animate.html
blobdad633ba9a2ca0ed10574c973b036f76b738d0db
1 <!DOCTYPE html>
2 <meta charset=utf-8>
3 <title>Animatable.animate</title>
4 <link rel="help" href="https://drafts.csswg.org/web-animations/#dom-animatable-animate">
5 <script src="/resources/testharness.js"></script>
6 <script src="/resources/testharnessreport.js"></script>
7 <script src="../../testcommon.js"></script>
8 <script src="../../resources/easing-tests.js"></script>
9 <script src="../../resources/keyframe-utils.js"></script>
10 <script src="../../resources/keyframe-tests.js"></script>
11 <script src="../../resources/timing-utils.js"></script>
12 <script src="../../resources/timing-tests.js"></script>
13 <style>
14 .pseudo::before {content: '';}
15 .pseudo::after {content: '';}
16 .pseudo::marker {content: '';}
17 </style>
18 <body>
19 <div id="log"></div>
20 <iframe width="10" height="10" id="iframe"></iframe>
21 <script>
22 'use strict';
24 // Tests on Element
26 test(t => {
27 const anim = createDiv(t).animate(null);
28 assert_class_string(anim, 'Animation', 'Returned object is an Animation');
29 }, 'Element.animate() creates an Animation object');
31 test(t => {
32 const iframe = window.frames[0];
33 const div = createDiv(t, iframe.document);
34 const anim = Element.prototype.animate.call(div, null);
35 assert_equals(Object.getPrototypeOf(anim), iframe.Animation.prototype,
36 'The prototype of the created Animation is that defined on'
37 + ' the relevant global for the target element');
38 assert_not_equals(Object.getPrototypeOf(anim), Animation.prototype,
39 'The prototype of the created Animation is NOT that of'
40 + ' the current global');
41 }, 'Element.animate() creates an Animation object in the relevant realm of'
42 + ' the target element');
44 test(t => {
45 const div = createDiv(t);
46 const anim = Element.prototype.animate.call(div, null);
47 assert_class_string(anim.effect, 'KeyframeEffect',
48 'Returned Animation has a KeyframeEffect');
49 }, 'Element.animate() creates an Animation object with a KeyframeEffect');
51 test(t => {
52 const iframe = window.frames[0];
53 const div = createDiv(t, iframe.document);
54 const anim = Element.prototype.animate.call(div, null);
55 assert_equals(Object.getPrototypeOf(anim.effect),
56 iframe.KeyframeEffect.prototype,
57 'The prototype of the created KeyframeEffect is that defined on'
58 + ' the relevant global for the target element');
59 assert_not_equals(Object.getPrototypeOf(anim.effect),
60 KeyframeEffect.prototype,
61 'The prototype of the created KeyframeEffect is NOT that of'
62 + ' the current global');
63 }, 'Element.animate() creates an Animation object with a KeyframeEffect'
64 + ' that is created in the relevant realm of the target element');
66 for (const subtest of gEmptyKeyframeListTests) {
67 test(t => {
68 const anim = createDiv(t).animate(subtest, 2000);
69 assert_not_equals(anim, null);
70 }, 'Element.animate() accepts empty keyframe lists ' +
71 `(input: ${JSON.stringify(subtest)})`);
74 for (const subtest of gKeyframesTests) {
75 test(t => {
76 const anim = createDiv(t).animate(subtest.input, 2000);
77 assert_frame_lists_equal(anim.effect.getKeyframes(), subtest.output);
78 }, `Element.animate() accepts ${subtest.desc}`);
81 for (const subtest of gInvalidKeyframesTests) {
82 test(t => {
83 const div = createDiv(t);
84 assert_throws_js(TypeError, () => {
85 div.animate(subtest.input, 2000);
86 });
87 }, `Element.animate() does not accept ${subtest.desc}`);
90 test(t => {
91 const anim = createDiv(t).animate(null, 2000);
92 assert_equals(anim.effect.getTiming().duration, 2000);
93 assert_default_timing_except(anim.effect, ['duration']);
94 }, 'Element.animate() accepts a double as an options argument');
96 test(t => {
97 const anim = createDiv(t).animate(null,
98 { duration: Infinity, fill: 'forwards' });
99 assert_equals(anim.effect.getTiming().duration, Infinity);
100 assert_equals(anim.effect.getTiming().fill, 'forwards');
101 assert_default_timing_except(anim.effect, ['duration', 'fill']);
102 }, 'Element.animate() accepts a KeyframeAnimationOptions argument');
104 test(t => {
105 const anim = createDiv(t).animate(null);
106 assert_default_timing_except(anim.effect, []);
107 }, 'Element.animate() accepts an absent options argument');
109 for (const invalid of gBadDelayValues) {
110 test(t => {
111 assert_throws_js(TypeError, () => {
112 createDiv(t).animate(null, { delay: invalid });
114 }, `Element.animate() does not accept invalid delay value: ${invalid}`);
117 test(t => {
118 const anim = createDiv(t).animate(null, { duration: 'auto' });
119 assert_equals(anim.effect.getTiming().duration, 'auto', 'set duration \'auto\'');
120 assert_equals(anim.effect.getComputedTiming().duration, 0,
121 'getComputedTiming() after set duration \'auto\'');
122 }, 'Element.animate() accepts a duration of \'auto\' using a dictionary'
123 + ' object');
125 for (const invalid of gBadDurationValues) {
126 if (typeof invalid === 'string' && !isNaN(parseFloat(invalid))) {
127 continue;
129 test(t => {
130 assert_throws_js(TypeError, () => {
131 createDiv(t).animate(null, invalid);
133 }, 'Element.animate() does not accept invalid duration value: '
134 + (typeof invalid === 'string' ? `"${invalid}"` : invalid));
137 for (const invalid of gBadDurationValues) {
138 test(t => {
139 assert_throws_js(TypeError, () => {
140 createDiv(t).animate(null, { duration: invalid });
142 }, 'Element.animate() does not accept invalid duration value: '
143 + (typeof invalid === 'string' ? `"${invalid}"` : invalid)
144 + ' using a dictionary object');
147 for (const invalidEasing of gInvalidEasings) {
148 test(t => {
149 assert_throws_js(TypeError, () => {
150 createDiv(t).animate({ easing: invalidEasing }, 2000);
152 }, `Element.animate() does not accept invalid easing: '${invalidEasing}'`);
155 for (const invalid of gBadIterationStartValues) {
156 test(t => {
157 assert_throws_js(TypeError, () => {
158 createDiv(t).animate(null, { iterationStart: invalid });
160 }, 'Element.animate() does not accept invalid iterationStart value: ' +
161 invalid);
164 for (const invalid of gBadIterationsValues) {
165 test(t => {
166 assert_throws_js(TypeError, () => {
167 createDiv(t).animate(null, { iterations: invalid });
169 }, 'Element.animate() does not accept invalid iterations value: ' +
170 invalid);
173 test(t => {
174 const anim = createDiv(t).animate(null, 2000);
175 assert_equals(anim.id, '');
176 }, 'Element.animate() correctly sets the id attribute when no id is specified');
178 test(t => {
179 const anim = createDiv(t).animate(null, { id: 'test' });
180 assert_equals(anim.id, 'test');
181 }, 'Element.animate() correctly sets the id attribute');
183 test(t => {
184 const anim = createDiv(t).animate(null, 2000);
185 assert_equals(anim.timeline, document.timeline);
186 }, 'Element.animate() correctly sets the Animation\'s timeline');
188 async_test(t => {
189 const iframe = document.createElement('iframe');
190 iframe.width = 10;
191 iframe.height = 10;
193 iframe.addEventListener('load', t.step_func(() => {
194 const div = createDiv(t, iframe.contentDocument);
195 const anim = div.animate(null, 2000);
196 assert_equals(anim.timeline, iframe.contentDocument.timeline);
197 iframe.remove();
198 t.done();
199 }));
201 document.body.appendChild(iframe);
202 }, 'Element.animate() correctly sets the Animation\'s timeline when ' +
203 'triggered on an element in a different document');
205 for (const subtest of gAnimationTimelineTests) {
206 test(t => {
207 const anim = createDiv(t).animate(null, { timeline: subtest.timeline });
208 assert_not_equals(anim, null,
209 'An animation sohuld be created');
210 assert_equals(anim.timeline, subtest.expectedTimeline,
211 'Animation timeline should be '+
212 subtest.expectedTimelineDescription);
213 }, 'Element.animate() correctly sets the Animation\'s timeline '
214 + subtest.description + ' in KeyframeAnimationOptions.');
217 test(t => {
218 const anim = createDiv(t).animate(null, 2000);
219 assert_equals(anim.playState, 'running');
220 }, 'Element.animate() calls play on the Animation');
222 promise_test(async t => {
223 const div = createDiv(t);
225 let gotTransition = false;
226 div.addEventListener('transitionrun', () => {
227 gotTransition = true;
230 // Setup transition start point.
231 div.style.transition = 'opacity 100s';
232 getComputedStyle(div).opacity;
234 // Update specified style but don't flush style.
235 div.style.opacity = '0.5';
237 // Trigger a new animation at the same time.
238 const anim = div.animate({ opacity: [0, 1] }, 100 * MS_PER_SEC);
240 // If Element.animate() produces a style change event it will have triggered
241 // a transition.
243 // If it does NOT produce a style change event, the animation will override
244 // the before-change style and after-change style such that a transition is
245 // never triggered.
247 // Wait for the animation to start and then for one more animation
248 // frame to give the transitionrun event a chance to be dispatched.
249 await anim.ready;
250 await waitForAnimationFrames(1);
252 assert_false(gotTransition, 'A transition should NOT have been triggered');
253 }, 'Element.animate() does NOT trigger a style change event');
255 // Tests on pseudo-elements
256 // Some tests occur twice (on pseudo-elements with and without content)
257 // in order to test both code paths for tree-abiding pseudo-elements in blink.
259 test(t => {
260 const div = createDiv(t);
261 div.classList.add('pseudo');
262 const anim = div.animate(null, {pseudoElement: '::before'});
263 assert_class_string(anim, 'Animation', 'The returned object is an Animation');
264 }, 'animate() with pseudoElement parameter creates an Animation object');
266 test(t => {
267 const div = createDiv(t);
268 const anim = div.animate(null, {pseudoElement: '::before'});
269 assert_class_string(anim, 'Animation', 'The returned object is an Animation');
270 }, 'animate() with pseudoElement parameter without content creates an Animation object');
272 test(t => {
273 const div = createDiv(t);
274 div.classList.add('pseudo');
275 div.style.display = 'list-item';
276 const anim = div.animate(null, {pseudoElement: '::marker'});
277 assert_class_string(anim, 'Animation', 'The returned object is an Animation for ::marker');
278 }, 'animate() with pseudoElement parameter creates an Animation object for ::marker');
280 test(t => {
281 const div = createDiv(t);
282 div.classList.add('pseudo');
283 div.textContent = 'foo';
284 const anim = div.animate(null, {pseudoElement: '::first-line'});
285 assert_class_string(anim, 'Animation', 'The returned object is an Animation for ::first-line');
286 }, 'animate() with pseudoElement parameter creates an Animation object for ::first-line');
288 test(t => {
289 const div = createDiv(t);
290 div.classList.add('pseudo');
291 const anim = div.animate(null, {pseudoElement: '::before'});
292 assert_equals(anim.effect.target, div, 'The returned element has the correct target element');
293 assert_equals(anim.effect.pseudoElement, '::before',
294 'The returned Animation targets the correct selector');
295 }, 'animate() with pseudoElement an Animation object targeting ' +
296 'the correct pseudo-element');
298 test(t => {
299 const div = createDiv(t);
300 const anim = div.animate(null, {pseudoElement: '::before'});
301 assert_equals(anim.effect.target, div, 'The returned element has the correct target element');
302 assert_equals(anim.effect.pseudoElement, '::before',
303 'The returned Animation targets the correct selector');
304 }, 'animate() with pseudoElement without content creates an Animation object targeting ' +
305 'the correct pseudo-element');
307 test(t => {
308 const div = createDiv(t);
309 div.classList.add('pseudo');
310 div.style.display = 'list-item';
311 const anim = div.animate(null, {pseudoElement: '::marker'});
312 assert_equals(anim.effect.target, div, 'The returned element has the correct target element');
313 assert_equals(anim.effect.pseudoElement, '::marker',
314 'The returned Animation targets the correct selector');
315 }, 'animate() with pseudoElement an Animation object targeting ' +
316 'the correct pseudo-element for ::marker');
318 test(t => {
319 const div = createDiv(t);
320 div.classList.add('pseudo');
321 div.textContent = 'foo';
322 const anim = div.animate(null, {pseudoElement: '::first-line'});
323 assert_equals(anim.effect.target, div, 'The returned element has the correct target element');
324 assert_equals(anim.effect.pseudoElement, '::first-line',
325 'The returned Animation targets the correct selector');
326 }, 'animate() with pseudoElement an Animation object targeting ' +
327 'the correct pseudo-element for ::first-line');
329 for (const pseudo of [
331 'before',
332 ':abc',
333 '::abc',
334 '::placeholder',
335 ]) {
336 test(t => {
337 const div = createDiv(t);
338 assert_throws_dom("SyntaxError", () => {
339 div.animate(null, {pseudoElement: pseudo});
341 }, `animate() with a non-null invalid pseudoElement '${pseudo}' throws a ` +
342 `SyntaxError`);
345 </script>
346 </body>