diff options
Diffstat (limited to 'layout/style/test/test_animations_event_order.html')
-rw-r--r-- | layout/style/test/test_animations_event_order.html | 710 |
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> |