test_animation_observers_sync.html (60674B)
1 <!DOCTYPE html> 2 <meta charset=utf-8> 3 <title> 4 Test chrome-only MutationObserver animation notifications (sync tests) 5 </title> 6 <!-- 7 8 This file contains synchronous tests for animation mutation observers. 9 10 In general we prefer to write synchronous tests since they are less likely to 11 timeout when run on automation. Tests that require asynchronous steps (e.g. 12 waiting on events) should be added to test_animations_observers_async.html 13 instead. 14 15 --> 16 <script type="application/javascript" src="../testharness.js"></script> 17 <script type="application/javascript" src="../testharnessreport.js"></script> 18 <script type="application/javascript" src="../testcommon.js"></script> 19 <div id="log"></div> 20 <style> 21 @keyframes anim { 22 to { transform: translate(100px); } 23 } 24 @keyframes anotherAnim { 25 to { transform: translate(0px); } 26 } 27 </style> 28 <script> 29 30 /** 31 * Return a new MutationObserver which observing |target| element 32 * with { animations: true, subtree: |subtree| } option. 33 * 34 * NOTE: This observer should be used only with takeRecords(). If any of 35 * MutationRecords are observed in the callback of the MutationObserver, 36 * it will raise an assertion. 37 */ 38 function setupSynchronousObserver(t, target, subtree) { 39 var observer = new MutationObserver(records => { 40 assert_unreached("Any MutationRecords should not be observed in this " + 41 "callback"); 42 }); 43 t.add_cleanup(() => { 44 observer.disconnect(); 45 }); 46 observer.observe(target, { animations: true, subtree }); 47 return observer; 48 } 49 50 function assert_record_list(actual, expected, desc, index, listName) { 51 assert_equals(actual.length, expected.length, 52 `${desc} - record[${index}].${listName} length`); 53 if (actual.length != expected.length) { 54 return; 55 } 56 for (var i = 0; i < actual.length; i++) { 57 assert_not_equals(actual.indexOf(expected[i]), -1, 58 `${desc} - record[${index}].${listName} contains expected Animation`); 59 } 60 } 61 62 function assert_equals_records(actual, expected, desc) { 63 assert_equals(actual.length, expected.length, `${desc} - number of records`); 64 if (actual.length != expected.length) { 65 return; 66 } 67 for (var i = 0; i < actual.length; i++) { 68 assert_record_list(actual[i].addedAnimations, 69 expected[i].added, desc, i, "addedAnimations"); 70 assert_record_list(actual[i].changedAnimations, 71 expected[i].changed, desc, i, "changedAnimations"); 72 assert_record_list(actual[i].removedAnimations, 73 expected[i].removed, desc, i, "removedAnimations"); 74 } 75 } 76 77 function runTest() { 78 [ { subtree: false }, 79 { subtree: true } 80 ].forEach(aOptions => { 81 test(t => { 82 var div = addDiv(t); 83 var observer = 84 setupSynchronousObserver(t, 85 aOptions.subtree ? div.parentNode : div, 86 aOptions.subtree); 87 88 var anim = div.animate({ opacity: [ 0, 1 ] }, 200 * MS_PER_SEC); 89 90 assert_equals_records(observer.takeRecords(), 91 [{ added: [anim], changed: [], removed: [] }], 92 "records after animation is added"); 93 94 anim.effect.updateTiming({ duration: 100 * MS_PER_SEC }); 95 assert_equals_records(observer.takeRecords(), 96 [{ added: [], changed: [anim], removed: [] }], 97 "records after duration is changed"); 98 99 anim.effect.updateTiming({ duration: 100 * MS_PER_SEC }); 100 assert_equals_records(observer.takeRecords(), 101 [], "records after assigning same value"); 102 103 anim.currentTime = anim.effect.getComputedTiming().duration * 2; 104 anim.finish(); 105 assert_equals_records(observer.takeRecords(), 106 [{ added: [], changed: [], removed: [anim] }], 107 "records after animation end"); 108 109 anim.effect.updateTiming({ 110 duration: anim.effect.getComputedTiming().duration * 3 111 }); 112 assert_equals_records(observer.takeRecords(), 113 [{ added: [anim], changed: [], removed: [] }], 114 "records after animation restarted"); 115 116 anim.effect.updateTiming({ duration: 'auto' }); 117 assert_equals_records(observer.takeRecords(), 118 [{ added: [], changed: [], removed: [anim] }], 119 "records after duration set \"auto\""); 120 121 anim.effect.updateTiming({ duration: 'auto' }); 122 assert_equals_records(observer.takeRecords(), 123 [], "records after assigning same value \"auto\""); 124 }, "change_duration_and_currenttime"); 125 126 test(t => { 127 var div = addDiv(t); 128 var observer = 129 setupSynchronousObserver(t, 130 aOptions.subtree ? div.parentNode : div, 131 aOptions.subtree); 132 133 var anim = div.animate({ opacity: [ 0, 1 ] }, 100 * MS_PER_SEC); 134 135 assert_equals_records(observer.takeRecords(), 136 [{ added: [anim], changed: [], removed: [] }], 137 "records after animation is added"); 138 139 anim.effect.updateTiming({ endDelay: 10 * MS_PER_SEC }); 140 assert_equals_records(observer.takeRecords(), 141 [{ added: [], changed: [anim], removed: [] }], 142 "records after endDelay is changed"); 143 144 anim.effect.updateTiming({ endDelay: 10 * MS_PER_SEC }); 145 assert_equals_records(observer.takeRecords(), 146 [], "records after assigning same value"); 147 148 anim.currentTime = 109 * MS_PER_SEC; 149 assert_equals_records(observer.takeRecords(), 150 [{ added: [], changed: [], removed: [anim] }], 151 "records after currentTime during endDelay"); 152 153 anim.effect.updateTiming({ endDelay: -110 * MS_PER_SEC }); 154 assert_equals_records(observer.takeRecords(), 155 [], "records after assigning negative value"); 156 }, "change_enddelay_and_currenttime"); 157 158 test(t => { 159 var div = addDiv(t); 160 var observer = 161 setupSynchronousObserver(t, 162 aOptions.subtree ? div.parentNode : div, 163 aOptions.subtree); 164 165 var anim = div.animate({ opacity: [ 0, 1 ] }, 166 { duration: 100 * MS_PER_SEC, 167 endDelay: -100 * MS_PER_SEC }); 168 assert_equals_records(observer.takeRecords(), 169 [], "records after animation is added"); 170 }, "zero_end_time"); 171 172 test(t => { 173 var div = addDiv(t); 174 var observer = 175 setupSynchronousObserver(t, 176 aOptions.subtree ? div.parentNode : div, 177 aOptions.subtree); 178 179 var anim = div.animate({ opacity: [ 0, 1 ] }, 100 * MS_PER_SEC); 180 181 assert_equals_records(observer.takeRecords(), 182 [{ added: [anim], changed: [], removed: [] }], 183 "records after animation is added"); 184 185 anim.effect.updateTiming({ iterations: 2 }); 186 assert_equals_records(observer.takeRecords(), 187 [{ added: [], changed: [anim], removed: [] }], 188 "records after iterations is changed"); 189 190 anim.effect.updateTiming({ iterations: 2 }); 191 assert_equals_records(observer.takeRecords(), 192 [], "records after assigning same value"); 193 194 anim.effect.updateTiming({ iterations: 0 }); 195 assert_equals_records(observer.takeRecords(), 196 [{ added: [], changed: [], removed: [anim] }], 197 "records after animation end"); 198 199 anim.effect.updateTiming({ iterations: Infinity }); 200 assert_equals_records(observer.takeRecords(), 201 [{ added: [anim], changed: [], removed: [] }], 202 "records after animation restarted"); 203 }, "change_iterations"); 204 205 test(t => { 206 var div = addDiv(t); 207 var observer = 208 setupSynchronousObserver(t, 209 aOptions.subtree ? div.parentNode : div, 210 aOptions.subtree); 211 212 var anim = div.animate({ opacity: [ 0, 1 ] }, 100 * MS_PER_SEC); 213 214 assert_equals_records(observer.takeRecords(), 215 [{ added: [anim], changed: [], removed: [] }], 216 "records after animation is added"); 217 218 anim.effect.updateTiming({ delay: 100 }); 219 assert_equals_records(observer.takeRecords(), 220 [{ added: [], changed: [anim], removed: [] }], 221 "records after delay is changed"); 222 223 anim.effect.updateTiming({ delay: 100 }); 224 assert_equals_records(observer.takeRecords(), 225 [], "records after assigning same value"); 226 227 anim.effect.updateTiming({ delay: -100 * MS_PER_SEC }); 228 assert_equals_records(observer.takeRecords(), 229 [{ added: [], changed: [], removed: [anim] }], 230 "records after animation end"); 231 232 anim.effect.updateTiming({ delay: 0 }); 233 assert_equals_records(observer.takeRecords(), 234 [{ added: [anim], changed: [], removed: [] }], 235 "records after animation restarted"); 236 }, "change_delay"); 237 238 test(t => { 239 var div = addDiv(t); 240 var observer = 241 setupSynchronousObserver(t, 242 aOptions.subtree ? div.parentNode : div, 243 aOptions.subtree); 244 245 var anim = div.animate({ opacity: [ 0, 1 ] }, 246 { duration: 100 * MS_PER_SEC, 247 easing: "steps(2, start)" }); 248 249 assert_equals_records(observer.takeRecords(), 250 [{ added: [anim], changed: [], removed: [] }], 251 "records after animation is added"); 252 253 anim.effect.updateTiming({ easing: "steps(2, end)" }); 254 assert_equals_records(observer.takeRecords(), 255 [{ added: [], changed: [anim], removed: [] }], 256 "records after easing is changed"); 257 258 anim.effect.updateTiming({ easing: "steps(2, end)" }); 259 assert_equals_records(observer.takeRecords(), 260 [], "records after assigning same value"); 261 }, "change_easing"); 262 263 test(t => { 264 var div = addDiv(t); 265 var observer = 266 setupSynchronousObserver(t, 267 aOptions.subtree ? div.parentNode : div, 268 aOptions.subtree); 269 270 var anim = div.animate({ opacity: [ 0, 1 ] }, 271 { duration: 100, delay: -100 }); 272 assert_equals_records(observer.takeRecords(), 273 [], "records after assigning negative value"); 274 }, "negative_delay_in_constructor"); 275 276 test(t => { 277 var div = addDiv(t); 278 var observer = 279 setupSynchronousObserver(t, 280 aOptions.subtree ? div.parentNode : div, 281 aOptions.subtree); 282 283 var effect = new KeyframeEffect(null, 284 { opacity: [ 0, 1 ] }, 285 { duration: 100 * MS_PER_SEC }); 286 var anim = new Animation(effect, document.timeline); 287 anim.play(); 288 assert_equals_records(observer.takeRecords(), 289 [], "no records after animation is added"); 290 }, "create_animation_without_target"); 291 292 test(t => { 293 var div = addDiv(t); 294 var observer = 295 setupSynchronousObserver(t, 296 aOptions.subtree ? div.parentNode : div, 297 aOptions.subtree); 298 299 var anim = div.animate({ opacity: [ 0, 1 ] }, 300 { duration: 100 * MS_PER_SEC }); 301 assert_equals_records(observer.takeRecords(), 302 [{ added: [anim], changed: [], removed: [] }], 303 "records after animation is added"); 304 305 anim.effect.target = div; 306 assert_equals_records(observer.takeRecords(), 307 [], "no records after setting the same target"); 308 309 anim.effect.target = null; 310 assert_equals_records(observer.takeRecords(), 311 [{ added: [], changed: [], removed: [anim] }], 312 "records after setting null"); 313 314 anim.effect.target = null; 315 assert_equals_records(observer.takeRecords(), 316 [], "records after setting redundant null"); 317 }, "set_redundant_animation_target"); 318 319 test(t => { 320 var div = addDiv(t); 321 var observer = 322 setupSynchronousObserver(t, 323 aOptions.subtree ? div.parentNode : div, 324 aOptions.subtree); 325 326 var anim = div.animate({ opacity: [ 0, 1 ] }, 327 { duration: 100 * MS_PER_SEC }); 328 assert_equals_records(observer.takeRecords(), 329 [{ added: [anim], changed: [], removed: [] }], 330 "records after animation is added"); 331 332 anim.effect = null; 333 assert_equals_records(observer.takeRecords(), 334 [{ added: [], changed: [], removed: [anim] }], 335 "records after animation is removed"); 336 }, "set_null_animation_effect"); 337 338 test(t => { 339 var div = addDiv(t); 340 var observer = 341 setupSynchronousObserver(t, 342 aOptions.subtree ? div.parentNode : div, 343 aOptions.subtree); 344 345 var anim = new Animation(); 346 anim.play(); 347 anim.effect = new KeyframeEffect(div, { opacity: [ 0, 1 ] }, 348 100 * MS_PER_SEC); 349 assert_equals_records(observer.takeRecords(), 350 [{ added: [anim], changed: [], removed: [] }], 351 "records after animation is added"); 352 }, "set_effect_on_null_effect_animation"); 353 354 test(t => { 355 var div = addDiv(t); 356 var observer = 357 setupSynchronousObserver(t, 358 aOptions.subtree ? div.parentNode : div, 359 aOptions.subtree); 360 361 var anim = div.animate({ marginLeft: [ "0px", "100px" ] }, 362 100 * MS_PER_SEC); 363 assert_equals_records(observer.takeRecords(), 364 [{ added: [anim], changed: [], removed: [] }], 365 "records after animation is added"); 366 367 anim.effect = new KeyframeEffect(div, { opacity: [ 0, 1 ] }, 368 100 * MS_PER_SEC); 369 assert_equals_records(observer.takeRecords(), 370 [{ added: [], changed: [anim], removed: [] }], 371 "records after replace effects"); 372 }, "replace_effect_targeting_on_the_same_element"); 373 374 test(t => { 375 var div = addDiv(t); 376 var observer = 377 setupSynchronousObserver(t, 378 aOptions.subtree ? div.parentNode : div, 379 aOptions.subtree); 380 381 var anim = div.animate({ marginLeft: [ "0px", "100px" ] }, 382 100 * MS_PER_SEC); 383 assert_equals_records(observer.takeRecords(), 384 [{ added: [anim], changed: [], removed: [] }], 385 "records after animation is added"); 386 387 anim.currentTime = 60 * MS_PER_SEC; 388 assert_equals_records(observer.takeRecords(), 389 [{ added: [], changed: [anim], removed: [] }], 390 "records after animation is changed"); 391 392 anim.effect = new KeyframeEffect(div, { opacity: [ 0, 1 ] }, 393 50 * MS_PER_SEC); 394 assert_equals_records(observer.takeRecords(), 395 [{ added: [], changed: [], removed: [anim] }], 396 "records after replacing effects"); 397 }, "replace_effect_targeting_on_the_same_element_not_in_effect"); 398 399 test(t => { 400 var div = addDiv(t); 401 var observer = 402 setupSynchronousObserver(t, 403 aOptions.subtree ? div.parentNode : div, 404 aOptions.subtree); 405 406 var anim = div.animate({ }, 100 * MS_PER_SEC); 407 assert_equals_records(observer.takeRecords(), 408 [{ added: [anim], changed: [], removed: [] }], 409 "records after animation is added"); 410 411 anim.effect.composite = "add"; 412 assert_equals_records(observer.takeRecords(), 413 [{ added: [], changed: [anim], removed: [] }], 414 "records after composite is changed"); 415 416 anim.effect.composite = "add"; 417 assert_equals_records(observer.takeRecords(), 418 [], "no record after setting the same composite"); 419 420 }, "set_composite"); 421 422 // Test that starting a single animation that is cancelled by calling 423 // cancel() dispatches an added notification and then a removed 424 // notification. 425 test(t => { 426 var div = addDiv(t, { style: "animation: anim 100s forwards" }); 427 var observer = 428 setupSynchronousObserver(t, 429 aOptions.subtree ? div.parentNode : div, 430 aOptions.subtree); 431 432 var animations = div.getAnimations(); 433 assert_equals(animations.length, 1, 434 "getAnimations().length after animation start"); 435 436 assert_equals_records(observer.takeRecords(), 437 [{ added: animations, changed: [], removed: [] }], 438 "records after animation start"); 439 440 animations[0].cancel(); 441 442 assert_equals_records(observer.takeRecords(), 443 [{ added: [], changed: [], removed: animations }], 444 "records after animation end"); 445 446 // Re-trigger the animation. 447 animations[0].play(); 448 449 // Single MutationRecord for the Animation (re-)addition. 450 assert_equals_records(observer.takeRecords(), 451 [{ added: animations, changed: [], removed: [] }], 452 "records after animation start"); 453 }, "single_animation_cancelled_api"); 454 455 // Test that updating a property on the Animation object dispatches a changed 456 // notification. 457 [ 458 { prop: "playbackRate", val: 0.5 }, 459 { prop: "startTime", val: 50 * MS_PER_SEC }, 460 { prop: "currentTime", val: 50 * MS_PER_SEC }, 461 ].forEach(aChangeTest => { 462 test(t => { 463 // We use a forwards fill mode so that even if the change we make causes 464 // the animation to become finished, it will still be "relevant" so we 465 // won't mark it as removed. 466 var div = addDiv(t, { style: "animation: anim 100s forwards" }); 467 var observer = 468 setupSynchronousObserver(t, 469 aOptions.subtree ? div.parentNode : div, 470 aOptions.subtree); 471 472 var animations = div.getAnimations(); 473 assert_equals(animations.length, 1, 474 "getAnimations().length after animation start"); 475 476 assert_equals_records(observer.takeRecords(), 477 [{ added: animations, changed: [], removed: [] }], 478 "records after animation start"); 479 480 // Update the property. 481 animations[0][aChangeTest.prop] = aChangeTest.val; 482 483 // Make a redundant change. 484 // eslint-disable-next-line no-self-assign 485 animations[0][aChangeTest.prop] = animations[0][aChangeTest.prop]; 486 487 assert_equals_records(observer.takeRecords(), 488 [{ added: [], changed: animations, removed: [] }], 489 "records after animation property change"); 490 }, `single_animation_api_change_${aChangeTest.prop}`); 491 }); 492 493 // Test that making a redundant change to currentTime while an Animation 494 // is pause-pending still generates a change MutationRecord since setting 495 // the currentTime to any value in this state aborts the pending pause. 496 test(t => { 497 var div = addDiv(t, { style: "animation: anim 100s" }); 498 var observer = 499 setupSynchronousObserver(t, 500 aOptions.subtree ? div.parentNode : div, 501 aOptions.subtree); 502 503 var animations = div.getAnimations(); 504 assert_equals(animations.length, 1, 505 "getAnimations().length after animation start"); 506 507 assert_equals_records(observer.takeRecords(), 508 [{ added: animations, changed: [], removed: [] }], 509 "records after animation start"); 510 511 animations[0].pause(); 512 513 // We are now pause-pending. Even if we make a redundant change to the 514 // currentTime, we should still get a change record because setting the 515 // currentTime while pause-pending has the effect of cancelling a pause. 516 // eslint-disable-next-line no-self-assign 517 animations[0].currentTime = animations[0].currentTime; 518 519 // Two MutationRecords for the Animation changes: one for pausing, one 520 // for aborting the pause. 521 assert_equals_records(observer.takeRecords(), 522 [{ added: [], changed: animations, removed: [] }, 523 { added: [], changed: animations, removed: [] }], 524 "records after pausing then seeking"); 525 }, "change_currentTime_while_pause_pending"); 526 527 // Test that calling finish() on a forwards-filling Animation dispatches 528 // a changed notification. 529 test(t => { 530 var div = addDiv(t, { style: "animation: anim 100s forwards" }); 531 var observer = 532 setupSynchronousObserver(t, 533 aOptions.subtree ? div.parentNode : div, 534 aOptions.subtree); 535 536 var animations = div.getAnimations(); 537 assert_equals(animations.length, 1, 538 "getAnimations().length after animation start"); 539 540 assert_equals_records(observer.takeRecords(), 541 [{ added: animations, changed: [], removed: [] }], 542 "records after animation start"); 543 544 animations[0].finish(); 545 546 assert_equals_records(observer.takeRecords(), 547 [{ added: [], changed: animations, removed: [] }], 548 "records after finish()"); 549 550 // Redundant finish. 551 animations[0].finish(); 552 553 // Ensure no change records. 554 assert_equals_records(observer.takeRecords(), 555 [], "records after redundant finish()"); 556 }, "finish_with_forwards_fill"); 557 558 // Test that calling finish() on an Animation that does not fill forwards, 559 // dispatches a removal notification. 560 test(t => { 561 var div = addDiv(t, { style: "animation: anim 100s" }); 562 var observer = 563 setupSynchronousObserver(t, 564 aOptions.subtree ? div.parentNode : div, 565 aOptions.subtree); 566 567 var animations = div.getAnimations(); 568 assert_equals(animations.length, 1, 569 "getAnimations().length after animation start"); 570 571 assert_equals_records(observer.takeRecords(), 572 [{ added: animations, changed: [], removed: [] }], 573 "records after animation start"); 574 575 animations[0].finish(); 576 577 // Single MutationRecord for the Animation removal. 578 assert_equals_records(observer.takeRecords(), 579 [{ added: [], changed: [], removed: animations }], 580 "records after finishing"); 581 }, "finish_without_fill"); 582 583 // Test that calling finish() on a forwards-filling Animation dispatches 584 test(t => { 585 var div = addDiv(t, { style: "animation: anim 100s" }); 586 var observer = 587 setupSynchronousObserver(t, 588 aOptions.subtree ? div.parentNode : div, 589 aOptions.subtree); 590 591 var animation = div.getAnimations()[0]; 592 assert_equals_records(observer.takeRecords(), 593 [{ added: [animation], changed: [], removed: []}], 594 "records after creation"); 595 animation.id = "new id"; 596 assert_equals_records(observer.takeRecords(), 597 [{ added: [], changed: [animation], removed: []}], 598 "records after id is changed"); 599 600 animation.id = "new id"; 601 assert_equals_records(observer.takeRecords(), 602 [], "records after assigning same value with id"); 603 }, "change_id"); 604 605 // Test that calling reverse() dispatches a changed notification. 606 test(t => { 607 var div = addDiv(t, { style: "animation: anim 100s both" }); 608 var observer = 609 setupSynchronousObserver(t, 610 aOptions.subtree ? div.parentNode : div, 611 aOptions.subtree); 612 613 var animations = div.getAnimations(); 614 assert_equals(animations.length, 1, 615 "getAnimations().length after animation start"); 616 617 assert_equals_records(observer.takeRecords(), 618 [{ added: animations, changed: [], removed: [] }], 619 "records after animation start"); 620 621 animations[0].reverse(); 622 623 assert_equals_records(observer.takeRecords(), 624 [{ added: [], changed: animations, removed: [] }], 625 "records after calling reverse()"); 626 }, "reverse"); 627 628 // Test that calling reverse() does *not* dispatch a changed notification 629 // when playbackRate == 0. 630 test(t => { 631 var div = addDiv(t, { style: "animation: anim 100s both" }); 632 var observer = 633 setupSynchronousObserver(t, 634 aOptions.subtree ? div.parentNode : div, 635 aOptions.subtree); 636 637 var animations = div.getAnimations(); 638 assert_equals(animations.length, 1, 639 "getAnimations().length after animation start"); 640 641 assert_equals_records(observer.takeRecords(), 642 [{ added: animations, changed: [], removed: [] }], 643 "records after animation start"); 644 645 // Seek to the middle and set playbackRate to zero. 646 animations[0].currentTime = 50 * MS_PER_SEC; 647 animations[0].playbackRate = 0; 648 649 // Two MutationRecords, one for each change. 650 assert_equals_records(observer.takeRecords(), 651 [{ added: [], changed: animations, removed: [] }, 652 { added: [], changed: animations, removed: [] }], 653 "records after seeking and setting playbackRate"); 654 655 animations[0].reverse(); 656 657 // We should get no notifications. 658 assert_equals_records(observer.takeRecords(), 659 [], "records after calling reverse()"); 660 }, "reverse_with_zero_playbackRate"); 661 662 // Test that reverse() on an Animation does *not* dispatch a changed 663 // notification when it throws an exception. 664 test(t => { 665 // Start an infinite animation 666 var div = addDiv(t, { style: "animation: anim 10s infinite" }); 667 var observer = 668 setupSynchronousObserver(t, 669 aOptions.subtree ? div.parentNode : div, 670 aOptions.subtree); 671 672 var animations = div.getAnimations(); 673 assert_equals(animations.length, 1, 674 "getAnimations().length after animation start"); 675 676 assert_equals_records(observer.takeRecords(), 677 [{ added: animations, changed: [], removed: [] }], 678 "records after animation start"); 679 680 // Shift the animation into the future such that when we call reverse 681 // it will try to seek to the (infinite) end. 682 animations[0].startTime = 100 * MS_PER_SEC; 683 684 assert_equals_records(observer.takeRecords(), 685 [{ added: [], changed: animations, removed: [] }], 686 "records after adjusting startTime"); 687 688 // Reverse: should throw 689 assert_throws('InvalidStateError', () => { 690 animations[0].reverse(); 691 }, 'reverse() on future infinite animation throws an exception'); 692 693 // We should get no notifications. 694 assert_equals_records(observer.takeRecords(), 695 [], "records after calling reverse()"); 696 }, "reverse_with_exception"); 697 698 // Test that attempting to start an animation that should already be finished 699 // does not send any notifications. 700 test(t => { 701 // Start an animation that should already be finished. 702 var div = addDiv(t, { style: "animation: anim 1s -2s;" }); 703 var observer = 704 setupSynchronousObserver(t, 705 aOptions.subtree ? div.parentNode : div, 706 aOptions.subtree); 707 708 // The animation should cause no Animations to be created. 709 var animations = div.getAnimations(); 710 assert_equals(animations.length, 0, 711 "getAnimations().length after animation start"); 712 713 // And we should get no notifications. 714 assert_equals_records(observer.takeRecords(), 715 [], "records after attempted animation start"); 716 }, "already_finished"); 717 718 test(t => { 719 var div = addDiv(t, { style: "animation: anim 100s, anotherAnim 100s" }); 720 var observer = 721 setupSynchronousObserver(t, 722 aOptions.subtree ? div.parentNode : div, 723 aOptions.subtree); 724 725 var animations = div.getAnimations(); 726 727 assert_equals_records(observer.takeRecords(), 728 [{ added: animations, changed: [], removed: []}], 729 "records after creation"); 730 731 div.style.animation = "anotherAnim 100s, anim 100s"; 732 animations = div.getAnimations(); 733 734 assert_equals_records(observer.takeRecords(), 735 [{ added: [], changed: animations, removed: []}], 736 "records after the order is changed"); 737 738 div.style.animation = "anotherAnim 100s, anim 100s"; 739 740 assert_equals_records(observer.takeRecords(), 741 [], "no records after applying the same order"); 742 }, "animation_order_change"); 743 744 test(t => { 745 var div = addDiv(t); 746 var observer = 747 setupSynchronousObserver(t, 748 aOptions.subtree ? div.parentNode : div, 749 aOptions.subtree); 750 751 var anim = div.animate({ opacity: [ 0, 1 ] }, 752 { duration: 100 * MS_PER_SEC, 753 iterationComposite: 'replace' }); 754 755 assert_equals_records(observer.takeRecords(), 756 [{ added: [anim], changed: [], removed: [] }], 757 "records after animation is added"); 758 759 anim.effect.iterationComposite = 'accumulate'; 760 assert_equals_records(observer.takeRecords(), 761 [{ added: [], changed: [anim], removed: [] }], 762 "records after iterationComposite is changed"); 763 764 anim.effect.iterationComposite = 'accumulate'; 765 assert_equals_records(observer.takeRecords(), 766 [], "no record after setting the same iterationComposite"); 767 768 }, "set_iterationComposite"); 769 770 test(t => { 771 var div = addDiv(t); 772 var observer = 773 setupSynchronousObserver(t, 774 aOptions.subtree ? div.parentNode : div, 775 aOptions.subtree); 776 777 var anim = div.animate({ opacity: [ 0, 1 ] }, 778 { duration: 100 * MS_PER_SEC }); 779 780 assert_equals_records(observer.takeRecords(), 781 [{ added: [anim], changed: [], removed: [] }], 782 "records after animation is added"); 783 784 anim.effect.setKeyframes({ opacity: 0.1 }); 785 assert_equals_records(observer.takeRecords(), 786 [{ added: [], changed: [anim], removed: [] }], 787 "records after keyframes are changed"); 788 789 anim.effect.setKeyframes({ opacity: 0.1 }); 790 assert_equals_records(observer.takeRecords(), 791 [], "no record after setting the same keyframes"); 792 793 anim.effect.setKeyframes(null); 794 assert_equals_records(observer.takeRecords(), 795 [{ added: [], changed: [anim], removed: [] }], 796 "records after keyframes are set to empty"); 797 798 }, "set_keyframes"); 799 800 // Test that starting a single transition that is cancelled by resetting 801 // the transition-property property dispatches an added notification and 802 // then a removed notification. 803 test(t => { 804 var div = 805 addDiv(t, { style: "transition: background-color 100s; " + 806 "background-color: yellow;" }); 807 var observer = 808 setupSynchronousObserver(t, 809 aOptions.subtree ? div.parentNode : div, 810 aOptions.subtree); 811 812 getComputedStyle(div).transitionProperty; 813 div.style.backgroundColor = "lime"; 814 815 // The transition should cause the creation of a single Animation. 816 var animations = div.getAnimations(); 817 assert_equals(animations.length, 1, 818 "getAnimations().length after transition start"); 819 820 assert_equals_records(observer.takeRecords(), 821 [{ added: animations, changed: [], removed: [] }], 822 "records after transition start"); 823 824 // Cancel the transition by setting transition-property. 825 div.style.transitionProperty = "none"; 826 getComputedStyle(div).transitionProperty; 827 828 assert_equals_records(observer.takeRecords(), 829 [{ added: [], changed: [], removed: animations }], 830 "records after transition end"); 831 }, "single_transition_cancelled_property"); 832 833 // Test that starting a single transition that is cancelled by setting 834 // style to the currently animated value dispatches an added 835 // notification and then a removed notification. 836 test(t => { 837 // A long transition with a predictable value. 838 var div = 839 addDiv(t, { style: "transition: z-index 100s -51s; " + 840 "z-index: 10;" }); 841 var observer = 842 setupSynchronousObserver(t, 843 aOptions.subtree ? div.parentNode : div, 844 aOptions.subtree); 845 getComputedStyle(div).transitionProperty; 846 div.style.zIndex = "100"; 847 848 // The transition should cause the creation of a single Animation. 849 var animations = div.getAnimations(); 850 assert_equals(animations.length, 1, 851 "getAnimations().length after transition start"); 852 853 assert_equals_records(observer.takeRecords(), 854 [{ added: animations, changed: [], removed: [] }], 855 "records after transition start"); 856 857 // Cancel the transition by setting the current animation value. 858 let value = "83"; 859 assert_equals(getComputedStyle(div).zIndex, value, 860 "half-way transition value"); 861 div.style.zIndex = value; 862 getComputedStyle(div).transitionProperty; 863 864 assert_equals_records(observer.takeRecords(), 865 [{ added: [], changed: [], removed: animations }], 866 "records after transition end"); 867 }, "single_transition_cancelled_value"); 868 869 // Test that starting a single transition that is cancelled by setting 870 // style to a non-interpolable value dispatches an added notification 871 // and then a removed notification. 872 test(t => { 873 var div = 874 addDiv(t, { style: "transition: line-height 100s; " + 875 "line-height: 16px;" }); 876 var observer = 877 setupSynchronousObserver(t, 878 aOptions.subtree ? div.parentNode : div, 879 aOptions.subtree); 880 881 getComputedStyle(div).transitionProperty; 882 div.style.lineHeight = "100px"; 883 884 // The transition should cause the creation of a single Animation. 885 var animations = div.getAnimations(); 886 assert_equals(animations.length, 1, 887 "getAnimations().length after transition start"); 888 889 assert_equals_records(observer.takeRecords(), 890 [{ added: animations, changed: [], removed: [] }], 891 "records after transition start"); 892 893 // Cancel the transition by setting line-height to a non-interpolable value. 894 div.style.lineHeight = "normal"; 895 getComputedStyle(div).transitionProperty; 896 897 assert_equals_records(observer.takeRecords(), 898 [{ added: [], changed: [], removed: animations }], 899 "records after transition end"); 900 }, "single_transition_cancelled_noninterpolable"); 901 902 // Test that starting a single transition and then reversing it 903 // dispatches an added notification, then a simultaneous removed and 904 // added notification, then a removed notification once finished. 905 test(t => { 906 var div = 907 addDiv(t, { style: "transition: background-color 100s step-start; " + 908 "background-color: yellow;" }); 909 var observer = 910 setupSynchronousObserver(t, 911 aOptions.subtree ? div.parentNode : div, 912 aOptions.subtree); 913 914 getComputedStyle(div).transitionProperty; 915 div.style.backgroundColor = "lime"; 916 917 var animations = div.getAnimations(); 918 919 // The transition should cause the creation of a single Animation. 920 assert_equals(animations.length, 1, 921 "getAnimations().length after transition start"); 922 923 var firstAnimation = animations[0]; 924 assert_equals_records(observer.takeRecords(), 925 [{ added: [firstAnimation], changed: [], removed: [] }], 926 "records after transition start"); 927 928 firstAnimation.currentTime = 50 * MS_PER_SEC; 929 930 // Reverse the transition by setting the background-color back to its 931 // original value. 932 div.style.backgroundColor = "yellow"; 933 934 // The reversal should cause the creation of a new Animation. 935 animations = div.getAnimations(); 936 assert_equals(animations.length, 1, 937 "getAnimations().length after transition reversal"); 938 939 var secondAnimation = animations[0]; 940 941 assert_true(firstAnimation != secondAnimation, 942 "second Animation should be different from the first"); 943 944 assert_equals_records(observer.takeRecords(), 945 [{ added: [], changed: [firstAnimation], removed: [] }, 946 { added: [secondAnimation], changed: [], removed: [firstAnimation] }], 947 "records after transition reversal"); 948 949 // Cancel the transition. 950 div.style.transitionProperty = "none"; 951 getComputedStyle(div).transitionProperty; 952 953 assert_equals_records(observer.takeRecords(), 954 [{ added: [], changed: [], removed: [secondAnimation] }], 955 "records after transition end"); 956 }, "single_transition_reversed"); 957 958 // Test that multiple transitions starting and ending on an element 959 // at the same time get batched up into a single MutationRecord. 960 test(t => { 961 var div = 962 addDiv(t, { style: "transition-duration: 100s; " + 963 "transition-property: color, background-color, line-height" + 964 "background-color: yellow; line-height: 16px" }); 965 var observer = 966 setupSynchronousObserver(t, 967 aOptions.subtree ? div.parentNode : div, 968 aOptions.subtree); 969 getComputedStyle(div).transitionProperty; 970 971 div.style.backgroundColor = "lime"; 972 div.style.color = "blue"; 973 div.style.lineHeight = "24px"; 974 975 // The transitions should cause the creation of three Animations. 976 var animations = div.getAnimations(); 977 assert_equals(animations.length, 3, 978 "getAnimations().length after transition starts"); 979 980 assert_equals_records(observer.takeRecords(), 981 [{ added: animations, changed: [], removed: [] }], 982 "records after transition starts"); 983 984 assert_equals(animations.filter(p => p.playState == "running").length, 3, 985 "number of running Animations"); 986 987 // Seek well into each animation. 988 animations.forEach(p => p.currentTime = 50 * MS_PER_SEC); 989 990 // Prepare the set of expected change MutationRecords, one for each 991 // animation that was seeked. 992 var seekRecords = animations.map( 993 p => ({ added: [], changed: [p], removed: [] }) 994 ); 995 996 // Cancel one of the transitions by setting transition-property. 997 div.style.transitionProperty = "background-color, line-height"; 998 999 var colorAnimation = animations.filter(p => p.playState != "running"); 1000 var otherAnimations = animations.filter(p => p.playState == "running"); 1001 1002 assert_equals(colorAnimation.length, 1, 1003 "number of non-running Animations after cancelling one"); 1004 assert_equals(otherAnimations.length, 2, 1005 "number of running Animations after cancelling one"); 1006 1007 assert_equals_records(observer.takeRecords(), 1008 seekRecords.concat({ added: [], changed: [], removed: colorAnimation }), 1009 "records after color transition end"); 1010 1011 // Cancel the remaining transitions. 1012 div.style.transitionProperty = "none"; 1013 getComputedStyle(div).transitionProperty; 1014 1015 assert_equals_records(observer.takeRecords(), 1016 [{ added: [], changed: [], removed: otherAnimations }], 1017 "records after other transition ends"); 1018 }, "multiple_transitions"); 1019 1020 // Test that starting a single animation that is cancelled by resetting 1021 // the animation-name property dispatches an added notification and 1022 // then a removed notification. 1023 test(t => { 1024 var div = 1025 addDiv(t, { style: "animation: anim 100s" }); 1026 var observer = 1027 setupSynchronousObserver(t, 1028 aOptions.subtree ? div.parentNode : div, 1029 aOptions.subtree); 1030 1031 // The animation should cause the creation of a single Animation. 1032 var animations = div.getAnimations(); 1033 assert_equals(animations.length, 1, 1034 "getAnimations().length after animation start"); 1035 1036 assert_equals_records(observer.takeRecords(), 1037 [{ added: animations, changed: [], removed: [] }], 1038 "records after animation start"); 1039 1040 // Cancel the animation by setting animation-name. 1041 div.style.animationName = "none"; 1042 getComputedStyle(div).animationName; 1043 1044 assert_equals_records(observer.takeRecords(), 1045 [{ added: [], changed: [], removed: animations }], 1046 "records after animation end"); 1047 }, "single_animation_cancelled_name"); 1048 1049 // Test that starting a single animation that is cancelled by updating 1050 // the animation-duration property dispatches an added notification and 1051 // then a removed notification. 1052 test(t => { 1053 var div = 1054 addDiv(t, { style: "animation: anim 100s" }); 1055 var observer = 1056 setupSynchronousObserver(t, 1057 aOptions.subtree ? div.parentNode : div, 1058 aOptions.subtree); 1059 1060 // The animation should cause the creation of a single Animation. 1061 var animations = div.getAnimations(); 1062 assert_equals(animations.length, 1, 1063 "getAnimations().length after animation start"); 1064 1065 assert_equals_records(observer.takeRecords(), 1066 [{ added: animations, changed: [], removed: [] }], 1067 "records after animation start"); 1068 1069 // Advance the animation by a second. 1070 animations[0].currentTime += 1 * MS_PER_SEC; 1071 1072 // Cancel the animation by setting animation-duration to a value less 1073 // than a second. 1074 div.style.animationDuration = "0.1s"; 1075 getComputedStyle(div).animationName; 1076 1077 assert_equals_records(observer.takeRecords(), 1078 [{ added: [], changed: animations, removed: [] }, 1079 { added: [], changed: [], removed: animations }], 1080 "records after animation end"); 1081 }, "single_animation_cancelled_duration"); 1082 1083 // Test that starting a single animation that is cancelled by updating 1084 // the animation-delay property dispatches an added notification and 1085 // then a removed notification. 1086 test(t => { 1087 var div = 1088 addDiv(t, { style: "animation: anim 100s" }); 1089 var observer = 1090 setupSynchronousObserver(t, 1091 aOptions.subtree ? div.parentNode : div, 1092 aOptions.subtree); 1093 1094 // The animation should cause the creation of a single Animation. 1095 var animations = div.getAnimations(); 1096 assert_equals(animations.length, 1, 1097 "getAnimations().length after animation start"); 1098 1099 assert_equals_records(observer.takeRecords(), 1100 [{ added: animations, changed: [], removed: [] }], 1101 "records after animation start"); 1102 1103 // Cancel the animation by setting animation-delay. 1104 div.style.animationDelay = "-200s"; 1105 getComputedStyle(div).animationName; 1106 1107 assert_equals_records(observer.takeRecords(), 1108 [{ added: [], changed: [], removed: animations }], 1109 "records after animation end"); 1110 }, "single_animation_cancelled_delay"); 1111 1112 // Test that starting a single animation that is cancelled by updating 1113 // the animation-iteration-count property dispatches an added notification 1114 // and then a removed notification. 1115 test(t => { 1116 // A short, repeated animation. 1117 var div = 1118 addDiv(t, { style: "animation: anim 0.5s infinite;" }); 1119 var observer = 1120 setupSynchronousObserver(t, 1121 aOptions.subtree ? div.parentNode : div, 1122 aOptions.subtree); 1123 1124 // The animation should cause the creation of a single Animation. 1125 var animations = div.getAnimations(); 1126 assert_equals(animations.length, 1, 1127 "getAnimations().length after animation start"); 1128 1129 assert_equals_records(observer.takeRecords(), 1130 [{ added: animations, changed: [], removed: [] }], 1131 "records after animation start"); 1132 1133 // Advance the animation until we are past the first iteration. 1134 animations[0].currentTime += 1 * MS_PER_SEC; 1135 1136 assert_equals_records(observer.takeRecords(), 1137 [{ added: [], changed: animations, removed: [] }], 1138 "records after seeking animations"); 1139 1140 // Cancel the animation by setting animation-iteration-count. 1141 div.style.animationIterationCount = "1"; 1142 getComputedStyle(div).animationName; 1143 1144 assert_equals_records(observer.takeRecords(), 1145 [{ added: [], changed: [], removed: animations }], 1146 "records after animation end"); 1147 }, "single_animation_cancelled_iteration_count"); 1148 1149 // Test that updating an animation property dispatches a changed notification. 1150 [ 1151 { name: "duration", prop: "animationDuration", val: "200s" }, 1152 { name: "timing", prop: "animationTimingFunction", val: "linear" }, 1153 { name: "iteration", prop: "animationIterationCount", val: "2" }, 1154 { name: "direction", prop: "animationDirection", val: "reverse" }, 1155 { name: "state", prop: "animationPlayState", val: "paused" }, 1156 { name: "delay", prop: "animationDelay", val: "-1s" }, 1157 { name: "fill", prop: "animationFillMode", val: "both" }, 1158 ].forEach(aChangeTest => { 1159 test(t => { 1160 // Start a long animation. 1161 var div = addDiv(t, { style: "animation: anim 100s;" }); 1162 var observer = 1163 setupSynchronousObserver(t, 1164 aOptions.subtree ? div.parentNode : div, 1165 aOptions.subtree); 1166 1167 // The animation should cause the creation of a single Animation. 1168 var animations = div.getAnimations(); 1169 assert_equals(animations.length, 1, 1170 "getAnimations().length after animation start"); 1171 1172 assert_equals_records(observer.takeRecords(), 1173 [{ added: animations, changed: [], removed: [] }], 1174 "records after animation start"); 1175 1176 // Change a property of the animation such that it keeps running. 1177 div.style[aChangeTest.prop] = aChangeTest.val; 1178 getComputedStyle(div).animationName; 1179 1180 assert_equals_records(observer.takeRecords(), 1181 [{ added: [], changed: animations, removed: [] }], 1182 "records after animation change"); 1183 1184 // Cancel the animation. 1185 div.style.animationName = "none"; 1186 getComputedStyle(div).animationName; 1187 1188 assert_equals_records(observer.takeRecords(), 1189 [{ added: [], changed: [], removed: animations }], 1190 "records after animation end"); 1191 }, `single_animation_change_${aChangeTest.name}`); 1192 }); 1193 1194 // Test that calling finish() on a pause-pending (but otherwise finished) 1195 // animation dispatches a changed notification. 1196 test(t => { 1197 var div = 1198 addDiv(t, { style: "animation: anim 100s forwards" }); 1199 var observer = 1200 setupSynchronousObserver(t, 1201 aOptions.subtree ? div.parentNode : div, 1202 aOptions.subtree); 1203 1204 // The animation should cause the creation of a single Animation. 1205 var animations = div.getAnimations(); 1206 assert_equals(animations.length, 1, 1207 "getAnimations().length after animation start"); 1208 1209 assert_equals_records(observer.takeRecords(), 1210 [{ added: animations, changed: [], removed: [] }], 1211 "records after animation start"); 1212 1213 // Finish and pause. 1214 animations[0].finish(); 1215 animations[0].pause(); 1216 assert_true(animations[0].pending && animations[0].playState === "paused", 1217 "playState after finishing and calling pause()"); 1218 1219 // Call finish() again to abort the pause 1220 animations[0].finish(); 1221 assert_equals(animations[0].playState, "finished", 1222 "playState after finishing again"); 1223 1224 // Wait for three MutationRecords for the Animation changes to 1225 // be delivered: one for each finish(), pause(), finish() operation. 1226 assert_equals_records(observer.takeRecords(), 1227 [{ added: [], changed: animations, removed: [] }, 1228 { added: [], changed: animations, removed: [] }, 1229 { added: [], changed: animations, removed: [] }], 1230 "records after finish(), pause(), finish()"); 1231 1232 // Cancel the animation. 1233 div.style = ""; 1234 getComputedStyle(div).animationName; 1235 1236 assert_equals_records(observer.takeRecords(), 1237 [{ added: [], changed: [], removed: animations }], 1238 "records after animation end"); 1239 }, "finish_from_pause_pending"); 1240 1241 // Test that calling play() on a finished Animation that fills forwards 1242 // dispatches a changed notification. 1243 test(t => { 1244 // Animation with a forwards fill 1245 var div = 1246 addDiv(t, { style: "animation: anim 100s forwards" }); 1247 var observer = 1248 setupSynchronousObserver(t, 1249 aOptions.subtree ? div.parentNode : div, 1250 aOptions.subtree); 1251 1252 // The animation should cause the creation of a single Animation. 1253 var animations = div.getAnimations(); 1254 assert_equals(animations.length, 1, 1255 "getAnimations().length after animation start"); 1256 1257 assert_equals_records(observer.takeRecords(), 1258 [{ added: animations, changed: [], removed: [] }], 1259 "records after animation start"); 1260 1261 // Seek to the end 1262 animations[0].finish(); 1263 1264 assert_equals_records(observer.takeRecords(), 1265 [{ added: [], changed: animations, removed: [] }], 1266 "records after finish()"); 1267 1268 // Since we are filling forwards, calling play() should produce a 1269 // change record since the animation remains relevant. 1270 animations[0].play(); 1271 1272 assert_equals_records(observer.takeRecords(), 1273 [{ added: [], changed: animations, removed: [] }], 1274 "records after play()"); 1275 1276 // Cancel the animation. 1277 div.style = ""; 1278 getComputedStyle(div).animationName; 1279 1280 assert_equals_records(observer.takeRecords(), 1281 [{ added: [], changed: [], removed: animations }], 1282 "records after animation end"); 1283 }, "play_filling_forwards"); 1284 1285 // Test that calling pause() on an Animation dispatches a changed 1286 // notification. 1287 test(t => { 1288 var div = 1289 addDiv(t, { style: "animation: anim 100s" }); 1290 var observer = 1291 setupSynchronousObserver(t, 1292 aOptions.subtree ? div.parentNode : div, 1293 aOptions.subtree); 1294 1295 // The animation should cause the creation of a single Animation. 1296 var animations = div.getAnimations(); 1297 assert_equals(animations.length, 1, 1298 "getAnimations().length after animation start"); 1299 1300 assert_equals_records(observer.takeRecords(), 1301 [{ added: animations, changed: [], removed: [] }], 1302 "records after animation start"); 1303 1304 // Pause 1305 animations[0].pause(); 1306 1307 assert_equals_records(observer.takeRecords(), 1308 [{ added: [], changed: animations, removed: [] }], 1309 "records after pause()"); 1310 1311 // Redundant pause 1312 animations[0].pause(); 1313 1314 assert_equals_records(observer.takeRecords(), 1315 [], "records after redundant pause()"); 1316 1317 // Cancel the animation. 1318 div.style = ""; 1319 getComputedStyle(div).animationName; 1320 1321 assert_equals_records(observer.takeRecords(), 1322 [{ added: [], changed: [], removed: animations }], 1323 "records after animation end"); 1324 }, "pause"); 1325 1326 // Test that calling pause() on an Animation that is pause-pending 1327 // does not dispatch an additional changed notification. 1328 test(t => { 1329 var div = 1330 addDiv(t, { style: "animation: anim 100s" }); 1331 var observer = 1332 setupSynchronousObserver(t, 1333 aOptions.subtree ? div.parentNode : div, 1334 aOptions.subtree); 1335 1336 // The animation should cause the creation of a single Animation. 1337 var animations = div.getAnimations(); 1338 assert_equals(animations.length, 1, 1339 "getAnimations().length after animation start"); 1340 1341 assert_equals_records(observer.takeRecords(), 1342 [{ added: animations, changed: [], removed: [] }], 1343 "records after animation start"); 1344 1345 // Pause 1346 animations[0].pause(); 1347 1348 // We are now pause-pending, but pause again 1349 animations[0].pause(); 1350 1351 assert_equals_records(observer.takeRecords(), 1352 [{ added: [], changed: animations, removed: [] }], 1353 "records after pause()"); 1354 1355 // Cancel the animation. 1356 div.style = ""; 1357 getComputedStyle(div).animationName; 1358 1359 assert_equals_records(observer.takeRecords(), 1360 [{ added: [], changed: [], removed: animations }], 1361 "records after animation end"); 1362 }, "pause_while_pause_pending"); 1363 1364 // Test that calling play() on an Animation that is pause-pending 1365 // dispatches a changed notification. 1366 test(t => { 1367 var div = 1368 addDiv(t, { style: "animation: anim 100s" }); 1369 var observer = 1370 setupSynchronousObserver(t, 1371 aOptions.subtree ? div.parentNode : div, 1372 aOptions.subtree); 1373 1374 // The animation should cause the creation of a single Animation. 1375 var animations = div.getAnimations(); 1376 assert_equals(animations.length, 1, 1377 "getAnimations().length after animation start"); 1378 1379 assert_equals_records(observer.takeRecords(), 1380 [{ added: animations, changed: [], removed: [] }], 1381 "records after animation start"); 1382 1383 // Pause 1384 animations[0].pause(); 1385 1386 // We are now pause-pending. If we play() now, we will abort the pause 1387 animations[0].play(); 1388 1389 assert_equals_records(observer.takeRecords(), 1390 [{ added: [], changed: animations, removed: [] }, 1391 { added: [], changed: animations, removed: [] }], 1392 "records after aborting a pause()"); 1393 1394 // Cancel the animation. 1395 div.style = ""; 1396 getComputedStyle(div).animationName; 1397 1398 assert_equals_records(observer.takeRecords(), 1399 [{ added: [], changed: [], removed: animations }], 1400 "records after animation end"); 1401 }, "aborted_pause"); 1402 1403 // Test that calling play() on a finished Animation that does *not* fill 1404 // forwards dispatches an addition notification. 1405 test(t => { 1406 var div = 1407 addDiv(t, { style: "animation: anim 100s" }); 1408 var observer = 1409 setupSynchronousObserver(t, 1410 aOptions.subtree ? div.parentNode : div, 1411 aOptions.subtree); 1412 1413 // The animation should cause the creation of a single Animation. 1414 var animations = div.getAnimations(); 1415 assert_equals(animations.length, 1, 1416 "getAnimations().length after animation start"); 1417 1418 assert_equals_records(observer.takeRecords(), 1419 [{ added: animations, changed: [], removed: [] }], 1420 "records after animation start"); 1421 1422 // Seek to the end 1423 animations[0].finish(); 1424 1425 assert_equals_records(observer.takeRecords(), 1426 [{ added: [], changed: [], removed: animations }], 1427 "records after finish()"); 1428 1429 // Since we are *not* filling forwards, calling play() is equivalent 1430 // to creating a new animation since it becomes relevant again. 1431 animations[0].play(); 1432 1433 assert_equals_records(observer.takeRecords(), 1434 [{ added: animations, changed: [], removed: [] }], 1435 "records after play()"); 1436 1437 // Cancel the animation. 1438 div.style = ""; 1439 getComputedStyle(div).animationName; 1440 1441 assert_equals_records(observer.takeRecords(), 1442 [{ added: [], changed: [], removed: animations }], 1443 "records after animation end"); 1444 }, "play_after_finish"); 1445 1446 }); 1447 1448 test(t => { 1449 var div = addDiv(t); 1450 var observer = setupSynchronousObserver(t, div, true); 1451 1452 var child = document.createElement("div"); 1453 div.appendChild(child); 1454 1455 var anim1 = div.animate({ marginLeft: [ "0px", "50px" ] }, 1456 100 * MS_PER_SEC); 1457 var anim2 = child.animate({ marginLeft: [ "0px", "100px" ] }, 1458 50 * MS_PER_SEC); 1459 assert_equals_records(observer.takeRecords(), 1460 [{ added: [anim1], changed: [], removed: [] }, 1461 { added: [anim2], changed: [], removed: [] }], 1462 "records after animation is added"); 1463 1464 // After setting a new effect, we remove the current animation, anim1, 1465 // because it is no longer attached to |div|, and then remove the previous 1466 // animation, anim2. Finally, add back the anim1 which is in effect on 1467 // |child| now. In addition, we sort them by tree order and they are 1468 // batched. 1469 anim1.effect = anim2.effect; 1470 assert_equals_records(observer.takeRecords(), 1471 [{ added: [], changed: [], removed: [anim1] }, // div 1472 { added: [anim1], changed: [], removed: [anim2] }], // child 1473 "records after animation effects are changed"); 1474 }, "set_effect_with_previous_animation"); 1475 1476 test(t => { 1477 var div = addDiv(t); 1478 var observer = setupSynchronousObserver(t, document, true); 1479 1480 var anim = div.animate({ opacity: [ 0, 1 ] }, 1481 { duration: 100 * MS_PER_SEC }); 1482 1483 var newTarget = document.createElement("div"); 1484 1485 assert_equals_records(observer.takeRecords(), 1486 [{ added: [anim], changed: [], removed: [] }], 1487 "records after animation is added"); 1488 1489 anim.effect.target = null; 1490 assert_equals_records(observer.takeRecords(), 1491 [{ added: [], changed: [], removed: [anim] }], 1492 "records after setting null"); 1493 1494 anim.effect.target = div; 1495 assert_equals_records(observer.takeRecords(), 1496 [{ added: [anim], changed: [], removed: [] }], 1497 "records after setting a target"); 1498 1499 anim.effect.target = addDiv(t); 1500 assert_equals_records(observer.takeRecords(), 1501 [{ added: [], changed: [], removed: [anim] }, 1502 { added: [anim], changed: [], removed: [] }], 1503 "records after setting a different target"); 1504 }, "set_animation_target"); 1505 1506 test(t => { 1507 var div = addDiv(t); 1508 var observer = setupSynchronousObserver(t, div, true); 1509 1510 var anim = div.animate({ opacity: [ 0, 1 ] }, 1511 { duration: 200 * MS_PER_SEC, 1512 pseudoElement: '::before' }); 1513 1514 assert_equals_records(observer.takeRecords(), 1515 [{ added: [anim], changed: [], removed: [] }], 1516 "records after animation is added"); 1517 1518 anim.effect.updateTiming({ duration: 100 * MS_PER_SEC }); 1519 assert_equals_records(observer.takeRecords(), 1520 [{ added: [], changed: [anim], removed: [] }], 1521 "records after duration is changed"); 1522 1523 anim.effect.updateTiming({ duration: 100 * MS_PER_SEC }); 1524 assert_equals_records(observer.takeRecords(), 1525 [], "records after assigning same value"); 1526 1527 anim.currentTime = anim.effect.getComputedTiming().duration * 2; 1528 anim.finish(); 1529 assert_equals_records(observer.takeRecords(), 1530 [{ added: [], changed: [], removed: [anim] }], 1531 "records after animation end"); 1532 1533 anim.effect.updateTiming({ 1534 duration: anim.effect.getComputedTiming().duration * 3 1535 }); 1536 assert_equals_records(observer.takeRecords(), 1537 [{ added: [anim], changed: [], removed: [] }], 1538 "records after animation restarted"); 1539 1540 anim.effect.updateTiming({ duration: "auto" }); 1541 assert_equals_records(observer.takeRecords(), 1542 [{ added: [], changed: [], removed: [anim] }], 1543 "records after duration set \"auto\""); 1544 1545 anim.effect.updateTiming({ duration: "auto" }); 1546 assert_equals_records(observer.takeRecords(), 1547 [], "records after assigning same value \"auto\""); 1548 }, "change_duration_and_currenttime_on_pseudo_elements"); 1549 1550 test(t => { 1551 var div = addDiv(t); 1552 var observer = setupSynchronousObserver(t, div, false); 1553 1554 var anim = div.animate({ opacity: [ 0, 1 ] }, 1555 { duration: 100 * MS_PER_SEC }); 1556 var pAnim = div.animate({ opacity: [ 0, 1 ] }, 1557 { duration: 100 * MS_PER_SEC, 1558 pseudoElement: "::before" }); 1559 1560 assert_equals_records(observer.takeRecords(), 1561 [{ added: [anim], changed: [], removed: [] }], 1562 "records after animation is added"); 1563 1564 anim.finish(); 1565 pAnim.finish(); 1566 1567 assert_equals_records(observer.takeRecords(), 1568 [{ added: [], changed: [], removed: [anim] }], 1569 "records after animation is finished"); 1570 }, "exclude_animations_targeting_pseudo_elements"); 1571 } 1572 1573 W3CTest.runner.expectAssertions(0, 12); // bug 1189015 1574 runTest(); 1575 1576 </script>