summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/dom/events/resources/prefixed-animation-event-tests.js
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/dom/events/resources/prefixed-animation-event-tests.js')
-rw-r--r--testing/web-platform/tests/dom/events/resources/prefixed-animation-event-tests.js366
1 files changed, 366 insertions, 0 deletions
diff --git a/testing/web-platform/tests/dom/events/resources/prefixed-animation-event-tests.js b/testing/web-platform/tests/dom/events/resources/prefixed-animation-event-tests.js
new file mode 100644
index 0000000000..021b6bb9df
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/resources/prefixed-animation-event-tests.js
@@ -0,0 +1,366 @@
+'use strict'
+
+// Runs a set of tests for a given prefixed/unprefixed animation event (e.g.
+// animationstart/webkitAnimationStart).
+//
+// The eventDetails object must have the following form:
+// {
+// isTransition: false, <-- can be omitted, default false
+// unprefixedType: 'animationstart',
+// prefixedType: 'webkitAnimationStart',
+// animationCssStyle: '1ms', <-- must NOT include animation name or
+// transition property
+// }
+function runAnimationEventTests(eventDetails) {
+ const {
+ isTransition,
+ unprefixedType,
+ prefixedType,
+ animationCssStyle
+ } = eventDetails;
+
+ // Derive the DOM event handler names, e.g. onanimationstart.
+ const unprefixedHandler = `on${unprefixedType}`;
+ const prefixedHandler = `on${prefixedType.toLowerCase()}`;
+
+ const style = document.createElement('style');
+ document.head.appendChild(style);
+ if (isTransition) {
+ style.sheet.insertRule(
+ `.baseStyle { width: 100px; transition: width ${animationCssStyle}; }`);
+ style.sheet.insertRule('.transition { width: 200px !important; }');
+ } else {
+ style.sheet.insertRule('@keyframes anim {}');
+ }
+
+ function triggerAnimation(div) {
+ if (isTransition) {
+ div.classList.add('transition');
+ } else {
+ div.style.animation = `anim ${animationCssStyle}`;
+ }
+ }
+
+ test(t => {
+ const div = createDiv(t);
+
+ assert_equals(div[unprefixedHandler], null,
+ `${unprefixedHandler} should initially be null`);
+ assert_equals(div[prefixedHandler], null,
+ `${prefixedHandler} should initially be null`);
+
+ // Setting one should not affect the other.
+ div[unprefixedHandler] = () => { };
+
+ assert_not_equals(div[unprefixedHandler], null,
+ `setting ${unprefixedHandler} should make it non-null`);
+ assert_equals(div[prefixedHandler], null,
+ `setting ${unprefixedHandler} should not affect ${prefixedHandler}`);
+
+ div[prefixedHandler] = () => { };
+
+ assert_not_equals(div[prefixedHandler], null,
+ `setting ${prefixedHandler} should make it non-null`);
+ assert_not_equals(div[unprefixedHandler], div[prefixedHandler],
+ 'the setters should be different');
+ }, `${unprefixedHandler} and ${prefixedHandler} are not aliases`);
+
+ // The below tests primarily test the interactions of prefixed animation
+ // events in the algorithm for invoking events:
+ // https://dom.spec.whatwg.org/#concept-event-listener-invoke
+
+ promise_test(async t => {
+ const div = createDiv(t);
+
+ let receivedEventCount = 0;
+ addTestScopedEventHandler(t, div, prefixedHandler, () => {
+ receivedEventCount++;
+ });
+ addTestScopedEventListener(t, div, prefixedType, () => {
+ receivedEventCount++;
+ });
+
+ // The HTML spec[0] specifies that the prefixed event handlers have an
+ // 'Event handler event type' of the appropriate prefixed event type. E.g.
+ // onwebkitanimationend creates a listener for the event type
+ // 'webkitAnimationEnd'.
+ //
+ // [0]: https://html.spec.whatwg.org/multipage/webappapis.html#event-handlers-on-elements,-document-objects,-and-window-objects
+ div.dispatchEvent(new AnimationEvent(prefixedType));
+ assert_equals(receivedEventCount, 2,
+ 'prefixed listener and handler received event');
+ }, `dispatchEvent of a ${prefixedType} event does trigger a ` +
+ `prefixed event handler or listener`);
+
+ promise_test(async t => {
+ const div = createDiv(t);
+
+ let receivedEvent = false;
+ addTestScopedEventHandler(t, div, unprefixedHandler, () => {
+ receivedEvent = true;
+ });
+ addTestScopedEventListener(t, div, unprefixedType, () => {
+ receivedEvent = true;
+ });
+
+ div.dispatchEvent(new AnimationEvent(prefixedType));
+ assert_false(receivedEvent,
+ 'prefixed listener or handler received event');
+ }, `dispatchEvent of a ${prefixedType} event does not trigger an ` +
+ `unprefixed event handler or listener`);
+
+
+ promise_test(async t => {
+ const div = createDiv(t);
+
+ let receivedEvent = false;
+ addTestScopedEventHandler(t, div, prefixedHandler, () => {
+ receivedEvent = true;
+ });
+ addTestScopedEventListener(t, div, prefixedType, () => {
+ receivedEvent = true;
+ });
+
+ // The rewrite rules from
+ // https://dom.spec.whatwg.org/#concept-event-listener-invoke step 8 do not
+ // apply because isTrusted will be false.
+ div.dispatchEvent(new AnimationEvent(unprefixedType));
+ assert_false(receivedEvent, 'prefixed listener or handler received event');
+ }, `dispatchEvent of an ${unprefixedType} event does not trigger a ` +
+ `prefixed event handler or listener`);
+
+ promise_test(async t => {
+ const div = createDiv(t);
+
+ let receivedEvent = false;
+ addTestScopedEventHandler(t, div, prefixedHandler, () => {
+ receivedEvent = true;
+ });
+
+ triggerAnimation(div);
+ await waitForEventThenAnimationFrame(t, unprefixedType);
+ assert_true(receivedEvent, `received ${prefixedHandler} event`);
+ }, `${prefixedHandler} event handler should trigger for an animation`);
+
+ promise_test(async t => {
+ const div = createDiv(t);
+
+ let receivedPrefixedEvent = false;
+ addTestScopedEventHandler(t, div, prefixedHandler, () => {
+ receivedPrefixedEvent = true;
+ });
+ let receivedUnprefixedEvent = false;
+ addTestScopedEventHandler(t, div, unprefixedHandler, () => {
+ receivedUnprefixedEvent = true;
+ });
+
+ triggerAnimation(div);
+ await waitForEventThenAnimationFrame(t, unprefixedType);
+ assert_true(receivedUnprefixedEvent, `received ${unprefixedHandler} event`);
+ assert_false(receivedPrefixedEvent, `received ${prefixedHandler} event`);
+ }, `${prefixedHandler} event handler should not trigger if an unprefixed ` +
+ `event handler also exists`);
+
+ promise_test(async t => {
+ const div = createDiv(t);
+
+ let receivedPrefixedEvent = false;
+ addTestScopedEventHandler(t, div, prefixedHandler, () => {
+ receivedPrefixedEvent = true;
+ });
+ let receivedUnprefixedEvent = false;
+ addTestScopedEventListener(t, div, unprefixedType, () => {
+ receivedUnprefixedEvent = true;
+ });
+
+ triggerAnimation(div);
+ await waitForEventThenAnimationFrame(t, unprefixedHandler);
+ assert_true(receivedUnprefixedEvent, `received ${unprefixedHandler} event`);
+ assert_false(receivedPrefixedEvent, `received ${prefixedHandler} event`);
+ }, `${prefixedHandler} event handler should not trigger if an unprefixed ` +
+ `listener also exists`);
+
+ promise_test(async t => {
+ // We use a parent/child relationship to be able to register both prefixed
+ // and unprefixed event handlers without the deduplication logic kicking in.
+ const parent = createDiv(t);
+ const child = createDiv(t);
+ parent.appendChild(child);
+ // After moving the child, we have to clean style again.
+ getComputedStyle(child).transition;
+ getComputedStyle(child).width;
+
+ let observedUnprefixedType;
+ addTestScopedEventHandler(t, parent, unprefixedHandler, e => {
+ observedUnprefixedType = e.type;
+ });
+ let observedPrefixedType;
+ addTestScopedEventHandler(t, child, prefixedHandler, e => {
+ observedPrefixedType = e.type;
+ });
+
+ triggerAnimation(child);
+ await waitForEventThenAnimationFrame(t, unprefixedType);
+
+ assert_equals(observedUnprefixedType, unprefixedType);
+ assert_equals(observedPrefixedType, prefixedType);
+ }, `event types for prefixed and unprefixed ${unprefixedType} event ` +
+ `handlers should be named appropriately`);
+
+ promise_test(async t => {
+ const div = createDiv(t);
+
+ let receivedEvent = false;
+ addTestScopedEventListener(t, div, prefixedType, () => {
+ receivedEvent = true;
+ });
+
+ triggerAnimation(div);
+ await waitForEventThenAnimationFrame(t, unprefixedHandler);
+ assert_true(receivedEvent, `received ${prefixedType} event`);
+ }, `${prefixedType} event listener should trigger for an animation`);
+
+ promise_test(async t => {
+ const div = createDiv(t);
+
+ let receivedPrefixedEvent = false;
+ addTestScopedEventListener(t, div, prefixedType, () => {
+ receivedPrefixedEvent = true;
+ });
+ let receivedUnprefixedEvent = false;
+ addTestScopedEventListener(t, div, unprefixedType, () => {
+ receivedUnprefixedEvent = true;
+ });
+
+ triggerAnimation(div);
+ await waitForEventThenAnimationFrame(t, unprefixedHandler);
+ assert_true(receivedUnprefixedEvent, `received ${unprefixedType} event`);
+ assert_false(receivedPrefixedEvent, `received ${prefixedType} event`);
+ }, `${prefixedType} event listener should not trigger if an unprefixed ` +
+ `listener also exists`);
+
+ promise_test(async t => {
+ const div = createDiv(t);
+
+ let receivedPrefixedEvent = false;
+ addTestScopedEventListener(t, div, prefixedType, () => {
+ receivedPrefixedEvent = true;
+ });
+ let receivedUnprefixedEvent = false;
+ addTestScopedEventHandler(t, div, unprefixedHandler, () => {
+ receivedUnprefixedEvent = true;
+ });
+
+ triggerAnimation(div);
+ await waitForEventThenAnimationFrame(t, unprefixedHandler);
+ assert_true(receivedUnprefixedEvent, `received ${unprefixedType} event`);
+ assert_false(receivedPrefixedEvent, `received ${prefixedType} event`);
+ }, `${prefixedType} event listener should not trigger if an unprefixed ` +
+ `event handler also exists`);
+
+ promise_test(async t => {
+ // We use a parent/child relationship to be able to register both prefixed
+ // and unprefixed event listeners without the deduplication logic kicking in.
+ const parent = createDiv(t);
+ const child = createDiv(t);
+ parent.appendChild(child);
+ // After moving the child, we have to clean style again.
+ getComputedStyle(child).transition;
+ getComputedStyle(child).width;
+
+ let observedUnprefixedType;
+ addTestScopedEventListener(t, parent, unprefixedType, e => {
+ observedUnprefixedType = e.type;
+ });
+ let observedPrefixedType;
+ addTestScopedEventListener(t, child, prefixedType, e => {
+ observedPrefixedType = e.type;
+ });
+
+ triggerAnimation(child);
+ await waitForEventThenAnimationFrame(t, unprefixedHandler);
+
+ assert_equals(observedUnprefixedType, unprefixedType);
+ assert_equals(observedPrefixedType, prefixedType);
+ }, `event types for prefixed and unprefixed ${unprefixedType} event ` +
+ `listeners should be named appropriately`);
+
+ promise_test(async t => {
+ const div = createDiv(t);
+
+ let receivedEvent = false;
+ addTestScopedEventListener(t, div, prefixedType.toLowerCase(), () => {
+ receivedEvent = true;
+ });
+ addTestScopedEventListener(t, div, prefixedType.toUpperCase(), () => {
+ receivedEvent = true;
+ });
+
+ triggerAnimation(div);
+ await waitForEventThenAnimationFrame(t, unprefixedHandler);
+ assert_false(receivedEvent, `received ${prefixedType} event`);
+ }, `${prefixedType} event listener is case sensitive`);
+}
+
+// Below are utility functions.
+
+// Creates a div element, appends it to the document body and removes the
+// created element during test cleanup.
+function createDiv(test) {
+ const element = document.createElement('div');
+ element.classList.add('baseStyle');
+ document.body.appendChild(element);
+ test.add_cleanup(() => {
+ element.remove();
+ });
+
+ // Flush style before returning. Some browsers only do partial style re-calc,
+ // so ask for all important properties to make sure they are applied.
+ getComputedStyle(element).transition;
+ getComputedStyle(element).width;
+
+ return element;
+}
+
+// Adds an event handler for |handlerName| (calling |callback|) to the given
+// |target|, that will automatically be cleaned up at the end of the test.
+function addTestScopedEventHandler(test, target, handlerName, callback) {
+ assert_regexp_match(
+ handlerName, /^on/, 'Event handler names must start with "on"');
+ assert_equals(target[handlerName], null,
+ `${handlerName} must be supported and not previously set`);
+ target[handlerName] = callback;
+ // We need this cleaned up even if the event handler doesn't run.
+ test.add_cleanup(() => {
+ if (target[handlerName])
+ target[handlerName] = null;
+ });
+}
+
+// Adds an event listener for |type| (calling |callback|) to the given
+// |target|, that will automatically be cleaned up at the end of the test.
+function addTestScopedEventListener(test, target, type, callback) {
+ target.addEventListener(type, callback);
+ // We need this cleaned up even if the event handler doesn't run.
+ test.add_cleanup(() => {
+ target.removeEventListener(type, callback);
+ });
+}
+
+// Returns a promise that will resolve once the passed event (|eventName|) has
+// triggered and one more animation frame has happened. Automatically chooses
+// between an event handler or event listener based on whether |eventName|
+// begins with 'on'.
+//
+// We always listen on window as we don't want to interfere with the test via
+// triggering the prefixed event deduplication logic.
+function waitForEventThenAnimationFrame(test, eventName) {
+ return new Promise((resolve, _) => {
+ const eventFunc = eventName.startsWith('on')
+ ? addTestScopedEventHandler : addTestScopedEventListener;
+ eventFunc(test, window, eventName, () => {
+ // rAF once to give the event under test time to come through.
+ requestAnimationFrame(resolve);
+ });
+ });
+}