Document-getAnimations.tentative.html (15325B)
1 <!doctype html> 2 <meta charset=utf-8> 3 <title>Document.getAnimations() for CSS animations</title> 4 <link rel="help" href="https://drafts.csswg.org/css-animations-2/#animation-composite-order"> 5 <script src="/resources/testharness.js"></script> 6 <script src="/resources/testharnessreport.js"></script> 7 <script src="support/testcommon.js"></script> 8 <style> 9 @keyframes animLeft { 10 to { left: 100px } 11 } 12 @keyframes animTop { 13 to { top: 100px } 14 } 15 @keyframes animBottom { 16 to { bottom: 100px } 17 } 18 @keyframes animRight { 19 to { right: 100px } 20 } 21 22 @keyframes anim1 { 23 to { left: 100px } 24 } 25 @keyframes anim2 { 26 to { top: 100px } 27 } 28 @keyframes anim3 { 29 to { bottom: 100px } 30 } 31 @keyframes anim4 { 32 to { right: 100px } 33 } 34 35 </style> 36 <div id="log"></div> 37 <script> 38 'use strict'; 39 40 test(t => { 41 assert_equals(document.getAnimations().length, 0, 42 'getAnimations returns an empty sequence for a document' 43 + ' with no animations'); 44 }, 'getAnimations for non-animated content'); 45 46 test(t => { 47 const div = addDiv(t); 48 49 // Add an animation 50 div.style.animation = 'animLeft 100s'; 51 assert_equals(document.getAnimations().length, 1, 52 'getAnimations returns a running CSS Animation'); 53 54 // Add another animation 55 div.style.animation = 'animLeft 100s, animTop 100s'; 56 assert_equals(document.getAnimations().length, 2, 57 'getAnimations returns two running CSS Animations'); 58 59 // Remove both 60 div.style.animation = ''; 61 assert_equals(document.getAnimations().length, 0, 62 'getAnimations returns no running CSS Animations'); 63 }, 'getAnimations for CSS Animations'); 64 65 test(t => { 66 const div = addDiv(t); 67 const animation1 = 'animLeft 100s' 68 const animation2 = 'animBottom 100s' 69 div.style.animation = animation1; 70 const animations1 = document.getAnimations(); 71 assert_equals(animations1.length, 1, 72 'getAnimations returns all running CSS Animations'); 73 div.style.animation = animation2 + ', ' + animation1; 74 const animations = document.getAnimations(); 75 assert_equals(animations.length, 2, 76 'getAnimations returns all running CSS Animations'); 77 assert_equals(animations[0].animationName, 'animBottom', 78 'Order of first animation returned'); 79 assert_equals(animations[1].animationName, 'animLeft', 80 'Order of second animation returned'); 81 }, 'Order of CSS Animations - within an element unaffected by start time'); 82 83 test(t => { 84 const div = addDiv(t); 85 div.style.animation = 'animLeft 100s, animTop 100s, animRight 100s, ' + 86 'animBottom 100s'; 87 88 const animations = document.getAnimations(); 89 assert_equals(animations.length, 4, 90 'getAnimations returns all running CSS Animations'); 91 assert_equals(animations[0].animationName, 'animLeft', 92 'Order of first animation returned'); 93 assert_equals(animations[1].animationName, 'animTop', 94 'Order of second animation returned'); 95 assert_equals(animations[2].animationName, 'animRight', 96 'Order of third animation returned'); 97 assert_equals(animations[3].animationName, 'animBottom', 98 'Order of fourth animation returned'); 99 }, 'Order of CSS Animations - within an element'); 100 101 test(t => { 102 const div1 = addDiv(t, { style: 'animation: animLeft 100s' }); 103 const div2 = addDiv(t, { style: 'animation: animLeft 100s' }); 104 const div3 = addDiv(t, { style: 'animation: animLeft 100s' }); 105 const div4 = addDiv(t, { style: 'animation: animLeft 100s' }); 106 107 let animations = document.getAnimations(); 108 assert_equals(animations.length, 4, 109 'getAnimations returns all running CSS Animations'); 110 assert_equals(animations[0].effect.target, div1, 111 'Order of first animation returned'); 112 assert_equals(animations[1].effect.target, div2, 113 'Order of second animation returned'); 114 assert_equals(animations[2].effect.target, div3, 115 'Order of third animation returned'); 116 assert_equals(animations[3].effect.target, div4, 117 'Order of fourth animation returned'); 118 119 // Order should be depth-first pre-order so add some depth as follows: 120 // 121 // <parent> 122 // / | 123 // 2 3 124 // / \ 125 // 1 4 126 // 127 // Which should give: 2, 1, 4, 3 128 div2.appendChild(div1); 129 div2.appendChild(div4); 130 animations = document.getAnimations(); 131 assert_equals(animations[0].effect.target, div2, 132 'Order of first animation returned after tree surgery'); 133 assert_equals(animations[1].effect.target, div1, 134 'Order of second animation returned after tree surgery'); 135 assert_equals(animations[2].effect.target, div4, 136 'Order of third animation returned after tree surgery'); 137 assert_equals(animations[3].effect.target, div3, 138 'Order of fourth animation returned after tree surgery'); 139 140 }, 'Order of CSS Animations - across elements'); 141 142 test(t => { 143 const div1 = addDiv(t, { style: 'animation: animLeft 100s, animTop 100s' }); 144 const div2 = addDiv(t, { style: 'animation: animBottom 100s' }); 145 146 let expectedResults = [ [ div1, 'animLeft' ], 147 [ div1, 'animTop' ], 148 [ div2, 'animBottom' ] ]; 149 let animations = document.getAnimations(); 150 assert_equals(animations.length, expectedResults.length, 151 'getAnimations returns all running CSS Animations'); 152 animations.forEach((anim, i) => { 153 assert_equals(anim.effect.target, expectedResults[i][0], 154 'Target of animation in position ' + i); 155 assert_equals(anim.animationName, expectedResults[i][1], 156 'Name of animation in position ' + i); 157 }); 158 159 // Modify tree structure and animation list 160 div2.appendChild(div1); 161 div1.style.animation = 'animLeft 100s, animRight 100s, animTop 100s'; 162 163 expectedResults = [ [ div2, 'animBottom' ], 164 [ div1, 'animLeft' ], 165 [ div1, 'animRight' ], 166 [ div1, 'animTop' ] ]; 167 animations = document.getAnimations(); 168 assert_equals(animations.length, expectedResults.length, 169 'getAnimations returns all running CSS Animations after ' + 170 'making changes'); 171 animations.forEach((anim, i) => { 172 assert_equals(anim.effect.target, expectedResults[i][0], 173 'Target of animation in position ' + i + ' after changes'); 174 assert_equals(anim.animationName, expectedResults[i][1], 175 'Name of animation in position ' + i + ' after changes'); 176 }); 177 }, 'Order of CSS Animations - across and within elements'); 178 179 test(t => { 180 const div = addDiv(t, { style: 'animation: animLeft 100s, animTop 100s' }); 181 const animLeft = document.getAnimations()[0]; 182 assert_equals(animLeft.animationName, 'animLeft', 183 'Originally, animLeft animation comes first'); 184 185 // Disassociate animLeft from markup and restart 186 div.style.animation = 'animTop 100s'; 187 animLeft.play(); 188 189 const animations = document.getAnimations(); 190 assert_equals(animations.length, 2, 191 'getAnimations returns markup-bound and free animations'); 192 assert_equals(animations[0].animationName, 'animTop', 193 'Markup-bound animations come first'); 194 assert_equals(animations[1], animLeft, 'Free animations come last'); 195 }, 'Order of CSS Animations - markup-bound vs free animations'); 196 197 test(t => { 198 // Add an animation first 199 const div = addDiv(t, { style: 'animation: animLeft 100s' }); 200 const animLeft = document.getAnimations()[0]; 201 // Disassociate animLeft from markup and restart 202 div.style.animation = ''; 203 animLeft.play(); 204 205 div.style.top = '0px'; 206 div.style.transition = 'all 100s'; 207 flushComputedStyle(div); 208 // *Then* add a transition 209 div.style.top = '100px'; 210 flushComputedStyle(div); 211 212 // Although the transition was added later, it should come first in the list 213 const animations = document.getAnimations(); 214 assert_equals(animations.length, 2, 215 'Both CSS animations and transitions are returned'); 216 assert_class_string(animations[0], 'CSSTransition', 'Transition comes first'); 217 assert_equals(animations[1], animLeft, 'Free animations come last'); 218 }, 'Order of CSS Animations - free animation vs CSS Transitions'); 219 220 test(t => { 221 const div = addDiv(t, { style: 'animation: animLeft 100s, animTop 100s' }); 222 const animLeft = document.getAnimations()[0]; 223 const animTop = document.getAnimations()[1]; 224 225 // Disassociate both animations from markup and restart in opposite order 226 div.style.animation = ''; 227 animTop.play(); 228 animLeft.play(); 229 230 const animations = document.getAnimations(); 231 assert_equals(animations.length, 2, 232 'getAnimations returns free animations'); 233 assert_equals(animations[0], animTop, 234 'Free animations are returned in the order they are started'); 235 assert_equals(animations[1], animLeft, 236 'Animations started later are returned later'); 237 238 // Restarting an animation should have no effect 239 animTop.cancel(); 240 animTop.play(); 241 assert_equals(document.getAnimations()[0], animTop, 242 'After restarting, the ordering of free animations' + 243 ' does not change'); 244 }, 'Order of CSS Animations - free animations'); 245 246 test(t => { 247 // Add an animation first 248 const div = addDiv(t, { style: 'animation: animLeft 100s' }); 249 div.style.top = '0px'; 250 div.style.transition = 'all 100s'; 251 flushComputedStyle(div); 252 253 // *Then* add a transition 254 div.style.top = '100px'; 255 flushComputedStyle(div); 256 257 // Although the transition was added later, it should come first in the list 258 const animations = document.getAnimations(); 259 assert_equals(animations.length, 2, 260 'Both CSS animations and transitions are returned'); 261 assert_class_string(animations[0], 'CSSTransition', 'Transition comes first'); 262 assert_class_string(animations[1], 'CSSAnimation', 'Animation comes second'); 263 }, 'Order of CSS Animations and CSS Transitions'); 264 265 test(t => { 266 const div = addDiv(t, { style: 'animation: animLeft 100s forwards' }); 267 div.getAnimations()[0].finish(); 268 assert_equals(document.getAnimations().length, 1, 269 'Forwards-filling CSS animations are returned'); 270 }, 'Finished but filling CSS Animations are returned'); 271 272 test(t => { 273 const div = addDiv(t, { style: 'animation: animLeft 100s' }); 274 div.getAnimations()[0].finish(); 275 assert_equals(document.getAnimations().length, 0, 276 'Non-filling finished CSS animations are not returned'); 277 }, 'Finished but not filling CSS Animations are not returned'); 278 279 test(t => { 280 const div = addDiv(t, { style: 'animation: animLeft 100s 100s' }); 281 assert_equals(document.getAnimations().length, 1, 282 'Yet-to-start CSS animations are returned'); 283 }, 'Yet-to-start CSS Animations are returned'); 284 285 test(t => { 286 const div = addDiv(t, { style: 'animation: animLeft 100s' }); 287 div.getAnimations()[0].cancel(); 288 assert_equals(document.getAnimations().length, 0, 289 'CSS animations canceled by the API are not returned'); 290 }, 'CSS Animations canceled via the API are not returned'); 291 292 test(t => { 293 const div = addDiv(t, { style: 'animation: animLeft 100s' }); 294 const anim = div.getAnimations()[0]; 295 anim.cancel(); 296 anim.play(); 297 assert_equals(document.getAnimations().length, 1, 298 'CSS animations canceled and restarted by the API are ' + 299 'returned'); 300 }, 'CSS Animations canceled and restarted via the API are returned'); 301 302 test(t => { 303 addStyle(t, { 304 '#parent::after': "content: ''; animation: anim1 100s ; ", 305 '#parent::before': "content: ''; animation: anim2 100s;", 306 '#child::after': "content: ''; animation: anim3 100s;", 307 '#child::before': "content: ''; animation: anim4 100s;", 308 }); 309 const parent = addDiv(t, { id: 'parent' }); 310 const child = addDiv(t, { id: 'child'}); 311 parent.appendChild(child); 312 var animations = document.getAnimations(); 313 var expectedAnimations = [ 314 [parent, '::before', 'anim2'], 315 [parent, '::after', 'anim1'], 316 [child, '::before', 'anim4'], 317 [child, '::after', 'anim3'], 318 ]; 319 pseudoAnimCompare(animations, expectedAnimations) 320 321 // Swap animation1 and aimation3's effect 322 var anim1 = animations[0]; 323 var anim3 = animations[2]; 324 const temp = anim1.effect; 325 anim1.effect = anim3.effect; 326 anim3.effect = temp; 327 328 animations = document.getAnimations(); 329 expectedAnimations = [ 330 [child, '::before', 'anim2'], 331 [parent, '::after', 'anim1'], 332 [parent, '::before', 'anim4'], 333 [child, '::after', 'anim3'], 334 ]; 335 pseudoAnimCompare(animations, expectedAnimations) 336 }, 'pseudo element with replaced target does not affect animation ordering'); 337 338 function pseudoAnimCompare(animations, expectedAnimations) { 339 assert_equals( 340 animations.length, 341 expectedAnimations.length, 342 'CSS animations on both pseudo-elements and elements are returned' 343 ); 344 for (const [index, expected] of expectedAnimations.entries()) { 345 const [element, pseudo, name] = expected; 346 const actual = animations[index]; 347 if (pseudo) { 348 assert_equals( 349 actual.effect.target, 350 element, 351 `Animation #${index + 1} has the expected target` 352 ); 353 assert_equals( 354 actual.effect.pseudoElement, 355 pseudo, 356 `Animation #${index + 1} has the expected pseudo type` 357 ); 358 if (name) { 359 assert_equals( 360 actual.animationName, 361 name, 362 `Animation #${index + 1} has the expected name` 363 ); 364 } 365 } else { 366 assert_equals( 367 actual.effect.target, 368 element, 369 `Animation #${index + 1} has the expected target` 370 ); 371 assert_equals( 372 actual.effect.pseudoElement, 373 null, 374 `Animation #${index + 1} has a null pseudo type` 375 ); 376 } 377 } 378 } 379 380 function pseudoTest(description, testMarkerPseudos) { 381 test(t => { 382 // Create two divs with the following arrangement: 383 // 384 // parent 385 // (::marker,) // Optionally 386 // ::before, 387 // ::after 388 // | 389 // child 390 391 addStyle(t, { 392 '#parent::after': "content: ''; animation: animLeft 100s;", 393 '#parent::before': "content: ''; animation: animRight 100s;", 394 }); 395 396 if (testMarkerPseudos) { 397 addStyle(t, { 398 '#parent': 'display: list-item;', 399 '#parent::marker': "content: ''; animation: animLeft 100s;", 400 }); 401 } 402 403 const parent = addDiv(t, { id: 'parent' }); 404 const child = addDiv(t); 405 parent.appendChild(child); 406 for (const div of [parent, child]) { 407 div.setAttribute('style', 'animation: animBottom 100s'); 408 } 409 410 const expectedAnimations = [ 411 [parent, undefined], 412 [parent, '::marker'], 413 [parent, '::before'], 414 [parent, '::after'], 415 [child, undefined], 416 ]; 417 if (!testMarkerPseudos) { 418 expectedAnimations.splice(1, 1); 419 } 420 421 const animations = document.getAnimations(); 422 pseudoAnimCompare(animations, expectedAnimations) 423 }, description); 424 } 425 426 pseudoTest('CSS Animations targetting (pseudo-)elements should have correct ' 427 + 'order after sorting', false); 428 pseudoTest('CSS Animations targetting (pseudo-)elements should have correct ' 429 + 'order after sorting (::marker)', true); 430 </script>