5aa7416dcc0c2031d77d286d53a4cd73edb062ee
[WebKit-https.git] / LayoutTests / imported / w3c / web-platform-tests / css / css-animations / Element-getAnimations.tentative.html
1 <!doctype html>
2 <meta charset=utf-8>
3 <title>Element.getAnimations() for CSS animations</title>
4 <!--  TODO: Add a more specific link for this once it is specified.  -->
5 <link rel="help" href="https://drafts.csswg.org/css-animations-2/">
6 <script src="/resources/testharness.js"></script>
7 <script src="/resources/testharnessreport.js"></script>
8 <script src="support/testcommon.js"></script>
9 <style>
10 @keyframes anim1 {
11   to { left: 100px }
12 }
13 @keyframes anim2 {
14   to { top: 100px }
15 }
16 @keyframes multiPropAnim {
17   to { background: green, opacity: 0.5, left: 100px, top: 100px }
18 }
19 ::before {
20   content: ''
21 }
22 ::after {
23   content: ''
24 }
25 @keyframes empty { }
26 </style>
27 <div id="log"></div>
28 <script>
29 'use strict';
30
31 test(t => {
32   const div = addDiv(t);
33   assert_equals(div.getAnimations().length, 0,
34     'getAnimations returns an empty sequence for an element'
35     + ' with no animations');
36 }, 'getAnimations for non-animated content');
37
38 promise_test(async t => {
39   const div = addDiv(t);
40
41   // Add an animation
42   div.style.animation = 'anim1 100s';
43   let animations = div.getAnimations();
44   assert_equals(animations.length, 1,
45     'getAnimations returns an Animation running CSS Animations');
46   await animations[0].ready;
47
48   // Add a second animation
49   div.style.animation = 'anim1 100s, anim2 100s';
50   animations = div.getAnimations();
51   assert_equals(animations.length, 2,
52     'getAnimations returns one CSSAnimation for each value of animation-name');
53   // (We don't check the order of the Animations since that is covered by tests
54   //  later in this file.)
55 }, 'getAnimations for CSS Animations');
56
57 test(t => {
58   const div = addDiv(t, { style: 'animation: anim1 100s' });
59   assert_class_string(div.getAnimations()[0], 'CSSAnimation',
60                       'Interface of returned animation is CSSAnimation');
61 }, 'getAnimations returns CSSAnimation objects for CSS Animations');
62
63 test(t => {
64   const div = addDiv(t);
65
66   // Add an animation that targets multiple properties
67   div.style.animation = 'multiPropAnim 100s';
68   assert_equals(div.getAnimations().length, 1,
69     'getAnimations returns only one Animation for a CSS Animation'
70     + ' that targets multiple properties');
71 }, 'getAnimations for multi-property animations');
72
73 promise_test(async t => {
74   const div = addDiv(t);
75
76   // Add an animation
77   div.style.backgroundColor = 'red';
78   div.style.animation = 'anim1 100s';
79   getComputedStyle(div).backgroundColor;
80
81   // Wait until a frame after the animation starts, then add a transition
82   let animations = div.getAnimations();
83   await animations[0].ready;
84   await waitForFrame();
85
86   div.style.transition = 'all 100s';
87   div.style.backgroundColor = 'green';
88
89   animations = div.getAnimations();
90   assert_equals(animations.length, 2,
91     'getAnimations returns Animations for both animations and'
92     + ' transitions that run simultaneously');
93   assert_class_string(animations[0], 'CSSTransition',
94                       'First-returned animation is the CSS Transition');
95   assert_class_string(animations[1], 'CSSAnimation',
96                       'Second-returned animation is the CSS Animation');
97 }, 'getAnimations for both CSS Animations and CSS Transitions at once');
98
99 async_test(t => {
100   const div = addDiv(t);
101
102   // Set up event listener
103   div.addEventListener('animationend', t.step_func(() => {
104     assert_equals(div.getAnimations().length, 0,
105       'getAnimations does not return Animations for finished '
106       + ' (and non-forwards-filling) CSS Animations');
107     t.done();
108   }));
109
110   // Add a very short animation
111   div.style.animation = 'anim1 0.01s';
112 }, 'getAnimations for CSS Animations that have finished');
113
114 async_test(t => {
115   const div = addDiv(t);
116
117   // Set up event listener
118   div.addEventListener('animationend', t.step_func(() => {
119     assert_equals(div.getAnimations().length, 1,
120       'getAnimations returns Animations for CSS Animations that have'
121       + ' finished but are filling forwards');
122     t.done();
123   }));
124
125   // Add a very short animation
126   div.style.animation = 'anim1 0.01s forwards';
127 }, 'getAnimations for CSS Animations that have finished but are'
128    + ' forwards filling');
129
130 test(t => {
131   const div = addDiv(t);
132   div.style.animation = 'none 100s';
133
134   let animations = div.getAnimations();
135   assert_equals(animations.length, 0,
136     'getAnimations returns an empty sequence for an element'
137     + ' with animation-name: none');
138
139   div.style.animation = 'none 100s, anim1 100s';
140   animations = div.getAnimations();
141   assert_equals(animations.length, 1,
142     'getAnimations returns Animations only for those CSS Animations whose'
143     + ' animation-name is not none');
144 }, 'getAnimations for CSS Animations with animation-name: none');
145
146 test(t => {
147   const div = addDiv(t);
148   div.style.animation = 'missing 100s';
149   let animations = div.getAnimations();
150   assert_equals(animations.length, 0,
151     'getAnimations returns an empty sequence for an element'
152     + ' with animation-name: missing');
153
154   div.style.animation = 'anim1 100s, missing 100s';
155   animations = div.getAnimations();
156   assert_equals(animations.length, 1,
157     'getAnimations returns Animations only for those CSS Animations whose'
158     + ' animation-name is found');
159 }, 'getAnimations for CSS Animations with animation-name: missing');
160
161 promise_test(async t => {
162   const div = addDiv(t);
163   div.style.animation = 'anim1 100s, notyet 100s';
164   let animations = div.getAnimations();
165   assert_equals(animations.length, 1,
166     'getAnimations initally only returns Animations for CSS Animations whose'
167     + ' animation-name is found');
168
169   await animations[0].ready;
170   await waitForFrame();
171
172   const keyframes = '@keyframes notyet { to { left: 100px; } }';
173   document.styleSheets[0].insertRule(keyframes, 0);
174   animations = div.getAnimations();
175   assert_equals(animations.length, 2,
176     'getAnimations includes Animation when @keyframes rule is added'
177     + ' later');
178   await waitForAllAnimations(animations);
179
180   assert_true(animations[0].startTime < animations[1].startTime,
181     'Newly added animation has a later start time');
182   document.styleSheets[0].deleteRule(0);
183 }, 'getAnimations for CSS Animations where the @keyframes rule is added'
184    + ' later');
185
186 test(t => {
187   const div = addDiv(t);
188   div.style.animation = 'anim1 100s, anim1 100s';
189   assert_equals(div.getAnimations().length, 2,
190     'getAnimations returns one Animation for each CSS animation-name'
191     + ' even if the names are duplicated');
192 }, 'getAnimations for CSS Animations with duplicated animation-name');
193
194 test(t => {
195   const div = addDiv(t);
196   div.style.animation = 'empty 100s';
197   assert_equals(div.getAnimations().length, 1,
198     'getAnimations returns Animations for CSS animations with an'
199     + ' empty keyframes rule');
200 }, 'getAnimations for CSS Animations with empty keyframes rule');
201
202 promise_test(async t => {
203   const div = addDiv(t);
204   div.style.animation = 'anim1 100s 100s';
205   const animations = div.getAnimations();
206   assert_equals(animations.length, 1,
207     'getAnimations returns animations for CSS animations whose'
208     + ' delay makes them start later');
209   await animations[0].ready;
210   await waitForFrame();
211
212   assert_true(animations[0].startTime <= document.timeline.currentTime,
213     'For CSS Animations in delay phase, the start time of the Animation is'
214     + ' not in the future');
215 }, 'getAnimations for CSS animations in delay phase');
216
217 test(t => {
218   const div = addDiv(t);
219   div.style.animation = 'anim1 0s 100s';
220   assert_equals(div.getAnimations().length, 1,
221     'getAnimations returns animations for CSS animations whose'
222     + ' duration is zero');
223   div.remove();
224 }, 'getAnimations for zero-duration CSS Animations');
225
226 test(t => {
227   const div = addDiv(t);
228   div.style.animation = 'anim1 100s';
229   const originalAnimation = div.getAnimations()[0];
230
231   // Update pause state (an Animation change)
232   div.style.animationPlayState = 'paused';
233   const pendingAnimation = div.getAnimations()[0];
234   assert_equals(pendingAnimation.playState, 'paused',
235                 'animation\'s play state is updated');
236   assert_equals(originalAnimation, pendingAnimation,
237                 'getAnimations returns the same objects even when their'
238                 + ' play state changes');
239
240   // Update duration (an Animation change)
241   div.style.animationDuration = '200s';
242   const extendedAnimation = div.getAnimations()[0];
243   assert_equals(
244     extendedAnimation.effect.getTiming().duration,
245     200 * MS_PER_SEC,
246     'animation\'s duration has been updated'
247   );
248   assert_equals(originalAnimation, extendedAnimation,
249                 'getAnimations returns the same objects even when their'
250                 + ' duration changes');
251 }, 'getAnimations returns objects with the same identity');
252
253 test(t => {
254   const div = addDiv(t);
255   div.style.animation = 'anim1 100s';
256
257   assert_equals(div.getAnimations().length, 1,
258     'getAnimations returns an animation before canceling');
259
260   const animation = div.getAnimations()[0];
261
262   animation.cancel();
263   assert_equals(div.getAnimations().length, 0,
264     'getAnimations does not return canceled animations');
265
266   animation.play();
267   assert_equals(div.getAnimations().length, 1,
268     'getAnimations returns canceled animations that have been re-started');
269
270 }, 'getAnimations for CSS Animations that are canceled');
271
272 promise_test(async t => {
273   const div = addDiv(t);
274   div.style.animation = 'anim2 100s';
275
276   await div.getAnimations()[0].ready;
277
278   // Prepend to the list and test that even though anim1 was triggered
279   // *after* anim2, it should come first because it appears first
280   // in the animation-name property.
281   div.style.animation = 'anim1 100s, anim2 100s';
282   let anims = div.getAnimations();
283   assert_equals(anims[0].animationName, 'anim1',
284                 'animation order after prepending to list');
285   assert_equals(anims[1].animationName, 'anim2',
286                 'animation order after prepending to list');
287
288   // Normally calling cancel and play would this push anim1 to the top of
289   // the stack but it shouldn't for CSS animations that map an the
290   // animation-name property.
291   const anim1 = anims[0];
292   anim1.cancel();
293   anim1.play();
294   anims = div.getAnimations();
295   assert_equals(anims[0].animationName, 'anim1',
296                 'animation order after canceling and restarting');
297   assert_equals(anims[1].animationName, 'anim2',
298                 'animation order after canceling and restarting');
299 }, 'getAnimations for CSS Animations follows animation-name order');
300
301 test(t => {
302   addStyle(t, { '#target::after': 'animation: anim1 10s;',
303                 '#target::before': 'animation: anim1 10s;' });
304   const target = addDiv(t, { 'id': 'target' });
305   target.style.animation = 'anim1 100s';
306
307   const animations = target.getAnimations({ subtree: false });
308   assert_equals(animations.length, 1,
309                 'Should find only the element');
310   assert_equals(animations[0].effect.target, target,
311                 'Effect target should be the element');
312 }, '{ subtree: false } on a leaf element returns the element\'s animations'
313    + ' and ignore pseudo-elements');
314
315 test(t => {
316   addStyle(t, { '#target::after': 'animation: anim1 10s;',
317                 '#target::before': 'animation: anim1 10s;' });
318   const target = addDiv(t, { 'id': 'target' });
319   target.style.animation = 'anim1 100s';
320
321   const animations = target.getAnimations({ subtree: true });
322   assert_equals(animations.length, 3,
323                 'getAnimations({ subtree: true }) ' +
324                 'should return animations on pseudo-elements');
325   assert_equals(animations[0].effect.target, target,
326                 'The animation targeting the parent element ' +
327                 'should be returned first');
328   assert_equals(animations[1].effect.target.type, '::before',
329                 'The animation targeting the ::before pseudo-element ' +
330                 'should be returned second');
331   assert_equals(animations[2].effect.target.type, '::after',
332                 'The animation targeting the ::after pesudo-element ' +
333                 'should be returned last');
334 }, '{ subtree: true } on a leaf element returns the element\'s animations'
335    + ' and its pseudo-elements\' animations');
336
337 test(t => {
338   addStyle(t, { '#parent::after': 'animation: anim1 10s;',
339                 '#parent::before': 'animation: anim1 10s;',
340                 '#child::after': 'animation: anim1 10s;',
341                 '#child::before': 'animation: anim1 10s;' });
342   const parent = addDiv(t, { 'id': 'parent' });
343   parent.style.animation = 'anim1 100s';
344   const child = addDiv(t, { 'id': 'child' });
345   child.style.animation = 'anim1 100s';
346   parent.appendChild(child);
347
348   const animations = parent.getAnimations({ subtree: false });
349   assert_equals(animations.length, 1,
350                 'Should find only the element even if it has a child');
351   assert_equals(animations[0].effect.target, parent,
352                 'Effect target shuld be the element');
353 }, '{ subtree: false } on an element with a child returns only the element\'s'
354    + ' animations');
355
356 test(t => {
357   addStyle(t, { '#parent::after': 'animation: anim1 10s;',
358                 '#parent::before': 'animation: anim1 10s;',
359                 '#child::after': 'animation: anim1 10s;',
360                 '#child::before': 'animation: anim1 10s;' });
361   const parent = addDiv(t, { 'id': 'parent' });
362   const child = addDiv(t, { 'id': 'child' });
363   parent.style.animation = 'anim1 100s';
364   child.style.animation = 'anim1 100s';
365   parent.appendChild(child);
366
367   const animations = parent.getAnimations({ subtree: true });
368   assert_equals(animations.length, 6,
369                 'Should find all elements, pesudo-elements that parent has');
370
371   assert_equals(animations[0].effect.target, parent,
372                 'The animation targeting the parent element ' +
373                 'should be returned first');
374   assert_equals(animations[1].effect.target.type, '::before',
375                 'The animation targeting the ::before pseudo-element ' +
376                 'should be returned second');
377   assert_equals(animations[1].effect.target.element, parent,
378                 'This ::before element should be child of parent element');
379   assert_equals(animations[2].effect.target.type, '::after',
380                 'The animation targeting the ::after pesudo-element ' +
381                 'should be returned third');
382   assert_equals(animations[2].effect.target.element, parent,
383                 'This ::after element should be child of parent element');
384
385   assert_equals(animations[3].effect.target, child,
386                 'The animation targeting the child element ' +
387                 'should be returned fourth');
388   assert_equals(animations[4].effect.target.type, '::before',
389                 'The animation targeting the ::before pseudo-element ' +
390                 'should be returned fifth');
391   assert_equals(animations[4].effect.target.element, child,
392                 'This ::before element should be child of child element');
393   assert_equals(animations[5].effect.target.type, '::after',
394                 'The animation targeting the ::after pesudo-element ' +
395                 'should be returned last');
396   assert_equals(animations[5].effect.target.element, child,
397                 'This ::after element should be child of child element');
398 }, '{ subtree: true } on an element with a child returns animations from the'
399    + ' element, its pseudo-elements, its child and its child pseudo-elements');
400
401 test(t => {
402   const parent = addDiv(t, { 'id': 'parent' });
403   const child1 = addDiv(t, { 'id': 'child1' });
404   const grandchild1 = addDiv(t, { 'id': 'grandchild1' });
405   const grandchild2 = addDiv(t, { 'id': 'grandchild2' });
406   const child2 = addDiv(t, { 'id': 'child2' });
407
408   parent.style.animation = 'anim1 100s';
409   child1.style.animation = 'anim1 100s';
410   grandchild1.style.animation = 'anim1 100s';
411   grandchild2.style.animation = 'anim1 100s';
412   child2.style.animation = 'anim1 100s';
413
414   parent.appendChild(child1);
415   child1.appendChild(grandchild1);
416   child1.appendChild(grandchild2);
417   parent.appendChild(child2);
418
419   const animations = parent.getAnimations({ subtree: true });
420   assert_equals(
421     parent.getAnimations({ subtree: true }).length, 5,
422                          'Should find all descendants of the element');
423
424   assert_equals(animations[0].effect.target, parent,
425                 'The animation targeting the parent element ' +
426                 'should be returned first');
427
428   assert_equals(animations[1].effect.target, child1,
429                 'The animation targeting the child1 element ' +
430                 'should be returned second');
431
432   assert_equals(animations[2].effect.target, grandchild1,
433                 'The animation targeting the grandchild1 element ' +
434                 'should be returned third');
435
436   assert_equals(animations[3].effect.target, grandchild2,
437                 'The animation targeting the grandchild2 element ' +
438                 'should be returned fourth');
439
440   assert_equals(animations[4].effect.target, child2,
441                 'The animation targeting the child2 element ' +
442                 'should be returned last');
443
444 }, '{ subtree: true } on an element with many descendants returns animations'
445    + ' from all the descendants');
446
447 </script>