update-and-send-events-replacement.html (29182B)
1 <!doctype html> 2 <meta charset=utf-8> 3 <title>Update animations and send events (replacement)</title> 4 <link rel="help" href="https://drafts.csswg.org/web-animations/#update-animations-and-send-events"> 5 <script src="/resources/testharness.js"></script> 6 <script src="/resources/testharnessreport.js"></script> 7 <script src="../../testcommon.js"></script> 8 <style> 9 @keyframes opacity-animation { 10 to { opacity: 1 } 11 } 12 </style> 13 <div id="log"></div> 14 <script> 15 'use strict'; 16 17 promise_test(async t => { 18 const div = createDiv(t); 19 20 const animA = div.animate({ opacity: 1 }, { duration: 1, fill: 'forwards' }); 21 const animB = div.animate({ opacity: 1 }, { duration: 1, fill: 'forwards' }); 22 await animA.finished; 23 24 assert_equals(animA.replaceState, 'removed'); 25 assert_equals(animB.replaceState, 'active'); 26 }, 'Removes an animation when another covers the same properties'); 27 28 promise_test(async t => { 29 const div = createDiv(t); 30 31 const animA = div.animate({ opacity: 1 }, { duration: 1, fill: 'forwards' }); 32 await animA.finished; 33 34 assert_equals(animA.replaceState, 'active'); 35 36 const animB = div.animate({ opacity: 1 }, { duration: 1, fill: 'forwards' }); 37 await animB.finished; 38 39 assert_equals(animA.replaceState, 'removed'); 40 assert_equals(animB.replaceState, 'active'); 41 }, 'Removes an animation after another animation finishes'); 42 43 promise_test(async t => { 44 const div = createDiv(t); 45 46 const animA = div.animate( 47 { opacity: 1, width: '100px' }, 48 { duration: 1, fill: 'forwards' } 49 ); 50 await animA.finished; 51 52 assert_equals(animA.replaceState, 'active'); 53 54 const animB = div.animate( 55 { width: '200px' }, 56 { duration: 1, fill: 'forwards' } 57 ); 58 await animB.finished; 59 60 assert_equals(animA.replaceState, 'active'); 61 assert_equals(animB.replaceState, 'active'); 62 63 const animC = div.animate( 64 { opacity: 0.5 }, 65 { duration: 1, fill: 'forwards' } 66 ); 67 await animC.finished; 68 69 assert_equals(animA.replaceState, 'removed'); 70 assert_equals(animB.replaceState, 'active'); 71 assert_equals(animC.replaceState, 'active'); 72 }, 'Removes an animation after multiple other animations finish'); 73 74 promise_test(async t => { 75 const div = createDiv(t); 76 77 const animA = div.animate( 78 { opacity: 1 }, 79 { duration: 100 * MS_PER_SEC, fill: 'forwards' } 80 ); 81 const animB = div.animate({ opacity: 1 }, { duration: 1, fill: 'forwards' }); 82 await animB.finished; 83 84 assert_equals(animB.replaceState, 'active'); 85 assert_equals(animB.replaceState, 'active'); 86 87 // Seek animA to just before it finishes since we want to test the behavior 88 // when the animation finishes by the ticking of the timeline, not by seeking 89 // (that is covered in a separate test). 90 91 animA.currentTime = 99.99 * MS_PER_SEC; 92 await animA.finished; 93 94 assert_equals(animA.replaceState, 'removed'); 95 assert_equals(animB.replaceState, 'active'); 96 }, 'Removes an animation after it finishes'); 97 98 promise_test(async t => { 99 const div = createDiv(t); 100 101 const animA = div.animate({ opacity: 1 }, { duration: 1, fill: 'forwards' }); 102 const animB = div.animate( 103 { opacity: 1 }, 104 { duration: 100 * MS_PER_SEC, fill: 'forwards' } 105 ); 106 await animA.finished; 107 108 assert_equals(animA.replaceState, 'active'); 109 assert_equals(animB.replaceState, 'active'); 110 111 animB.finish(); 112 113 // Replacement should not happen until the next time the "update animations 114 // and send events" procedure runs. 115 116 assert_equals(animA.replaceState, 'active'); 117 assert_equals(animB.replaceState, 'active'); 118 119 await waitForNextFrame(); 120 121 assert_equals(animA.replaceState, 'removed'); 122 assert_equals(animB.replaceState, 'active'); 123 }, 'Removes an animation after seeking another animation'); 124 125 promise_test(async t => { 126 const div = createDiv(t); 127 128 const animA = div.animate( 129 { opacity: 1 }, 130 { duration: 100 * MS_PER_SEC, fill: 'forwards' } 131 ); 132 const animB = div.animate({ opacity: 1 }, { duration: 1, fill: 'forwards' }); 133 await animB.finished; 134 135 assert_equals(animA.replaceState, 'active'); 136 assert_equals(animB.replaceState, 'active'); 137 138 animA.finish(); 139 140 // Replacement should not happen until the next time the "update animations 141 // and send events" procedure runs. 142 143 assert_equals(animA.replaceState, 'active'); 144 assert_equals(animB.replaceState, 'active'); 145 146 await waitForNextFrame(); 147 148 assert_equals(animA.replaceState, 'removed'); 149 assert_equals(animB.replaceState, 'active'); 150 }, 'Removes an animation after seeking it'); 151 152 promise_test(async t => { 153 const div = createDiv(t); 154 155 const animA = div.animate({ opacity: 1 }, { duration: 1, fill: 'forwards' }); 156 const animB = div.animate({ opacity: 1 }, 1); 157 await animA.finished; 158 159 assert_equals(animA.replaceState, 'active'); 160 assert_equals(animB.replaceState, 'active'); 161 162 animB.effect.updateTiming({ fill: 'forwards' }); 163 164 // Replacement should not happen until the next time the "update animations 165 // and send events" procedure runs. 166 167 assert_equals(animA.replaceState, 'active'); 168 assert_equals(animB.replaceState, 'active'); 169 170 await waitForNextFrame(); 171 172 assert_equals(animA.replaceState, 'removed'); 173 assert_equals(animB.replaceState, 'active'); 174 }, 'Removes an animation after updating the fill mode of another animation'); 175 176 promise_test(async t => { 177 const div = createDiv(t); 178 179 const animA = div.animate({ opacity: 1 }, 1); 180 const animB = div.animate({ opacity: 1 }, { duration: 1, fill: 'forwards' }); 181 await animA.finished; 182 183 assert_equals(animA.replaceState, 'active'); 184 assert_equals(animB.replaceState, 'active'); 185 186 animA.effect.updateTiming({ fill: 'forwards' }); 187 188 // Replacement should not happen until the next time the "update animations 189 // and send events" procedure runs. 190 191 assert_equals(animA.replaceState, 'active'); 192 assert_equals(animB.replaceState, 'active'); 193 194 await waitForNextFrame(); 195 196 assert_equals(animA.replaceState, 'removed'); 197 assert_equals(animB.replaceState, 'active'); 198 }, 'Removes an animation after updating its fill mode'); 199 200 promise_test(async t => { 201 const div = createDiv(t); 202 203 const animA = div.animate({ opacity: 1 }, { duration: 1, fill: 'forwards' }); 204 const animB = div.animate({ opacity: 1 }, 1); 205 await animA.finished; 206 207 assert_equals(animA.replaceState, 'active'); 208 assert_equals(animB.replaceState, 'active'); 209 210 animB.effect = new KeyframeEffect( 211 div, 212 { opacity: 1 }, 213 { 214 duration: 1, 215 fill: 'forwards', 216 } 217 ); 218 219 assert_equals(animA.replaceState, 'active'); 220 assert_equals(animB.replaceState, 'active'); 221 222 await waitForNextFrame(); 223 224 assert_equals(animA.replaceState, 'removed'); 225 assert_equals(animB.replaceState, 'active'); 226 }, "Removes an animation after updating another animation's effect to one with different timing"); 227 228 promise_test(async t => { 229 const div = createDiv(t); 230 231 const animA = div.animate({ opacity: 1 }, 1); 232 const animB = div.animate({ opacity: 1 }, { duration: 1, fill: 'forwards' }); 233 await animB.finished; 234 235 assert_equals(animA.replaceState, 'active'); 236 assert_equals(animB.replaceState, 'active'); 237 238 animA.effect = new KeyframeEffect( 239 div, 240 { opacity: 1 }, 241 { 242 duration: 1, 243 fill: 'forwards', 244 } 245 ); 246 247 assert_equals(animA.replaceState, 'active'); 248 assert_equals(animB.replaceState, 'active'); 249 250 await waitForNextFrame(); 251 252 assert_equals(animA.replaceState, 'removed'); 253 assert_equals(animB.replaceState, 'active'); 254 }, 'Removes an animation after updating its effect to one with different timing'); 255 256 promise_test(async t => { 257 const div = createDiv(t); 258 259 const animA = div.animate({ opacity: 1 }, { duration: 1, fill: 'forwards' }); 260 const animB = div.animate( 261 { opacity: 1 }, 262 { duration: 100 * MS_PER_SEC, fill: 'forwards' } 263 ); 264 265 await animA.finished; 266 267 // Set up a timeline that makes animB finished 268 animB.timeline = new DocumentTimeline({ 269 originTime: 270 document.timeline.currentTime - 100 * MS_PER_SEC - animB.startTime, 271 }); 272 273 assert_equals(animA.replaceState, 'active'); 274 assert_equals(animB.replaceState, 'active'); 275 276 await waitForNextFrame(); 277 278 assert_equals(animA.replaceState, 'removed'); 279 assert_equals(animB.replaceState, 'active'); 280 }, "Removes an animation after updating another animation's timeline"); 281 282 promise_test(async t => { 283 const div = createDiv(t); 284 285 const animA = div.animate( 286 { opacity: 1 }, 287 { duration: 100 * MS_PER_SEC, fill: 'forwards' } 288 ); 289 const animB = div.animate({ opacity: 1 }, { duration: 1, fill: 'forwards' }); 290 291 await animB.finished; 292 293 // Set up a timeline that makes animA finished 294 animA.timeline = new DocumentTimeline({ 295 originTime: 296 document.timeline.currentTime - 100 * MS_PER_SEC - animA.startTime, 297 }); 298 299 assert_equals(animA.replaceState, 'active'); 300 assert_equals(animB.replaceState, 'active'); 301 302 await waitForNextFrame(); 303 304 assert_equals(animA.replaceState, 'removed'); 305 assert_equals(animB.replaceState, 'active'); 306 }, 'Removes an animation after updating its timeline'); 307 308 promise_test(async t => { 309 const div = createDiv(t); 310 311 const animA = div.animate({ opacity: 1 }, { duration: 1, fill: 'forwards' }); 312 const animB = div.animate( 313 { width: '100px' }, 314 { duration: 1, fill: 'forwards' } 315 ); 316 await animA.finished; 317 318 assert_equals(animA.replaceState, 'active'); 319 assert_equals(animB.replaceState, 'active'); 320 321 animB.effect.setKeyframes({ width: '100px', opacity: 1 }); 322 323 assert_equals(animA.replaceState, 'active'); 324 assert_equals(animB.replaceState, 'active'); 325 326 await waitForNextFrame(); 327 328 assert_equals(animA.replaceState, 'removed'); 329 assert_equals(animB.replaceState, 'active'); 330 }, "Removes an animation after updating another animation's effect's properties"); 331 332 promise_test(async t => { 333 const div = createDiv(t); 334 335 const animA = div.animate( 336 { opacity: 1, width: '100px' }, 337 { duration: 1, fill: 'forwards' } 338 ); 339 const animB = div.animate( 340 { width: '200px' }, 341 { duration: 1, fill: 'forwards' } 342 ); 343 await animA.finished; 344 345 assert_equals(animA.replaceState, 'active'); 346 assert_equals(animB.replaceState, 'active'); 347 348 animA.effect.setKeyframes({ width: '100px' }); 349 350 assert_equals(animA.replaceState, 'active'); 351 assert_equals(animB.replaceState, 'active'); 352 353 await waitForNextFrame(); 354 355 assert_equals(animA.replaceState, 'removed'); 356 assert_equals(animB.replaceState, 'active'); 357 }, "Removes an animation after updating its effect's properties"); 358 359 promise_test(async t => { 360 const div = createDiv(t); 361 362 const animA = div.animate({ opacity: 1 }, { duration: 1, fill: 'forwards' }); 363 const animB = div.animate( 364 { width: '100px' }, 365 { duration: 1, fill: 'forwards' } 366 ); 367 await animA.finished; 368 369 assert_equals(animA.replaceState, 'active'); 370 assert_equals(animB.replaceState, 'active'); 371 372 animB.effect = new KeyframeEffect( 373 div, 374 { width: '100px', opacity: 1 }, 375 { duration: 1, fill: 'forwards' } 376 ); 377 378 assert_equals(animA.replaceState, 'active'); 379 assert_equals(animB.replaceState, 'active'); 380 381 await waitForNextFrame(); 382 383 assert_equals(animA.replaceState, 'removed'); 384 assert_equals(animB.replaceState, 'active'); 385 }, "Removes an animation after updating another animation's effect to one with different properties"); 386 387 promise_test(async t => { 388 const div = createDiv(t); 389 390 const animA = div.animate( 391 { opacity: 1, width: '100px' }, 392 { duration: 1, fill: 'forwards' } 393 ); 394 const animB = div.animate( 395 { width: '200px' }, 396 { duration: 1, fill: 'forwards' } 397 ); 398 await animA.finished; 399 400 assert_equals(animA.replaceState, 'active'); 401 assert_equals(animB.replaceState, 'active'); 402 403 animA.effect = new KeyframeEffect( 404 div, 405 { width: '100px' }, 406 { 407 duration: 1, 408 fill: 'forwards', 409 } 410 ); 411 412 assert_equals(animA.replaceState, 'active'); 413 assert_equals(animB.replaceState, 'active'); 414 415 await waitForNextFrame(); 416 417 assert_equals(animA.replaceState, 'removed'); 418 assert_equals(animB.replaceState, 'active'); 419 }, 'Removes an animation after updating its effect to one with different properties'); 420 421 promise_test(async t => { 422 const div = createDiv(t); 423 424 const animA = div.animate( 425 { marginLeft: '10px' }, 426 { duration: 1, fill: 'forwards' } 427 ); 428 const animB = div.animate( 429 { margin: '20px' }, 430 { duration: 1, fill: 'forwards' } 431 ); 432 await animA.finished; 433 434 assert_equals(animA.replaceState, 'removed'); 435 assert_equals(animB.replaceState, 'active'); 436 }, 'Removes an animation when another animation uses a shorthand'); 437 438 promise_test(async t => { 439 const div = createDiv(t); 440 441 const animA = div.animate( 442 { margin: '10px' }, 443 { duration: 1, fill: 'forwards' } 444 ); 445 const animB = div.animate( 446 { 447 marginLeft: '10px', 448 marginTop: '20px', 449 marginRight: '30px', 450 marginBottom: '40px', 451 }, 452 { duration: 1, fill: 'forwards' } 453 ); 454 await animA.finished; 455 456 assert_equals(animA.replaceState, 'removed'); 457 assert_equals(animB.replaceState, 'active'); 458 }, 'Removes an animation that uses a shorthand'); 459 460 promise_test(async t => { 461 const div = createDiv(t); 462 463 const animA = div.animate( 464 { marginLeft: '10px' }, 465 { duration: 1, fill: 'forwards' } 466 ); 467 const animB = div.animate( 468 { marginInlineStart: '20px' }, 469 { duration: 1, fill: 'forwards' } 470 ); 471 await animA.finished; 472 473 assert_equals(animA.replaceState, 'removed'); 474 assert_equals(animB.replaceState, 'active'); 475 }, 'Removes an animation by another animation using logical properties'); 476 477 promise_test(async t => { 478 const div = createDiv(t); 479 480 const animA = div.animate( 481 { marginInlineStart: '10px' }, 482 { duration: 1, fill: 'forwards' } 483 ); 484 const animB = div.animate( 485 { marginLeft: '20px' }, 486 { duration: 1, fill: 'forwards' } 487 ); 488 await animA.finished; 489 490 assert_equals(animA.replaceState, 'removed'); 491 assert_equals(animB.replaceState, 'active'); 492 }, 'Removes an animation using logical properties'); 493 494 promise_test(async t => { 495 const div = createDiv(t); 496 497 const animA = div.animate( 498 { marginTop: '10px' }, 499 { duration: 1, fill: 'forwards' } 500 ); 501 const animB = div.animate( 502 { marginInlineStart: '20px' }, 503 { duration: 1, fill: 'forwards' } 504 ); 505 await animA.finished; 506 507 assert_equals(animA.replaceState, 'active'); 508 assert_equals(animB.replaceState, 'active'); 509 510 div.style.writingMode = 'vertical-rl'; 511 512 assert_equals(animA.replaceState, 'active'); 513 assert_equals(animB.replaceState, 'active'); 514 515 await waitForNextFrame(); 516 517 assert_equals(animA.replaceState, 'removed'); 518 assert_equals(animB.replaceState, 'active'); 519 }, 'Removes an animation by another animation using logical properties after updating the context'); 520 521 promise_test(async t => { 522 const divA = createDiv(t); 523 const divB = createDiv(t); 524 525 const animA = divA.animate({ opacity: 1 }, { duration: 1, fill: 'forwards' }); 526 const animB = divB.animate({ opacity: 1 }, { duration: 1, fill: 'forwards' }); 527 await animA.finished; 528 529 assert_equals(animA.replaceState, 'active'); 530 assert_equals(animB.replaceState, 'active'); 531 532 animB.effect.target = divA; 533 534 assert_equals(animA.replaceState, 'active'); 535 assert_equals(animB.replaceState, 'active'); 536 537 await waitForNextFrame(); 538 539 assert_equals(animA.replaceState, 'removed'); 540 assert_equals(animB.replaceState, 'active'); 541 }, "Removes an animation after updating another animation's effect's target"); 542 543 promise_test(async t => { 544 const divA = createDiv(t); 545 const divB = createDiv(t); 546 547 const animA = divA.animate({ opacity: 1 }, { duration: 1, fill: 'forwards' }); 548 const animB = divB.animate({ opacity: 1 }, { duration: 1, fill: 'forwards' }); 549 await animA.finished; 550 551 assert_equals(animA.replaceState, 'active'); 552 assert_equals(animB.replaceState, 'active'); 553 554 animA.effect.target = divB; 555 556 assert_equals(animA.replaceState, 'active'); 557 assert_equals(animB.replaceState, 'active'); 558 559 await waitForNextFrame(); 560 561 assert_equals(animA.replaceState, 'removed'); 562 assert_equals(animB.replaceState, 'active'); 563 }, "Removes an animation after updating its effect's target"); 564 565 promise_test(async t => { 566 const divA = createDiv(t); 567 const divB = createDiv(t); 568 569 const animA = divA.animate({ opacity: 1 }, { duration: 1, fill: 'forwards' }); 570 const animB = divB.animate({ opacity: 1 }, { duration: 1, fill: 'forwards' }); 571 await animA.finished; 572 573 assert_equals(animA.replaceState, 'active'); 574 assert_equals(animB.replaceState, 'active'); 575 576 animB.effect = new KeyframeEffect( 577 divA, 578 { opacity: 1 }, 579 { 580 duration: 1, 581 fill: 'forwards', 582 } 583 ); 584 585 assert_equals(animA.replaceState, 'active'); 586 assert_equals(animB.replaceState, 'active'); 587 588 await waitForNextFrame(); 589 590 assert_equals(animA.replaceState, 'removed'); 591 assert_equals(animB.replaceState, 'active'); 592 }, "Removes an animation after updating another animation's effect to one with a different target"); 593 594 promise_test(async t => { 595 const divA = createDiv(t); 596 const divB = createDiv(t); 597 598 const animA = divA.animate({ opacity: 1 }, { duration: 1, fill: 'forwards' }); 599 const animB = divB.animate({ opacity: 1 }, { duration: 1, fill: 'forwards' }); 600 await animA.finished; 601 602 assert_equals(animA.replaceState, 'active'); 603 assert_equals(animB.replaceState, 'active'); 604 605 animA.effect = new KeyframeEffect( 606 divB, 607 { opacity: 1 }, 608 { 609 duration: 1, 610 fill: 'forwards', 611 } 612 ); 613 614 assert_equals(animA.replaceState, 'active'); 615 assert_equals(animB.replaceState, 'active'); 616 617 await waitForNextFrame(); 618 619 assert_equals(animA.replaceState, 'removed'); 620 assert_equals(animB.replaceState, 'active'); 621 }, 'Removes an animation after updating its effect to one with a different target'); 622 623 promise_test(async t => { 624 const div = createDiv(t); 625 div.style.animation = 'opacity-animation 1ms forwards'; 626 const cssAnimation = div.getAnimations()[0]; 627 628 const scriptAnimation = div.animate( 629 { opacity: 1 }, 630 { 631 duration: 1, 632 fill: 'forwards', 633 } 634 ); 635 await scriptAnimation.finished; 636 637 assert_equals(cssAnimation.replaceState, 'active'); 638 assert_equals(scriptAnimation.replaceState, 'active'); 639 }, 'Does NOT remove a CSS animation tied to markup'); 640 641 promise_test(async t => { 642 const div = createDiv(t); 643 div.style.animation = 'opacity-animation 1ms forwards'; 644 const cssAnimation = div.getAnimations()[0]; 645 646 // Break tie to markup 647 div.style.animationName = 'none'; 648 assert_equals(cssAnimation.playState, 'idle'); 649 650 // Restart animation 651 cssAnimation.play(); 652 653 const scriptAnimation = div.animate( 654 { opacity: 1 }, 655 { 656 duration: 1, 657 fill: 'forwards', 658 } 659 ); 660 await scriptAnimation.finished; 661 662 assert_equals(cssAnimation.replaceState, 'removed'); 663 assert_equals(scriptAnimation.replaceState, 'active'); 664 }, 'Removes a CSS animation no longer tied to markup'); 665 666 promise_test(async t => { 667 // Setup transition 668 const div = createDiv(t); 669 div.style.opacity = '0'; 670 div.style.transition = 'opacity 1ms'; 671 getComputedStyle(div).opacity; 672 div.style.opacity = '1'; 673 const cssTransition = div.getAnimations()[0]; 674 cssTransition.effect.updateTiming({ fill: 'forwards' }); 675 676 const scriptAnimation = div.animate( 677 { opacity: 1 }, 678 { 679 duration: 1, 680 fill: 'forwards', 681 } 682 ); 683 await scriptAnimation.finished; 684 685 assert_equals(cssTransition.replaceState, 'active'); 686 assert_equals(scriptAnimation.replaceState, 'active'); 687 }, 'Does NOT remove a CSS transition tied to markup'); 688 689 promise_test(async t => { 690 // Setup transition 691 const div = createDiv(t); 692 div.style.opacity = '0'; 693 div.style.transition = 'opacity 1ms'; 694 getComputedStyle(div).opacity; 695 div.style.opacity = '1'; 696 const cssTransition = div.getAnimations()[0]; 697 cssTransition.effect.updateTiming({ fill: 'forwards' }); 698 699 // Break tie to markup 700 div.style.transitionProperty = 'none'; 701 assert_equals(cssTransition.playState, 'idle'); 702 703 // Restart transition 704 cssTransition.play(); 705 706 const scriptAnimation = div.animate( 707 { opacity: 1 }, 708 { 709 duration: 1, 710 fill: 'forwards', 711 } 712 ); 713 await scriptAnimation.finished; 714 715 assert_equals(cssTransition.replaceState, 'removed'); 716 assert_equals(scriptAnimation.replaceState, 'active'); 717 }, 'Removes a CSS transition no longer tied to markup'); 718 719 promise_test(async t => { 720 const div = createDiv(t); 721 722 const animA = div.animate({ opacity: 1 }, { duration: 1, fill: 'forwards' }); 723 const animB = div.animate({ opacity: 1 }, { duration: 1, fill: 'forwards' }); 724 const eventWatcher = new EventWatcher(t, animA, 'remove'); 725 726 const event = await eventWatcher.wait_for('remove'); 727 728 assert_times_equal(event.timelineTime, document.timeline.currentTime); 729 assert_times_equal(event.currentTime, 1); 730 }, 'Dispatches an event when removing'); 731 732 promise_test(async t => { 733 const div = createDiv(t); 734 735 const animA = div.animate({ opacity: 1 }, { duration: 1, fill: 'forwards' }); 736 const animB = div.animate({ opacity: 1 }, { duration: 1, fill: 'forwards' }); 737 const eventWatcher = new EventWatcher(t, animA, 'remove'); 738 739 await eventWatcher.wait_for('remove'); 740 741 // Check we don't get another event 742 animA.addEventListener( 743 'remove', 744 t.step_func(() => { 745 assert_unreached('remove event should not be fired a second time'); 746 }) 747 ); 748 749 // Restart animation 750 animA.play(); 751 752 await waitForNextFrame(); 753 754 // Finish animation 755 animA.finish(); 756 757 await waitForNextFrame(); 758 }, 'Does NOT dispatch a remove event twice'); 759 760 promise_test(async t => { 761 const div = createDiv(t); 762 763 const animA = div.animate({ opacity: 1 }, { duration: 1, fill: 'forwards' }); 764 const animB = div.animate( 765 { opacity: 1 }, 766 { duration: 100 * MS_PER_SEC, fill: 'forwards' } 767 ); 768 await animA.finished; 769 770 assert_equals(animA.replaceState, 'active'); 771 772 animB.finish(); 773 animB.currentTime = 0; 774 775 await waitForNextFrame(); 776 777 assert_equals(animA.replaceState, 'active'); 778 }, "Does NOT remove an animation after making a redundant change to another animation's current time"); 779 780 promise_test(async t => { 781 const div = createDiv(t); 782 783 const animA = div.animate( 784 { opacity: 1 }, 785 { duration: 100 * MS_PER_SEC, fill: 'forwards' } 786 ); 787 const animB = div.animate({ opacity: 1 }, { duration: 1, fill: 'forwards' }); 788 await animB.finished; 789 790 assert_equals(animA.replaceState, 'active'); 791 792 animA.finish(); 793 animA.currentTime = 0; 794 795 await waitForNextFrame(); 796 797 assert_equals(animA.replaceState, 'active'); 798 }, 'Does NOT remove an animation after making a redundant change to its current time'); 799 800 promise_test(async t => { 801 const div = createDiv(t); 802 803 const animA = div.animate({ opacity: 1 }, { duration: 1, fill: 'forwards' }); 804 const animB = div.animate( 805 { opacity: 1 }, 806 { duration: 100 * MS_PER_SEC, fill: 'forwards' } 807 ); 808 await animA.finished; 809 810 assert_equals(animA.replaceState, 'active'); 811 812 // Set up a timeline that makes animB finished but then restore it 813 animB.timeline = new DocumentTimeline({ 814 originTime: 815 document.timeline.currentTime - 100 * MS_PER_SEC - animB.startTime, 816 }); 817 animB.timeline = document.timeline; 818 819 await waitForNextFrame(); 820 821 assert_equals(animA.replaceState, 'active'); 822 }, "Does NOT remove an animation after making a redundant change to another animation's timeline"); 823 824 promise_test(async t => { 825 const div = createDiv(t); 826 827 const animA = div.animate( 828 { opacity: 1 }, 829 { duration: 100 * MS_PER_SEC, fill: 'forwards' } 830 ); 831 const animB = div.animate({ opacity: 1 }, { duration: 1, fill: 'forwards' }); 832 await animB.finished; 833 834 assert_equals(animA.replaceState, 'active'); 835 836 // Set up a timeline that makes animA finished but then restore it 837 animA.timeline = new DocumentTimeline({ 838 originTime: 839 document.timeline.currentTime - 100 * MS_PER_SEC - animA.startTime, 840 }); 841 animA.timeline = document.timeline; 842 843 await waitForNextFrame(); 844 845 assert_equals(animA.replaceState, 'active'); 846 }, 'Does NOT remove an animation after making a redundant change to its timeline'); 847 848 promise_test(async t => { 849 const div = createDiv(t); 850 const animA = div.animate({ opacity: 1 }, { duration: 1, fill: 'forwards' }); 851 const animB = div.animate( 852 { marginLeft: '100px' }, 853 { 854 duration: 1, 855 fill: 'forwards', 856 } 857 ); 858 await animA.finished; 859 860 assert_equals(animA.replaceState, 'active'); 861 862 // Redundant change 863 animB.effect.setKeyframes({ marginLeft: '100px', opacity: 1 }); 864 animB.effect.setKeyframes({ marginLeft: '100px' }); 865 866 await waitForNextFrame(); 867 868 assert_equals(animA.replaceState, 'active'); 869 }, "Does NOT remove an animation after making a redundant change to another animation's effect's properties"); 870 871 promise_test(async t => { 872 const div = createDiv(t); 873 const animA = div.animate( 874 { marginLeft: '100px' }, 875 { 876 duration: 1, 877 fill: 'forwards', 878 } 879 ); 880 const animB = div.animate({ opacity: 1 }, { duration: 1, fill: 'forwards' }); 881 await animA.finished; 882 883 assert_equals(animA.replaceState, 'active'); 884 885 // Redundant change 886 animA.effect.setKeyframes({ opacity: 1 }); 887 animA.effect.setKeyframes({ marginLeft: '100px' }); 888 889 await waitForNextFrame(); 890 891 assert_equals(animA.replaceState, 'active'); 892 }, "Does NOT remove an animation after making a redundant change to its effect's properties"); 893 894 promise_test(async t => { 895 const div = createDiv(t); 896 897 const animA = div.animate({ opacity: 1 }, { duration: 1, fill: 'forwards' }); 898 const animB = div.animate({ opacity: 1 }, { duration: 1, fill: 'forwards' }); 899 animB.timeline = new DocumentTimeline(); 900 901 await animA.finished; 902 903 // If, for example, we only update the timeline for animA before checking 904 // replacement state, then animB will not be finished and animA will not be 905 // replaced. 906 907 assert_equals(animA.replaceState, 'removed'); 908 assert_equals(animB.replaceState, 'active'); 909 }, 'Updates ALL timelines before checking for replacement'); 910 911 promise_test(async t => { 912 const div = createDiv(t); 913 const animA = div.animate({ opacity: 1 }, { duration: 1, fill: 'forwards' }); 914 const animB = div.animate({ opacity: 1 }, { duration: 1, fill: 'forwards' }); 915 916 const events = []; 917 const logEvent = (targetName, eventType) => { 918 events.push(`${targetName}:${eventType}`); 919 }; 920 921 animA.addEventListener('finish', () => logEvent('animA', 'finish')); 922 animA.addEventListener('remove', () => logEvent('animA', 'remove')); 923 animB.addEventListener('finish', () => logEvent('animB', 'finish')); 924 animB.addEventListener('remove', () => logEvent('animB', 'remove')); 925 926 await animA.finished; 927 928 // Allow all events to be dispatched 929 930 await waitForNextFrame(); 931 932 assert_array_equals(events, [ 933 'animA:finish', 934 'animB:finish', 935 'animA:remove', 936 ]); 937 }, 'Dispatches remove events after finish events'); 938 939 promise_test(async t => { 940 const div = createDiv(t); 941 const animA = div.animate({ opacity: 1 }, { duration: 1, fill: 'forwards' }); 942 const animB = div.animate({ opacity: 1 }, { duration: 1, fill: 'forwards' }); 943 944 const eventWatcher = new EventWatcher(t, animA, 'remove'); 945 946 await animA.finished; 947 948 let rAFReceived = false; 949 requestAnimationFrame(() => (rAFReceived = true)); 950 951 await eventWatcher.wait_for('remove'); 952 953 assert_false( 954 rAFReceived, 955 'remove event should be fired before requestAnimationFrame' 956 ); 957 }, 'Fires remove event before requestAnimationFrame'); 958 959 promise_test(async t => { 960 const div = createDiv(t); 961 const animA = div.animate({ opacity: 1 }, { duration: 1, fill: 'forwards' }); 962 const animB = div.animate( 963 { width: '100px' }, 964 { duration: 1, fill: 'forwards' } 965 ); 966 const animC = div.animate( 967 { opacity: 0.5, width: '200px' }, 968 { duration: 1, fill: 'forwards' } 969 ); 970 971 // In the event handler for animA (which should be fired before that of animB) 972 // we make a change to animC so that it no longer covers animB. 973 // 974 // If the remove event for animB is not already queued by this point, it will 975 // fail to fire. 976 animA.addEventListener('remove', () => { 977 animC.effect.setKeyframes({ 978 opacity: 0.5, 979 }); 980 }); 981 982 const eventWatcher = new EventWatcher(t, animB, 'remove'); 983 await eventWatcher.wait_for('remove'); 984 985 assert_equals(animA.replaceState, 'removed'); 986 assert_equals(animB.replaceState, 'removed'); 987 assert_equals(animC.replaceState, 'active'); 988 }, 'Queues all remove events before running them'); 989 990 promise_test(async t => { 991 const outerIframe = document.createElement('iframe'); 992 outerIframe.width = 10; 993 outerIframe.height = 10; 994 await insertFrameAndAwaitLoad(t, outerIframe, document); 995 996 const innerIframe = document.createElement('iframe'); 997 innerIframe.width = 10; 998 innerIframe.height = 10; 999 await insertFrameAndAwaitLoad(t, innerIframe, outerIframe.contentDocument); 1000 1001 const div = createDiv(t, innerIframe.contentDocument); 1002 1003 const animA = div.animate({ opacity: 1 }, { duration: 1, fill: 'forwards' }); 1004 const animB = div.animate({ opacity: 1 }, { duration: 1, fill: 'forwards' }); 1005 1006 // Sanity check: The timeline for these animations should be the default 1007 // document timeline for div. 1008 assert_equals(animA.timeline, innerIframe.contentDocument.timeline); 1009 assert_equals(animB.timeline, innerIframe.contentDocument.timeline); 1010 1011 await animA.finished; 1012 1013 assert_equals(animA.replaceState, 'removed'); 1014 assert_equals(animB.replaceState, 'active'); 1015 }, 'Performs removal in deeply nested iframes'); 1016 1017 </script>