tor-browser

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

update-and-send-events.html (8690B)


      1 <!doctype html>
      2 <meta charset=utf-8>
      3 <title>Update animations and send events</title>
      4 <meta name="timeout" content="long">
      5 <link rel="help" href="https://drafts.csswg.org/web-animations/#update-animations-and-send-events">
      6 <script src="/resources/testharness.js"></script>
      7 <script src="/resources/testharnessreport.js"></script>
      8 <script src="../../testcommon.js"></script>
      9 <div id="log"></div>
     10 <script>
     11 'use strict';
     12 
     13 promise_test(async t => {
     14  const div = createDiv(t);
     15  const animation = div.animate(null, 100 * MS_PER_SEC);
     16 
     17  // The ready promise should be resolved as part of micro-task checkpoint
     18  // after updating the current time of all timeslines in the procedure to
     19  // "update animations and send events".
     20  await animation.ready;
     21 
     22  let rAFReceived = false;
     23  requestAnimationFrame(() => rAFReceived = true);
     24 
     25  const eventWatcher = new EventWatcher(t, animation, 'cancel');
     26  animation.cancel();
     27 
     28  await eventWatcher.wait_for('cancel');
     29 
     30  assert_false(rAFReceived,
     31    'cancel event should be fired before requestAnimationFrame');
     32 }, 'Fires cancel event before requestAnimationFrame');
     33 
     34 promise_test(async t => {
     35  const div = createDiv(t);
     36  const animation = div.animate(null, 100 * MS_PER_SEC);
     37 
     38  // Like the above test, the ready promise should be resolved micro-task
     39  // checkpoint after updating the current time of all timeslines in the
     40  // procedure to "update animations and send events".
     41  await animation.ready;
     42 
     43  let rAFReceived = false;
     44  requestAnimationFrame(() => rAFReceived = true);
     45 
     46  const eventWatcher = new EventWatcher(t, animation, 'finish');
     47  animation.finish();
     48 
     49  await eventWatcher.wait_for('finish');
     50 
     51  assert_false(rAFReceived,
     52    'finish event should be fired before requestAnimationFrame');
     53 }, 'Fires finish event before requestAnimationFrame');
     54 
     55 function animationType(anim) {
     56  if (anim instanceof CSSAnimation) {
     57    return 'CSSAnimation';
     58  } else if (anim instanceof CSSTransition) {
     59    return 'CSSTransition';
     60  } else {
     61    return 'ScriptAnimation';
     62  }
     63 }
     64 
     65 promise_test(async t => {
     66  createStyle(t, { '@keyframes anim': '' });
     67  const div = createDiv(t);
     68 
     69  getComputedStyle(div).marginLeft;
     70  div.style = 'animation: anim 100s; ' +
     71              'transition: margin-left 100s; ' +
     72              'margin-left: 100px;';
     73  div.animate(null, 100 * MS_PER_SEC);
     74  const animations = div.getAnimations();
     75 
     76  let receivedEvents = [];
     77  animations.forEach(anim => {
     78    anim.onfinish = event => {
     79      receivedEvents.push({
     80        type: animationType(anim) + ':' + event.type,
     81        timeStamp: event.timeStamp
     82      });
     83    };
     84  });
     85 
     86  await Promise.all(animations.map(anim => anim.ready));
     87 
     88  // Setting current time to the time just before the effect end.
     89  animations.forEach(anim => anim.currentTime = 100 * MS_PER_SEC - 1);
     90 
     91  await waitForNextFrame();
     92 
     93  assert_array_equals(receivedEvents.map(event => event.type),
     94    [ 'CSSTransition:finish', 'CSSAnimation:finish',
     95      'ScriptAnimation:finish' ],
     96    'finish events for various animation type should be sorted by composite ' +
     97    'order');
     98 }, 'Sorts finish events by composite order');
     99 
    100 promise_test(async t => {
    101  createStyle(t, { '@keyframes anim': '' });
    102  const div = createDiv(t);
    103 
    104  let receivedEvents = [];
    105  function receiveEvent(type, timeStamp) {
    106    receivedEvents.push({ type, timeStamp });
    107  }
    108 
    109  div.onanimationcancel = event => receiveEvent(event.type, event.timeStamp);
    110  div.ontransitioncancel = event => receiveEvent(event.type, event.timeStamp);
    111 
    112  getComputedStyle(div).marginLeft;
    113  div.style = 'animation: anim 100s; ' +
    114              'transition: margin-left 100s; ' +
    115              'margin-left: 100px;';
    116  div.animate(null, 100 * MS_PER_SEC);
    117  const animations = div.getAnimations();
    118 
    119  animations.forEach(anim => {
    120    anim.oncancel = event => {
    121      receiveEvent(animationType(anim) + ':' + event.type, event.timeStamp);
    122    };
    123  });
    124 
    125  await Promise.all(animations.map(anim => anim.ready));
    126 
    127  const timeInAnimationReady = document.timeline.currentTime;
    128 
    129  // Call cancel() in reverse composite order.  I.e. canceling for script
    130  // animation happen first, then for CSS animation and CSS transition.
    131  // 'cancel' events for these animations should be sorted by composite
    132  // order.
    133  animations.reverse().forEach(anim => anim.cancel());
    134 
    135  // requestAnimationFrame callback which is actually the _same_ frame since we
    136  // are currently operating in the `ready` callbac of the animations which
    137  // happens as part of the "Update animations and send events" procedure
    138  // _before_ we run animation frame callbacks.
    139  await waitForAnimationFrames(1);
    140 
    141  assert_times_equal(timeInAnimationReady, document.timeline.currentTime,
    142    'A rAF callback should happen in the same frame');
    143 
    144  assert_array_equals(receivedEvents.map(event => event.type),
    145    // This ordering needs more clarification in the spec, but the intention is
    146    // that the cancel playback event fires before the equivalent CSS cancel
    147    // event in each case.
    148    [ 'CSSTransition:cancel', 'CSSAnimation:cancel', 'ScriptAnimation:cancel',
    149      'transitioncancel', 'animationcancel' ],
    150    'cancel events should be sorted by composite order');
    151 }, 'Sorts cancel events by composite order');
    152 
    153 promise_test(async t => {
    154  const div = createDiv(t);
    155  getComputedStyle(div).marginLeft;
    156  div.style = 'transition: margin-left 100s; margin-left: 100px;';
    157  const anim = div.getAnimations()[0];
    158 
    159  let receivedEvents = [];
    160  anim.oncancel = event => receivedEvents.push(event);
    161 
    162  const eventWatcher = new EventWatcher(t, div, 'transitionstart');
    163  await eventWatcher.wait_for('transitionstart');
    164 
    165  const timeInEventCallback = document.timeline.currentTime;
    166 
    167  // Calling cancel() queues a cancel event
    168  anim.cancel();
    169 
    170  await waitForAnimationFrames(1);
    171  assert_times_equal(timeInEventCallback, document.timeline.currentTime,
    172    'A rAF callback should happen in the same frame');
    173 
    174  assert_array_equals(receivedEvents, [],
    175    'The queued cancel event shouldn\'t be dispatched in the same frame');
    176 
    177  await waitForAnimationFrames(1);
    178  assert_array_equals(receivedEvents.map(event => event.type), ['cancel'],
    179    'The cancel event should be dispatched in a later frame');
    180 }, 'Queues a cancel event in transitionstart event callback');
    181 
    182 promise_test(async t => {
    183  const div = createDiv(t);
    184  getComputedStyle(div).marginLeft;
    185  div.style = 'transition: margin-left 100s; margin-left: 100px;';
    186  const anim = div.getAnimations()[0];
    187 
    188  let receivedEvents = [];
    189  anim.oncancel = event => receivedEvents.push(event);
    190  div.ontransitioncancel = event => receivedEvents.push(event);
    191 
    192  await anim.ready;
    193 
    194  anim.cancel();
    195 
    196  await waitForAnimationFrames(1);
    197 
    198  assert_array_equals(receivedEvents.map(event => event.type),
    199    [ 'cancel', 'transitioncancel' ],
    200    'Playback and CSS events for the same transition should be sorted by ' +
    201    'schedule event time and composite order');
    202 }, 'Sorts events for the same transition');
    203 
    204 promise_test(async t => {
    205  const div = createDiv(t);
    206  const anim = div.animate(null, 100 * MS_PER_SEC);
    207 
    208  let receivedEvents = [];
    209  anim.oncancel = event => receivedEvents.push(event);
    210  anim.onfinish = event => receivedEvents.push(event);
    211 
    212  await anim.ready;
    213 
    214  anim.finish();
    215  anim.cancel();
    216 
    217  await waitForAnimationFrames(1);
    218 
    219  assert_array_equals(receivedEvents.map(event => event.type),
    220    [ 'finish', 'cancel' ],
    221    'Calling finish() synchronously queues a finish event when updating the ' +
    222    'finish state so it should appear before the cancel event');
    223 }, 'Playback events with the same timeline retain the order in which they are' +
    224   'queued');
    225 
    226 promise_test(async t => {
    227  const div = createDiv(t);
    228 
    229  // Create two animations with separate timelines
    230 
    231  const timelineA = document.timeline;
    232  const animA = div.animate(null, 100 * MS_PER_SEC);
    233 
    234  const timelineB = new DocumentTimeline();
    235  const animB = new Animation(
    236    new KeyframeEffect(div, null, 100 * MS_PER_SEC),
    237    timelineB
    238  );
    239  animB.play();
    240 
    241  animA.currentTime = 99.9 * MS_PER_SEC;
    242  animB.currentTime = 99.9 * MS_PER_SEC;
    243 
    244  // When the next tick happens both animations should be updated, and we will
    245  // notice that they are now finished. As a result their finished promise
    246  // callbacks should be queued. All of that should happen before we run the
    247  // next microtask checkpoint and actually run the promise callbacks and
    248  // hence the calls to cancel should not stop the existing callbacks from
    249  // being run.
    250 
    251  animA.finished.then(() => { animB.cancel() });
    252  animB.finished.then(() => { animA.cancel() });
    253 
    254  await Promise.all([animA.finished, animB.finished]);
    255 }, 'All timelines are updated before running microtasks');
    256 
    257 </script>