Bug 1839315: part 4) Link from `SheetLoadData::mWasAlternate` to spec. r=emilio DONTBUILD
[gecko.git] / layout / style / test / test_animations_event_order.html
blob7caee2bdcb41f2bb46b73689a8854994dc88d8ed
1 <!doctype html>
2 <html>
3 <!--
4 https://bugzilla.mozilla.org/show_bug.cgi?id=1183461
5 -->
6 <!--
7 This test is similar to those in test_animations.html with the exception
8 that those tests interact with a single element at a time. The tests in this
9 file are specifically concerned with testing the ordering of events between
10 elements for which most of the utilities in animation_utils.js are not
11 suited.
12 -->
13 <head>
14 <meta charset=utf-8>
15 <title>Test for CSS Animation and Transition event ordering
16 (Bug 1183461)</title>
17 <script src="/tests/SimpleTest/SimpleTest.js"></script>
18 <!-- We still need animation_utils.js for advance_clock -->
19 <script type="application/javascript" src="animation_utils.js"></script>
20 <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
21 <style>
22 @keyframes anim { to { margin-left: 100px } }
23 @keyframes animA { to { margin-left: 100px } }
24 @keyframes animB { to { margin-left: 100px } }
25 @keyframes animC { to { margin-left: 100px } }
26 </style>
27 </head>
28 <body>
29 <a target="_blank"
30 href="https://bugzilla.mozilla.org/show_bug.cgi?id=1183461">Mozilla Bug
31 1183461</a>
32 <div id="display"></div>
33 <pre id="test">
34 <script type="application/javascript">
35 'use strict';
37 /* eslint-disable no-shadow */
39 // Take over the refresh driver right from the start.
40 advance_clock(0);
42 // Common test scaffolding
44 var gEventsReceived = [];
45 var gDisplay = document.getElementById('display');
47 [ 'animationstart',
48 'animationiteration',
49 'animationend',
50 'animationcancel',
51 'transitionrun',
52 'transitionstart',
53 'transitionend',
54 'transitioncancel' ]
55 .forEach(event =>
56 gDisplay.addEventListener(event,
57 event => gEventsReceived.push(event)));
59 function checkEventOrder(...args) {
60 // Argument format:
61 // Arguments = ExpectedEvent*, desc
62 // ExpectedEvent =
63 // [ target|animationName|transitionProperty, (pseudo,) message ]
64 var expectedEvents = args.slice(0, -1);
65 var desc = args[args.length - 1];
66 var isTestingNameOrProperty = expectedEvents.length &&
67 typeof expectedEvents[0][0] == 'string';
69 var formatEvent = (target, nameOrProperty, pseudo, message) =>
70 isTestingNameOrProperty ?
71 `${nameOrProperty}${pseudo}:${message}` :
72 `${target.id}${pseudo}:${message}`;
74 var actual = gEventsReceived.map(
75 event => formatEvent(event.target,
76 event.animationName || event.propertyName,
77 event.pseudoElement, event.type)
78 ).join(';');
79 var expected = expectedEvents.map(
80 event => event.length == 3 ?
81 formatEvent(event[0], event[0], event[1], event[2]) :
82 formatEvent(event[0], event[0], '', event[1])
83 ).join(';');
84 is(actual, expected, desc);
85 gEventsReceived = [];
88 // 1. TESTS FOR SORTING BY TREE ORDER
90 // 1a. Test that simultaneous events are sorted by tree order (siblings)
92 var divs = [ document.createElement('div'),
93 document.createElement('div'),
94 document.createElement('div') ];
95 divs.forEach((div, i) => {
96 gDisplay.appendChild(div);
97 div.setAttribute('style', 'animation: anim 10s');
98 div.setAttribute('id', 'div' + i);
99 getComputedStyle(div).animationName; // trigger building of animation
102 advance_clock(0);
103 checkEventOrder([ divs[0], 'animationstart' ],
104 [ divs[1], 'animationstart' ],
105 [ divs[2], 'animationstart' ],
106 'Simultaneous start on siblings');
108 divs.forEach(div => div.remove());
109 divs = [];
111 // 1b. Test that simultaneous events are sorted by tree order (children)
113 divs = [ document.createElement('div'),
114 document.createElement('div'),
115 document.createElement('div') ];
117 // Create the following arrangement:
119 // display
120 // / \
121 // div[0] div[1]
122 // /
123 // div[2]
125 gDisplay.appendChild(divs[0]);
126 gDisplay.appendChild(divs[1]);
127 divs[0].appendChild(divs[2]);
129 divs.forEach((div, i) => {
130 div.setAttribute('style', 'animation: anim 10s');
131 div.setAttribute('id', 'div' + i);
132 getComputedStyle(div).animationName; // trigger building of animation
135 advance_clock(0);
136 checkEventOrder([ divs[0], 'animationstart' ],
137 [ divs[2], 'animationstart' ],
138 [ divs[1], 'animationstart' ],
139 'Simultaneous start on children');
141 divs.forEach(div => div.remove());
142 divs = [];
144 // 1c. Test that simultaneous events are sorted by tree order (pseudos)
146 divs = [ document.createElement('div'),
147 document.createElement('div') ];
149 // Create the following arrangement:
151 // display
152 // |
153 // div[0]
154 // ::before,
155 // ::after
156 // |
157 // div[1]
159 gDisplay.appendChild(divs[0]);
160 divs[0].appendChild(divs[1]);
162 divs.forEach((div, i) => {
163 div.setAttribute('style', 'animation: anim 10s');
164 div.setAttribute('id', 'div' + i);
167 var extraStyle = document.createElement('style');
168 document.head.appendChild(extraStyle);
169 var sheet = extraStyle.sheet;
170 sheet.insertRule('#div0::after { animation: anim 10s }', 0);
171 sheet.insertRule('#div0::before { animation: anim 10s }', 1);
172 sheet.insertRule('#div0::after, #div0::before { ' +
173 ' content: " " }', 2);
174 getComputedStyle(divs[0]).animationName; // build animation
175 getComputedStyle(divs[1]).animationName; // build animation
177 advance_clock(0);
178 checkEventOrder([ divs[0], 'animationstart' ],
179 [ divs[0], '::before', 'animationstart' ],
180 [ divs[0], '::after', 'animationstart' ],
181 [ divs[1], 'animationstart' ],
182 'Simultaneous start on pseudo-elements');
184 divs.forEach(div => div.remove());
185 divs = [];
187 sheet = undefined;
188 extraStyle.remove();
189 extraStyle = undefined;
191 // 2. TESTS FOR SORTING BY TIME
193 // 2a. Test that events are sorted by time
195 divs = [ document.createElement('div'),
196 document.createElement('div') ];
197 divs.forEach((div, i) => {
198 gDisplay.appendChild(div);
199 div.setAttribute('style', 'animation: anim 10s');
200 div.setAttribute('id', 'div' + i);
203 divs[0].style.animationDelay = '5s';
205 advance_clock(0);
206 advance_clock(20000);
208 checkEventOrder([ divs[1], 'animationstart' ],
209 [ divs[0], 'animationstart' ],
210 [ divs[1], 'animationend' ],
211 [ divs[0], 'animationend' ],
212 'Sorting of start and end events by time');
214 divs.forEach(div => div.remove());
215 divs = [];
217 // 2b. Test different events within the one element
219 var div = document.createElement('div');
220 gDisplay.appendChild(div);
221 div.style.animation = 'anim 4s 2, ' + // Repeat at t=4s
222 'anim 10s 5s, ' + // Start at t=5s
223 'anim 3s'; // End at t=3s
224 div.setAttribute('id', 'div');
225 getComputedStyle(div).animationName; // build animation
227 advance_clock(0);
228 advance_clock(5000);
230 checkEventOrder([ div, 'animationstart' ],
231 [ div, 'animationstart' ],
232 [ div, 'animationend' ],
233 [ div, 'animationiteration' ],
234 [ div, 'animationstart' ],
235 'Sorting of different events by time within an element');
237 div.remove();
238 div = undefined;
240 // 2c. Test negative delay is sorted equal to zero delay but before
241 // positive delay
243 divs = [ document.createElement('div'),
244 document.createElement('div'),
245 document.createElement('div') ];
246 divs.forEach((div, i) => {
247 gDisplay.appendChild(div);
248 div.setAttribute('id', 'div' + i);
251 divs[0].style.animation = 'anim 20s 5s'; // Positive delay, sorts last
252 divs[1].style.animation = 'anim 20s'; // 0s delay
253 divs[2].style.animation = 'anim 20s -5s'; // Negative delay, sorts same as
254 // 0s delay, i.e. use document
255 // position
257 advance_clock(0);
258 advance_clock(5000);
259 checkEventOrder([ divs[1], 'animationstart' ],
260 [ divs[2], 'animationstart' ],
261 [ divs[0], 'animationstart' ],
262 'Sorting of events including negative delay');
264 divs.forEach(div => div.remove());
265 divs = [];
267 // 3. TESTS FOR SORTING BY animation-name POSITION
269 // 3a. Test animation-name position
271 div = document.createElement('div');
272 gDisplay.appendChild(div);
273 div.style.animation = 'animA 10s, animB 5s, animC 5s 2';
274 div.setAttribute('id', 'div');
275 getComputedStyle(div).animationName; // build animation
277 advance_clock(0);
279 checkEventOrder([ 'animA', 'animationstart' ],
280 [ 'animB', 'animationstart' ],
281 [ 'animC', 'animationstart' ],
282 'Sorting of simultaneous animationstart events by ' +
283 'animation-name');
285 advance_clock(5000);
287 checkEventOrder([ 'animB', 'animationend' ],
288 [ 'animC', 'animationiteration' ],
289 'Sorting of different types of events by animation-name');
291 div.remove();
292 div = undefined;
294 // 3b. Test time trumps animation-name position
296 div = document.createElement('div');
297 gDisplay.appendChild(div);
298 div.style.animation = 'animA 10s 2s, animB 10s 1s';
299 div.setAttribute('id', 'div');
301 advance_clock(0);
302 advance_clock(3000);
304 checkEventOrder([ 'animB', 'animationstart' ],
305 [ 'animA', 'animationstart' ],
306 'Events are sorted by time first, before animation-position');
308 div.remove();
309 div = undefined;
311 // 4. TESTS FOR TRANSITIONS
313 // 4a. Test sorting transitions by document position
315 divs = [ document.createElement('div'),
316 document.createElement('div') ];
317 divs.forEach((div, i) => {
318 gDisplay.appendChild(div);
319 div.style.marginLeft = '0px';
320 div.style.transition = 'margin-left 10s';
321 div.setAttribute('id', 'div' + i);
324 getComputedStyle(divs[0]).marginLeft;
325 divs.forEach(div => div.style.marginLeft = '100px');
326 getComputedStyle(divs[0]).marginLeft;
328 advance_clock(0);
329 advance_clock(10000);
331 checkEventOrder([ divs[0], 'transitionrun' ],
332 [ divs[0], 'transitionstart' ],
333 [ divs[1], 'transitionrun' ],
334 [ divs[1], 'transitionstart' ],
335 [ divs[0], 'transitionend' ],
336 [ divs[1], 'transitionend' ],
337 'Simultaneous transitionrun/start/end on siblings');
339 divs.forEach(div => div.remove());
340 divs = [];
342 // 4b. Test sorting transitions by document position (children)
344 divs = [ document.createElement('div'),
345 document.createElement('div'),
346 document.createElement('div') ];
348 // Create the following arrangement:
350 // display
351 // / \
352 // div[0] div[1]
353 // /
354 // div[2]
356 gDisplay.appendChild(divs[0]);
357 gDisplay.appendChild(divs[1]);
358 divs[0].appendChild(divs[2]);
360 divs.forEach((div, i) => {
361 div.style.marginLeft = '0px';
362 div.style.transition = 'margin-left 10s';
363 div.setAttribute('id', 'div' + i);
366 getComputedStyle(divs[0]).marginLeft;
367 divs.forEach(div => div.style.marginLeft = '100px');
368 getComputedStyle(divs[0]).marginLeft;
370 advance_clock(0);
371 advance_clock(10000);
373 checkEventOrder([ divs[0], 'transitionrun' ],
374 [ divs[0], 'transitionstart' ],
375 [ divs[2], 'transitionrun' ],
376 [ divs[2], 'transitionstart' ],
377 [ divs[1], 'transitionrun' ],
378 [ divs[1], 'transitionstart' ],
379 [ divs[0], 'transitionend' ],
380 [ divs[2], 'transitionend' ],
381 [ divs[1], 'transitionend' ],
382 'Simultaneous transitionrun/start/end on children');
384 divs.forEach(div => div.remove());
385 divs = [];
387 // 4c. Test sorting transitions by document position (pseudos)
389 divs = [ document.createElement('div'),
390 document.createElement('div') ];
392 // Create the following arrangement:
394 // display
395 // |
396 // div[0]
397 // ::before,
398 // ::after
399 // |
400 // div[1]
402 gDisplay.appendChild(divs[0]);
403 divs[0].appendChild(divs[1]);
405 divs.forEach((div, i) => {
406 div.setAttribute('id', 'div' + i);
409 extraStyle = document.createElement('style');
410 document.head.appendChild(extraStyle);
411 sheet = extraStyle.sheet;
412 sheet.insertRule('div, #div0::after, #div0::before { ' +
413 ' transition: margin-left 10s; ' +
414 ' margin-left: 0px }', 0);
415 sheet.insertRule('div.active, #div0.active::after, #div0.active::before { ' +
416 ' margin-left: 100px }', 1);
417 sheet.insertRule('#div0::after, #div0::before { ' +
418 ' content: " " }', 2);
420 getComputedStyle(divs[0]).marginLeft;
421 divs.forEach(div => div.classList.add('active'));
422 getComputedStyle(divs[0]).marginLeft;
424 advance_clock(0);
425 advance_clock(10000);
427 checkEventOrder([ divs[0], 'transitionrun' ],
428 [ divs[0], 'transitionstart' ],
429 [ divs[0], '::before', 'transitionrun' ],
430 [ divs[0], '::before', 'transitionstart' ],
431 [ divs[0], '::after', 'transitionrun' ],
432 [ divs[0], '::after', 'transitionstart' ],
433 [ divs[1], 'transitionrun' ],
434 [ divs[1], 'transitionstart' ],
435 [ divs[0], 'transitionend' ],
436 [ divs[0], '::before', 'transitionend' ],
437 [ divs[0], '::after', 'transitionend' ],
438 [ divs[1], 'transitionend' ],
439 'Simultaneous transitionrun/start/end on pseudo-elements');
441 divs.forEach(div => div.remove());
442 divs = [];
444 sheet = undefined;
445 extraStyle.remove();
446 extraStyle = undefined;
448 // 4d. Test sorting transitions by time
450 divs = [ document.createElement('div'),
451 document.createElement('div') ];
452 divs.forEach((div, i) => {
453 gDisplay.appendChild(div);
454 div.style.marginLeft = '0px';
455 div.setAttribute('id', 'div' + i);
458 divs[0].style.transition = 'margin-left 10s';
459 divs[1].style.transition = 'margin-left 5s';
461 getComputedStyle(divs[0]).marginLeft;
462 divs.forEach(div => div.style.marginLeft = '100px');
463 getComputedStyle(divs[0]).marginLeft;
465 advance_clock(0);
466 advance_clock(10000);
468 checkEventOrder([ divs[0], 'transitionrun' ],
469 [ divs[0], 'transitionstart' ],
470 [ divs[1], 'transitionrun' ],
471 [ divs[1], 'transitionstart' ],
472 [ divs[1], 'transitionend' ],
473 [ divs[0], 'transitionend' ],
474 'Sorting of transitionrun/start/end events by time');
476 divs.forEach(div => div.remove());
477 divs = [];
479 // 4e. Test sorting transitions by time (with delay)
481 divs = [ document.createElement('div'),
482 document.createElement('div') ];
483 divs.forEach((div, i) => {
484 gDisplay.appendChild(div);
485 div.style.marginLeft = '0px';
486 div.setAttribute('id', 'div' + i);
489 divs[0].style.transition = 'margin-left 5s 5s';
490 divs[1].style.transition = 'margin-left 5s';
492 getComputedStyle(divs[0]).marginLeft;
493 divs.forEach(div => div.style.marginLeft = '100px');
494 getComputedStyle(divs[0]).marginLeft;
496 advance_clock(0);
497 advance_clock(10 * 1000);
499 checkEventOrder([ divs[0], 'transitionrun' ],
500 [ divs[1], 'transitionrun' ],
501 [ divs[1], 'transitionstart' ],
502 [ divs[0], 'transitionstart' ],
503 [ divs[1], 'transitionend' ],
504 [ divs[0], 'transitionend' ],
505 'Sorting of transitionrun/start/end events by time' +
506 '(including delay)');
508 divs.forEach(div => div.remove());
509 divs = [];
511 // 4f. Test sorting transitions by transition-property
513 div = document.createElement('div');
514 gDisplay.appendChild(div);
515 div.style.opacity = '0';
516 div.style.marginLeft = '0px';
517 div.style.transition = 'all 5s';
519 getComputedStyle(div).marginLeft;
520 div.style.opacity = '1';
521 div.style.marginLeft = '100px';
522 getComputedStyle(div).marginLeft;
524 advance_clock(0);
525 advance_clock(10000);
527 checkEventOrder([ 'margin-left', 'transitionrun' ],
528 [ 'margin-left', 'transitionstart' ],
529 [ 'opacity', 'transitionrun' ],
530 [ 'opacity', 'transitionstart' ],
531 [ 'margin-left', 'transitionend' ],
532 [ 'opacity', 'transitionend' ],
533 'Sorting of transitionrun/start/end events by ' +
534 'transition-property')
536 div.remove();
537 div = undefined;
539 // 4g. Test document position beats transition-property
541 divs = [ document.createElement('div'),
542 document.createElement('div') ];
543 divs.forEach((div, i) => {
544 gDisplay.appendChild(div);
545 div.style.marginLeft = '0px';
546 div.style.opacity = '0';
547 div.style.transition = 'all 10s';
548 div.setAttribute('id', 'div' + i);
551 getComputedStyle(divs[0]).marginLeft;
552 divs[0].style.opacity = '1';
553 divs[1].style.marginLeft = '100px';
554 getComputedStyle(divs[0]).marginLeft;
556 advance_clock(0);
557 advance_clock(10000);
559 checkEventOrder([ divs[0], 'transitionrun' ],
560 [ divs[0], 'transitionstart' ],
561 [ divs[1], 'transitionrun' ],
562 [ divs[1], 'transitionstart' ],
563 [ divs[0], 'transitionend' ],
564 [ divs[1], 'transitionend' ],
565 'Transition events are sorted by document position first, ' +
566 'before transition-property');
568 divs.forEach(div => div.remove());
569 divs = [];
571 // 4h. Test time beats transition-property
573 div = document.createElement('div');
574 gDisplay.appendChild(div);
575 div.style.opacity = '0';
576 div.style.marginLeft = '0px';
577 div.style.transition = 'margin-left 10s, opacity 5s';
579 getComputedStyle(div).marginLeft;
580 div.style.opacity = '1';
581 div.style.marginLeft = '100px';
582 getComputedStyle(div).marginLeft;
584 advance_clock(0);
585 advance_clock(10000);
587 checkEventOrder([ 'margin-left', 'transitionrun' ],
588 [ 'margin-left', 'transitionstart' ],
589 [ 'opacity', 'transitionrun' ],
590 [ 'opacity', 'transitionstart' ],
591 [ 'opacity', 'transitionend' ],
592 [ 'margin-left', 'transitionend' ],
593 'Transition events are sorted by time first, before ' +
594 'transition-property');
596 div.remove();
597 div = undefined;
599 // 4i. Test sorting transitions by document position (negative delay)
601 divs = [ document.createElement('div'),
602 document.createElement('div') ];
603 divs.forEach((div, i) => {
604 gDisplay.appendChild(div);
605 div.style.marginLeft = '0px';
606 div.setAttribute('id', 'div' + i);
609 divs[0].style.transition = 'margin-left 10s 5s';
610 divs[1].style.transition = 'margin-left 10s';
612 getComputedStyle(divs[0]).marginLeft;
613 divs.forEach(div => div.style.marginLeft = '100px');
614 getComputedStyle(divs[0]).marginLeft;
616 advance_clock(0);
617 advance_clock(15 * 1000);
619 checkEventOrder([ divs[0], 'transitionrun' ],
620 [ divs[1], 'transitionrun' ],
621 [ divs[1], 'transitionstart' ],
622 [ divs[0], 'transitionstart' ],
623 [ divs[1], 'transitionend' ],
624 [ divs[0], 'transitionend' ],
625 'Simultaneous transitionrun/start/end on siblings');
627 divs.forEach(div => div.remove());
628 divs = [];
630 // 4j. Test sorting transitions with cancel
631 // The order of transitioncancel is based on StyleManager.
633 divs = [ document.createElement('div'),
634 document.createElement('div') ];
635 divs.forEach((div, i) => {
636 gDisplay.appendChild(div);
637 div.style.marginLeft = '0px';
638 div.setAttribute('id', 'div' + i);
641 divs[0].style.transition = 'margin-left 10s 5s';
642 divs[1].style.transition = 'margin-left 10s';
644 getComputedStyle(divs[0]).marginLeft;
645 divs.forEach(div => div.style.marginLeft = '100px');
646 getComputedStyle(divs[0]).marginLeft;
648 advance_clock(0);
649 advance_clock(5 * 1000);
650 divs.forEach(div => {
651 div.style.display = 'none';
652 // The transitioncancel event order is not absolute when firing siblings
653 // transitioncancel on same elapsed time.
654 // Force to flush style for the element so that the transition on the element
655 // iscancelled and corresponding cancel event is queued respectively.
656 getComputedStyle(div).display;
658 advance_clock(10 * 1000);
660 checkEventOrder([ divs[0], 'transitionrun' ],
661 [ divs[1], 'transitionrun' ],
662 [ divs[1], 'transitionstart' ],
663 [ divs[0], 'transitionstart' ],
664 [ divs[0], 'transitioncancel' ],
665 [ divs[1], 'transitioncancel' ],
666 'Simultaneous transitionrun/start/cancel on siblings');
668 divs.forEach(div => div.remove());
669 divs = [];
672 // 4k. Test sorting animations with cancel
674 divs = [ document.createElement('div'),
675 document.createElement('div') ];
677 divs.forEach((div, i) => {
678 gDisplay.appendChild(div);
679 div.style.marginLeft = '0px';
680 div.setAttribute('id', 'div' + i);
683 divs[0].style.animation = 'anim 10s 5s';
684 divs[1].style.animation = 'anim 10s';
686 getComputedStyle(divs[0]).animation; // flush
688 advance_clock(0); // divs[1]'s animation start
689 advance_clock(5 * 1000); // divs[0]'s animation start
690 divs.forEach(div => {
691 div.style.display = 'none';
692 // The animationcancel event order is not absolute when firing siblings
693 // animationcancel on same elapsed time.
694 // Force to flush style for the element so that the transition on the element
695 // iscancelled and corresponding cancel event is queued respectively.
696 getComputedStyle(div).display;
698 advance_clock(10 * 1000);
700 checkEventOrder([ divs[1], 'animationstart' ],
701 [ divs[0], 'animationstart' ],
702 [ divs[0], 'animationcancel' ],
703 [ divs[1], 'animationcancel' ],
704 'Simultaneous animationcancel on siblings');
706 SpecialPowers.DOMWindowUtils.restoreNormalRefresh();
708 </script>
709 </body>
710 </html>