summaryrefslogtreecommitdiffstats
path: root/layout/style/test/test_animations_event_order.html
diff options
context:
space:
mode:
Diffstat (limited to 'layout/style/test/test_animations_event_order.html')
-rw-r--r--layout/style/test/test_animations_event_order.html710
1 files changed, 710 insertions, 0 deletions
diff --git a/layout/style/test/test_animations_event_order.html b/layout/style/test/test_animations_event_order.html
new file mode 100644
index 0000000000..7caee2bdcb
--- /dev/null
+++ b/layout/style/test/test_animations_event_order.html
@@ -0,0 +1,710 @@
+<!doctype html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1183461
+-->
+<!--
+ This test is similar to those in test_animations.html with the exception
+ that those tests interact with a single element at a time. The tests in this
+ file are specifically concerned with testing the ordering of events between
+ elements for which most of the utilities in animation_utils.js are not
+ suited.
+-->
+<head>
+ <meta charset=utf-8>
+ <title>Test for CSS Animation and Transition event ordering
+ (Bug 1183461)</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <!-- We still need animation_utils.js for advance_clock -->
+ <script type="application/javascript" src="animation_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ @keyframes anim { to { margin-left: 100px } }
+ @keyframes animA { to { margin-left: 100px } }
+ @keyframes animB { to { margin-left: 100px } }
+ @keyframes animC { to { margin-left: 100px } }
+ </style>
+</head>
+<body>
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1183461">Mozilla Bug
+ 1183461</a>
+<div id="display"></div>
+<pre id="test">
+<script type="application/javascript">
+'use strict';
+
+/* eslint-disable no-shadow */
+
+// Take over the refresh driver right from the start.
+advance_clock(0);
+
+// Common test scaffolding
+
+var gEventsReceived = [];
+var gDisplay = document.getElementById('display');
+
+[ 'animationstart',
+ 'animationiteration',
+ 'animationend',
+ 'animationcancel',
+ 'transitionrun',
+ 'transitionstart',
+ 'transitionend',
+ 'transitioncancel' ]
+ .forEach(event =>
+ gDisplay.addEventListener(event,
+ event => gEventsReceived.push(event)));
+
+function checkEventOrder(...args) {
+ // Argument format:
+ // Arguments = ExpectedEvent*, desc
+ // ExpectedEvent =
+ // [ target|animationName|transitionProperty, (pseudo,) message ]
+ var expectedEvents = args.slice(0, -1);
+ var desc = args[args.length - 1];
+ var isTestingNameOrProperty = expectedEvents.length &&
+ typeof expectedEvents[0][0] == 'string';
+
+ var formatEvent = (target, nameOrProperty, pseudo, message) =>
+ isTestingNameOrProperty ?
+ `${nameOrProperty}${pseudo}:${message}` :
+ `${target.id}${pseudo}:${message}`;
+
+ var actual = gEventsReceived.map(
+ event => formatEvent(event.target,
+ event.animationName || event.propertyName,
+ event.pseudoElement, event.type)
+ ).join(';');
+ var expected = expectedEvents.map(
+ event => event.length == 3 ?
+ formatEvent(event[0], event[0], event[1], event[2]) :
+ formatEvent(event[0], event[0], '', event[1])
+ ).join(';');
+ is(actual, expected, desc);
+ gEventsReceived = [];
+}
+
+// 1. TESTS FOR SORTING BY TREE ORDER
+
+// 1a. Test that simultaneous events are sorted by tree order (siblings)
+
+var divs = [ document.createElement('div'),
+ document.createElement('div'),
+ document.createElement('div') ];
+divs.forEach((div, i) => {
+ gDisplay.appendChild(div);
+ div.setAttribute('style', 'animation: anim 10s');
+ div.setAttribute('id', 'div' + i);
+ getComputedStyle(div).animationName; // trigger building of animation
+});
+
+advance_clock(0);
+checkEventOrder([ divs[0], 'animationstart' ],
+ [ divs[1], 'animationstart' ],
+ [ divs[2], 'animationstart' ],
+ 'Simultaneous start on siblings');
+
+divs.forEach(div => div.remove());
+divs = [];
+
+// 1b. Test that simultaneous events are sorted by tree order (children)
+
+divs = [ document.createElement('div'),
+ document.createElement('div'),
+ document.createElement('div') ];
+
+// Create the following arrangement:
+//
+// display
+// / \
+// div[0] div[1]
+// /
+// div[2]
+
+gDisplay.appendChild(divs[0]);
+gDisplay.appendChild(divs[1]);
+divs[0].appendChild(divs[2]);
+
+divs.forEach((div, i) => {
+ div.setAttribute('style', 'animation: anim 10s');
+ div.setAttribute('id', 'div' + i);
+ getComputedStyle(div).animationName; // trigger building of animation
+});
+
+advance_clock(0);
+checkEventOrder([ divs[0], 'animationstart' ],
+ [ divs[2], 'animationstart' ],
+ [ divs[1], 'animationstart' ],
+ 'Simultaneous start on children');
+
+divs.forEach(div => div.remove());
+divs = [];
+
+// 1c. Test that simultaneous events are sorted by tree order (pseudos)
+
+divs = [ document.createElement('div'),
+ document.createElement('div') ];
+
+// Create the following arrangement:
+//
+// display
+// |
+// div[0]
+// ::before,
+// ::after
+// |
+// div[1]
+
+gDisplay.appendChild(divs[0]);
+divs[0].appendChild(divs[1]);
+
+divs.forEach((div, i) => {
+ div.setAttribute('style', 'animation: anim 10s');
+ div.setAttribute('id', 'div' + i);
+});
+
+var extraStyle = document.createElement('style');
+document.head.appendChild(extraStyle);
+var sheet = extraStyle.sheet;
+sheet.insertRule('#div0::after { animation: anim 10s }', 0);
+sheet.insertRule('#div0::before { animation: anim 10s }', 1);
+sheet.insertRule('#div0::after, #div0::before { ' +
+ ' content: " " }', 2);
+getComputedStyle(divs[0]).animationName; // build animation
+getComputedStyle(divs[1]).animationName; // build animation
+
+advance_clock(0);
+checkEventOrder([ divs[0], 'animationstart' ],
+ [ divs[0], '::before', 'animationstart' ],
+ [ divs[0], '::after', 'animationstart' ],
+ [ divs[1], 'animationstart' ],
+ 'Simultaneous start on pseudo-elements');
+
+divs.forEach(div => div.remove());
+divs = [];
+
+sheet = undefined;
+extraStyle.remove();
+extraStyle = undefined;
+
+// 2. TESTS FOR SORTING BY TIME
+
+// 2a. Test that events are sorted by time
+
+divs = [ document.createElement('div'),
+ document.createElement('div') ];
+divs.forEach((div, i) => {
+ gDisplay.appendChild(div);
+ div.setAttribute('style', 'animation: anim 10s');
+ div.setAttribute('id', 'div' + i);
+});
+
+divs[0].style.animationDelay = '5s';
+
+advance_clock(0);
+advance_clock(20000);
+
+checkEventOrder([ divs[1], 'animationstart' ],
+ [ divs[0], 'animationstart' ],
+ [ divs[1], 'animationend' ],
+ [ divs[0], 'animationend' ],
+ 'Sorting of start and end events by time');
+
+divs.forEach(div => div.remove());
+divs = [];
+
+// 2b. Test different events within the one element
+
+var div = document.createElement('div');
+gDisplay.appendChild(div);
+div.style.animation = 'anim 4s 2, ' + // Repeat at t=4s
+ 'anim 10s 5s, ' + // Start at t=5s
+ 'anim 3s'; // End at t=3s
+div.setAttribute('id', 'div');
+getComputedStyle(div).animationName; // build animation
+
+advance_clock(0);
+advance_clock(5000);
+
+checkEventOrder([ div, 'animationstart' ],
+ [ div, 'animationstart' ],
+ [ div, 'animationend' ],
+ [ div, 'animationiteration' ],
+ [ div, 'animationstart' ],
+ 'Sorting of different events by time within an element');
+
+div.remove();
+div = undefined;
+
+// 2c. Test negative delay is sorted equal to zero delay but before
+// positive delay
+
+divs = [ document.createElement('div'),
+ document.createElement('div'),
+ document.createElement('div') ];
+divs.forEach((div, i) => {
+ gDisplay.appendChild(div);
+ div.setAttribute('id', 'div' + i);
+});
+
+divs[0].style.animation = 'anim 20s 5s'; // Positive delay, sorts last
+divs[1].style.animation = 'anim 20s'; // 0s delay
+divs[2].style.animation = 'anim 20s -5s'; // Negative delay, sorts same as
+ // 0s delay, i.e. use document
+ // position
+
+advance_clock(0);
+advance_clock(5000);
+checkEventOrder([ divs[1], 'animationstart' ],
+ [ divs[2], 'animationstart' ],
+ [ divs[0], 'animationstart' ],
+ 'Sorting of events including negative delay');
+
+divs.forEach(div => div.remove());
+divs = [];
+
+// 3. TESTS FOR SORTING BY animation-name POSITION
+
+// 3a. Test animation-name position
+
+div = document.createElement('div');
+gDisplay.appendChild(div);
+div.style.animation = 'animA 10s, animB 5s, animC 5s 2';
+div.setAttribute('id', 'div');
+getComputedStyle(div).animationName; // build animation
+
+advance_clock(0);
+
+checkEventOrder([ 'animA', 'animationstart' ],
+ [ 'animB', 'animationstart' ],
+ [ 'animC', 'animationstart' ],
+ 'Sorting of simultaneous animationstart events by ' +
+ 'animation-name');
+
+advance_clock(5000);
+
+checkEventOrder([ 'animB', 'animationend' ],
+ [ 'animC', 'animationiteration' ],
+ 'Sorting of different types of events by animation-name');
+
+div.remove();
+div = undefined;
+
+// 3b. Test time trumps animation-name position
+
+div = document.createElement('div');
+gDisplay.appendChild(div);
+div.style.animation = 'animA 10s 2s, animB 10s 1s';
+div.setAttribute('id', 'div');
+
+advance_clock(0);
+advance_clock(3000);
+
+checkEventOrder([ 'animB', 'animationstart' ],
+ [ 'animA', 'animationstart' ],
+ 'Events are sorted by time first, before animation-position');
+
+div.remove();
+div = undefined;
+
+// 4. TESTS FOR TRANSITIONS
+
+// 4a. Test sorting transitions by document position
+
+divs = [ document.createElement('div'),
+ document.createElement('div') ];
+divs.forEach((div, i) => {
+ gDisplay.appendChild(div);
+ div.style.marginLeft = '0px';
+ div.style.transition = 'margin-left 10s';
+ div.setAttribute('id', 'div' + i);
+});
+
+getComputedStyle(divs[0]).marginLeft;
+divs.forEach(div => div.style.marginLeft = '100px');
+getComputedStyle(divs[0]).marginLeft;
+
+advance_clock(0);
+advance_clock(10000);
+
+checkEventOrder([ divs[0], 'transitionrun' ],
+ [ divs[0], 'transitionstart' ],
+ [ divs[1], 'transitionrun' ],
+ [ divs[1], 'transitionstart' ],
+ [ divs[0], 'transitionend' ],
+ [ divs[1], 'transitionend' ],
+ 'Simultaneous transitionrun/start/end on siblings');
+
+divs.forEach(div => div.remove());
+divs = [];
+
+// 4b. Test sorting transitions by document position (children)
+
+divs = [ document.createElement('div'),
+ document.createElement('div'),
+ document.createElement('div') ];
+
+// Create the following arrangement:
+//
+// display
+// / \
+// div[0] div[1]
+// /
+// div[2]
+
+gDisplay.appendChild(divs[0]);
+gDisplay.appendChild(divs[1]);
+divs[0].appendChild(divs[2]);
+
+divs.forEach((div, i) => {
+ div.style.marginLeft = '0px';
+ div.style.transition = 'margin-left 10s';
+ div.setAttribute('id', 'div' + i);
+});
+
+getComputedStyle(divs[0]).marginLeft;
+divs.forEach(div => div.style.marginLeft = '100px');
+getComputedStyle(divs[0]).marginLeft;
+
+advance_clock(0);
+advance_clock(10000);
+
+checkEventOrder([ divs[0], 'transitionrun' ],
+ [ divs[0], 'transitionstart' ],
+ [ divs[2], 'transitionrun' ],
+ [ divs[2], 'transitionstart' ],
+ [ divs[1], 'transitionrun' ],
+ [ divs[1], 'transitionstart' ],
+ [ divs[0], 'transitionend' ],
+ [ divs[2], 'transitionend' ],
+ [ divs[1], 'transitionend' ],
+ 'Simultaneous transitionrun/start/end on children');
+
+divs.forEach(div => div.remove());
+divs = [];
+
+// 4c. Test sorting transitions by document position (pseudos)
+
+divs = [ document.createElement('div'),
+ document.createElement('div') ];
+
+// Create the following arrangement:
+//
+// display
+// |
+// div[0]
+// ::before,
+// ::after
+// |
+// div[1]
+
+gDisplay.appendChild(divs[0]);
+divs[0].appendChild(divs[1]);
+
+divs.forEach((div, i) => {
+ div.setAttribute('id', 'div' + i);
+});
+
+extraStyle = document.createElement('style');
+document.head.appendChild(extraStyle);
+sheet = extraStyle.sheet;
+sheet.insertRule('div, #div0::after, #div0::before { ' +
+ ' transition: margin-left 10s; ' +
+ ' margin-left: 0px }', 0);
+sheet.insertRule('div.active, #div0.active::after, #div0.active::before { ' +
+ ' margin-left: 100px }', 1);
+sheet.insertRule('#div0::after, #div0::before { ' +
+ ' content: " " }', 2);
+
+getComputedStyle(divs[0]).marginLeft;
+divs.forEach(div => div.classList.add('active'));
+getComputedStyle(divs[0]).marginLeft;
+
+advance_clock(0);
+advance_clock(10000);
+
+checkEventOrder([ divs[0], 'transitionrun' ],
+ [ divs[0], 'transitionstart' ],
+ [ divs[0], '::before', 'transitionrun' ],
+ [ divs[0], '::before', 'transitionstart' ],
+ [ divs[0], '::after', 'transitionrun' ],
+ [ divs[0], '::after', 'transitionstart' ],
+ [ divs[1], 'transitionrun' ],
+ [ divs[1], 'transitionstart' ],
+ [ divs[0], 'transitionend' ],
+ [ divs[0], '::before', 'transitionend' ],
+ [ divs[0], '::after', 'transitionend' ],
+ [ divs[1], 'transitionend' ],
+ 'Simultaneous transitionrun/start/end on pseudo-elements');
+
+divs.forEach(div => div.remove());
+divs = [];
+
+sheet = undefined;
+extraStyle.remove();
+extraStyle = undefined;
+
+// 4d. Test sorting transitions by time
+
+divs = [ document.createElement('div'),
+ document.createElement('div') ];
+divs.forEach((div, i) => {
+ gDisplay.appendChild(div);
+ div.style.marginLeft = '0px';
+ div.setAttribute('id', 'div' + i);
+});
+
+divs[0].style.transition = 'margin-left 10s';
+divs[1].style.transition = 'margin-left 5s';
+
+getComputedStyle(divs[0]).marginLeft;
+divs.forEach(div => div.style.marginLeft = '100px');
+getComputedStyle(divs[0]).marginLeft;
+
+advance_clock(0);
+advance_clock(10000);
+
+checkEventOrder([ divs[0], 'transitionrun' ],
+ [ divs[0], 'transitionstart' ],
+ [ divs[1], 'transitionrun' ],
+ [ divs[1], 'transitionstart' ],
+ [ divs[1], 'transitionend' ],
+ [ divs[0], 'transitionend' ],
+ 'Sorting of transitionrun/start/end events by time');
+
+divs.forEach(div => div.remove());
+divs = [];
+
+// 4e. Test sorting transitions by time (with delay)
+
+divs = [ document.createElement('div'),
+ document.createElement('div') ];
+divs.forEach((div, i) => {
+ gDisplay.appendChild(div);
+ div.style.marginLeft = '0px';
+ div.setAttribute('id', 'div' + i);
+});
+
+divs[0].style.transition = 'margin-left 5s 5s';
+divs[1].style.transition = 'margin-left 5s';
+
+getComputedStyle(divs[0]).marginLeft;
+divs.forEach(div => div.style.marginLeft = '100px');
+getComputedStyle(divs[0]).marginLeft;
+
+advance_clock(0);
+advance_clock(10 * 1000);
+
+checkEventOrder([ divs[0], 'transitionrun' ],
+ [ divs[1], 'transitionrun' ],
+ [ divs[1], 'transitionstart' ],
+ [ divs[0], 'transitionstart' ],
+ [ divs[1], 'transitionend' ],
+ [ divs[0], 'transitionend' ],
+ 'Sorting of transitionrun/start/end events by time' +
+ '(including delay)');
+
+divs.forEach(div => div.remove());
+divs = [];
+
+// 4f. Test sorting transitions by transition-property
+
+div = document.createElement('div');
+gDisplay.appendChild(div);
+div.style.opacity = '0';
+div.style.marginLeft = '0px';
+div.style.transition = 'all 5s';
+
+getComputedStyle(div).marginLeft;
+div.style.opacity = '1';
+div.style.marginLeft = '100px';
+getComputedStyle(div).marginLeft;
+
+advance_clock(0);
+advance_clock(10000);
+
+checkEventOrder([ 'margin-left', 'transitionrun' ],
+ [ 'margin-left', 'transitionstart' ],
+ [ 'opacity', 'transitionrun' ],
+ [ 'opacity', 'transitionstart' ],
+ [ 'margin-left', 'transitionend' ],
+ [ 'opacity', 'transitionend' ],
+ 'Sorting of transitionrun/start/end events by ' +
+ 'transition-property')
+
+div.remove();
+div = undefined;
+
+// 4g. Test document position beats transition-property
+
+divs = [ document.createElement('div'),
+ document.createElement('div') ];
+divs.forEach((div, i) => {
+ gDisplay.appendChild(div);
+ div.style.marginLeft = '0px';
+ div.style.opacity = '0';
+ div.style.transition = 'all 10s';
+ div.setAttribute('id', 'div' + i);
+});
+
+getComputedStyle(divs[0]).marginLeft;
+divs[0].style.opacity = '1';
+divs[1].style.marginLeft = '100px';
+getComputedStyle(divs[0]).marginLeft;
+
+advance_clock(0);
+advance_clock(10000);
+
+checkEventOrder([ divs[0], 'transitionrun' ],
+ [ divs[0], 'transitionstart' ],
+ [ divs[1], 'transitionrun' ],
+ [ divs[1], 'transitionstart' ],
+ [ divs[0], 'transitionend' ],
+ [ divs[1], 'transitionend' ],
+ 'Transition events are sorted by document position first, ' +
+ 'before transition-property');
+
+divs.forEach(div => div.remove());
+divs = [];
+
+// 4h. Test time beats transition-property
+
+div = document.createElement('div');
+gDisplay.appendChild(div);
+div.style.opacity = '0';
+div.style.marginLeft = '0px';
+div.style.transition = 'margin-left 10s, opacity 5s';
+
+getComputedStyle(div).marginLeft;
+div.style.opacity = '1';
+div.style.marginLeft = '100px';
+getComputedStyle(div).marginLeft;
+
+advance_clock(0);
+advance_clock(10000);
+
+checkEventOrder([ 'margin-left', 'transitionrun' ],
+ [ 'margin-left', 'transitionstart' ],
+ [ 'opacity', 'transitionrun' ],
+ [ 'opacity', 'transitionstart' ],
+ [ 'opacity', 'transitionend' ],
+ [ 'margin-left', 'transitionend' ],
+ 'Transition events are sorted by time first, before ' +
+ 'transition-property');
+
+div.remove();
+div = undefined;
+
+// 4i. Test sorting transitions by document position (negative delay)
+
+divs = [ document.createElement('div'),
+ document.createElement('div') ];
+divs.forEach((div, i) => {
+ gDisplay.appendChild(div);
+ div.style.marginLeft = '0px';
+ div.setAttribute('id', 'div' + i);
+});
+
+divs[0].style.transition = 'margin-left 10s 5s';
+divs[1].style.transition = 'margin-left 10s';
+
+getComputedStyle(divs[0]).marginLeft;
+divs.forEach(div => div.style.marginLeft = '100px');
+getComputedStyle(divs[0]).marginLeft;
+
+advance_clock(0);
+advance_clock(15 * 1000);
+
+checkEventOrder([ divs[0], 'transitionrun' ],
+ [ divs[1], 'transitionrun' ],
+ [ divs[1], 'transitionstart' ],
+ [ divs[0], 'transitionstart' ],
+ [ divs[1], 'transitionend' ],
+ [ divs[0], 'transitionend' ],
+ 'Simultaneous transitionrun/start/end on siblings');
+
+divs.forEach(div => div.remove());
+divs = [];
+
+// 4j. Test sorting transitions with cancel
+// The order of transitioncancel is based on StyleManager.
+
+divs = [ document.createElement('div'),
+ document.createElement('div') ];
+divs.forEach((div, i) => {
+ gDisplay.appendChild(div);
+ div.style.marginLeft = '0px';
+ div.setAttribute('id', 'div' + i);
+});
+
+divs[0].style.transition = 'margin-left 10s 5s';
+divs[1].style.transition = 'margin-left 10s';
+
+getComputedStyle(divs[0]).marginLeft;
+divs.forEach(div => div.style.marginLeft = '100px');
+getComputedStyle(divs[0]).marginLeft;
+
+advance_clock(0);
+advance_clock(5 * 1000);
+divs.forEach(div => {
+ div.style.display = 'none';
+ // The transitioncancel event order is not absolute when firing siblings
+ // transitioncancel on same elapsed time.
+ // Force to flush style for the element so that the transition on the element
+ // iscancelled and corresponding cancel event is queued respectively.
+ getComputedStyle(div).display;
+});
+advance_clock(10 * 1000);
+
+checkEventOrder([ divs[0], 'transitionrun' ],
+ [ divs[1], 'transitionrun' ],
+ [ divs[1], 'transitionstart' ],
+ [ divs[0], 'transitionstart' ],
+ [ divs[0], 'transitioncancel' ],
+ [ divs[1], 'transitioncancel' ],
+ 'Simultaneous transitionrun/start/cancel on siblings');
+
+divs.forEach(div => div.remove());
+divs = [];
+
+
+// 4k. Test sorting animations with cancel
+
+divs = [ document.createElement('div'),
+ document.createElement('div') ];
+
+divs.forEach((div, i) => {
+ gDisplay.appendChild(div);
+ div.style.marginLeft = '0px';
+ div.setAttribute('id', 'div' + i);
+});
+
+divs[0].style.animation = 'anim 10s 5s';
+divs[1].style.animation = 'anim 10s';
+
+getComputedStyle(divs[0]).animation; // flush
+
+advance_clock(0); // divs[1]'s animation start
+advance_clock(5 * 1000); // divs[0]'s animation start
+divs.forEach(div => {
+ div.style.display = 'none';
+ // The animationcancel event order is not absolute when firing siblings
+ // animationcancel on same elapsed time.
+ // Force to flush style for the element so that the transition on the element
+ // iscancelled and corresponding cancel event is queued respectively.
+ getComputedStyle(div).display;
+});
+advance_clock(10 * 1000);
+
+checkEventOrder([ divs[1], 'animationstart' ],
+ [ divs[0], 'animationstart' ],
+ [ divs[0], 'animationcancel' ],
+ [ divs[1], 'animationcancel' ],
+ 'Simultaneous animationcancel on siblings');
+
+SpecialPowers.DOMWindowUtils.restoreNormalRefresh();
+
+</script>
+</body>
+</html>