tor-browser

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

updating-the-finished-state.html (16333B)


      1 <!DOCTYPE html>
      2 <meta charset=utf-8>
      3 <title>Updating the finished state</title>
      4 <meta name="timeout" content="long">
      5 <link rel="help" href="https://drafts.csswg.org/web-animations/#updating-the-finished-state">
      6 <script src="/resources/testharness.js"></script>
      7 <script src="/resources/testharnessreport.js"></script>
      8 <script src="../../testcommon.js"></script>
      9 <body>
     10 <div id="log"></div>
     11 <script>
     12 'use strict';
     13 
     14 // --------------------------------------------------------------------
     15 //
     16 // TESTS FOR UPDATING THE HOLD TIME
     17 //
     18 // --------------------------------------------------------------------
     19 
     20 // CASE 1: playback rate > 0 and current time >= target effect end
     21 // (Also the start time is resolved and there is pending task)
     22 
     23 // Did seek = false
     24 promise_test(async t => {
     25  const anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
     26 
     27  // Here and in the following tests we wait until ready resolves as
     28  // otherwise we don't have a resolved start time. We test the case
     29  // where the start time is unresolved in a subsequent test.
     30  await anim.ready;
     31 
     32  // Seek to 1ms before the target end and then wait 1ms
     33  anim.currentTime = 100 * MS_PER_SEC - 1;
     34  await waitForAnimationFramesWithDelay(1);
     35 
     36  assert_equals(anim.currentTime, 100 * MS_PER_SEC,
     37                'Hold time is set to target end clamping current time');
     38 }, 'Updating the finished state when playing past end');
     39 
     40 // Did seek = true
     41 promise_test(async t => {
     42  const anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
     43 
     44  await anim.ready;
     45 
     46  anim.currentTime = 200 * MS_PER_SEC;
     47  await waitForNextFrame();
     48 
     49  assert_equals(anim.currentTime, 200 * MS_PER_SEC,
     50                'Hold time is set so current time should NOT change');
     51 }, 'Updating the finished state when seeking past end');
     52 
     53 // Test current time == target end
     54 //
     55 // We can't really write a test for current time == target end with
     56 // did seek = false since that would imply setting up an animation where
     57 // the next animation frame time happens to exactly align with the target end.
     58 //
     59 // Fortunately, we don't need to test that case since even if the implementation
     60 // fails to set the hold time on such a tick, it should be mostly unobservable
     61 // (on the subsequent tick the hold time will be set to the same value anyway).
     62 
     63 // Did seek = true
     64 promise_test(async t => {
     65  const anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
     66  await anim.ready;
     67 
     68  anim.currentTime = 100 * MS_PER_SEC;
     69  await waitForNextFrame();
     70 
     71  assert_equals(anim.currentTime, 100 * MS_PER_SEC,
     72                'Hold time is set so current time should NOT change');
     73 }, 'Updating the finished state when seeking exactly to end');
     74 
     75 
     76 // CASE 2: playback rate < 0 and current time <= 0
     77 // (Also the start time is resolved and there is pending task)
     78 
     79 // Did seek = false
     80 promise_test(async t => {
     81  const anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
     82  anim.playbackRate = -1;
     83  anim.play(); // Make sure animation is not initially finished
     84 
     85  await anim.ready;
     86 
     87  // Seek to 1ms before 0 and then wait 1ms
     88  anim.currentTime = 1;
     89  await waitForAnimationFramesWithDelay(1);
     90 
     91  assert_equals(anim.currentTime, 0 * MS_PER_SEC,
     92                'Hold time is set to zero clamping current time');
     93 }, 'Updating the finished state when playing in reverse past zero');
     94 
     95 // Did seek = true
     96 promise_test(async t => {
     97  const anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
     98  anim.playbackRate = -1;
     99  anim.play();
    100 
    101  await anim.ready;
    102 
    103  anim.currentTime = -100 * MS_PER_SEC;
    104  await waitForNextFrame();
    105 
    106  assert_equals(anim.currentTime, -100 * MS_PER_SEC,
    107                'Hold time is set so current time should NOT change');
    108 }, 'Updating the finished state when seeking a reversed animation past zero');
    109 
    110 // As before, it's difficult to test current time == 0 for did seek = false but
    111 // it doesn't really matter.
    112 
    113 // Did seek = true
    114 promise_test(async t => {
    115  const anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
    116  anim.playbackRate = -1;
    117  anim.play();
    118  await anim.ready;
    119 
    120  anim.currentTime = 0;
    121  await waitForNextFrame();
    122 
    123  assert_equals(anim.currentTime, 0 * MS_PER_SEC,
    124                'Hold time is set so current time should NOT change');
    125 }, 'Updating the finished state when seeking a reversed animation exactly'
    126   + ' to zero');
    127 
    128 // CASE 3: playback rate > 0 and current time < target end OR
    129 //         playback rate < 0 and current time > 0
    130 // (Also the start time is resolved and there is pending task)
    131 
    132 // Did seek = false; playback rate > 0
    133 promise_test(async t => {
    134  const anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
    135 
    136  // We want to test that the hold time is cleared so first we need to
    137  // put the animation in a state where the hold time is set.
    138  anim.finish();
    139  await anim.ready;
    140 
    141  assert_equals(anim.currentTime, 100 * MS_PER_SEC,
    142                'Hold time is initially set');
    143  // Then extend the duration so that the hold time is cleared and on
    144  // the next tick the current time will increase.
    145  anim.effect.updateTiming({
    146    duration: anim.effect.getComputedTiming().duration * 2,
    147  });
    148  await waitForNextFrame();
    149 
    150  assert_greater_than(anim.currentTime, 100 * MS_PER_SEC,
    151                      'Hold time is not set so current time should increase');
    152 }, 'Updating the finished state when playing before end');
    153 
    154 // Did seek = true; playback rate > 0
    155 promise_test(async t => {
    156  const anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
    157  anim.finish();
    158  await anim.ready;
    159 
    160  anim.currentTime = 50 * MS_PER_SEC;
    161  // When did seek = true, updating the finished state: (i) updates
    162  // the animation's start time and (ii) clears the hold time.
    163  // We can test both by checking that the currentTime is initially
    164  // updated and then increases.
    165  assert_equals(anim.currentTime, 50 * MS_PER_SEC, 'Start time is updated');
    166  await waitForNextFrame();
    167 
    168  assert_greater_than(anim.currentTime, 50 * MS_PER_SEC,
    169                      'Hold time is not set so current time should increase');
    170 }, 'Updating the finished state when seeking before end');
    171 
    172 // Did seek = false; playback rate < 0
    173 //
    174 // Unfortunately it is not possible to test this case. We need to have
    175 // a hold time set, a resolved start time, and then perform some
    176 // operation that updates the finished state with did seek set to true.
    177 //
    178 // However, the only situation where this could arrive is when we
    179 // replace the timeline and that procedure is likely to change. For all
    180 // other cases we either have an unresolved start time (e.g. when
    181 // paused), we don't have a set hold time (e.g. regular playback), or
    182 // the current time is zero (and anything that gets us out of that state
    183 // will set did seek = true).
    184 
    185 // Did seek = true; playback rate < 0
    186 promise_test(async t => {
    187  const anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
    188  anim.playbackRate = -1;
    189  await anim.ready;
    190 
    191  anim.currentTime = 50 * MS_PER_SEC;
    192  assert_equals(anim.currentTime, 50 * MS_PER_SEC, 'Start time is updated');
    193  await waitForNextFrame();
    194 
    195  assert_less_than(anim.currentTime, 50 * MS_PER_SEC,
    196                    'Hold time is not set so current time should decrease');
    197 }, 'Updating the finished state when seeking a reversed animation before end');
    198 
    199 // CASE 4: playback rate == 0
    200 
    201 // current time < 0
    202 promise_test(async t => {
    203  const anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
    204  anim.playbackRate = 0;
    205  await anim.ready;
    206 
    207  anim.currentTime = -100 * MS_PER_SEC;
    208  await waitForNextFrame();
    209 
    210  assert_equals(anim.currentTime, -100 * MS_PER_SEC,
    211                'Hold time should not be cleared so current time should'
    212                + ' NOT change');
    213 }, 'Updating the finished state when playback rate is zero and the'
    214   + ' current time is less than zero');
    215 
    216 // current time < target end
    217 promise_test(async t => {
    218  const anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
    219  anim.playbackRate = 0;
    220  await anim.ready;
    221 
    222  anim.currentTime = 50 * MS_PER_SEC;
    223  await waitForNextFrame();
    224 
    225  assert_equals(anim.currentTime, 50 * MS_PER_SEC,
    226                'Hold time should not be cleared so current time should'
    227                + ' NOT change');
    228 }, 'Updating the finished state when playback rate is zero and the'
    229   + ' current time is less than end');
    230 
    231 // current time > target end
    232 promise_test(async t => {
    233  const anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
    234  anim.playbackRate = 0;
    235  await anim.ready;
    236 
    237  anim.currentTime = 200 * MS_PER_SEC;
    238  await waitForNextFrame();
    239 
    240  assert_equals(anim.currentTime, 200 * MS_PER_SEC,
    241                'Hold time should not be cleared so current time should'
    242                + ' NOT change');
    243 }, 'Updating the finished state when playback rate is zero and the'
    244   + ' current time is greater than end');
    245 
    246 // CASE 5: current time unresolved
    247 
    248 promise_test(async t => {
    249  const anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
    250  anim.cancel();
    251  // Trigger a change that will cause the "update the finished state"
    252  // procedure to run.
    253  anim.effect.updateTiming({ duration: 200 * MS_PER_SEC });
    254  assert_equals(anim.currentTime, null,
    255                'The animation hold time / start time should not be updated');
    256  // The "update the finished state" procedure is supposed to run after any
    257  // change to timing, but just in case an implementation defers that, let's
    258  // wait a frame and check that the hold time / start time has still not been
    259  // updated.
    260  await waitForAnimationFrames(1);
    261 
    262  assert_equals(anim.currentTime, null,
    263                'The animation hold time / start time should not be updated');
    264 }, 'Updating the finished state when current time is unresolved');
    265 
    266 // CASE 6: has a pending task
    267 
    268 test(t => {
    269  const anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
    270  anim.cancel();
    271  anim.currentTime = 75 * MS_PER_SEC;
    272  anim.play();
    273  // We now have a pending task and a resolved current time.
    274  //
    275  // In the next step we will adjust the timing so that the current time
    276  // is greater than the target end. At this point the "update the finished
    277  // state" procedure should run and if we fail to check for a pending task
    278  // we will set the hold time to the target end, i.e. 50ms.
    279  anim.effect.updateTiming({ duration: 50 * MS_PER_SEC });
    280  assert_equals(anim.currentTime, 75 * MS_PER_SEC,
    281                'Hold time should not be updated');
    282 }, 'Updating the finished state when there is a pending task');
    283 
    284 // CASE 7: start time unresolved
    285 
    286 // Did seek = false
    287 promise_test(async t => {
    288  const anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
    289  anim.cancel();
    290  // Make it so that only the start time is unresolved (to avoid overlapping
    291  // with the test case where current time is unresolved)
    292  anim.currentTime = 150 * MS_PER_SEC;
    293  // Trigger a change that will cause the "update the finished state"
    294  // procedure to run (did seek = false).
    295  anim.effect.updateTiming({ duration: 200 * MS_PER_SEC });
    296  await waitForAnimationFrames(1);
    297 
    298  assert_equals(anim.currentTime, 150 * MS_PER_SEC,
    299                'The animation hold time should not be updated');
    300  assert_equals(anim.startTime, null,
    301                'The animation start time should not be updated');
    302 }, 'Updating the finished state when start time is unresolved and'
    303   + ' did seek = false');
    304 
    305 // Did seek = true
    306 test(t => {
    307  const anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
    308  anim.cancel();
    309  anim.currentTime = 150 * MS_PER_SEC;
    310  // Trigger a change that will cause the "update the finished state"
    311  // procedure to run.
    312  anim.currentTime = 50 * MS_PER_SEC;
    313  assert_equals(anim.currentTime, 50 * MS_PER_SEC,
    314                'The animation hold time should not be updated');
    315  assert_equals(anim.startTime, null,
    316                'The animation start time should not be updated');
    317 }, 'Updating the finished state when start time is unresolved and'
    318   + ' did seek = true');
    319 
    320 // --------------------------------------------------------------------
    321 //
    322 // TESTS FOR RUNNING FINISH NOTIFICATION STEPS
    323 //
    324 // --------------------------------------------------------------------
    325 
    326 function waitForFinishEventAndPromise(animation) {
    327  const eventPromise = new Promise(resolve => {
    328    animation.onfinish = resolve;
    329  });
    330  return Promise.all([eventPromise, animation.finished]);
    331 }
    332 
    333 promise_test(t => {
    334  const animation = createDiv(t).animate(null, 1);
    335  animation.onfinish =
    336    t.unreached_func('Seeking to finish should not fire finish event');
    337  animation.finished.then(
    338    t.unreached_func('Seeking to finish should not resolve finished promise'));
    339  animation.currentTime = 1;
    340  animation.currentTime = 0;
    341  animation.pause();
    342  return waitForAnimationFrames(3);
    343 }, 'Finish notification steps don\'t run when the animation seeks to finish'
    344   + ' and then seeks back again');
    345 
    346 promise_test(async t => {
    347  const animation = createDiv(t).animate(null, 1);
    348  await animation.ready;
    349 
    350  return waitForFinishEventAndPromise(animation);
    351 }, 'Finish notification steps run when the animation completes normally');
    352 
    353 promise_test(async t => {
    354  const effect = new KeyframeEffect(null, null, 1);
    355  const animation = new Animation(effect, document.timeline);
    356  animation.play();
    357  await animation.ready;
    358 
    359  return waitForFinishEventAndPromise(animation);
    360 }, 'Finish notification steps run when an animation without a target'
    361   + ' effect completes normally');
    362 
    363 promise_test(async t => {
    364  const animation = createDiv(t).animate(null, 1);
    365  await animation.ready;
    366 
    367  animation.currentTime = 10;
    368  return waitForFinishEventAndPromise(animation);
    369 }, 'Finish notification steps run when the animation seeks past finish');
    370 
    371 promise_test(async t => {
    372  const animation = createDiv(t).animate(null, 1);
    373  await animation.ready;
    374 
    375  // Register for notifications now since once we seek away from being
    376  // finished the 'finished' promise will be replaced.
    377  const finishNotificationSteps = waitForFinishEventAndPromise(animation);
    378  animation.finish();
    379  animation.currentTime = 0;
    380  animation.pause();
    381  return finishNotificationSteps;
    382 }, 'Finish notification steps run when the animation completes with .finish(),'
    383   + ' even if we then seek away');
    384 
    385 promise_test(async t => {
    386  const animation = createDiv(t).animate(null, 1);
    387  const initialFinishedPromise = animation.finished;
    388  await animation.finished;
    389 
    390  animation.currentTime = 0;
    391  assert_not_equals(initialFinishedPromise, animation.finished);
    392 }, 'Animation finished promise is replaced after seeking back to start');
    393 
    394 promise_test(async t => {
    395  const animation = createDiv(t).animate(null, 1);
    396  const initialFinishedPromise = animation.finished;
    397  await animation.finished;
    398 
    399  animation.play();
    400  assert_not_equals(initialFinishedPromise, animation.finished);
    401 }, 'Animation finished promise is replaced after replaying from start');
    402 
    403 async_test(t => {
    404  const animation = createDiv(t).animate(null, 1);
    405  animation.onfinish = event => {
    406    animation.currentTime = 0;
    407    animation.onfinish = event => {
    408      t.done();
    409    };
    410  };
    411 }, 'Animation finish event is fired again after seeking back to start');
    412 
    413 async_test(t => {
    414  const animation = createDiv(t).animate(null, 1);
    415  animation.onfinish = event => {
    416    animation.play();
    417    animation.onfinish = event => {
    418      t.done();
    419    };
    420  };
    421 }, 'Animation finish event is fired again after replaying from start');
    422 
    423 async_test(t => {
    424  const anim = createDiv(t).animate(null,
    425                                    { duration: 100000, endDelay: 50000 });
    426  anim.onfinish = t.step_func(event => {
    427    assert_unreached('finish event should not be fired');
    428  });
    429 
    430  anim.ready.then(() => {
    431    anim.currentTime = 100000;
    432    return waitForAnimationFrames(2);
    433  }).then(t.step_func(() => {
    434    t.done();
    435  }));
    436 }, 'finish event is not fired at the end of the active interval when the'
    437   + ' endDelay has not expired');
    438 
    439 async_test(t => {
    440  const anim = createDiv(t).animate(null,
    441                                    { duration: 100000, endDelay: 30000 });
    442  anim.ready.then(() => {
    443    anim.currentTime = 110000; // during endDelay
    444    anim.onfinish = t.step_func(event => {
    445      assert_unreached('onfinish event should not be fired during endDelay');
    446    });
    447    return waitForAnimationFrames(2);
    448  }).then(t.step_func(() => {
    449    anim.onfinish = t.step_func(event => {
    450      t.done();
    451    });
    452    anim.currentTime = 130000; // after endTime
    453  }));
    454 }, 'finish event is fired after the endDelay has expired');
    455 
    456 </script>
    457 </body>