tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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>