• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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// }
14function 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.
309function 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.
327function 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.
342function 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.
357function 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}
367