<!
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>