test_animations_event_order.html (21449B)
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'; 36 37 /* eslint-disable no-shadow */ 38 39 // Take over the refresh driver right from the start. 40 advance_clock(0); 41 42 // Common test scaffolding 43 44 var gEventsReceived = []; 45 var gDisplay = document.getElementById('display'); 46 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))); 58 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'; 68 69 var formatEvent = (target, nameOrProperty, pseudo, message) => 70 isTestingNameOrProperty ? 71 `${nameOrProperty}${pseudo}:${message}` : 72 `${target.id}${pseudo}:${message}`; 73 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 = []; 86 } 87 88 // 1. TESTS FOR SORTING BY TREE ORDER 89 90 // 1a. Test that simultaneous events are sorted by tree order (siblings) 91 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 100 }); 101 102 advance_clock(0); 103 checkEventOrder([ divs[0], 'animationstart' ], 104 [ divs[1], 'animationstart' ], 105 [ divs[2], 'animationstart' ], 106 'Simultaneous start on siblings'); 107 108 divs.forEach(div => div.remove()); 109 divs = []; 110 111 // 1b. Test that simultaneous events are sorted by tree order (children) 112 113 divs = [ document.createElement('div'), 114 document.createElement('div'), 115 document.createElement('div') ]; 116 117 // Create the following arrangement: 118 // 119 // display 120 // / \ 121 // div[0] div[1] 122 // / 123 // div[2] 124 125 gDisplay.appendChild(divs[0]); 126 gDisplay.appendChild(divs[1]); 127 divs[0].appendChild(divs[2]); 128 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 133 }); 134 135 advance_clock(0); 136 checkEventOrder([ divs[0], 'animationstart' ], 137 [ divs[2], 'animationstart' ], 138 [ divs[1], 'animationstart' ], 139 'Simultaneous start on children'); 140 141 divs.forEach(div => div.remove()); 142 divs = []; 143 144 // 1c. Test that simultaneous events are sorted by tree order (pseudos) 145 146 divs = [ document.createElement('div'), 147 document.createElement('div') ]; 148 149 // Create the following arrangement: 150 // 151 // display 152 // | 153 // div[0] 154 // ::before, 155 // ::after 156 // | 157 // div[1] 158 159 gDisplay.appendChild(divs[0]); 160 divs[0].appendChild(divs[1]); 161 162 divs.forEach((div, i) => { 163 div.setAttribute('style', 'animation: anim 10s'); 164 div.setAttribute('id', 'div' + i); 165 }); 166 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 176 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'); 183 184 divs.forEach(div => div.remove()); 185 divs = []; 186 187 sheet = undefined; 188 extraStyle.remove(); 189 extraStyle = undefined; 190 191 // 2. TESTS FOR SORTING BY TIME 192 193 // 2a. Test that events are sorted by time 194 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); 201 }); 202 203 divs[0].style.animationDelay = '5s'; 204 205 advance_clock(0); 206 advance_clock(20000); 207 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'); 213 214 divs.forEach(div => div.remove()); 215 divs = []; 216 217 // 2b. Test different events within the one element 218 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 226 227 advance_clock(0); 228 advance_clock(5000); 229 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'); 236 237 div.remove(); 238 div = undefined; 239 240 // 2c. Test negative delay is sorted equal to zero delay but before 241 // positive delay 242 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); 249 }); 250 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 256 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'); 263 264 divs.forEach(div => div.remove()); 265 divs = []; 266 267 // 3. TESTS FOR SORTING BY animation-name POSITION 268 269 // 3a. Test animation-name position 270 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 276 277 advance_clock(0); 278 279 checkEventOrder([ 'animA', 'animationstart' ], 280 [ 'animB', 'animationstart' ], 281 [ 'animC', 'animationstart' ], 282 'Sorting of simultaneous animationstart events by ' + 283 'animation-name'); 284 285 advance_clock(5000); 286 287 checkEventOrder([ 'animB', 'animationend' ], 288 [ 'animC', 'animationiteration' ], 289 'Sorting of different types of events by animation-name'); 290 291 div.remove(); 292 div = undefined; 293 294 // 3b. Test time trumps animation-name position 295 296 div = document.createElement('div'); 297 gDisplay.appendChild(div); 298 div.style.animation = 'animA 10s 2s, animB 10s 1s'; 299 div.setAttribute('id', 'div'); 300 301 advance_clock(0); 302 advance_clock(3000); 303 304 checkEventOrder([ 'animB', 'animationstart' ], 305 [ 'animA', 'animationstart' ], 306 'Events are sorted by time first, before animation-position'); 307 308 div.remove(); 309 div = undefined; 310 311 // 4. TESTS FOR TRANSITIONS 312 313 // 4a. Test sorting transitions by document position 314 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); 322 }); 323 324 getComputedStyle(divs[0]).marginLeft; 325 divs.forEach(div => div.style.marginLeft = '100px'); 326 getComputedStyle(divs[0]).marginLeft; 327 328 advance_clock(0); 329 advance_clock(10000); 330 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'); 338 339 divs.forEach(div => div.remove()); 340 divs = []; 341 342 // 4b. Test sorting transitions by document position (children) 343 344 divs = [ document.createElement('div'), 345 document.createElement('div'), 346 document.createElement('div') ]; 347 348 // Create the following arrangement: 349 // 350 // display 351 // / \ 352 // div[0] div[1] 353 // / 354 // div[2] 355 356 gDisplay.appendChild(divs[0]); 357 gDisplay.appendChild(divs[1]); 358 divs[0].appendChild(divs[2]); 359 360 divs.forEach((div, i) => { 361 div.style.marginLeft = '0px'; 362 div.style.transition = 'margin-left 10s'; 363 div.setAttribute('id', 'div' + i); 364 }); 365 366 getComputedStyle(divs[0]).marginLeft; 367 divs.forEach(div => div.style.marginLeft = '100px'); 368 getComputedStyle(divs[0]).marginLeft; 369 370 advance_clock(0); 371 advance_clock(10000); 372 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'); 383 384 divs.forEach(div => div.remove()); 385 divs = []; 386 387 // 4c. Test sorting transitions by document position (pseudos) 388 389 divs = [ document.createElement('div'), 390 document.createElement('div') ]; 391 392 // Create the following arrangement: 393 // 394 // display 395 // | 396 // div[0] 397 // ::before, 398 // ::after 399 // | 400 // div[1] 401 402 gDisplay.appendChild(divs[0]); 403 divs[0].appendChild(divs[1]); 404 405 divs.forEach((div, i) => { 406 div.setAttribute('id', 'div' + i); 407 }); 408 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); 419 420 getComputedStyle(divs[0]).marginLeft; 421 divs.forEach(div => div.classList.add('active')); 422 getComputedStyle(divs[0]).marginLeft; 423 424 advance_clock(0); 425 advance_clock(10000); 426 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'); 440 441 divs.forEach(div => div.remove()); 442 divs = []; 443 444 sheet = undefined; 445 extraStyle.remove(); 446 extraStyle = undefined; 447 448 // 4d. Test sorting transitions by time 449 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); 456 }); 457 458 divs[0].style.transition = 'margin-left 10s'; 459 divs[1].style.transition = 'margin-left 5s'; 460 461 getComputedStyle(divs[0]).marginLeft; 462 divs.forEach(div => div.style.marginLeft = '100px'); 463 getComputedStyle(divs[0]).marginLeft; 464 465 advance_clock(0); 466 advance_clock(10000); 467 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'); 475 476 divs.forEach(div => div.remove()); 477 divs = []; 478 479 // 4e. Test sorting transitions by time (with delay) 480 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); 487 }); 488 489 divs[0].style.transition = 'margin-left 5s 5s'; 490 divs[1].style.transition = 'margin-left 5s'; 491 492 getComputedStyle(divs[0]).marginLeft; 493 divs.forEach(div => div.style.marginLeft = '100px'); 494 getComputedStyle(divs[0]).marginLeft; 495 496 advance_clock(0); 497 advance_clock(10 * 1000); 498 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)'); 507 508 divs.forEach(div => div.remove()); 509 divs = []; 510 511 // 4f. Test sorting transitions by transition-property 512 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'; 518 519 getComputedStyle(div).marginLeft; 520 div.style.opacity = '1'; 521 div.style.marginLeft = '100px'; 522 getComputedStyle(div).marginLeft; 523 524 advance_clock(0); 525 advance_clock(10000); 526 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') 535 536 div.remove(); 537 div = undefined; 538 539 // 4g. Test document position beats transition-property 540 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); 549 }); 550 551 getComputedStyle(divs[0]).marginLeft; 552 divs[0].style.opacity = '1'; 553 divs[1].style.marginLeft = '100px'; 554 getComputedStyle(divs[0]).marginLeft; 555 556 advance_clock(0); 557 advance_clock(10000); 558 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'); 567 568 divs.forEach(div => div.remove()); 569 divs = []; 570 571 // 4h. Test time beats transition-property 572 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'; 578 579 getComputedStyle(div).marginLeft; 580 div.style.opacity = '1'; 581 div.style.marginLeft = '100px'; 582 getComputedStyle(div).marginLeft; 583 584 advance_clock(0); 585 advance_clock(10000); 586 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'); 595 596 div.remove(); 597 div = undefined; 598 599 // 4i. Test sorting transitions by document position (negative delay) 600 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); 607 }); 608 609 divs[0].style.transition = 'margin-left 10s 5s'; 610 divs[1].style.transition = 'margin-left 10s'; 611 612 getComputedStyle(divs[0]).marginLeft; 613 divs.forEach(div => div.style.marginLeft = '100px'); 614 getComputedStyle(divs[0]).marginLeft; 615 616 advance_clock(0); 617 advance_clock(15 * 1000); 618 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'); 626 627 divs.forEach(div => div.remove()); 628 divs = []; 629 630 // 4j. Test sorting transitions with cancel 631 // The order of transitioncancel is based on StyleManager. 632 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); 639 }); 640 641 divs[0].style.transition = 'margin-left 10s 5s'; 642 divs[1].style.transition = 'margin-left 10s'; 643 644 getComputedStyle(divs[0]).marginLeft; 645 divs.forEach(div => div.style.marginLeft = '100px'); 646 getComputedStyle(divs[0]).marginLeft; 647 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; 657 }); 658 advance_clock(10 * 1000); 659 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'); 667 668 divs.forEach(div => div.remove()); 669 divs = []; 670 671 672 // 4k. Test sorting animations with cancel 673 674 divs = [ document.createElement('div'), 675 document.createElement('div') ]; 676 677 divs.forEach((div, i) => { 678 gDisplay.appendChild(div); 679 div.style.marginLeft = '0px'; 680 div.setAttribute('id', 'div' + i); 681 }); 682 683 divs[0].style.animation = 'anim 10s 5s'; 684 divs[1].style.animation = 'anim 10s'; 685 686 getComputedStyle(divs[0]).animation; // flush 687 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; 697 }); 698 advance_clock(10 * 1000); 699 700 checkEventOrder([ divs[1], 'animationstart' ], 701 [ divs[0], 'animationstart' ], 702 [ divs[0], 'animationcancel' ], 703 [ divs[1], 'animationcancel' ], 704 'Simultaneous animationcancel on siblings'); 705 706 SpecialPowers.DOMWindowUtils.restoreNormalRefresh(); 707 708 </script> 709 </body> 710 </html>