Element-getAnimations.tentative.html (17378B)
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 const animations = target.getAnimations({ subtree: false }); 307 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 const animations = target.getAnimations({ subtree: true }); 321 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[0].effect.pseudoElement, null, 329 'The animation targeting the parent element ' + 330 'should be returned first') 331 assert_equals(animations[1].effect.pseudoElement, '::before', 332 'The animation targeting the ::before pseudo-element ' + 333 'should be returned second'); 334 assert_equals(animations[2].effect.pseudoElement, '::after', 335 'The animation targeting the ::after pesudo-element ' + 336 'should be returned last'); 337 }, '{ subtree: true } on a leaf element returns the element\'s animations' 338 + ' and its pseudo-elements\' animations'); 339 340 test(t => { 341 addStyle(t, { '#parent::after': 'animation: anim1 10s;', 342 '#parent::before': 'animation: anim1 10s;', 343 '#child::after': 'animation: anim1 10s;', 344 '#child::before': 'animation: anim1 10s;' }); 345 const parent = addDiv(t, { 'id': 'parent' }); 346 parent.style.animation = 'anim1 100s'; 347 const child = addDiv(t, { 'id': 'child' }); 348 child.style.animation = 'anim1 100s'; 349 parent.appendChild(child); 350 351 const animations = parent.getAnimations({ subtree: false }); 352 assert_equals(animations.length, 1, 353 'Should find only the element even if it has a child'); 354 assert_equals(animations[0].effect.target, parent, 355 'Effect target should be the element'); 356 }, '{ subtree: false } on an element with a child returns only the element\'s' 357 + ' animations'); 358 359 test(t => { 360 addStyle(t, { '#parent::after': 'animation: anim1 10s;', 361 '#parent::before': 'animation: anim1 10s;', 362 '#child::after': 'animation: anim1 10s;', 363 '#child::before': 'animation: anim1 10s;' }); 364 const parent = addDiv(t, { 'id': 'parent' }); 365 const child = addDiv(t, { 'id': 'child' }); 366 parent.style.animation = 'anim1 100s'; 367 child.style.animation = 'anim1 100s'; 368 parent.appendChild(child); 369 370 const animations = parent.getAnimations({ subtree: true }); 371 assert_equals(animations.length, 6, 372 'Should find all elements, pseudo-elements that parent has'); 373 374 assert_equals(animations[0].effect.target, parent, 375 'The animation targeting the parent element ' + 376 'should be returned first'); 377 assert_equals(animations[0].effect.pseudoElement, null, 378 'The animation targeting the parent element ' + 379 'should be returned first'); 380 assert_equals(animations[1].effect.pseudoElement, '::before', 381 'The animation targeting the ::before pseudo-element ' + 382 'should be returned second'); 383 assert_equals(animations[1].effect.target, parent, 384 'This ::before element should be child of parent element'); 385 assert_equals(animations[2].effect.pseudoElement, '::after', 386 'The animation targeting the ::after pesudo-element ' + 387 'should be returned third'); 388 assert_equals(animations[2].effect.target, parent, 389 'This ::after element should be child of parent element'); 390 391 assert_equals(animations[3].effect.target, child, 392 'The animation targeting the child element ' + 393 'should be returned fourth'); 394 assert_equals(animations[4].effect.pseudoElement, '::before', 395 'The animation targeting the ::before pseudo-element ' + 396 'should be returned fifth'); 397 assert_equals(animations[4].effect.target, child, 398 'This ::before element should be child of child element'); 399 assert_equals(animations[5].effect.pseudoElement, '::after', 400 'The animation targeting the ::after pesudo-element ' + 401 'should be returned last'); 402 assert_equals(animations[5].effect.target, child, 403 'This ::after element should be child of child element'); 404 }, '{ subtree: true } on an element with a child returns animations from the' 405 + ' element, its pseudo-elements, its child and its child pseudo-elements'); 406 407 test(t => { 408 const parent = addDiv(t, { 'id': 'parent' }); 409 const child1 = addDiv(t, { 'id': 'child1' }); 410 const grandchild1 = addDiv(t, { 'id': 'grandchild1' }); 411 const grandchild2 = addDiv(t, { 'id': 'grandchild2' }); 412 const child2 = addDiv(t, { 'id': 'child2' }); 413 414 parent.style.animation = 'anim1 100s'; 415 child1.style.animation = 'anim1 100s'; 416 grandchild1.style.animation = 'anim1 100s'; 417 grandchild2.style.animation = 'anim1 100s'; 418 child2.style.animation = 'anim1 100s'; 419 420 parent.appendChild(child1); 421 child1.appendChild(grandchild1); 422 child1.appendChild(grandchild2); 423 parent.appendChild(child2); 424 425 const animations = parent.getAnimations({ subtree: true }); 426 assert_equals( 427 parent.getAnimations({ subtree: true }).length, 5, 428 'Should find all descendants of the element'); 429 430 assert_equals(animations[0].effect.target, parent, 431 'The animation targeting the parent element ' + 432 'should be returned first'); 433 434 assert_equals(animations[1].effect.target, child1, 435 'The animation targeting the child1 element ' + 436 'should be returned second'); 437 438 assert_equals(animations[2].effect.target, grandchild1, 439 'The animation targeting the grandchild1 element ' + 440 'should be returned third'); 441 442 assert_equals(animations[3].effect.target, grandchild2, 443 'The animation targeting the grandchild2 element ' + 444 'should be returned fourth'); 445 446 assert_equals(animations[4].effect.target, child2, 447 'The animation targeting the child2 element ' + 448 'should be returned last'); 449 450 }, '{ subtree: true } on an element with many descendants returns animations' 451 + ' from all the descendants'); 452 453 </script>