test_animation_performance_warning.html (50983B)
1 <!doctype html> 2 <head> 3 <meta charset=utf-8> 4 <title>Bug 1196114 - Test metadata related to which animation properties 5 are running on the compositor</title> 6 <script type="application/javascript" src="../testharness.js"></script> 7 <script type="application/javascript" src="../testharnessreport.js"></script> 8 <script type="application/javascript" src="../testcommon.js"></script> 9 <style> 10 .compositable { 11 /* Element needs geometry to be eligible for layerization */ 12 width: 100px; 13 height: 100px; 14 background-color: white; 15 } 16 @keyframes fade { 17 from { opacity: 1 } 18 to { opacity: 0 } 19 } 20 @keyframes translate { 21 from { transform: none } 22 to { transform: translate(100px) } 23 } 24 </style> 25 </head> 26 <body> 27 <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1196114" 28 target="_blank">Mozilla Bug 1196114</a> 29 <div id="log"></div> 30 <script> 31 'use strict'; 32 33 // This is used for obtaining localized strings. 34 var gStringBundle; 35 36 W3CTest.runner.requestLongerTimeout(2); 37 38 const Services = SpecialPowers.Services; 39 Services.locale.requestedLocales = ["en-US"]; 40 41 SpecialPowers.pushPrefEnv({ "set": [ 42 // Need to set devPixelsPerPx explicitly to gain 43 // consistent pixel values in warning messages 44 // regardless of platform DPIs. 45 ["layout.css.devPixelsPerPx", 1], 46 ["layout.animation.prerender.partial", false], 47 ] }, 48 start); 49 50 function compare_property_state(a, b) { 51 if (a.property > b.property) { 52 return -1; 53 } else if (a.property < b.property) { 54 return 1; 55 } 56 if (a.runningOnCompositor != b.runningOnCompositor) { 57 return a.runningOnCompositor ? 1 : -1; 58 } 59 return a.warning > b.warning ? -1 : 1; 60 } 61 62 function assert_animation_property_state_equals(actual, expected) { 63 assert_equals(actual.length, expected.length, 'Number of properties'); 64 65 var sortedActual = actual.sort(compare_property_state); 66 var sortedExpected = expected.sort(compare_property_state); 67 68 for (var i = 0; i < sortedActual.length; i++) { 69 assert_equals(sortedActual[i].property, 70 sortedExpected[i].property, 71 'CSS property name should match'); 72 assert_equals(sortedActual[i].runningOnCompositor, 73 sortedExpected[i].runningOnCompositor, 74 'runningOnCompositor property should match'); 75 if (sortedExpected[i].warning instanceof RegExp) { 76 assert_regexp_match(sortedActual[i].warning, 77 sortedExpected[i].warning, 78 'warning message should match'); 79 } else if (sortedExpected[i].warning) { 80 assert_equals(sortedActual[i].warning, 81 gStringBundle.GetStringFromName(sortedExpected[i].warning), 82 'warning message should match'); 83 } 84 } 85 } 86 87 // Check that the animation is running on compositor and 88 // warning property is not set for the CSS property regardless 89 // expected values. 90 function assert_all_properties_running_on_compositor(actual, expected) { 91 assert_equals(actual.length, expected.length); 92 93 var sortedActual = actual.sort(compare_property_state); 94 var sortedExpected = expected.sort(compare_property_state); 95 96 for (var i = 0; i < sortedActual.length; i++) { 97 assert_equals(sortedActual[i].property, 98 sortedExpected[i].property, 99 'CSS property name should match'); 100 assert_true(sortedActual[i].runningOnCompositor, 101 'runningOnCompositor property should be true on ' + 102 sortedActual[i].property); 103 assert_not_exists(sortedActual[i], 'warning', 104 'warning property should not be set'); 105 } 106 } 107 108 function testBasicOperation() { 109 [ 110 { 111 desc: 'animations on compositor', 112 frames: { 113 opacity: [0, 1] 114 }, 115 expected: [ 116 { 117 property: 'opacity', 118 runningOnCompositor: true 119 } 120 ] 121 }, 122 { 123 desc: 'animations on main thread', 124 frames: { 125 zIndex: ['0', '999'] 126 }, 127 expected: [ 128 { 129 property: 'z-index', 130 runningOnCompositor: false 131 } 132 ] 133 }, 134 { 135 desc: 'animations on both threads', 136 frames: { 137 zIndex: ['0', '999'], 138 transform: ['translate(0px)', 'translate(100px)'] 139 }, 140 expected: [ 141 { 142 property: 'z-index', 143 runningOnCompositor: false 144 }, 145 { 146 property: 'transform', 147 runningOnCompositor: true 148 } 149 ] 150 }, 151 { 152 desc: 'two animation properties on compositor thread', 153 frames: { 154 opacity: [0, 1], 155 transform: ['translate(0px)', 'translate(100px)'] 156 }, 157 expected: [ 158 { 159 property: 'opacity', 160 runningOnCompositor: true 161 }, 162 { 163 property: 'transform', 164 runningOnCompositor: true 165 } 166 ] 167 }, 168 { 169 desc: 'two transform-like animation properties on compositor thread', 170 frames: { 171 transform: ['translate(0px)', 'translate(100px)'], 172 translate: ['0px', '100px'] 173 }, 174 expected: [ 175 { 176 property: 'transform', 177 runningOnCompositor: true 178 }, 179 { 180 property: 'translate', 181 runningOnCompositor: true 182 } 183 ] 184 }, 185 { 186 desc: 'opacity on compositor with animation of geometric properties', 187 frames: { 188 width: ['100px', '200px'], 189 opacity: [0, 1] 190 }, 191 expected: [ 192 { 193 property: 'width', 194 runningOnCompositor: false 195 }, 196 { 197 property: 'opacity', 198 runningOnCompositor: true 199 } 200 ] 201 }, 202 ].forEach(subtest => { 203 promise_test(async t => { 204 var animation = addDivAndAnimate(t, { class: 'compositable' }, 205 subtest.frames, 100 * MS_PER_SEC); 206 await waitForPaints(); 207 assert_animation_property_state_equals( 208 animation.effect.getProperties(), 209 subtest.expected); 210 }, subtest.desc); 211 }); 212 } 213 214 // Test adding/removing a 'width' property on the same animation object. 215 function testKeyframesWithGeometricProperties() { 216 [ 217 { 218 desc: 'transform', 219 frames: { 220 transform: ['translate(0px)', 'translate(100px)'] 221 }, 222 expected: { 223 withoutGeometric: [ 224 { 225 property: 'transform', 226 runningOnCompositor: true 227 } 228 ], 229 withGeometric: [ 230 { 231 property: 'width', 232 runningOnCompositor: false 233 }, 234 { 235 property: 'transform', 236 runningOnCompositor: true, 237 } 238 ] 239 } 240 }, 241 { 242 desc: 'translate', 243 frames: { 244 translate: ['0px', '100px'] 245 }, 246 expected: { 247 withoutGeometric: [ 248 { 249 property: 'translate', 250 runningOnCompositor: true 251 } 252 ], 253 withGeometric: [ 254 { 255 property: 'width', 256 runningOnCompositor: false 257 }, 258 { 259 property: 'translate', 260 runningOnCompositor: true, 261 } 262 ] 263 } 264 }, 265 { 266 desc: 'opacity and transform-like properties', 267 frames: { 268 opacity: [0, 1], 269 transform: ['translate(0px)', 'translate(100px)'], 270 translate: ['0px', '100px'] 271 }, 272 expected: { 273 withoutGeometric: [ 274 { 275 property: 'opacity', 276 runningOnCompositor: true 277 }, 278 { 279 property: 'transform', 280 runningOnCompositor: true 281 }, 282 { 283 property: 'translate', 284 runningOnCompositor: true 285 } 286 ], 287 withGeometric: [ 288 { 289 property: 'width', 290 runningOnCompositor: false 291 }, 292 { 293 property: 'opacity', 294 runningOnCompositor: true 295 }, 296 { 297 property: 'transform', 298 runningOnCompositor: true, 299 }, 300 { 301 property: 'translate', 302 runningOnCompositor: true, 303 } 304 ] 305 } 306 }, 307 ].forEach(subtest => { 308 promise_test(async t => { 309 var animation = addDivAndAnimate(t, { class: 'compositable' }, 310 subtest.frames, 100 * MS_PER_SEC); 311 await waitForPaints(); 312 313 // First, a transform animation is running on compositor. 314 assert_animation_property_state_equals( 315 animation.effect.getProperties(), 316 subtest.expected.withoutGeometric); 317 318 // Add a 'width' property. 319 var keyframes = animation.effect.getKeyframes(); 320 321 keyframes[0].width = '100px'; 322 keyframes[1].width = '200px'; 323 324 animation.effect.setKeyframes(keyframes); 325 await waitForFrame(); 326 327 // Now the transform animation is not running on compositor because of 328 // the 'width' property. 329 assert_animation_property_state_equals( 330 animation.effect.getProperties(), 331 subtest.expected.withGeometric); 332 333 // Remove the 'width' property. 334 var keyframes = animation.effect.getKeyframes(); 335 336 delete keyframes[0].width; 337 delete keyframes[1].width; 338 339 animation.effect.setKeyframes(keyframes); 340 await waitForFrame(); 341 342 // Finally, the transform animation is running on compositor. 343 assert_animation_property_state_equals( 344 animation.effect.getProperties(), 345 subtest.expected.withoutGeometric); 346 }, 'An animation has: ' + subtest.desc); 347 }); 348 } 349 350 // Test that the expected set of geometric properties all block transform 351 // animations. 352 function testSetOfGeometricProperties() { 353 const geometricProperties = [ 354 'width', 'height', 355 'top', 'right', 'bottom', 'left', 356 'margin-top', 'margin-right', 'margin-bottom', 'margin-left', 357 'padding-top', 'padding-right', 'padding-bottom', 'padding-left' 358 ]; 359 360 geometricProperties.forEach(property => { 361 promise_test(async t => { 362 const keyframes = { 363 [propertyToIDL(property)]: [ '100px', '200px' ], 364 transform: [ 'translate(0px)', 'translate(100px)' ] 365 }; 366 var animation = addDivAndAnimate(t, { class: 'compositable' }, 367 keyframes, 100 * MS_PER_SEC); 368 369 await waitForPaints(); 370 assert_animation_property_state_equals( 371 animation.effect.getProperties(), 372 [ 373 { 374 property, 375 runningOnCompositor: false 376 }, 377 { 378 property: 'transform', 379 runningOnCompositor: true, 380 } 381 ]); 382 }, `${property} is treated as a geometric property`); 383 }); 384 } 385 386 // Performance warning tests that set and clear a style property. 387 function testStyleChanges() { 388 [ 389 { 390 desc: 'preserve-3d transform', 391 frames: { 392 transform: ['translate(0px)', 'translate(100px)'] 393 }, 394 style: 'transform-style: preserve-3d', 395 expected: [ 396 { 397 property: 'transform', 398 runningOnCompositor: true, 399 } 400 ] 401 }, 402 { 403 desc: 'preserve-3d translate', 404 frames: { 405 translate: ['0px', '100px'] 406 }, 407 style: 'transform-style: preserve-3d', 408 expected: [ 409 { 410 property: 'translate', 411 runningOnCompositor: true, 412 } 413 ] 414 }, 415 { 416 desc: 'transform with backface-visibility:hidden', 417 frames: { 418 transform: ['translate(0px)', 'translate(100px)'] 419 }, 420 style: 'backface-visibility: hidden;', 421 expected: [ 422 { 423 property: 'transform', 424 runningOnCompositor: true, 425 } 426 ] 427 }, 428 { 429 desc: 'translate with backface-visibility:hidden', 430 frames: { 431 translate: ['0px', '100px'] 432 }, 433 style: 'backface-visibility: hidden;', 434 expected: [ 435 { 436 property: 'translate', 437 runningOnCompositor: true, 438 } 439 ] 440 }, 441 { 442 desc: 'opacity and transform-like properties with preserve-3d', 443 frames: { 444 opacity: [0, 1], 445 transform: ['translate(0px)', 'translate(100px)'], 446 translate: ['0px', '100px'] 447 }, 448 style: 'transform-style: preserve-3d', 449 expected: [ 450 { 451 property: 'opacity', 452 runningOnCompositor: true 453 }, 454 { 455 property: 'transform', 456 runningOnCompositor: true, 457 }, 458 { 459 property: 'translate', 460 runningOnCompositor: true, 461 } 462 ] 463 }, 464 { 465 desc: 'opacity and transform-like properties with ' + 466 'backface-visibility:hidden', 467 frames: { 468 opacity: [0, 1], 469 transform: ['translate(0px)', 'translate(100px)'], 470 translate: ['0px', '100px'] 471 }, 472 style: 'backface-visibility: hidden;', 473 expected: [ 474 { 475 property: 'opacity', 476 runningOnCompositor: true 477 }, 478 { 479 property: 'transform', 480 runningOnCompositor: true, 481 }, 482 { 483 property: 'translate', 484 runningOnCompositor: true, 485 } 486 ] 487 }, 488 ].forEach(subtest => { 489 promise_test(async t => { 490 var animation = addDivAndAnimate(t, { class: 'compositable' }, 491 subtest.frames, 100 * MS_PER_SEC); 492 await waitForPaints(); 493 assert_all_properties_running_on_compositor( 494 animation.effect.getProperties(), 495 subtest.expected); 496 animation.effect.target.style = subtest.style; 497 await waitForFrame(); 498 499 assert_animation_property_state_equals( 500 animation.effect.getProperties(), 501 subtest.expected); 502 animation.effect.target.style = ''; 503 await waitForFrame(); 504 505 assert_all_properties_running_on_compositor( 506 animation.effect.getProperties(), 507 subtest.expected); 508 }, subtest.desc); 509 }); 510 } 511 512 // Performance warning tests that set and clear the id property 513 function testIdChanges() { 514 [ 515 { 516 desc: 'moz-element referencing a transform', 517 frames: { 518 transform: ['translate(0px)', 'translate(100px)'] 519 }, 520 id: 'transformed', 521 createelement: 'width:100px; height:100px; background: -moz-element(#transformed)', 522 expected: [ 523 { 524 property: 'transform', 525 runningOnCompositor: false, 526 warning: 'CompositorAnimationWarningHasRenderingObserver' 527 } 528 ] 529 }, 530 { 531 desc: 'moz-element referencing a translate', 532 frames: { 533 translate: ['0px', '100px'] 534 }, 535 id: 'transformed', 536 createelement: 'width:100px; height:100px; background: -moz-element(#transformed)', 537 expected: [ 538 { 539 property: 'translate', 540 runningOnCompositor: false, 541 warning: 'CompositorAnimationWarningHasRenderingObserver' 542 } 543 ] 544 }, 545 { 546 desc: 'moz-element referencing a translate and transform', 547 frames: { 548 transform: ['translate(0px)', 'translate(100px)'], 549 translate: ['0px', '100px'] 550 }, 551 id: 'transformed', 552 createelement: 'width:100px; height:100px; background: -moz-element(#transformed)', 553 expected: [ 554 { 555 property: 'translate', 556 runningOnCompositor: false, 557 warning: 'CompositorAnimationWarningHasRenderingObserver' 558 }, 559 { 560 property: 'transform', 561 runningOnCompositor: false, 562 warning: 'CompositorAnimationWarningHasRenderingObserver' 563 } 564 ] 565 }, 566 ].forEach(subtest => { 567 promise_test(async t => { 568 if (subtest.createelement) { 569 addDiv(t, { style: subtest.createelement }); 570 } 571 572 var animation = addDivAndAnimate(t, { class: 'compositable' }, 573 subtest.frames, 100 * MS_PER_SEC); 574 await waitForPaints(); 575 576 assert_all_properties_running_on_compositor( 577 animation.effect.getProperties(), 578 subtest.expected); 579 animation.effect.target.id = subtest.id; 580 await waitForFrame(); 581 582 assert_animation_property_state_equals( 583 animation.effect.getProperties(), 584 subtest.expected); 585 animation.effect.target.id = ''; 586 await waitForFrame(); 587 588 assert_all_properties_running_on_compositor( 589 animation.effect.getProperties(), 590 subtest.expected); 591 }, subtest.desc); 592 }); 593 } 594 595 function testMultipleAnimations() { 596 [ 597 { 598 desc: 'opacity and transform-like properties with preserve-3d', 599 style: 'transform-style: preserve-3d', 600 animations: [ 601 { 602 frames: { 603 transform: ['translate(0px)', 'translate(100px)'] 604 }, 605 expected: [ 606 { 607 property: 'transform', 608 runningOnCompositor: true, 609 } 610 ] 611 }, 612 { 613 frames: { 614 translate: ['0px', '100px'] 615 }, 616 expected: [ 617 { 618 property: 'translate', 619 runningOnCompositor: true, 620 } 621 ] 622 }, 623 { 624 frames: { 625 opacity: [0, 1] 626 }, 627 expected: [ 628 { 629 property: 'opacity', 630 runningOnCompositor: true, 631 } 632 ] 633 } 634 ], 635 }, 636 { 637 desc: 'opacity and transform-like properties with ' + 638 'backface-visibility:hidden', 639 style: 'backface-visibility: hidden;', 640 animations: [ 641 { 642 frames: { 643 transform: ['translate(0px)', 'translate(100px)'] 644 }, 645 expected: [ 646 { 647 property: 'transform', 648 runningOnCompositor: true, 649 } 650 ] 651 }, 652 { 653 frames: { 654 translate: ['0px', '100px'] 655 }, 656 expected: [ 657 { 658 property: 'translate', 659 runningOnCompositor: true, 660 } 661 ] 662 }, 663 { 664 frames: { 665 opacity: [0, 1] 666 }, 667 expected: [ 668 { 669 property: 'opacity', 670 runningOnCompositor: true, 671 } 672 ] 673 } 674 ], 675 }, 676 ].forEach(subtest => { 677 promise_test(async t => { 678 var div = addDiv(t, { class: 'compositable' }); 679 var animations = subtest.animations.map(anim => { 680 var animation = div.animate(anim.frames, 100 * MS_PER_SEC); 681 682 // Bind expected values to animation object. 683 animation.expected = anim.expected; 684 return animation; 685 }); 686 await waitForPaints(); 687 688 animations.forEach(anim => { 689 assert_all_properties_running_on_compositor( 690 anim.effect.getProperties(), 691 anim.expected); 692 }); 693 div.style = subtest.style; 694 await waitForFrame(); 695 696 animations.forEach(anim => { 697 assert_animation_property_state_equals( 698 anim.effect.getProperties(), 699 anim.expected); 700 }); 701 div.style = ''; 702 await waitForFrame(); 703 704 animations.forEach(anim => { 705 assert_all_properties_running_on_compositor( 706 anim.effect.getProperties(), 707 anim.expected); 708 }); 709 }, 'Multiple animations: ' + subtest.desc); 710 }); 711 } 712 713 // Test adding/removing a 'width' keyframe on the same animation object, where 714 // multiple animation objects belong to the same element. 715 // The 'width' property is added to animations[1]. 716 function testMultipleAnimationsWithGeometricKeyframes() { 717 [ 718 { 719 desc: 'transform and opacity with geometric keyframes', 720 animations: [ 721 { 722 frames: { 723 transform: ['translate(0px)', 'translate(100px)'] 724 }, 725 expected: { 726 withoutGeometric: [ 727 { 728 property: 'transform', 729 runningOnCompositor: true 730 } 731 ], 732 withGeometric: [ 733 { 734 property: 'transform', 735 runningOnCompositor: true, 736 } 737 ] 738 } 739 }, 740 { 741 frames: { 742 opacity: [0, 1] 743 }, 744 expected: { 745 withoutGeometric: [ 746 { 747 property: 'opacity', 748 runningOnCompositor: true, 749 } 750 ], 751 withGeometric: [ 752 { 753 property: 'width', 754 runningOnCompositor: false, 755 }, 756 { 757 property: 'opacity', 758 runningOnCompositor: true, 759 } 760 ] 761 } 762 } 763 ], 764 }, 765 { 766 desc: 'opacity and transform with geometric keyframes', 767 animations: [ 768 { 769 frames: { 770 opacity: [0, 1] 771 }, 772 expected: { 773 withoutGeometric: [ 774 { 775 property: 'opacity', 776 runningOnCompositor: true, 777 } 778 ], 779 withGeometric: [ 780 { 781 property: 'opacity', 782 runningOnCompositor: true, 783 } 784 ] 785 } 786 }, 787 { 788 frames: { 789 transform: ['translate(0px)', 'translate(100px)'] 790 }, 791 expected: { 792 withoutGeometric: [ 793 { 794 property: 'transform', 795 runningOnCompositor: true 796 } 797 ], 798 withGeometric: [ 799 { 800 property: 'width', 801 runningOnCompositor: false, 802 }, 803 { 804 property: 'transform', 805 runningOnCompositor: true, 806 } 807 ] 808 } 809 } 810 ] 811 }, 812 { 813 desc: 'opacity and translate with geometric keyframes', 814 animations: [ 815 { 816 frames: { 817 opacity: [0, 1] 818 }, 819 expected: { 820 withoutGeometric: [ 821 { 822 property: 'opacity', 823 runningOnCompositor: true, 824 } 825 ], 826 withGeometric: [ 827 { 828 property: 'opacity', 829 runningOnCompositor: true, 830 } 831 ] 832 } 833 }, 834 { 835 frames: { 836 translate: ['0px', '100px'] 837 }, 838 expected: { 839 withoutGeometric: [ 840 { 841 property: 'translate', 842 runningOnCompositor: true 843 } 844 ], 845 withGeometric: [ 846 { 847 property: 'width', 848 runningOnCompositor: false, 849 }, 850 { 851 property: 'translate', 852 runningOnCompositor: true, 853 } 854 ] 855 } 856 } 857 ] 858 }, 859 ].forEach(subtest => { 860 promise_test(async t => { 861 var div = addDiv(t, { class: 'compositable' }); 862 var animations = subtest.animations.map(anim => { 863 var animation = div.animate(anim.frames, 100 * MS_PER_SEC); 864 865 // Bind expected values to animation object. 866 animation.expected = anim.expected; 867 return animation; 868 }); 869 await waitForPaints(); 870 // First, all animations are running on compositor. 871 animations.forEach(anim => { 872 assert_animation_property_state_equals( 873 anim.effect.getProperties(), 874 anim.expected.withoutGeometric); 875 }); 876 877 // Add a 'width' property to animations[1]. 878 var keyframes = animations[1].effect.getKeyframes(); 879 880 keyframes[0].width = '100px'; 881 keyframes[1].width = '200px'; 882 883 animations[1].effect.setKeyframes(keyframes); 884 await waitForFrame(); 885 886 // Now the transform animation is not running on compositor because of 887 // the 'width' property. 888 animations.forEach(anim => { 889 assert_animation_property_state_equals( 890 anim.effect.getProperties(), 891 anim.expected.withGeometric); 892 }); 893 894 // Remove the 'width' property from animations[1]. 895 var keyframes = animations[1].effect.getKeyframes(); 896 897 delete keyframes[0].width; 898 delete keyframes[1].width; 899 900 animations[1].effect.setKeyframes(keyframes); 901 await waitForFrame(); 902 903 // Finally, all animations are running on compositor. 904 animations.forEach(anim => { 905 assert_animation_property_state_equals( 906 anim.effect.getProperties(), 907 anim.expected.withoutGeometric); 908 }); 909 }, 'Multiple animations with geometric property: ' + subtest.desc); 910 }); 911 } 912 913 // Tests adding/removing 'width' animation on the same element which has async 914 // animations. 915 function testMultipleAnimationsWithGeometricAnimations() { 916 [ 917 { 918 desc: 'transform', 919 animations: [ 920 { 921 frames: { 922 transform: ['translate(0px)', 'translate(100px)'] 923 }, 924 expected: [ 925 { 926 property: 'transform', 927 runningOnCompositor: true, 928 } 929 ] 930 }, 931 ] 932 }, 933 { 934 desc: 'translate', 935 animations: [ 936 { 937 frames: { 938 translate: ['0px', '100px'] 939 }, 940 expected: [ 941 { 942 property: 'translate', 943 runningOnCompositor: true, 944 } 945 ] 946 }, 947 ] 948 }, 949 { 950 desc: 'opacity', 951 animations: [ 952 { 953 frames: { 954 opacity: [0, 1] 955 }, 956 expected: [ 957 { 958 property: 'opacity', 959 runningOnCompositor: true 960 } 961 ] 962 }, 963 ] 964 }, 965 { 966 desc: 'opacity, transform, and translate', 967 animations: [ 968 { 969 frames: { 970 transform: ['translate(0px)', 'translate(100px)'] 971 }, 972 expected: [ 973 { 974 property: 'transform', 975 runningOnCompositor: true, 976 } 977 ] 978 }, 979 { 980 frames: { 981 translate: ['0px', '100px'] 982 }, 983 expected: [ 984 { 985 property: 'translate', 986 runningOnCompositor: true, 987 } 988 ] 989 }, 990 { 991 frames: { 992 opacity: [0, 1] 993 }, 994 expected: [ 995 { 996 property: 'opacity', 997 runningOnCompositor: true, 998 } 999 ] 1000 } 1001 ], 1002 }, 1003 ].forEach(subtest => { 1004 promise_test(async t => { 1005 var div = addDiv(t, { class: 'compositable' }); 1006 var animations = subtest.animations.map(anim => { 1007 var animation = div.animate(anim.frames, 100 * MS_PER_SEC); 1008 1009 // Bind expected values to animation object. 1010 animation.expected = anim.expected; 1011 return animation; 1012 }); 1013 1014 var widthAnimation; 1015 1016 await waitForPaints(); 1017 animations.forEach(anim => { 1018 assert_all_properties_running_on_compositor( 1019 anim.effect.getProperties(), 1020 anim.expected); 1021 }); 1022 1023 // Append 'width' animation on the same element. 1024 widthAnimation = div.animate({ width: ['100px', '200px'] }, 1025 100 * MS_PER_SEC); 1026 await waitForFrame(); 1027 1028 // Now transform animations are not running on compositor because of 1029 // the 'width' animation. 1030 animations.forEach(anim => { 1031 assert_animation_property_state_equals( 1032 anim.effect.getProperties(), 1033 anim.expected); 1034 }); 1035 // Remove the 'width' animation. 1036 widthAnimation.cancel(); 1037 await waitForFrame(); 1038 1039 // Now all animations are running on compositor. 1040 animations.forEach(anim => { 1041 assert_all_properties_running_on_compositor( 1042 anim.effect.getProperties(), 1043 anim.expected); 1044 }); 1045 }, 'Multiple async animations and geometric animation: ' + subtest.desc); 1046 }); 1047 } 1048 1049 function testSmallElements() { 1050 [ 1051 { 1052 desc: 'opacity on small element', 1053 frames: { 1054 opacity: [0, 1] 1055 }, 1056 style: { style: 'width: 8px; height: 8px; background-color: red;' + 1057 // We need to set transform here to try creating an 1058 // individual frame for this opacity element. 1059 // Without this, this small element is created on the same 1060 // nsIFrame of mochitest iframe, i.e. the document which are 1061 // running this test, as a result the layer corresponding 1062 // to the frame is sent to compositor. 1063 'transform: translateX(100px);' }, 1064 expected: [ 1065 { 1066 property: 'opacity', 1067 runningOnCompositor: true 1068 } 1069 ] 1070 }, 1071 { 1072 desc: 'transform on small element', 1073 frames: { 1074 transform: ['translate(0px)', 'translate(100px)'] 1075 }, 1076 style: { style: 'width: 8px; height: 8px; background-color: red;' }, 1077 expected: [ 1078 { 1079 property: 'transform', 1080 runningOnCompositor: true 1081 } 1082 ] 1083 }, 1084 { 1085 desc: 'translate on small element', 1086 frames: { 1087 translate: ['0px', '100px'] 1088 }, 1089 style: { style: 'width: 8px; height: 8px; background-color: red;' }, 1090 expected: [ 1091 { 1092 property: 'translate', 1093 runningOnCompositor: true 1094 } 1095 ] 1096 }, 1097 ].forEach(subtest => { 1098 promise_test(async t => { 1099 var div = addDiv(t, subtest.style); 1100 var animation = div.animate(subtest.frames, 100 * MS_PER_SEC); 1101 await waitForPaints(); 1102 1103 assert_animation_property_state_equals( 1104 animation.effect.getProperties(), 1105 subtest.expected); 1106 }, subtest.desc); 1107 }); 1108 } 1109 1110 function testSynchronizedAnimations() { 1111 promise_test(async t => { 1112 const elemA = addDiv(t, { class: 'compositable' }); 1113 const elemB = addDiv(t, { class: 'compositable' }); 1114 1115 const animA = elemA.animate({ transform: [ 'translate(0px)', 1116 'translate(100px)' ] }, 1117 100 * MS_PER_SEC); 1118 const animB = elemB.animate({ marginLeft: [ '0px', '100px' ] }, 1119 100 * MS_PER_SEC); 1120 1121 await Promise.all([animA.ready, animB.ready]); 1122 await waitForPaints(); 1123 1124 assert_animation_property_state_equals( 1125 animA.effect.getProperties(), 1126 [ { property: 'transform', 1127 runningOnCompositor: true, 1128 } ]); 1129 }, 'Animations created within the same tick are synchronized' 1130 + ' (compositor animation created first)'); 1131 1132 promise_test(async t => { 1133 const elemA = addDiv(t, { class: 'compositable' }); 1134 const elemB = addDiv(t, { class: 'compositable' }); 1135 const elemC = addDiv(t, { class: 'compositable' }); 1136 1137 const animA = elemA.animate({ transform: [ 'translate(0px)', 1138 'translate(100px)' ] }, 1139 100 * MS_PER_SEC); 1140 const animB = elemB.animate({ translate: [ '0px', '100px' ] }, 1141 100 * MS_PER_SEC); 1142 const animC = elemC.animate({ marginLeft: [ '0px', '100px' ] }, 1143 100 * MS_PER_SEC); 1144 1145 await Promise.all([animA.ready, animB.ready, animC.ready]); 1146 await waitForPaints(); 1147 1148 assert_animation_property_state_equals( 1149 animA.effect.getProperties(), 1150 [ 1151 { property: 'transform', 1152 runningOnCompositor: true, 1153 } ]); 1154 assert_animation_property_state_equals( 1155 animB.effect.getProperties(), 1156 [ 1157 { property: 'translate', 1158 runningOnCompositor: true, 1159 } ]); 1160 }, 'Animations created within the same tick are synchronized' 1161 + ' (compositor animation created first/second)'); 1162 1163 promise_test(async t => { 1164 const elemA = addDiv(t, { class: 'compositable' }); 1165 const elemB = addDiv(t, { class: 'compositable' }); 1166 const elemC = addDiv(t, { class: 'compositable' }); 1167 1168 const animA = elemA.animate({ marginLeft: [ '0px', '100px' ] }, 1169 100 * MS_PER_SEC); 1170 const animB = elemB.animate({ transform: [ 'translate(0px)', 1171 'translate(100px)' ] }, 1172 100 * MS_PER_SEC); 1173 const animC = elemC.animate({ translate: [ '0px', '100px' ] }, 1174 100 * MS_PER_SEC); 1175 1176 await Promise.all([animA.ready, animB.ready, animC.ready]); 1177 await waitForPaints(); 1178 1179 assert_animation_property_state_equals( 1180 animB.effect.getProperties(), 1181 [ { property: 'transform', 1182 runningOnCompositor: true, 1183 } ]); 1184 assert_animation_property_state_equals( 1185 animC.effect.getProperties(), 1186 [ 1187 { property: 'translate', 1188 runningOnCompositor: true, 1189 } ]); 1190 }, 'Animations created within the same tick are synchronized' 1191 + ' (compositor animation created second/third)'); 1192 1193 promise_test(async t => { 1194 const attrs = { class: 'compositable', 1195 style: 'transition: all 100s' }; 1196 const elemA = addDiv(t, attrs); 1197 const elemB = addDiv(t, attrs); 1198 elemA.style.transform = 'translate(0px)'; 1199 elemB.style.marginLeft = '0px'; 1200 getComputedStyle(elemA).transform; 1201 getComputedStyle(elemB).marginLeft; 1202 1203 // Generally the sequence of steps is as follows: 1204 // 1205 // Tick -> requestAnimationFrame -> Style -> Paint -> Events (-> Tick...) 1206 // 1207 // In this test we want to set up two transitions during the "Events" 1208 // stage but only flush style for one such that the second one is actually 1209 // generated during the "Style" stage of the *next* tick. 1210 // 1211 // Web content often generates transitions in this way (that is, it doesn't 1212 // pay regard to when style is flushed and nor should it). However, we 1213 // still want transitions generated in this way to be synchronized. 1214 let timeForFirstFrame; 1215 await waitForIdle(); 1216 1217 timeForFirstFrame = document.timeline.currentTime; 1218 elemA.style.transform = 'translate(100px)'; 1219 // Flush style to trigger first transition 1220 getComputedStyle(elemA).transform; 1221 elemB.style.marginLeft = '100px'; 1222 // DON'T flush style here (this includes calling getAnimations!) 1223 await waitForFrame(); 1224 1225 assert_not_equals(timeForFirstFrame, document.timeline.currentTime, 1226 'Should be on the other side of a tick'); 1227 // Wait another tick so we can let the transition be started 1228 // by regular style resolution. 1229 await waitForFrame(); 1230 1231 const transitionA = elemA.getAnimations()[0]; 1232 assert_animation_property_state_equals( 1233 transitionA.effect.getProperties(), 1234 [ { property: 'transform', 1235 runningOnCompositor: true, 1236 } ]); 1237 }, 'Transitions created before and after a tick are synchronized'); 1238 1239 promise_test(async t => { 1240 const elemA = addDiv(t, { class: 'compositable' }); 1241 const elemB = addDiv(t, { class: 'compositable' }); 1242 1243 const animA = elemA.animate({ transform: [ 'translate(0px)', 1244 'translate(100px)' ], 1245 opacity: [ 0, 1 ] }, 1246 100 * MS_PER_SEC); 1247 const animB = elemB.animate({ marginLeft: [ '0px', '100px' ] }, 1248 100 * MS_PER_SEC); 1249 1250 await Promise.all([animA.ready, animB.ready]); 1251 await waitForPaints(); 1252 1253 assert_animation_property_state_equals( 1254 animA.effect.getProperties(), 1255 [ { property: 'transform', 1256 runningOnCompositor: true, 1257 }, 1258 { property: 'opacity', 1259 runningOnCompositor: true 1260 } ]); 1261 }, 'Opacity animations on the same element continue running on the' 1262 + ' compositor when transform animations are synchronized with geometric' 1263 + ' animations'); 1264 1265 promise_test(async t => { 1266 const transitionElem = addDiv(t, { 1267 style: 'margin-left: 0px; transition: margin-left 100s', 1268 }); 1269 getComputedStyle(transitionElem).marginLeft; 1270 1271 await waitForFrame(); 1272 1273 transitionElem.style.marginLeft = '100px'; 1274 const cssTransition = transitionElem.getAnimations()[0]; 1275 1276 const animationElem = addDiv(t, { 1277 class: 'compositable', 1278 style: 'animation: translate 100s', 1279 }); 1280 const cssAnimation = animationElem.getAnimations()[0]; 1281 1282 await Promise.all([cssTransition.ready, cssAnimation.ready]); 1283 await waitForPaints(); 1284 1285 assert_animation_property_state_equals(cssAnimation.effect.getProperties(), 1286 [{ property: 'transform', 1287 runningOnCompositor: true }]); 1288 }, 'CSS Animations are NOT synchronized with CSS Transitions'); 1289 1290 promise_test(async t => { 1291 const elemA = addDiv(t, { class: 'compositable' }); 1292 const elemB = addDiv(t, { class: 'compositable' }); 1293 1294 const animA = elemA.animate({ marginLeft: [ '0px', '100px' ] }, 1295 100 * MS_PER_SEC); 1296 await animA.ready; 1297 await waitForPaints(); 1298 1299 let animB = elemB.animate({ transform: [ 'translate(0px)', 1300 'translate(100px)' ] }, 1301 100 * MS_PER_SEC); 1302 await animB.ready; 1303 await waitForPaints(); 1304 1305 assert_animation_property_state_equals( 1306 animB.effect.getProperties(), 1307 [ { property: 'transform', 1308 runningOnCompositor: true } ]); 1309 }, 'Transform animations are NOT synchronized with geometric animations' 1310 + ' started in the previous frame'); 1311 1312 promise_test(async t => { 1313 const elemA = addDiv(t, { class: 'compositable' }); 1314 const elemB = addDiv(t, { class: 'compositable' }); 1315 1316 const animA = elemA.animate({ transform: [ 'translate(0px)', 1317 'translate(100px)' ] }, 1318 100 * MS_PER_SEC); 1319 await animA.ready; 1320 await waitForPaints(); 1321 1322 let animB = elemB.animate({ marginLeft: [ '0px', '100px' ] }, 1323 100 * MS_PER_SEC); 1324 await animB.ready; 1325 await waitForPaints(); 1326 1327 assert_animation_property_state_equals( 1328 animA.effect.getProperties(), 1329 [ { property: 'transform', 1330 runningOnCompositor: true } ]); 1331 }, 'Transform animations are NOT synchronized with geometric animations' 1332 + ' started in the next frame'); 1333 1334 promise_test(async t => { 1335 const elemA = addDiv(t, { class: 'compositable' }); 1336 const elemB = addDiv(t, { class: 'compositable' }); 1337 1338 const animA = elemA.animate({ transform: [ 'translate(0px)', 1339 'translate(100px)' ] }, 1340 100 * MS_PER_SEC); 1341 const animB = elemB.animate({ marginLeft: [ '0px', '100px' ] }, 1342 100 * MS_PER_SEC); 1343 animB.pause(); 1344 1345 await animA.ready; 1346 await waitForPaints(); 1347 1348 assert_animation_property_state_equals( 1349 animA.effect.getProperties(), 1350 [ { property: 'transform', runningOnCompositor: true } ]); 1351 }, 'Paused animations are not synchronized'); 1352 1353 promise_test(async t => { 1354 const elemA = addDiv(t, { class: 'compositable' }); 1355 const elemB = addDiv(t, { class: 'compositable' }); 1356 1357 const animA = elemA.animate({ transform: [ 'translate(0px)', 1358 'translate(100px)' ] }, 1359 100 * MS_PER_SEC); 1360 const animB = elemB.animate({ marginLeft: [ '0px', '100px' ] }, 1361 100 * MS_PER_SEC); 1362 1363 // Seek one of the animations so that their start times will differ 1364 animA.currentTime = 5000; 1365 1366 await Promise.all([animA.ready, animB.ready]); 1367 await waitForPaints(); 1368 1369 assert_not_equals(animA.startTime, animB.startTime, 1370 'Animations should have different start times'); 1371 assert_animation_property_state_equals( 1372 animA.effect.getProperties(), 1373 [ { property: 'transform', 1374 runningOnCompositor: true, 1375 } ]); 1376 }, 'Animations are synchronized based on when they are started' 1377 + ' and NOT their start time'); 1378 1379 promise_test(async t => { 1380 const elemA = addDiv(t, { class: 'compositable' }); 1381 const elemB = addDiv(t, { class: 'compositable' }); 1382 1383 const animA = elemA.animate({ transform: [ 'translate(0px)', 1384 'translate(100px)' ] }, 1385 100 * MS_PER_SEC); 1386 const animB = elemB.animate({ marginLeft: [ '0px', '100px' ] }, 1387 100 * MS_PER_SEC); 1388 1389 await Promise.all([animA.ready, animB.ready]); 1390 await waitForPaints(); 1391 1392 assert_animation_property_state_equals( 1393 animA.effect.getProperties(), 1394 [ { property: 'transform', 1395 runningOnCompositor: true } ]); 1396 // Restart animation 1397 animA.pause(); 1398 animA.play(); 1399 await animA.ready; 1400 await waitForPaints(); 1401 1402 assert_animation_property_state_equals( 1403 animA.effect.getProperties(), 1404 [ { property: 'transform', 1405 runningOnCompositor: true } ]); 1406 }, 'An initially synchronized animation may be unsynchronized if restarted'); 1407 1408 promise_test(async t => { 1409 const elemA = addDiv(t, { class: 'compositable' }); 1410 const elemB = addDiv(t, { class: 'compositable' }); 1411 1412 const animA = elemA.animate({ transform: [ 'translate(0px)', 1413 'translate(100px)' ] }, 1414 100 * MS_PER_SEC); 1415 const animB = elemB.animate({ marginLeft: [ '0px', '100px' ] }, 1416 100 * MS_PER_SEC); 1417 1418 // Clear target effect 1419 animB.effect.target = null; 1420 1421 await Promise.all([animA.ready, animB.ready]); 1422 await waitForPaints(); 1423 1424 assert_animation_property_state_equals( 1425 animA.effect.getProperties(), 1426 [ { property: 'transform', 1427 runningOnCompositor: true } ]); 1428 }, 'A geometric animation with no target element is not synchronized'); 1429 } 1430 1431 function testTooLargeFrame() { 1432 [ 1433 { 1434 property: 'transform', 1435 frames: { transform: ['translate(0px)', 'translate(100px)'] }, 1436 }, 1437 { 1438 property: 'translate', 1439 frames: { translate: ['0px', '100px'] }, 1440 }, 1441 ].forEach(subtest => { 1442 promise_test(async t => { 1443 var animation = addDivAndAnimate(t, 1444 { class: 'compositable' }, 1445 subtest.frames, 1446 100 * MS_PER_SEC); 1447 await waitForPaints(); 1448 1449 assert_animation_property_state_equals( 1450 animation.effect.getProperties(), 1451 [ { property: subtest.property, runningOnCompositor: true } ]); 1452 animation.effect.target.style = 'width: 10000px; height: 10000px'; 1453 await waitForFrame(); 1454 1455 // viewport depends on test environment. 1456 var expectedWarning = new RegExp( 1457 "Animation cannot be run on the compositor because the area of the frame " + 1458 "\\(\\d+\\) is too large relative to the viewport " + 1459 "\\(larger than \\d+\\)"); 1460 assert_animation_property_state_equals( 1461 animation.effect.getProperties(), 1462 [ { 1463 property: subtest.property, 1464 runningOnCompositor: false, 1465 warning: expectedWarning 1466 } ]); 1467 animation.effect.target.style = 'width: 100px; height: 100px'; 1468 await waitForFrame(); 1469 1470 // With WebRender we appear to stick to the previous layerization decision 1471 // after changing the bounds back to a smaller object. 1472 const isWebRender = 1473 SpecialPowers.DOMWindowUtils.layerManagerType.startsWith('WebRender'); 1474 assert_animation_property_state_equals( 1475 animation.effect.getProperties(), 1476 [ { property: subtest.property, runningOnCompositor: !isWebRender } ]); 1477 }, subtest.property + ' on too big element - area'); 1478 1479 promise_test(async t => { 1480 var animation = addDivAndAnimate(t, 1481 { class: 'compositable' }, 1482 subtest.frames, 1483 100 * MS_PER_SEC); 1484 await waitForPaints(); 1485 1486 assert_animation_property_state_equals( 1487 animation.effect.getProperties(), 1488 [ { property: subtest.property, runningOnCompositor: true } ]); 1489 animation.effect.target.style = 'width: 20000px; height: 1px'; 1490 await waitForFrame(); 1491 1492 // viewport depends on test environment. 1493 var expectedWarning = new RegExp( 1494 "Animation cannot be run on the compositor because the frame size " + 1495 "\\(20000, 1\\) is too large relative to the viewport " + 1496 "\\(larger than \\(\\d+, \\d+\\)\\) or larger than the " + 1497 "maximum allowed value \\(\\d+, \\d+\\)"); 1498 assert_animation_property_state_equals( 1499 animation.effect.getProperties(), 1500 [ { 1501 property: subtest.property, 1502 runningOnCompositor: false, 1503 warning: expectedWarning 1504 } ]); 1505 animation.effect.target.style = 'width: 100px; height: 100px'; 1506 await waitForFrame(); 1507 1508 const isWebRender = 1509 SpecialPowers.DOMWindowUtils.layerManagerType.startsWith('WebRender'); 1510 assert_animation_property_state_equals( 1511 animation.effect.getProperties(), 1512 [ { property: subtest.property, runningOnCompositor: !isWebRender } ]); 1513 }, subtest.property + ' on too big element - dimensions'); 1514 }); 1515 } 1516 1517 function testTransformSVG() { 1518 [ 1519 { 1520 property: 'transform', 1521 frames: { transform: ['translate(0px)', 'translate(100px)'] }, 1522 }, 1523 { 1524 property: 'translate', 1525 frames: { translate: ['0px', '100px'] }, 1526 }, 1527 { 1528 property: 'rotate', 1529 frames: { rotate: ['0deg', '45deg'] }, 1530 }, 1531 { 1532 property: 'scale', 1533 frames: { scale: ['1', '2'] }, 1534 }, 1535 ].forEach(subtest => { 1536 promise_test(async t => { 1537 var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); 1538 svg.setAttribute('width', '100'); 1539 svg.setAttribute('height', '100'); 1540 var rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect'); 1541 rect.setAttribute('width', '100'); 1542 rect.setAttribute('height', '100'); 1543 rect.setAttribute('fill', 'red'); 1544 svg.appendChild(rect); 1545 document.body.appendChild(svg); 1546 t.add_cleanup(() => { 1547 svg.remove(); 1548 }); 1549 1550 var animation = svg.animate(subtest.frames, 100 * MS_PER_SEC); 1551 await waitForPaints(); 1552 1553 assert_animation_property_state_equals( 1554 animation.effect.getProperties(), 1555 [ { property: subtest.property, runningOnCompositor: true } ]); 1556 svg.setAttribute('transform', 'translate(10, 20)'); 1557 await waitForFrame(); 1558 1559 assert_animation_property_state_equals( 1560 animation.effect.getProperties(), 1561 [ { 1562 property: subtest.property, 1563 runningOnCompositor: true, 1564 } ]); 1565 svg.removeAttribute('transform'); 1566 await waitForFrame(); 1567 1568 assert_animation_property_state_equals( 1569 animation.effect.getProperties(), 1570 [ { property: subtest.property, runningOnCompositor: true } ]); 1571 }, subtest.property + ' of nsIFrame with SVG transform'); 1572 }); 1573 } 1574 1575 function testImportantRuleOverride() { 1576 promise_test(async t => { 1577 const elem = addDiv(t, { class: 'compositable' }); 1578 const anim = elem.animate({ translate: [ '0px', '100px' ], 1579 rotate: ['0deg', '90deg'] }, 1580 100 * MS_PER_SEC); 1581 1582 await waitForAnimationReadyToRestyle(anim); 1583 await waitForPaints(); 1584 1585 assert_animation_property_state_equals( 1586 anim.effect.getProperties(), 1587 [ { property: 'translate', runningOnCompositor: true }, 1588 { property: 'rotate', runningOnCompositor: true } ] 1589 ); 1590 1591 elem.style.setProperty('rotate', '45deg', 'important'); 1592 getComputedStyle(elem).rotate; 1593 1594 await waitForFrame(); 1595 1596 assert_animation_property_state_equals( 1597 anim.effect.getProperties(), 1598 [ 1599 { 1600 property: 'translate', 1601 runningOnCompositor: false, 1602 warning: 1603 'CompositorAnimationWarningTransformIsBlockedByImportantRules' 1604 }, 1605 { 1606 property: 'rotate', 1607 runningOnCompositor: false, 1608 warning: 1609 'CompositorAnimationWarningTransformIsBlockedByImportantRules' 1610 }, 1611 ] 1612 ); 1613 }, 'The animations of transform-like properties are not running on the ' + 1614 'compositor because any of the properties has important rules'); 1615 } 1616 1617 function testCurrentColor() { 1618 if (SpecialPowers.DOMWindowUtils.layerManagerType.startsWith('WebRender')) { 1619 return; // skip this test until bug 1510030 landed. 1620 } 1621 promise_test(async t => { 1622 const animation = addDivAndAnimate(t, { class: 'compositable' }, 1623 { backgroundColor: [ 'currentColor', 1624 'red' ] }, 1625 100 * MS_PER_SEC); 1626 await waitForPaints(); 1627 1628 assert_animation_property_state_equals( 1629 animation.effect.getProperties(), 1630 [ { property: 'background-color', 1631 runningOnCompositor: false, 1632 warning: 'CompositorAnimationWarningHasCurrentColor' 1633 } ]); 1634 }, 'Background color animations with `current-color` don\'t run on the ' 1635 + 'compositor'); 1636 } 1637 1638 function start() { 1639 var bundleService = SpecialPowers.Cc['@mozilla.org/intl/stringbundle;1'] 1640 .getService(SpecialPowers.Ci.nsIStringBundleService); 1641 gStringBundle = bundleService 1642 .createBundle("chrome://global/locale/layout_errors.properties"); 1643 1644 testBasicOperation(); 1645 testKeyframesWithGeometricProperties(); 1646 testSetOfGeometricProperties(); 1647 testStyleChanges(); 1648 testIdChanges(); 1649 testMultipleAnimations(); 1650 testMultipleAnimationsWithGeometricKeyframes(); 1651 testMultipleAnimationsWithGeometricAnimations(); 1652 testSmallElements(); 1653 testSynchronizedAnimations(); 1654 testTooLargeFrame(); 1655 testTransformSVG(); 1656 testImportantRuleOverride(); 1657 testCurrentColor(); 1658 1659 promise_test(async t => { 1660 var div = addDiv(t, { class: 'compositable', 1661 style: 'animation: fade 100s' }); 1662 var cssAnimation = div.getAnimations()[0]; 1663 var scriptAnimation = div.animate({ opacity: [ 1, 0 ] }, 100 * MS_PER_SEC); 1664 1665 await waitForPaints(); 1666 assert_animation_property_state_equals( 1667 cssAnimation.effect.getProperties(), 1668 [ { property: 'opacity', runningOnCompositor: true } ]); 1669 assert_animation_property_state_equals( 1670 scriptAnimation.effect.getProperties(), 1671 [ { property: 'opacity', runningOnCompositor: true } ]); 1672 }, 'overridden animation'); 1673 1674 promise_test(async t => { 1675 const keyframes = { 1676 width: [ '100px', '200px' ], 1677 transform: [ 'translate(0px)', 'translate(100px)' ], 1678 "--foo": ["--bar", "--baz"], 1679 }; 1680 const animation = addDivAndAnimate(t, { class: 'compositable' }, 1681 keyframes, 100 * MS_PER_SEC); 1682 await waitForPaints(); 1683 1684 assert_true(true, "Didn't crash"); 1685 }, 'Warning with custom props'); 1686 1687 done(); 1688 } 1689 1690 </script> 1691 1692 </body>