tor-browser

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

update-and-send-events-replacement.html (29182B)


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