test_running_on_compositor.html (63477B)
1 <!doctype html> 2 <head> 3 <meta charset=utf-8> 4 <title>Bug 1045994 - Add a chrome-only property to inspect if an animation is 5 running on the compositor or not</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 @keyframes anim { 11 to { transform: translate(100px) } 12 } 13 @keyframes transform-starts-with-none { 14 0% { transform: none } 15 99% { transform: none } 16 100% { transform: translate(100px) } 17 } 18 @keyframes opacity { 19 to { opacity: 0 } 20 } 21 @keyframes zIndex_and_translate { 22 to { z-index: 999; transform: translate(100px); } 23 } 24 @keyframes z-index { 25 to { z-index: 999; } 26 } 27 @keyframes rotate { 28 from { transform: rotate(0deg); } 29 to { transform: rotate(360deg); } 30 } 31 @keyframes rotate-and-opacity { 32 from { transform: rotate(0deg); opacity: 1;} 33 to { transform: rotate(360deg); opacity: 0;} 34 } 35 div { 36 /* Element needs geometry to be eligible for layerization */ 37 width: 100px; 38 height: 100px; 39 background-color: white; 40 } 41 </style> 42 </head> 43 <body> 44 <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1045994" 45 target="_blank">Mozilla Bug 1045994</a> 46 <div id="log"></div> 47 <script> 48 'use strict'; 49 50 /** 51 * Test for bug 1045994 - Add a chrome-only property to inspect if an 52 * animation is running on the compositor or not 53 */ 54 55 const omtaEnabled = isOMTAEnabled(); 56 57 function assert_animation_is_running_on_compositor(animation, desc) { 58 assert_equals(animation.isRunningOnCompositor, omtaEnabled, 59 desc + ' at ' + animation.currentTime + 'ms'); 60 } 61 62 function assert_animation_is_not_running_on_compositor(animation, desc) { 63 assert_equals(animation.isRunningOnCompositor, false, 64 desc + ' at ' + animation.currentTime + 'ms'); 65 } 66 67 promise_test(async t => { 68 // FIXME: When we implement Element.animate, use that here instead of CSS 69 // so that we remove any dependency on the CSS mapping. 70 var div = addDiv(t, { style: 'animation: anim 100s' }); 71 var animation = div.getAnimations()[0]; 72 73 // If the animation starts at the current timeline time, we need to wait for 74 // one more frame to avoid receiving the fake timer-based MozAfterPaint event. 75 // FIXME: Bug 1419226: Drop this 'animation.ready' and 'waitForFrame'. Once 76 // MozAfterPaint is fired reliably, we just need to wait for a MozAfterPaint 77 // here. 78 await animation.ready; 79 80 if (animationStartsRightNow(animation)) { 81 await waitForNextFrame(); 82 } 83 84 await waitForPaints(); 85 86 assert_animation_is_running_on_compositor(animation, 87 'Animation reports that it is running on the compositor' 88 + ' during playback'); 89 90 div.style.animationPlayState = 'paused'; 91 92 await animation.ready; 93 await waitForPaints(); 94 95 assert_animation_is_not_running_on_compositor(animation, 96 'Animation reports that it is NOT running on the compositor' 97 + ' when paused'); 98 }, 'Basic test'); 99 100 promise_test(async t => { 101 var div = addDiv(t, { style: 'animation: z-index 100s' }); 102 var animation = div.getAnimations()[0]; 103 104 await waitForPaints(); 105 106 assert_animation_is_not_running_on_compositor(animation, 107 'Animation reports that it is NOT running on the compositor' 108 + ' for animation of "z-index"'); 109 }, 'isRunningOnCompositor is false for animation of "z-index"'); 110 111 promise_test(async t => { 112 var div = addDiv(t, { style: 'animation: zIndex_and_translate 100s' }); 113 var animation = div.getAnimations()[0]; 114 115 await waitForPaints(); 116 117 assert_animation_is_running_on_compositor(animation, 118 'Animation reports that it is running on the compositor' 119 + ' when the animation has two properties, where one can run' 120 + ' on the compositor, the other cannot'); 121 }, 'isRunningOnCompositor is true if the animation has at least one ' + 122 'property can run on compositor'); 123 124 promise_test(async t => { 125 var div = addDiv(t, { style: 'animation: anim 100s' }); 126 var animation = div.getAnimations()[0]; 127 128 await waitForPaints(); 129 130 animation.pause(); 131 await animation.ready; 132 133 await waitForPaints(); 134 135 assert_animation_is_not_running_on_compositor(animation, 136 'Animation reports that it is NOT running on the compositor' 137 + ' when animation.pause() is called'); 138 }, 'isRunningOnCompositor is false when the animation.pause() is called'); 139 140 promise_test(async t => { 141 var div = addDiv(t, { style: 'animation: anim 100s' }); 142 var animation = div.getAnimations()[0]; 143 144 await waitForPaints(); 145 146 animation.finish(); 147 assert_animation_is_not_running_on_compositor(animation, 148 'Animation reports that it is NOT running on the compositor' 149 + ' immediately after animation.finish() is called'); 150 // Check that we don't set the flag back again on the next tick. 151 await waitForFrame(); 152 153 assert_animation_is_not_running_on_compositor(animation, 154 'Animation reports that it is NOT running on the compositor' 155 + ' on the next tick after animation.finish() is called'); 156 }, 'isRunningOnCompositor is false when the animation.finish() is called'); 157 158 promise_test(async t => { 159 var div = addDiv(t, { style: 'animation: anim 100s' }); 160 var animation = div.getAnimations()[0]; 161 162 await waitForPaints(); 163 164 animation.currentTime = 100 * MS_PER_SEC; 165 assert_animation_is_not_running_on_compositor(animation, 166 'Animation reports that it is NOT running on the compositor' 167 + ' immediately after manually seeking the animation to the end'); 168 // Check that we don't set the flag back again on the next tick. 169 await waitForFrame(); 170 171 assert_animation_is_not_running_on_compositor(animation, 172 'Animation reports that it is NOT running on the compositor' 173 + ' on the next tick after manually seeking the animation to the end'); 174 }, 'isRunningOnCompositor is false when manually seeking the animation to ' + 175 'the end'); 176 177 promise_test(async t => { 178 var div = addDiv(t, { style: 'animation: anim 100s' }); 179 var animation = div.getAnimations()[0]; 180 181 await waitForPaints(); 182 183 animation.cancel(); 184 assert_animation_is_not_running_on_compositor(animation, 185 'Animation reports that it is NOT running on the compositor' 186 + ' immediately after animation.cancel() is called'); 187 // Check that we don't set the flag back again on the next tick. 188 await waitForFrame(); 189 190 assert_animation_is_not_running_on_compositor(animation, 191 'Animation reports that it is NOT running on the compositor' 192 + ' on the next tick after animation.cancel() is called'); 193 }, 'isRunningOnCompositor is false when animation.cancel() is called'); 194 195 // This is to test that we don't simply clobber the flag when ticking 196 // animations and then set it again during painting. 197 promise_test(async t => { 198 var div = addDiv(t, { style: 'animation: anim 100s' }); 199 var animation = div.getAnimations()[0]; 200 201 await waitForPaints(); 202 203 await new Promise(resolve => { 204 window.requestAnimationFrame(() => { 205 t.step(() => { 206 assert_animation_is_running_on_compositor(animation, 207 'Animation reports that it is running on the compositor' 208 + ' in requestAnimationFrame callback'); 209 }); 210 211 resolve(); 212 }); 213 }); 214 }, 'isRunningOnCompositor is true in requestAnimationFrame callback'); 215 216 promise_test(async t => { 217 var div = addDiv(t, { style: 'animation: anim 100s' }); 218 var animation = div.getAnimations()[0]; 219 220 await waitForPaints(); 221 222 await new Promise(resolve => { 223 var observer = new MutationObserver(records => { 224 var changedAnimation; 225 226 records.forEach(record => { 227 changedAnimation = 228 record.changedAnimations.find(changedAnim => { 229 return changedAnim == animation; 230 }); 231 }); 232 233 t.step(() => { 234 assert_true(!!changedAnimation, 'The animation should be recorded ' 235 + 'as one of the changedAnimations'); 236 237 assert_animation_is_running_on_compositor(animation, 238 'Animation reports that it is running on the compositor' 239 + ' in MutationObserver callback'); 240 }); 241 242 resolve(); 243 }); 244 observer.observe(div, { animations: true, subtree: false }); 245 t.add_cleanup(() => { 246 observer.disconnect(); 247 }); 248 div.style.animationDuration = "200s"; 249 }); 250 }, 'isRunningOnCompositor is true in MutationObserver callback'); 251 252 // This is to test that we don't temporarily clear the flag when forcing 253 // an unthrottled sample. 254 promise_test(async t => { 255 var div = addDiv(t, { style: 'animation: rotate 100s' }); 256 var animation = div.getAnimations()[0]; 257 258 await waitForPaints(); 259 260 await new Promise(resolve => { 261 var timeAtStart = window.performance.now(); 262 function handleFrame() { 263 t.step(() => { 264 assert_animation_is_running_on_compositor(animation, 265 'Animation reports that it is running on the compositor' 266 + ' in requestAnimationFrame callback'); 267 }); 268 269 // we have to wait at least 200ms because this animation is 270 // unthrottled on every 200ms. 271 // See https://hg.mozilla.org/mozilla-central/file/cafb1c90f794/layout/style/AnimationCommon.cpp#l863 272 if (window.performance.now() - timeAtStart > 200) { 273 resolve(); 274 return; 275 } 276 window.requestAnimationFrame(handleFrame); 277 } 278 window.requestAnimationFrame(handleFrame); 279 }); 280 }, 'isRunningOnCompositor remains true in requestAnimationFrameCallback for ' + 281 'overflow animation'); 282 283 promise_test(async t => { 284 var div = addDiv(t, { style: 'transition: opacity 100s; opacity: 1' }); 285 286 getComputedStyle(div).opacity; 287 288 div.style.opacity = 0; 289 var animation = div.getAnimations()[0]; 290 291 await waitForPaints(); 292 293 assert_animation_is_running_on_compositor(animation, 294 'Transition reports that it is running on the compositor' 295 + ' during playback for opacity transition'); 296 }, 'isRunningOnCompositor for transitions'); 297 298 promise_test(async t => { 299 var div = addDiv(t, { style: 'animation: rotate-and-opacity 100s; ' + 300 'backface-visibility: hidden; ' + 301 'transform: none !important;' }); 302 var animation = div.getAnimations()[0]; 303 304 await waitForPaints(); 305 306 assert_animation_is_running_on_compositor(animation, 307 'If an animation has a property that can run on the compositor and a ' 308 + 'property that cannot (due to Gecko limitations) but where the latter' 309 + 'property is overridden in the CSS cascade, the animation should ' 310 + 'still report that it is running on the compositor'); 311 }, 'isRunningOnCompositor is true when a property that would otherwise block ' + 312 'running on the compositor is overridden in the CSS cascade'); 313 314 promise_test(async t => { 315 var animation = addDivAndAnimate(t, 316 {}, 317 { opacity: [ 0, 1 ] }, 200 * MS_PER_SEC); 318 319 await waitForPaints(); 320 321 assert_animation_is_running_on_compositor(animation, 322 'Animation reports that it is running on the compositor'); 323 324 animation.currentTime = 150 * MS_PER_SEC; 325 animation.effect.updateTiming({ duration: 100 * MS_PER_SEC }); 326 327 assert_animation_is_not_running_on_compositor(animation, 328 'Animation reports that it is NOT running on the compositor' 329 + ' when the animation is set a shorter duration than current time'); 330 }, 'animation is immediately removed from compositor' + 331 'when the duration is made shorter than the current time'); 332 333 promise_test(async t => { 334 var animation = addDivAndAnimate(t, 335 {}, 336 { opacity: [ 0, 1 ] }, 100 * MS_PER_SEC); 337 338 await waitForPaints(); 339 340 assert_animation_is_running_on_compositor(animation, 341 'Animation reports that it is running on the compositor'); 342 343 animation.currentTime = 500 * MS_PER_SEC; 344 345 assert_animation_is_not_running_on_compositor(animation, 346 'Animation reports that it is NOT running on the compositor' 347 + ' when finished'); 348 349 animation.effect.updateTiming({ duration: 1000 * MS_PER_SEC }); 350 await waitForFrame(); 351 352 assert_animation_is_running_on_compositor(animation, 353 'Animation reports that it is running on the compositor' 354 + ' when restarted'); 355 }, 'animation is added to compositor' + 356 ' when the duration is made longer than the current time'); 357 358 promise_test(async t => { 359 var animation = addDivAndAnimate(t, 360 {}, 361 { opacity: [ 0, 1 ] }, 100 * MS_PER_SEC); 362 363 await waitForPaints(); 364 365 assert_animation_is_running_on_compositor(animation, 366 'Animation reports that it is running on the compositor'); 367 368 animation.effect.updateTiming({ endDelay: 100 * MS_PER_SEC }); 369 370 assert_animation_is_running_on_compositor(animation, 371 'Animation reports that it is running on the compositor' 372 + ' when endDelay is changed'); 373 374 animation.currentTime = 110 * MS_PER_SEC; 375 await waitForFrame(); 376 377 assert_animation_is_not_running_on_compositor(animation, 378 'Animation reports that it is NOT running on the compositor' 379 + ' when currentTime is during endDelay'); 380 }, 'animation is removed from compositor' + 381 ' when current time is made longer than the duration even during endDelay'); 382 383 promise_test(async t => { 384 var animation = addDivAndAnimate(t, 385 {}, 386 { opacity: [ 0, 1 ] }, 100 * MS_PER_SEC); 387 388 await waitForPaints(); 389 390 assert_animation_is_running_on_compositor(animation, 391 'Animation reports that it is running on the compositor'); 392 393 animation.effect.updateTiming({ endDelay: -200 * MS_PER_SEC }); 394 await waitForFrame(); 395 396 assert_animation_is_not_running_on_compositor(animation, 397 'Animation reports that it is NOT running on the compositor' 398 + ' when endTime is negative value'); 399 }, 'animation is removed from compositor' + 400 ' when endTime is negative value'); 401 402 promise_test(async t => { 403 var animation = addDivAndAnimate(t, 404 {}, 405 { opacity: [ 0, 1 ] }, 200 * MS_PER_SEC); 406 407 await waitForPaints(); 408 409 assert_animation_is_running_on_compositor(animation, 410 'Animation reports that it is running on the compositor'); 411 412 animation.effect.updateTiming({ endDelay: -100 * MS_PER_SEC }); 413 await waitForFrame(); 414 415 assert_animation_is_running_on_compositor(animation, 416 'Animation reports that it is running on the compositor' 417 + ' when endTime is positive and endDelay is negative'); 418 animation.currentTime = 110 * MS_PER_SEC; 419 await waitForFrame(); 420 421 assert_animation_is_not_running_on_compositor(animation, 422 'Animation reports that it is NOT running on the compositor' 423 + ' when currentTime is after endTime'); 424 }, 'animation is NOT running on compositor' + 425 ' when endTime is positive and endDelay is negative'); 426 427 promise_test(async t => { 428 var effect = new KeyframeEffect(null, 429 { opacity: [ 0, 1 ] }, 430 100 * MS_PER_SEC); 431 var animation = new Animation(effect, document.timeline); 432 animation.play(); 433 434 var div = addDiv(t); 435 436 await waitForPaints(); 437 438 assert_animation_is_not_running_on_compositor(animation, 439 'Animation with null target reports that it is not running ' + 440 'on the compositor'); 441 442 animation.effect.target = div; 443 await waitForFrame(); 444 445 assert_animation_is_running_on_compositor(animation, 446 'Animation reports that it is running on the compositor ' + 447 'after setting a valid target'); 448 }, 'animation is added to the compositor when setting a valid target'); 449 450 promise_test(async t => { 451 var div = addDiv(t); 452 var animation = div.animate({ opacity: [ 0, 1 ] }, 100 * MS_PER_SEC); 453 454 await waitForPaints(); 455 456 assert_animation_is_running_on_compositor(animation, 457 'Animation reports that it is running on the compositor'); 458 459 animation.effect.target = null; 460 assert_animation_is_not_running_on_compositor(animation, 461 'Animation reports that it is NOT running on the ' + 462 'compositor after setting null target'); 463 }, 'animation is removed from the compositor when setting null target'); 464 465 promise_test(async t => { 466 var div = addDiv(t); 467 var animation = div.animate({ opacity: [ 0, 1 ] }, 468 { duration: 100 * MS_PER_SEC, 469 delay: 100 * MS_PER_SEC, 470 fill: 'backwards' }); 471 472 await waitForPaints(); 473 474 assert_animation_is_running_on_compositor(animation, 475 'Animation with fill:backwards in delay phase reports ' + 476 'that it is running on the compositor'); 477 478 animation.currentTime = 100 * MS_PER_SEC; 479 await waitForFrame(); 480 481 assert_animation_is_running_on_compositor(animation, 482 'Animation with fill:backwards in delay phase reports ' + 483 'that it is running on the compositor after delay phase'); 484 }, 'animation with fill:backwards in delay phase is running on the ' + 485 ' compositor while it is in delay phase'); 486 487 promise_test(async t => { 488 const animation = addDiv(t).animate({ opacity: [ 0, 1 ] }, 100 * MS_PER_SEC); 489 animation.playbackRate = -1; 490 animation.currentTime = 200 * MS_PER_SEC; 491 492 await waitForPaints(); 493 494 assert_animation_is_running_on_compositor(animation, 495 'Animation with negative playback rate is runnning on the' 496 + ' compositor even before it reaches the active interval'); 497 }, 'animation with negative playback rate is sent to the compositor even in' 498 + ' after phase'); 499 500 promise_test(async t => { 501 var div = addDiv(t); 502 var animation = div.animate([{ opacity: 1, offset: 0 }, 503 { opacity: 1, offset: 0.99 }, 504 { opacity: 0, offset: 1 }], 100 * MS_PER_SEC); 505 506 var another = addDiv(t); 507 508 await waitForPaints(); 509 510 assert_animation_is_running_on_compositor(animation, 511 'Opacity animation on a 100% opacity keyframe reports ' + 512 'that it is running on the compositor from the begining'); 513 514 animation.effect.target = another; 515 await waitForFrame(); 516 517 assert_animation_is_running_on_compositor(animation, 518 'Opacity animation on a 100% opacity keyframe keeps ' + 519 'running on the compositor after changing the target ' + 520 'element'); 521 }, '100% opacity animations with keeps running on the ' + 522 'compositor after changing the target element'); 523 524 promise_test(async t => { 525 var div = addDiv(t); 526 var animation = div.animate({ color: ['red', 'black'] }, 100 * MS_PER_SEC); 527 528 await waitForPaints(); 529 530 assert_animation_is_not_running_on_compositor(animation, 531 'Color animation reports that it is not running on the ' + 532 'compositor'); 533 534 animation.effect.setKeyframes([{ opacity: 1, offset: 0 }, 535 { opacity: 1, offset: 0.99 }, 536 { opacity: 0, offset: 1 }]); 537 await waitForFrame(); 538 539 assert_animation_is_running_on_compositor(animation, 540 '100% opacity animation set by using setKeyframes reports ' + 541 'that it is running on the compositor'); 542 }, '100% opacity animation set up by converting an existing animation with ' + 543 'cannot be run on the compositor, is running on the compositor'); 544 545 promise_test(async t => { 546 var div = addDiv(t); 547 var animation = div.animate({ color: ['red', 'black'] }, 100 * MS_PER_SEC); 548 var effect = new KeyframeEffect(div, 549 [{ opacity: 1, offset: 0 }, 550 { opacity: 1, offset: 0.99 }, 551 { opacity: 0, offset: 1 }], 552 100 * MS_PER_SEC); 553 554 await waitForPaints(); 555 556 assert_animation_is_not_running_on_compositor(animation, 557 'Color animation reports that it is not running on the ' + 558 'compositor'); 559 560 animation.effect = effect; 561 await waitForFrame(); 562 563 assert_animation_is_running_on_compositor(animation, 564 '100% opacity animation set up by changing effects reports ' + 565 'that it is running on the compositor'); 566 }, '100% opacity animation set up by changing the effects on an existing ' + 567 'animation which cannot be run on the compositor, is running on the ' + 568 'compositor'); 569 570 promise_test(async t => { 571 var div = addDiv(t, { style: "opacity: 1 ! important" }); 572 573 var animation = div.animate({ opacity: [0, 1] }, 100 * MS_PER_SEC); 574 575 await waitForPaints(); 576 577 assert_animation_is_not_running_on_compositor(animation, 578 'Opacity animation on an element which has 100% opacity style with ' + 579 '!important flag reports that it is not running on the compositor'); 580 // Clear important flag from the opacity style on the target element. 581 div.style.setProperty("opacity", "1", ""); 582 await waitForFrame(); 583 584 assert_animation_is_running_on_compositor(animation, 585 'Opacity animation reports that it is running on the compositor after ' 586 + 'clearing the !important flag'); 587 }, 'Clearing *important* opacity style on the target element sends the ' + 588 'animation to the compositor'); 589 590 promise_test(async t => { 591 var div = addDiv(t); 592 var lowerAnimation = div.animate({ opacity: [1, 0] }, 100 * MS_PER_SEC); 593 var higherAnimation = div.animate({ opacity: [0, 1] }, 100 * MS_PER_SEC); 594 595 await waitForPaints(); 596 597 assert_animation_is_running_on_compositor(higherAnimation, 598 'A higher-priority opacity animation on an element ' + 599 'reports that it is running on the compositor'); 600 assert_animation_is_running_on_compositor(lowerAnimation, 601 'A lower-priority opacity animation on the same ' + 602 'element also reports that it is running on the compositor'); 603 }, 'Opacity animations on the same element run on the compositor'); 604 605 promise_test(async t => { 606 var div = addDiv(t, { style: 'transition: opacity 100s; opacity: 1' }); 607 608 getComputedStyle(div).opacity; 609 610 div.style.opacity = 0; 611 getComputedStyle(div).opacity; 612 613 var transition = div.getAnimations()[0]; 614 var animation = div.animate({ opacity: [0, 1] }, 100 * MS_PER_SEC); 615 616 await waitForPaints(); 617 618 assert_animation_is_running_on_compositor(animation, 619 'An opacity animation on an element reports that' + 620 'that it is running on the compositor'); 621 assert_animation_is_running_on_compositor(transition, 622 'An opacity transition on the same element reports that ' + 623 'it is running on the compositor'); 624 }, 'Both of transition and script animation on the same element run on the ' + 625 'compositor'); 626 627 promise_test(async t => { 628 var div = addDiv(t); 629 var importantOpacityElement = addDiv(t, { style: "opacity: 1 ! important" }); 630 631 var animation = div.animate({ opacity: [1, 0] }, 100 * MS_PER_SEC); 632 633 await waitForPaints(); 634 635 assert_animation_is_running_on_compositor(animation, 636 'Opacity animation on an element reports ' + 637 'that it is running on the compositor'); 638 639 animation.effect.target = null; 640 await waitForFrame(); 641 642 assert_animation_is_not_running_on_compositor(animation, 643 'Animation is no longer running on the compositor after ' + 644 'removing from the element'); 645 animation.effect.target = importantOpacityElement; 646 await waitForFrame(); 647 648 assert_animation_is_not_running_on_compositor(animation, 649 'Animation is NOT running on the compositor even after ' + 650 'being applied to a different element which has an ' + 651 '!important opacity declaration'); 652 }, 'Animation continues not running on the compositor after being ' + 653 'applied to an element which has an important declaration and ' + 654 'having previously been temporarily associated with no target element'); 655 656 promise_test(async t => { 657 var div = addDiv(t); 658 var another = addDiv(t); 659 660 var lowerAnimation = div.animate({ opacity: [1, 0] }, 100 * MS_PER_SEC); 661 var higherAnimation = another.animate({ opacity: [0, 1] }, 100 * MS_PER_SEC); 662 663 await waitForPaints(); 664 665 assert_animation_is_running_on_compositor(lowerAnimation, 666 'An opacity animation on an element reports that ' + 667 'it is running on the compositor'); 668 assert_animation_is_running_on_compositor(higherAnimation, 669 'Opacity animation on a different element reports ' + 670 'that it is running on the compositor'); 671 672 lowerAnimation.effect.target = null; 673 await waitForFrame(); 674 675 assert_animation_is_not_running_on_compositor(lowerAnimation, 676 'Animation is no longer running on the compositor after ' + 677 'being removed from the element'); 678 lowerAnimation.effect.target = another; 679 await waitForFrame(); 680 681 assert_animation_is_running_on_compositor(lowerAnimation, 682 'A lower-priority animation begins running ' + 683 'on the compositor after being applied to an element ' + 684 'which has a higher-priority animation'); 685 assert_animation_is_running_on_compositor(higherAnimation, 686 'A higher-priority animation continues to run on the ' + 687 'compositor even after a lower-priority animation is ' + 688 'applied to the same element'); 689 }, 'Animation begins running on the compositor after being applied ' + 690 'to an element which has a higher-priority animation and after ' + 691 'being temporarily associated with no target element'); 692 693 promise_test(async t => { 694 var div = addDiv(t); 695 var another = addDiv(t); 696 697 var lowerAnimation = div.animate({ opacity: [1, 0] }, 100 * MS_PER_SEC); 698 var higherAnimation = another.animate({ opacity: [0, 1] }, 100 * MS_PER_SEC); 699 700 await waitForPaints(); 701 702 assert_animation_is_running_on_compositor(lowerAnimation, 703 'An opacity animation on an element reports that ' + 704 'it is running on the compositor'); 705 assert_animation_is_running_on_compositor(higherAnimation, 706 'Opacity animation on a different element reports ' + 707 'that it is running on the compositor'); 708 709 higherAnimation.effect.target = null; 710 await waitForFrame(); 711 712 assert_animation_is_not_running_on_compositor(higherAnimation, 713 'Animation is no longer running on the compositor after ' + 714 'being removed from the element'); 715 higherAnimation.effect.target = div; 716 await waitForFrame(); 717 718 assert_animation_is_running_on_compositor(lowerAnimation, 719 'Animation continues running on the compositor after ' + 720 'a higher-priority animation applied to the same element'); 721 assert_animation_is_running_on_compositor(higherAnimation, 722 'A higher-priority animation begins to running on the ' + 723 'compositor after being applied to an element which has ' + 724 'a lower-priority-animation'); 725 }, 'Animation begins running on the compositor after being applied ' + 726 'to an element which has a lower-priority animation once after ' + 727 'disassociating with an element'); 728 729 var delayPhaseTests = [ 730 { 731 desc: 'script animation of opacity', 732 setupAnimation: t => { 733 return addDiv(t).animate( 734 { opacity: [0, 1] }, 735 { delay: 100 * MS_PER_SEC, duration: 100 * MS_PER_SEC }); 736 }, 737 }, 738 { 739 desc: 'script animation of transform', 740 setupAnimation: t => { 741 return addDiv(t).animate( 742 { transform: ['translateX(0px)', 'translateX(100px)'] }, 743 { delay: 100 * MS_PER_SEC, duration: 100 * MS_PER_SEC }); 744 }, 745 }, 746 { 747 desc: 'CSS animation of opacity', 748 setupAnimation: t => { 749 return addDiv(t, { style: 'animation: opacity 100s 100s' }) 750 .getAnimations()[0]; 751 }, 752 }, 753 { 754 desc: 'CSS animation of transform', 755 setupAnimation: t => { 756 return addDiv(t, { style: 'animation: anim 100s 100s' }) 757 .getAnimations()[0]; 758 }, 759 }, 760 { 761 desc: 'CSS transition of opacity', 762 setupAnimation: t => { 763 var div = addDiv(t, { style: 'transition: opacity 100s 100s' }); 764 getComputedStyle(div).opacity; 765 766 div.style.opacity = 0; 767 return div.getAnimations()[0]; 768 }, 769 }, 770 { 771 desc: 'CSS transition of transform', 772 setupAnimation: t => { 773 var div = addDiv(t, { style: 'transition: transform 100s 100s' }); 774 getComputedStyle(div).transform; 775 776 div.style.transform = 'translateX(100px)'; 777 return div.getAnimations()[0]; 778 }, 779 }, 780 ]; 781 782 delayPhaseTests.forEach(test => { 783 promise_test(async t => { 784 var animation = test.setupAnimation(t); 785 786 await waitForPaints(); 787 788 assert_animation_is_running_on_compositor(animation, 789 test.desc + ' reports that it is running on the ' 790 + 'compositor even though it is in the delay phase'); 791 }, 'isRunningOnCompositor for ' + test.desc + ' is true even though ' + 792 'it is in the delay phase'); 793 }); 794 795 // The purpose of thie test cases is to check that 796 // NS_FRAME_MAY_BE_TRANSFORMED flag on the associated nsIFrame persists 797 // after transform style on the frame is removed. 798 var delayPhaseWithTransformStyleTests = [ 799 { 800 desc: 'script animation of transform with transform style', 801 setupAnimation: t => { 802 return addDiv(t, { style: 'transform: translateX(10px)' }).animate( 803 { transform: ['translateX(0px)', 'translateX(100px)'] }, 804 { delay: 100 * MS_PER_SEC, duration: 100 * MS_PER_SEC }); 805 }, 806 }, 807 { 808 desc: 'CSS animation of transform with transform style', 809 setupAnimation: t => { 810 return addDiv(t, { style: 'animation: anim 100s 100s;' + 811 'transform: translateX(10px)' }) 812 .getAnimations()[0]; 813 }, 814 }, 815 { 816 desc: 'CSS transition of transform with transform style', 817 setupAnimation: t => { 818 var div = addDiv(t, { style: 'transition: transform 100s 100s;' + 819 'transform: translateX(10px)'}); 820 getComputedStyle(div).transform; 821 822 div.style.transform = 'translateX(100px)'; 823 return div.getAnimations()[0]; 824 }, 825 }, 826 ]; 827 828 delayPhaseWithTransformStyleTests.forEach(test => { 829 promise_test(async t => { 830 var animation = test.setupAnimation(t); 831 832 await waitForPaints(); 833 834 assert_animation_is_running_on_compositor(animation, 835 test.desc + ' reports that it is running on the ' 836 + 'compositor even though it is in the delay phase'); 837 838 // Remove the initial transform style during delay phase. 839 animation.effect.target.style.transform = 'none'; 840 await animation.ready; 841 842 assert_animation_is_running_on_compositor(animation, 843 test.desc + ' reports that it keeps running on the ' 844 + 'compositor after removing the initial transform style'); 845 }, 'isRunningOnCompositor for ' + test.desc + ' is true after removing ' + 846 'the initial transform style during the delay phase'); 847 }); 848 849 var startsWithNoneTests = [ 850 { 851 desc: 'script animation of transform starts with transform:none segment', 852 setupAnimation: t => { 853 return addDiv(t).animate( 854 { transform: ['none', 'none', 'translateX(100px)'] }, 100 * MS_PER_SEC); 855 }, 856 }, 857 { 858 desc: 'CSS animation of transform starts with transform:none segment', 859 setupAnimation: t => { 860 return addDiv(t, 861 { style: 'animation: transform-starts-with-none 100s 100s' }) 862 .getAnimations()[0]; 863 }, 864 }, 865 ]; 866 867 startsWithNoneTests.forEach(test => { 868 promise_test(async t => { 869 var animation = test.setupAnimation(t); 870 871 await waitForPaints(); 872 873 assert_animation_is_running_on_compositor(animation, 874 test.desc + ' reports that it is running on the ' 875 + 'compositor even though it is in transform:none segment'); 876 }, 'isRunningOnCompositor for ' + test.desc + ' is true even though ' + 877 'it is in transform:none segment'); 878 }); 879 880 promise_test(async t => { 881 var div = addDiv(t, { style: 'opacity: 1 ! important' }); 882 883 var animation = div.animate( 884 { opacity: [0, 1] }, 885 { delay: 100 * MS_PER_SEC, duration: 100 * MS_PER_SEC }); 886 887 await waitForPaints(); 888 889 assert_animation_is_not_running_on_compositor(animation, 890 'Opacity animation on an element which has opacity:1 important style' 891 + 'reports that it is not running on the compositor'); 892 // Clear the opacity style on the target element. 893 div.style.setProperty("opacity", "1", ""); 894 await waitForFrame(); 895 896 assert_animation_is_running_on_compositor(animation, 897 'Opacity animations reports that it is running on the compositor after ' 898 + 'clearing the opacity style on the element'); 899 }, 'Clearing *important* opacity style on the target element sends the ' + 900 'animation to the compositor even if the animation is in the delay phase'); 901 902 promise_test(async t => { 903 var opaqueDiv = addDiv(t, { style: 'opacity: 1 ! important' }); 904 var anotherDiv = addDiv(t); 905 906 var animation = opaqueDiv.animate( 907 { opacity: [0, 1] }, 908 { delay: 100 * MS_PER_SEC, duration: 100 * MS_PER_SEC }); 909 910 await waitForPaints(); 911 912 assert_animation_is_not_running_on_compositor(animation, 913 'Opacity animation on an element which has opacity:1 important style' 914 + 'reports that it is not running on the compositor'); 915 // Changing target element to another element which has no opacity style. 916 animation.effect.target = anotherDiv; 917 await waitForFrame(); 918 919 assert_animation_is_running_on_compositor(animation, 920 'Opacity animations reports that it is running on the compositor after ' 921 + 'changing the target element to another elemenent having no ' 922 + 'opacity style'); 923 }, 'Changing target element of opacity animation sends the animation to the ' + 924 'the compositor even if the animation is in the delay phase'); 925 926 promise_test(async t => { 927 var animation = 928 addDivAndAnimate(t, 929 {}, 930 { width: ['100px', '200px'] }, 931 { duration: 100 * MS_PER_SEC, delay: 100 * MS_PER_SEC }); 932 933 await waitForPaints(); 934 935 assert_animation_is_not_running_on_compositor(animation, 936 'Width animation reports that it is not running on the compositor ' 937 + 'in the delay phase'); 938 // Changing to property runnable on the compositor. 939 animation.effect.setKeyframes({ opacity: [0, 1] }); 940 await waitForFrame(); 941 942 assert_animation_is_running_on_compositor(animation, 943 'Opacity animation reports that it is running on the compositor ' 944 + 'after changing the property from width property in the delay phase'); 945 }, 'Dynamic change to a property runnable on the compositor ' + 946 'in the delay phase'); 947 948 promise_test(async t => { 949 var div = addDiv(t, { style: 'transition: opacity 100s; ' + 950 'opacity: 0 !important' }); 951 getComputedStyle(div).opacity; 952 953 div.style.setProperty('opacity', '1', 'important'); 954 getComputedStyle(div).opacity; 955 956 var animation = div.getAnimations()[0]; 957 958 await waitForPaints(); 959 960 assert_animation_is_running_on_compositor(animation, 961 'Transition reports that it is running on the compositor even if the ' + 962 'property is overridden by an !important rule'); 963 }, 'Transitions override important rules'); 964 965 promise_test(async t => { 966 var div = addDiv(t, { style: 'transition: opacity 100s; ' + 967 'opacity: 0 !important' }); 968 getComputedStyle(div).opacity; 969 970 div.animate({ opacity: [ 0, 1 ] }, 100 * MS_PER_SEC); 971 972 div.style.setProperty('opacity', '1', 'important'); 973 getComputedStyle(div).opacity; 974 975 var [transition, animation] = div.getAnimations(); 976 977 await waitForPaints(); 978 979 assert_animation_is_not_running_on_compositor(transition, 980 'Transition suppressed by an animation which is overridden by an ' + 981 '!important rule reports that it is NOT running on the compositor'); 982 assert_animation_is_not_running_on_compositor(animation, 983 'Animation overridden by an !important rule reports that it is ' + 984 'NOT running on the compositor'); 985 }, 'Neither transition nor animation does run on the compositor if the ' + 986 'property is overridden by an !important rule'); 987 988 promise_test(async t => { 989 var div = addDiv(t, { style: 'display: table' }); 990 var animation = 991 div.animate({ transform: ['rotate(0deg)', 'rotate(360deg)'] }, 992 100 * MS_PER_SEC); 993 994 await waitForAnimationReadyToRestyle(animation); 995 996 await waitForPaints(); 997 998 assert_animation_is_running_on_compositor(animation, 999 'Transform animation on display:table element should be running on the' 1000 + ' compositor'); 1001 }, 'Transform animation on display:table element runs on the compositor'); 1002 1003 promise_test(async t => { 1004 const div = addDiv(t, { style: 'display: table' }); 1005 const animation = div.animate(null, 100 * MS_PER_SEC); 1006 const effect = new KeyframeEffect(div, 1007 { transform: ['none', 'none']}, 1008 100 * MS_PER_SEC); 1009 1010 await waitForAnimationReadyToRestyle(animation); 1011 1012 animation.effect = effect; 1013 1014 await waitForNextFrame(); 1015 await waitForPaints(); 1016 1017 assert_animation_is_running_on_compositor( 1018 animation, 1019 'Transform animation on table element should be running on the compositor' 1020 ); 1021 }, 'Empty transform effect assigned after the fact to display:table content' 1022 + ' runs on the compositor'); 1023 1024 promise_test(async t => { 1025 const div = addDiv(t); 1026 const animation = div.animate({ backgroundColor: ['blue', 'green'] }, 1027 100 * MS_PER_SEC); 1028 1029 await waitForAnimationReadyToRestyle(animation); 1030 await waitForPaints(); 1031 1032 assert_animation_is_running_on_compositor(animation, 1033 'background-color animation should be running on the compositor'); 1034 }, 'background-color animation runs on the compositor'); 1035 1036 promise_test(async t => { 1037 const div = addDiv(t); 1038 const animation = div.animate({ backgroundColor: ['blue', 'green'] }, 1039 100 * MS_PER_SEC); 1040 1041 await waitForAnimationReadyToRestyle(animation); 1042 await waitForPaints(); 1043 1044 assert_animation_is_running_on_compositor(animation, 1045 'background-color animation should be running on the compositor'); 1046 1047 // Add a red opaque background image covering the background color animation. 1048 div.style.backgroundImage = 1049 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAIAAAAC64' + 1050 'paAAAAG0lEQVR42mP8z0A%2BYKJA76jmUc2jmkc1U0EzACKcASfOgGoMAAAAAElFTkSuQmCC)'; 1051 1052 await waitForAnimationReadyToRestyle(animation); 1053 await waitForPaints(); 1054 1055 // Bug 1712246. We should optimize this case eventually. 1056 //assert_animation_is_not_running_on_compositor(animation, 1057 // 'Opaque background image stops background-color animations from running ' + 1058 // 'on the compositor'); 1059 }, 'Opaque background image stops background-color animations from running ' + 1060 ' on the compositor'); 1061 1062 promise_test(async t => { 1063 await SpecialPowers.pushPrefEnv({ 1064 set: [["gfx.omta.background-color", false]] 1065 }); 1066 1067 const div = addDiv(t); 1068 const animation = div.animate({ backgroundColor: ['blue', 'green'] }, 1069 100 * MS_PER_SEC); 1070 1071 await waitForAnimationReadyToRestyle(animation); 1072 await waitForPaints(); 1073 1074 assert_animation_is_not_running_on_compositor(animation, 1075 'background-color animation should NOT be running on the compositor ' + 1076 'if the pref is disabled'); 1077 }, 'background-color animation does not run on the compositor if the pref ' + 1078 'is disabled'); 1079 1080 promise_test(async t => { 1081 const div = addDiv(t); 1082 const animation = div.animate({ translate: ['0px', '100px'] }, 1083 100 * MS_PER_SEC); 1084 1085 await waitForAnimationReadyToRestyle(animation); 1086 await waitForPaints(); 1087 1088 assert_animation_is_running_on_compositor(animation, 1089 'translate animation should be running on the compositor'); 1090 }, 'translate animation runs on the compositor'); 1091 1092 promise_test(async t => { 1093 const div = addDiv(t); 1094 const animation = div.animate({ rotate: ['0deg', '45deg'] }, 1095 100 * MS_PER_SEC); 1096 1097 await waitForAnimationReadyToRestyle(animation); 1098 await waitForPaints(); 1099 1100 assert_animation_is_running_on_compositor(animation, 1101 'rotate animation should be running on the compositor'); 1102 }, 'rotate animation runs on the compositor'); 1103 1104 promise_test(async t => { 1105 const div = addDiv(t); 1106 const animation = div.animate({ scale: ['1 1', '2 2'] }, 1107 100 * MS_PER_SEC); 1108 1109 await waitForAnimationReadyToRestyle(animation); 1110 await waitForPaints(); 1111 1112 assert_animation_is_running_on_compositor(animation, 1113 'scale animation should be running on the compositor'); 1114 }, 'scale animation runs on the compositor'); 1115 1116 promise_test(async t => { 1117 const div = addDiv(t); 1118 const animation = div.animate({ translate: ['0px', '100px'], 1119 rotate: ['0deg', '45deg'], 1120 transform: ['translate(20px)', 1121 'translate(30px)'] }, 1122 100 * MS_PER_SEC); 1123 1124 await waitForAnimationReadyToRestyle(animation); 1125 await waitForPaints(); 1126 1127 assert_animation_is_running_on_compositor(animation, 1128 'multiple transform-like properties animation should be running on the ' + 1129 'compositor'); 1130 1131 const properties = animation.effect.getProperties(); 1132 properties.forEach(property => { 1133 assert_true(property.runningOnCompositor, 1134 property.property + ' is running on the compositor'); 1135 }); 1136 }, 'Multiple transform-like properties animation runs on the compositor'); 1137 1138 promise_test(async t => { 1139 const div = addDiv(t); 1140 const animation = div.animate({ offsetPath: ['none', 'none'] }, 1141 100 * MS_PER_SEC); 1142 1143 await waitForAnimationReadyToRestyle(animation); 1144 await waitForPaints(); 1145 1146 assert_animation_is_running_on_compositor(animation, 1147 'offset-path animation should be running on the compositor even if ' + 1148 'it is always none'); 1149 }, 'offset-path none animation runs on the compositor'); 1150 1151 promise_test(async t => { 1152 const div = addDiv(t); 1153 const animation = div.animate({ offsetPath: ['path("M0 0l100 100")', 1154 'path("M0 0l200 200")'] }, 1155 100 * MS_PER_SEC); 1156 1157 await waitForAnimationReadyToRestyle(animation); 1158 await waitForPaints(); 1159 1160 assert_animation_is_running_on_compositor(animation, 1161 'offset-path:path() animation should be running on the compositor'); 1162 }, 'offset-path:path() animation runs on the compositor'); 1163 1164 promise_test(async t => { 1165 const div = addDiv(t); 1166 const animation = div.animate({ offsetPath: ['ray(0deg)', 1167 'ray(180deg)'] }, 1168 100 * MS_PER_SEC); 1169 1170 await waitForAnimationReadyToRestyle(animation); 1171 await waitForPaints(); 1172 1173 assert_animation_is_running_on_compositor(animation, 1174 'offset-path:ray() animation should be running on the compositor'); 1175 }, 'offset-path:ray() animation runs on the compositor'); 1176 1177 promise_test(async t => { 1178 const div = addDiv(t); 1179 const animation = div.animate({ offsetPath: ['inset(0px)', 1180 'inset(10px)'] }, 1181 100 * MS_PER_SEC); 1182 1183 await waitForAnimationReadyToRestyle(animation); 1184 await waitForPaints(); 1185 1186 assert_animation_is_running_on_compositor(animation, 1187 'offset-path:inset() animation should be running on the compositor'); 1188 }, 'offset-path:inset() animation runs on the compositor'); 1189 1190 promise_test(async t => { 1191 const div = addDiv(t); 1192 const animation = div.animate({ offsetPath: ['circle(10px)', 1193 'circle(20px)'] }, 1194 100 * MS_PER_SEC); 1195 1196 await waitForAnimationReadyToRestyle(animation); 1197 await waitForPaints(); 1198 1199 assert_animation_is_running_on_compositor(animation, 1200 'offset-path:circle() animation should be running on the compositor'); 1201 }, 'offset-path:circle() animation runs on the compositor'); 1202 1203 promise_test(async t => { 1204 const div = addDiv(t); 1205 const animation = div.animate({ offsetPath: ['ellipse(10px 20px)', 1206 'ellipse(20px 40px)'] }, 1207 100 * MS_PER_SEC); 1208 1209 await waitForAnimationReadyToRestyle(animation); 1210 await waitForPaints(); 1211 1212 assert_animation_is_running_on_compositor(animation, 1213 'offset-path:ellipse() animation should be running on the compositor'); 1214 }, 'offset-path:ellipse() animation runs on the compositor'); 1215 1216 promise_test(async t => { 1217 const div = addDiv(t); 1218 const animation = div.animate({ offsetPath: ['polygon(0px 0px)', 1219 'polygon(50px 50px)'] }, 1220 100 * MS_PER_SEC); 1221 1222 await waitForAnimationReadyToRestyle(animation); 1223 await waitForPaints(); 1224 1225 assert_animation_is_running_on_compositor(animation, 1226 'offset-path:polygon() animation should be running on the compositor'); 1227 }, 'offset-path:polygon() animation runs on the compositor'); 1228 1229 promise_test(async t => { 1230 const div = addDiv(t); 1231 const animation = div.animate({ offsetPath: ['padding-box', 1232 'padding-box'] }, 1233 100 * MS_PER_SEC); 1234 1235 await waitForAnimationReadyToRestyle(animation); 1236 await waitForPaints(); 1237 1238 assert_animation_is_running_on_compositor(animation, 1239 'offset-path:padding-box animation should be running on the compositor'); 1240 }, 'offset-path:padding-box animation runs on the compositor'); 1241 1242 promise_test(async t => { 1243 const div = addDiv(t); 1244 const animation = div.animate({ offsetPath: ['content-box', 1245 'content-box'] }, 1246 100 * MS_PER_SEC); 1247 1248 await waitForAnimationReadyToRestyle(animation); 1249 await waitForPaints(); 1250 1251 assert_animation_is_running_on_compositor(animation, 1252 'offset-path:content-box animation should be running on the compositor'); 1253 }, 'offset-path:content-box animation runs on the compositor'); 1254 1255 promise_test(async t => { 1256 const div = addDiv(t); 1257 const animation = div.animate({ offsetPath: ['xywh(0% 0% 10px 10px)', 1258 'xywh(10% 10% 20px 20px)'] }, 1259 100 * MS_PER_SEC); 1260 1261 await waitForAnimationReadyToRestyle(animation); 1262 await waitForPaints(); 1263 1264 assert_animation_is_running_on_compositor(animation, 1265 'offset-path:xywh() animation should be running on the compositor'); 1266 }, 'offset-path:xywh() animation runs on the compositor'); 1267 1268 promise_test(async t => { 1269 const div = addDiv(t); 1270 const animation = div.animate({ offsetPath: ['rect(0% 0% 10px 10px)', 1271 'rect(10% 10% 20px 20px)'] }, 1272 100 * MS_PER_SEC); 1273 1274 await waitForAnimationReadyToRestyle(animation); 1275 await waitForPaints(); 1276 1277 assert_animation_is_running_on_compositor(animation, 1278 'offset-path:rect() animation should be running on the compositor'); 1279 }, 'offset-path:rect() animation runs on the compositor'); 1280 1281 promise_test(async t => { 1282 const div = addDiv(t); 1283 const animation = div.animate({ offsetDistance: ['0%', '100%'] }, 1284 100 * MS_PER_SEC); 1285 1286 await waitForAnimationReadyToRestyle(animation); 1287 await waitForPaints(); 1288 1289 assert_animation_is_not_running_on_compositor(animation, 1290 'offset-distance animation is not running on the compositor because ' + 1291 'offset-path is none'); 1292 1293 const newAnim = div.animate({ offsetPath: ['None', 'None'] }, 1294 100 * MS_PER_SEC); 1295 await waitForAnimationReadyToRestyle(newAnim); 1296 await waitForPaints(); 1297 1298 assert_animation_is_running_on_compositor(animation, 1299 'offset-distance animation should be running on the compositor'); 1300 assert_animation_is_running_on_compositor(newAnim, 1301 'new added offset-path animation should be running on the compositor'); 1302 }, 'offset-distance animation runs on the compositor'); 1303 1304 promise_test(async t => { 1305 const div = addDiv(t); 1306 const animation = div.animate({ offsetRotate: ['0deg', '45deg'] }, 1307 100 * MS_PER_SEC); 1308 1309 await waitForAnimationReadyToRestyle(animation); 1310 await waitForPaints(); 1311 1312 assert_animation_is_not_running_on_compositor(animation, 1313 'offset-rotate animation is not running on the compositor because ' + 1314 'offset-path is none'); 1315 1316 const newAnim = div.animate({ offsetPath: ['None', 'None'] }, 1317 100 * MS_PER_SEC); 1318 await waitForAnimationReadyToRestyle(newAnim); 1319 await waitForPaints(); 1320 1321 assert_animation_is_running_on_compositor(animation, 1322 'offset-rotate animation should be running on the compositor'); 1323 assert_animation_is_running_on_compositor(newAnim, 1324 'new added offset-path animation should be running on the compositor'); 1325 }, 'offset-rotate animation runs on the compositor'); 1326 1327 promise_test(async t => { 1328 const div = addDiv(t); 1329 const animation = div.animate({ offsetAnchor: ['0% 0%', '100% 100%'] }, 1330 100 * MS_PER_SEC); 1331 1332 await waitForAnimationReadyToRestyle(animation); 1333 await waitForPaints(); 1334 1335 assert_animation_is_not_running_on_compositor(animation, 1336 'offset-anchor animation is not running on the compositor because ' + 1337 'offset-path is none'); 1338 1339 const newAnim = div.animate({ offsetPath: ['None', 'None'] }, 1340 100 * MS_PER_SEC); 1341 await waitForAnimationReadyToRestyle(newAnim); 1342 await waitForPaints(); 1343 1344 assert_animation_is_running_on_compositor(animation, 1345 'offset-anchor animation should be running on the compositor'); 1346 assert_animation_is_running_on_compositor(newAnim, 1347 'new added offset-path animation should be running on the compositor'); 1348 }, 'offset-anchor animation runs on the compositor'); 1349 1350 promise_test(async t => { 1351 const div = addDiv(t); 1352 const animation = div.animate({ offsetPosition: ['0% 0%', '100% 100%'] }, 1353 100 * MS_PER_SEC); 1354 1355 await waitForAnimationReadyToRestyle(animation); 1356 await waitForPaints(); 1357 1358 assert_animation_is_not_running_on_compositor(animation, 1359 'offset-position animation is not running on the compositor because ' + 1360 'offset-path is none'); 1361 1362 const newAnim = div.animate({ offsetPath: ['None', 'None'] }, 1363 100 * MS_PER_SEC); 1364 await waitForAnimationReadyToRestyle(newAnim); 1365 await waitForPaints(); 1366 1367 assert_animation_is_running_on_compositor(animation, 1368 'offset-position animation should be running on the compositor'); 1369 assert_animation_is_running_on_compositor(newAnim, 1370 'new added offset-path animation should be running on the compositor'); 1371 }, 'offset-position animation runs on the compositor'); 1372 1373 promise_test(async t => { 1374 const div = addDiv(t); 1375 const animation = div.animate({ translate: ['0px', '100px'], 1376 rotate: ['0deg', '45deg'], 1377 transform: ['translate(0px)', 1378 'translate(100px)'], 1379 offsetDistance: ['0%', '100%'] }, 1380 100 * MS_PER_SEC); 1381 await waitForAnimationReadyToRestyle(animation); 1382 await waitForPaints(); 1383 1384 assert_animation_is_running_on_compositor(animation, 1385 'Animation is running on the compositor even though we do not have ' + 1386 'offset-path'); 1387 1388 div.style.offsetPath = 'path("M50 0v100")'; 1389 getComputedStyle(div).offsetPath; 1390 1391 await waitForPaints(); 1392 1393 assert_animation_is_running_on_compositor(animation, 1394 'Animation is running on the compositor'); 1395 1396 }, 'Multiple transform-like properties (include motion-path) animation runs ' + 1397 'on the compositor'); 1398 1399 promise_test(async t => { 1400 const div = addDiv(t); 1401 const animation = div.animate({ translate: ['0px', '100px'], 1402 rotate: ['0deg', '45deg'], 1403 transform: ['translate(20px)', 1404 'translate(30px)'], 1405 offsetDistance: ['0%', '100%'] }, 1406 100 * MS_PER_SEC); 1407 1408 div.style.setProperty('translate', '50px', 'important'); 1409 getComputedStyle(div).translate; 1410 1411 await waitForAnimationReadyToRestyle(animation); 1412 await waitForPaints(); 1413 1414 assert_animation_is_not_running_on_compositor(animation, 1415 'Animation overridden by an !important rule reports that it is ' + 1416 'NOT running on the compositor'); 1417 1418 const properties = animation.effect.getProperties(); 1419 properties.forEach(property => { 1420 assert_true(!property.runningOnCompositor, 1421 property.property + ' is not running on the compositor'); 1422 }); 1423 }, 'Multiple transform-like properties animation does not runs on the ' + 1424 'compositor because one of the transform-like property is overridden ' + 1425 'by an !important rule'); 1426 1427 // FIXME: Bug 1593106: We should still run the animations on the compositor if 1428 // offset-* doesn't have any effect. 1429 promise_test(async t => { 1430 const div = addDiv(t); 1431 const animation = div.animate({ translate: ['0px', '100px'], 1432 rotate: ['0deg', '45deg'], 1433 transform: ['translate(0px)', 1434 'translate(100px)'], 1435 offsetDistance: ['0%', '100%'] }, 1436 100 * MS_PER_SEC); 1437 1438 div.style.setProperty('offset-distance', '50%', 'important'); 1439 getComputedStyle(div).offsetDistance; 1440 1441 await waitForAnimationReadyToRestyle(animation); 1442 await waitForPaints(); 1443 1444 assert_animation_is_not_running_on_compositor(animation, 1445 'Animation overridden by an !important rule reports that it is ' + 1446 'NOT running on the compositor'); 1447 1448 const properties = animation.effect.getProperties(); 1449 properties.forEach(property => { 1450 assert_true(!property.runningOnCompositor, 1451 property.property + ' is not running on the compositor'); 1452 }); 1453 }, 'Multiple transform-like properties animation does not runs on the ' + 1454 'compositor because one of the offset-* property is overridden ' + 1455 'by an !important rule'); 1456 1457 promise_test(async t => { 1458 const div = addDiv(t); 1459 const animation = div.animate({ rotate: ['0deg', '45deg'], 1460 transform: ['translate(20px)', 1461 'translate(30px)'], 1462 offsetDistance: ['0%', '100%'] }, 1463 100 * MS_PER_SEC); 1464 1465 div.style.setProperty('translate', '50px', 'important'); 1466 getComputedStyle(div).translate; 1467 1468 await waitForAnimationReadyToRestyle(animation); 1469 await waitForPaints(); 1470 1471 assert_animation_is_running_on_compositor(animation, 1472 'Animation is still running on the compositor'); 1473 1474 const properties = animation.effect.getProperties(); 1475 properties.forEach(property => { 1476 assert_true(property.runningOnCompositor, 1477 property.property + ' is running on the compositor'); 1478 }); 1479 }, 'Multiple transform-like properties animation still runs on the ' + 1480 'compositor because the overridden-by-!important property does not have ' + 1481 'animation'); 1482 1483 promise_test(async t => { 1484 // We should run the animations on the compositor for this case: 1485 // 1. A transition of 'translate' 1486 // 2. An !important rule on 'translate' 1487 // 3. An animation of 'scale' 1488 const div = addDiv(t, { style: 'translate: 100px !important;' }); 1489 const animation = div.animate({ rotate: ['0deg', '45deg'] }, 1490 100 * MS_PER_SEC); 1491 div.style.transition = 'translate 100s'; 1492 getComputedStyle(div).transition; 1493 1494 await waitForAnimationReadyToRestyle(animation); 1495 await waitForPaints(); 1496 1497 assert_animation_is_running_on_compositor(animation, 1498 'rotate animation should be running on the compositor'); 1499 1500 div.style.setProperty('translate', '200px', 'important'); 1501 getComputedStyle(div).translate; 1502 1503 const anims = div.getAnimations(); 1504 await waitForPaints(); 1505 1506 assert_animation_is_running_on_compositor(anims[0], 1507 `${anims[0].effect.getProperties()[0].property} animation should be ` + 1508 `running on the compositor`); 1509 assert_animation_is_running_on_compositor(anims[1], 1510 `${anims[1].effect.getProperties()[0].property} animation should be ` + 1511 `running on the compositor`); 1512 }, 'Transform-like animations and transitions still runs on the compositor ' + 1513 'because the !important rule is overridden by a transition, and the ' + 1514 'transition property does not have animations'); 1515 1516 promise_test(async t => { 1517 const container = addDiv(t, { style: 'transform-style: preserve-3d;' }); 1518 const targetA = addDiv(t, { style: 'transform-style: preserve-3d' }); 1519 const targetB = addDiv(t, { style: 'transform-style: preserve-3d' }); 1520 const targetC = addDiv(t); 1521 container.appendChild(targetA); 1522 targetA.append(targetB); 1523 targetB.append(targetC); 1524 1525 const animation1 = targetA.animate({ rotate: ['0 0 1 0deg', '1 1 1 45deg'] }, 1526 100 * MS_PER_SEC); 1527 1528 const animation2 = targetC.animate({ rotate: ['0 0 1 0deg', '0 1 1 100deg'] }, 1529 100 * MS_PER_SEC); 1530 1531 await waitForAnimationReadyToRestyle(animation1); 1532 await waitForAnimationReadyToRestyle(animation2); 1533 await waitForPaints(); 1534 1535 assert_animation_is_running_on_compositor(animation1, 1536 'rotate animation in the 3d rendering context should be running on the ' + 1537 'compositor'); 1538 assert_animation_is_running_on_compositor(animation2, 1539 'rotate animation in the 3d rendering context should be running on the ' + 1540 'compositor'); 1541 }, 'Transform-like animations in the 3d rendering context should runs on the ' + 1542 'compositor'); 1543 1544 promise_test(async t => { 1545 const container = addDiv(t, { style: 'transform-style: preserve-3d;' }); 1546 const target = addDiv(t, { style: 'transform-style: preserve-3d;' }); 1547 const innerA = addDiv(t, { style: 'width: 50px; height: 50px;' }); 1548 // The frame of innerB is too large, so this makes its ancenstors and children 1549 // in the 3d context be not allowed the async animations. 1550 const innerB = addDiv(t, { style: 'rotate: 0 1 1 100deg; ' + 1551 'transform-style: preserve-3d; ' + 1552 'text-indent: -9999em' }); 1553 const innerB2 = addDiv(t, { style: 'rotate: 0 1 1 45deg;' }); 1554 const innerBText = document.createTextNode("innerB"); 1555 container.appendChild(target); 1556 target.appendChild(innerA); 1557 target.appendChild(innerB); 1558 innerB.appendChild(innerBText); 1559 innerB.appendChild(innerB2); 1560 1561 const animation1 = target.animate({ rotate: ['0 0 1 0deg', '1 1 1 45deg'] }, 1562 100 * MS_PER_SEC); 1563 1564 const animation2 = innerA.animate({ rotate: ['0 0 1 0deg', '0 1 1 100deg'] }, 1565 100 * MS_PER_SEC); 1566 1567 const animation3 = innerB2.animate({ rotate: ['0 0 1 0deg', '0 1 1 90deg'] }, 1568 100 * MS_PER_SEC); 1569 1570 await waitForAnimationReadyToRestyle(animation1); 1571 await waitForAnimationReadyToRestyle(animation2); 1572 await waitForAnimationReadyToRestyle(animation3); 1573 await waitForPaints(); 1574 1575 const isPartialPrerenderEnabled = 1576 SpecialPowers.getBoolPref('layout.animation.prerender.partial'); 1577 1578 if (isPartialPrerenderEnabled) { 1579 assert_animation_is_running_on_compositor(animation1, 1580 'rotate animation in the 3d rendering context should be running on ' + 1581 'the compositor even if one of its inner frames is too large'); 1582 assert_animation_is_running_on_compositor(animation2, 1583 'rotate animation in the 3d rendering context is still running on ' + 1584 'the compositor because its display item is created earlier'); 1585 assert_animation_is_running_on_compositor(animation3, 1586 'rotate animation in the 3d rendering context should be running on ' + 1587 'the compositor even if one of its parent frames is too large'); 1588 } else { 1589 assert_animation_is_not_running_on_compositor(animation1, 1590 'rotate animation in the 3d rendering context should not be running on ' + 1591 'the compositor because one of its inner frames is too large'); 1592 assert_animation_is_running_on_compositor(animation2, 1593 'rotate animation in the 3d rendering context is still running on ' + 1594 'the compositor because its display item is created earlier'); 1595 assert_animation_is_not_running_on_compositor(animation3, 1596 'rotate animation in the 3d rendering context should not be running on ' + 1597 'the compositor because one of its parent frames is too large'); 1598 } 1599 innerBText.remove(); 1600 }, 'Transform-like animations in the 3d rendering context should run on ' + 1601 'the compositor even if it is the ancestor of ones whose frames are too ' + 1602 'large or its ancestor is not allowed to run on the compositor'); 1603 1604 promise_test(async t => { 1605 const container = addDiv(t, { style: 'transform-style: preserve-3d;' }); 1606 const target = addDiv(t, { style: 'transform-style: preserve-3d;' }); 1607 // The frame of innerA is too large, so this makes its ancenstors and children 1608 // in the 3d context be not allowed the async animations. 1609 const innerA = addDiv(t, { style: 'transform-style: preserve-3d; ' + 1610 'text-indent: -9999em' }); 1611 const innerAText = document.createTextNode("innerA"); 1612 const innerB = addDiv(t, { style: 'width: 50px; height: 50px;' }); 1613 container.appendChild(target); 1614 target.appendChild(innerA); 1615 target.appendChild(innerB); 1616 innerA.appendChild(innerAText); 1617 1618 const animation1 = target.animate({ rotate: ['0 0 1 0deg', '1 1 1 45deg'] }, 1619 100 * MS_PER_SEC); 1620 1621 const animation2 = innerA.animate({ rotate: ['0 0 1 0deg', '0 1 1 100deg'] }, 1622 100 * MS_PER_SEC); 1623 1624 const animation3 = innerB.animate({ rotate: ['0 0 1 0deg', '0 1 1 90deg'] }, 1625 100 * MS_PER_SEC); 1626 1627 await waitForAnimationReadyToRestyle(animation1); 1628 await waitForAnimationReadyToRestyle(animation2); 1629 await waitForAnimationReadyToRestyle(animation3); 1630 await waitForPaints(); 1631 1632 const isPartialPrerenderEnabled = 1633 SpecialPowers.getBoolPref('layout.animation.prerender.partial'); 1634 1635 if (isPartialPrerenderEnabled) { 1636 assert_animation_is_running_on_compositor(animation1, 1637 'rotate animation in the 3d rendering context should be running on ' + 1638 'the compositor even if one of its inner frames is too large'); 1639 assert_animation_is_running_on_compositor(animation2, 1640 'rotate animation in the 3d rendering context should be running on ' + 1641 'the compositor even if its frame size is too large'); 1642 assert_animation_is_running_on_compositor(animation3, 1643 'rotate animation in the 3d rendering context should be running on ' + 1644 'the compositor even if its previous sibling frame is too large'); 1645 } else { 1646 assert_animation_is_not_running_on_compositor(animation1, 1647 'rotate animation in the 3d rendering context should not be running on ' + 1648 'the compositor because one of its inner frames is too large'); 1649 assert_animation_is_not_running_on_compositor(animation2, 1650 'rotate animation in the 3d rendering context should not be running on ' + 1651 'the compositor because its frame size is too large'); 1652 assert_animation_is_not_running_on_compositor(animation3, 1653 'rotate animation in the 3d rendering context should not be running on ' + 1654 'the compositor because its previous sibling frame is too large'); 1655 } 1656 innerAText.remove(); 1657 }, 'Transform-like animations in the 3d rendering context should run on ' + 1658 'the compositor even if its previous sibling frame size is too large'); 1659 1660 </script> 1661 </body>