tor-browser

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

prefixed-animation-event-tests.js (13386B)


      1 'use strict'
      2 
      3 // Runs a set of tests for a given prefixed/unprefixed animation event (e.g.
      4 // animationstart/webkitAnimationStart).
      5 //
      6 // The eventDetails object must have the following form:
      7 // {
      8 //   isTransition: false, <-- can be omitted, default false
      9 //   unprefixedType: 'animationstart',
     10 //   prefixedType: 'webkitAnimationStart',
     11 //   animationCssStyle: '1ms',  <-- must NOT include animation name or
     12 //                                  transition property
     13 // }
     14 function runAnimationEventTests(eventDetails) {
     15  const {
     16    isTransition,
     17    unprefixedType,
     18    prefixedType,
     19    animationCssStyle
     20  } = eventDetails;
     21 
     22  // Derive the DOM event handler names, e.g. onanimationstart.
     23  const unprefixedHandler = `on${unprefixedType}`;
     24  const prefixedHandler = `on${prefixedType.toLowerCase()}`;
     25 
     26  const style = document.createElement('style');
     27  document.head.appendChild(style);
     28  if (isTransition) {
     29    style.sheet.insertRule(
     30      `.baseStyle { width: 100px; transition: width ${animationCssStyle}; }`);
     31    style.sheet.insertRule('.transition { width: 200px !important; }');
     32  } else {
     33    style.sheet.insertRule('@keyframes anim {}');
     34  }
     35 
     36  function triggerAnimation(div) {
     37    if (isTransition) {
     38      div.classList.add('transition');
     39    } else {
     40      div.style.animation = `anim ${animationCssStyle}`;
     41    }
     42  }
     43 
     44  test(t => {
     45    const div = createDiv(t);
     46 
     47    assert_equals(div[unprefixedHandler], null,
     48        `${unprefixedHandler} should initially be null`);
     49    assert_equals(div[prefixedHandler], null,
     50        `${prefixedHandler} should initially be null`);
     51 
     52    // Setting one should not affect the other.
     53    div[unprefixedHandler] = () => { };
     54 
     55    assert_not_equals(div[unprefixedHandler], null,
     56        `setting ${unprefixedHandler} should make it non-null`);
     57    assert_equals(div[prefixedHandler], null,
     58        `setting ${unprefixedHandler} should not affect ${prefixedHandler}`);
     59 
     60    div[prefixedHandler] = () => { };
     61 
     62    assert_not_equals(div[prefixedHandler], null,
     63        `setting ${prefixedHandler} should make it non-null`);
     64    assert_not_equals(div[unprefixedHandler], div[prefixedHandler],
     65        'the setters should be different');
     66  }, `${unprefixedHandler} and ${prefixedHandler} are not aliases`);
     67 
     68  // The below tests primarily test the interactions of prefixed animation
     69  // events in the algorithm for invoking events:
     70  // https://dom.spec.whatwg.org/#concept-event-listener-invoke
     71 
     72  promise_test(async t => {
     73    const div = createDiv(t);
     74 
     75    let receivedEventCount = 0;
     76    addTestScopedEventHandler(t, div, prefixedHandler, () => {
     77      receivedEventCount++;
     78    });
     79    addTestScopedEventListener(t, div, prefixedType, () => {
     80      receivedEventCount++;
     81    });
     82 
     83    // The HTML spec[0] specifies that the prefixed event handlers have an
     84    // 'Event handler event type' of the appropriate prefixed event type. E.g.
     85    // onwebkitanimationend creates a listener for the event type
     86    // 'webkitAnimationEnd'.
     87    //
     88    // [0]: https://html.spec.whatwg.org/multipage/webappapis.html#event-handlers-on-elements,-document-objects,-and-window-objects
     89    div.dispatchEvent(new AnimationEvent(prefixedType));
     90    assert_equals(receivedEventCount, 2,
     91                'prefixed listener and handler received event');
     92  }, `dispatchEvent of a ${prefixedType} event does trigger a ` +
     93      `prefixed event handler or listener`);
     94 
     95  promise_test(async t => {
     96    const div = createDiv(t);
     97 
     98    let receivedEvent = false;
     99    addTestScopedEventHandler(t, div, unprefixedHandler, () => {
    100      receivedEvent = true;
    101    });
    102    addTestScopedEventListener(t, div, unprefixedType, () => {
    103      receivedEvent = true;
    104    });
    105 
    106    div.dispatchEvent(new AnimationEvent(prefixedType));
    107    assert_false(receivedEvent,
    108                'prefixed listener or handler received event');
    109  }, `dispatchEvent of a ${prefixedType} event does not trigger an ` +
    110    `unprefixed event handler or listener`);
    111 
    112 
    113  promise_test(async t => {
    114    const div = createDiv(t);
    115 
    116    let receivedEvent = false;
    117    addTestScopedEventHandler(t, div, prefixedHandler, () => {
    118      receivedEvent = true;
    119    });
    120    addTestScopedEventListener(t, div, prefixedType, () => {
    121      receivedEvent = true;
    122    });
    123 
    124    // The rewrite rules from
    125    // https://dom.spec.whatwg.org/#concept-event-listener-invoke step 8 do not
    126    // apply because isTrusted will be false.
    127    div.dispatchEvent(new AnimationEvent(unprefixedType));
    128    assert_false(receivedEvent, 'prefixed listener or handler received event');
    129  }, `dispatchEvent of an ${unprefixedType} event does not trigger a ` +
    130      `prefixed event handler or listener`);
    131 
    132  promise_test(async t => {
    133    const div = createDiv(t);
    134 
    135    let receivedEvent = false;
    136    addTestScopedEventHandler(t, div, prefixedHandler, () => {
    137      receivedEvent = true;
    138    });
    139 
    140    triggerAnimation(div);
    141    await waitForEventThenAnimationFrame(t, unprefixedType);
    142    assert_true(receivedEvent, `received ${prefixedHandler} event`);
    143  }, `${prefixedHandler} event handler should trigger for an animation`);
    144 
    145  promise_test(async t => {
    146    const div = createDiv(t);
    147 
    148    let receivedPrefixedEvent = false;
    149    addTestScopedEventHandler(t, div, prefixedHandler, () => {
    150      receivedPrefixedEvent = true;
    151    });
    152    let receivedUnprefixedEvent = false;
    153    addTestScopedEventHandler(t, div, unprefixedHandler, () => {
    154      receivedUnprefixedEvent = true;
    155    });
    156 
    157    triggerAnimation(div);
    158    await waitForEventThenAnimationFrame(t, unprefixedType);
    159    assert_true(receivedUnprefixedEvent, `received ${unprefixedHandler} event`);
    160    assert_false(receivedPrefixedEvent, `received ${prefixedHandler} event`);
    161  }, `${prefixedHandler} event handler should not trigger if an unprefixed ` +
    162      `event handler also exists`);
    163 
    164  promise_test(async t => {
    165    const div = createDiv(t);
    166 
    167    let receivedPrefixedEvent = false;
    168    addTestScopedEventHandler(t, div, prefixedHandler, () => {
    169      receivedPrefixedEvent = true;
    170    });
    171    let receivedUnprefixedEvent = false;
    172    addTestScopedEventListener(t, div, unprefixedType, () => {
    173      receivedUnprefixedEvent = true;
    174    });
    175 
    176    triggerAnimation(div);
    177    await waitForEventThenAnimationFrame(t, unprefixedHandler);
    178    assert_true(receivedUnprefixedEvent, `received ${unprefixedHandler} event`);
    179    assert_false(receivedPrefixedEvent, `received ${prefixedHandler} event`);
    180  }, `${prefixedHandler} event handler should not trigger if an unprefixed ` +
    181      `listener also exists`);
    182 
    183  promise_test(async t => {
    184    // We use a parent/child relationship to be able to register both prefixed
    185    // and unprefixed event handlers without the deduplication logic kicking in.
    186    const parent = createDiv(t);
    187    const child = createDiv(t);
    188    parent.appendChild(child);
    189    // After moving the child, we have to clean style again.
    190    getComputedStyle(child).transition;
    191    getComputedStyle(child).width;
    192 
    193    let observedUnprefixedType;
    194    addTestScopedEventHandler(t, parent, unprefixedHandler, e => {
    195      observedUnprefixedType = e.type;
    196    });
    197    let observedPrefixedType;
    198    addTestScopedEventHandler(t, child, prefixedHandler, e => {
    199      observedPrefixedType = e.type;
    200    });
    201 
    202    triggerAnimation(child);
    203    await waitForEventThenAnimationFrame(t, unprefixedType);
    204 
    205    assert_equals(observedUnprefixedType, unprefixedType);
    206    assert_equals(observedPrefixedType, prefixedType);
    207  }, `event types for prefixed and unprefixed ${unprefixedType} event ` +
    208    `handlers should be named appropriately`);
    209 
    210  promise_test(async t => {
    211    const div = createDiv(t);
    212 
    213    let receivedEvent = false;
    214    addTestScopedEventListener(t, div, prefixedType, () => {
    215      receivedEvent = true;
    216    });
    217 
    218    triggerAnimation(div);
    219    await waitForEventThenAnimationFrame(t, unprefixedHandler);
    220    assert_true(receivedEvent, `received ${prefixedType} event`);
    221  }, `${prefixedType} event listener should trigger for an animation`);
    222 
    223  promise_test(async t => {
    224    const div = createDiv(t);
    225 
    226    let receivedPrefixedEvent = false;
    227    addTestScopedEventListener(t, div, prefixedType, () => {
    228      receivedPrefixedEvent = true;
    229    });
    230    let receivedUnprefixedEvent = false;
    231    addTestScopedEventListener(t, div, unprefixedType, () => {
    232      receivedUnprefixedEvent = true;
    233    });
    234 
    235    triggerAnimation(div);
    236    await waitForEventThenAnimationFrame(t, unprefixedHandler);
    237    assert_true(receivedUnprefixedEvent, `received ${unprefixedType} event`);
    238    assert_false(receivedPrefixedEvent, `received ${prefixedType} event`);
    239  }, `${prefixedType} event listener should not trigger if an unprefixed ` +
    240      `listener also exists`);
    241 
    242  promise_test(async t => {
    243    const div = createDiv(t);
    244 
    245    let receivedPrefixedEvent = false;
    246    addTestScopedEventListener(t, div, prefixedType, () => {
    247      receivedPrefixedEvent = true;
    248    });
    249    let receivedUnprefixedEvent = false;
    250    addTestScopedEventHandler(t, div, unprefixedHandler, () => {
    251      receivedUnprefixedEvent = true;
    252    });
    253 
    254    triggerAnimation(div);
    255    await waitForEventThenAnimationFrame(t, unprefixedHandler);
    256    assert_true(receivedUnprefixedEvent, `received ${unprefixedType} event`);
    257    assert_false(receivedPrefixedEvent, `received ${prefixedType} event`);
    258  }, `${prefixedType} event listener should not trigger if an unprefixed ` +
    259       `event handler also exists`);
    260 
    261  promise_test(async t => {
    262    // We use a parent/child relationship to be able to register both prefixed
    263    // and unprefixed event listeners without the deduplication logic kicking in.
    264    const parent = createDiv(t);
    265    const child = createDiv(t);
    266    parent.appendChild(child);
    267    // After moving the child, we have to clean style again.
    268    getComputedStyle(child).transition;
    269    getComputedStyle(child).width;
    270 
    271    let observedUnprefixedType;
    272    addTestScopedEventListener(t, parent, unprefixedType, e => {
    273      observedUnprefixedType = e.type;
    274    });
    275    let observedPrefixedType;
    276    addTestScopedEventListener(t, child, prefixedType, e => {
    277      observedPrefixedType = e.type;
    278    });
    279 
    280    triggerAnimation(child);
    281    await waitForEventThenAnimationFrame(t, unprefixedHandler);
    282 
    283    assert_equals(observedUnprefixedType, unprefixedType);
    284    assert_equals(observedPrefixedType, prefixedType);
    285  }, `event types for prefixed and unprefixed ${unprefixedType} event ` +
    286      `listeners should be named appropriately`);
    287 
    288  promise_test(async t => {
    289    const div = createDiv(t);
    290 
    291    let receivedEvent = false;
    292    addTestScopedEventListener(t, div, prefixedType.toLowerCase(), () => {
    293      receivedEvent = true;
    294    });
    295    addTestScopedEventListener(t, div, prefixedType.toUpperCase(), () => {
    296      receivedEvent = true;
    297    });
    298 
    299    triggerAnimation(div);
    300    await waitForEventThenAnimationFrame(t, unprefixedHandler);
    301    assert_false(receivedEvent, `received ${prefixedType} event`);
    302  }, `${prefixedType} event listener is case sensitive`);
    303 }
    304 
    305 // Below are utility functions.
    306 
    307 // Creates a div element, appends it to the document body and removes the
    308 // created element during test cleanup.
    309 function createDiv(test) {
    310  const element = document.createElement('div');
    311  element.classList.add('baseStyle');
    312  document.body.appendChild(element);
    313  test.add_cleanup(() => {
    314    element.remove();
    315  });
    316 
    317  // Flush style before returning. Some browsers only do partial style re-calc,
    318  // so ask for all important properties to make sure they are applied.
    319  getComputedStyle(element).transition;
    320  getComputedStyle(element).width;
    321 
    322  return element;
    323 }
    324 
    325 // Adds an event handler for |handlerName| (calling |callback|) to the given
    326 // |target|, that will automatically be cleaned up at the end of the test.
    327 function addTestScopedEventHandler(test, target, handlerName, callback) {
    328  assert_regexp_match(
    329      handlerName, /^on/, 'Event handler names must start with "on"');
    330  assert_equals(target[handlerName], null,
    331                `${handlerName} must be supported and not previously set`);
    332  target[handlerName] = callback;
    333  // We need this cleaned up even if the event handler doesn't run.
    334  test.add_cleanup(() => {
    335    if (target[handlerName])
    336      target[handlerName] = null;
    337  });
    338 }
    339 
    340 // Adds an event listener for |type| (calling |callback|) to the given
    341 // |target|, that will automatically be cleaned up at the end of the test.
    342 function addTestScopedEventListener(test, target, type, callback) {
    343  target.addEventListener(type, callback);
    344  // We need this cleaned up even if the event handler doesn't run.
    345  test.add_cleanup(() => {
    346    target.removeEventListener(type, callback);
    347  });
    348 }
    349 
    350 // Returns a promise that will resolve once the passed event (|eventName|) has
    351 // triggered and one more animation frame has happened. Automatically chooses
    352 // between an event handler or event listener based on whether |eventName|
    353 // begins with 'on'.
    354 //
    355 // We always listen on window as we don't want to interfere with the test via
    356 // triggering the prefixed event deduplication logic.
    357 function waitForEventThenAnimationFrame(test, eventName) {
    358  return new Promise((resolve, _) => {
    359    const eventFunc = eventName.startsWith('on')
    360        ? addTestScopedEventHandler : addTestScopedEventListener;
    361    eventFunc(test, window, eventName, () => {
    362      // rAF once to give the event under test time to come through.
    363      requestAnimationFrame(resolve);
    364    });
    365  });
    366 }