event-dispatch.tentative.html (16872B)
1 <!doctype html> 2 <meta charset=utf-8> 3 <title>CSS transition event dispatch</title> 4 <link rel="help" href="https://drafts.csswg.org/css-transitions-2/#event-dispatch"> 5 <script src="/resources/testharness.js"></script> 6 <script src="/resources/testharnessreport.js"></script> 7 <script src="support/helper.js"></script> 8 <div id="log"></div> 9 <script> 10 'use strict'; 11 12 // All transition events should be received on the next animation frame. If 13 // two animation frames pass before receiving the expected events then we 14 // can immediately fail the current test. 15 const transitionEventsTimeout = () => { 16 return waitForAnimationFrames(2); 17 }; 18 19 const setupTransition = (t, transitionStyle) => { 20 const div = addDiv(t, { style: 'transition: ' + transitionStyle }); 21 const watcher = new EventWatcher(t, div, [ 'transitionrun', 22 'transitionstart', 23 'transitionend', 24 'transitioncancel' ], 25 transitionEventsTimeout); 26 getComputedStyle(div).marginLeft; 27 28 div.style.marginLeft = '100px'; 29 const transition = div.getAnimations()[0]; 30 31 return { transition, watcher, div }; 32 }; 33 34 // On the next frame (i.e. when events are queued), whether or not the 35 // transition is still pending depends on the implementation. 36 promise_test(async t => { 37 const { transition, watcher } = 38 setupTransition(t, 'margin-left 100s 100s'); 39 const evt = await watcher.wait_for('transitionrun'); 40 assert_equals(evt.elapsedTime, 0.0); 41 }, 'Idle -> Pending or Before'); 42 43 promise_test(async t => { 44 const { transition, watcher } = 45 setupTransition(t, 'margin-left 100s 100s'); 46 // Force the transition to leave the idle phase 47 transition.startTime = document.timeline.currentTime; 48 const evt = await watcher.wait_for('transitionrun'); 49 assert_equals(evt.elapsedTime, 0.0); 50 }, 'Idle -> Before'); 51 52 promise_test(async t => { 53 const { transition, watcher } = setupTransition(t, 'margin-left 100s 100s'); 54 55 // Seek to Active phase. 56 transition.currentTime = 100 * MS_PER_SEC; 57 transition.pause(); 58 const events = await watcher.wait_for(['transitionrun', 'transitionstart'], { 59 record: 'all', 60 }); 61 assert_equals(events[0].elapsedTime, 0.0); 62 assert_equals(events[1].elapsedTime, 0.0); 63 }, 'Idle or Pending -> Active'); 64 65 promise_test(async t => { 66 const { transition, watcher } = setupTransition(t, 'margin-left 100s 100s'); 67 68 // Seek to After phase. 69 transition.finish(); 70 const events = await watcher.wait_for( 71 ['transitionrun', 'transitionstart', 'transitionend'], 72 { 73 record: 'all', 74 } 75 ); 76 assert_equals(events[0].elapsedTime, 0.0); 77 assert_equals(events[1].elapsedTime, 0.0); 78 assert_equals(events[2].elapsedTime, 100.0); 79 }, 'Idle or Pending -> After'); 80 81 promise_test(async t => { 82 const { transition, watcher, div } = 83 setupTransition(t, 'margin-left 100s 100s'); 84 85 await Promise.all([ watcher.wait_for('transitionrun'), transition.ready ]); 86 87 // Make idle 88 div.style.display = 'none'; 89 getComputedStyle(div).marginLeft; 90 const evt = await watcher.wait_for('transitioncancel'); 91 assert_equals(evt.elapsedTime, 0.0); 92 }, 'Before -> Idle (display: none)'); 93 94 promise_test(async t => { 95 const { transition, watcher } = 96 setupTransition(t, 'margin-left 100s 100s'); 97 98 await Promise.all([ watcher.wait_for('transitionrun'), transition.ready ]); 99 100 // Make idle 101 transition.timeline = null; 102 const evt = await watcher.wait_for('transitioncancel'); 103 assert_equals(evt.elapsedTime, 0.0); 104 }, 'Before -> Idle (Animation.timeline = null)'); 105 106 promise_test(async t => { 107 const { transition, watcher } = 108 setupTransition(t, 'margin-left 100s 100s'); 109 110 await Promise.all([ watcher.wait_for('transitionrun'), transition.ready ]); 111 112 transition.currentTime = 100 * MS_PER_SEC; 113 const evt = await watcher.wait_for('transitionstart'); 114 assert_equals(evt.elapsedTime, 0.0); 115 }, 'Before -> Active'); 116 117 promise_test(async t => { 118 const { transition, watcher } = setupTransition(t, 'margin-left 100s 100s'); 119 120 await Promise.all([ watcher.wait_for('transitionrun'), transition.ready ]); 121 // Seek to After phase. 122 transition.currentTime = 200 * MS_PER_SEC; 123 const events = await watcher.wait_for(['transitionstart', 'transitionend'], { 124 record: 'all', 125 }); 126 127 assert_equals(events[0].elapsedTime, 0.0); 128 assert_equals(events[1].elapsedTime, 100.0); 129 }, 'Before -> After'); 130 131 promise_test(async t => { 132 const { transition, watcher, div } = setupTransition(t, 'margin-left 100s'); 133 134 // Seek to Active start position. 135 transition.pause(); 136 await watcher.wait_for([ 'transitionrun', 'transitionstart' ]); 137 138 // Make idle 139 div.style.display = 'none'; 140 getComputedStyle(div).marginLeft; 141 const evt = await watcher.wait_for('transitioncancel'); 142 assert_equals(evt.elapsedTime, 0.0); 143 }, 'Active -> Idle, no delay (display: none)'); 144 145 promise_test(async t => { 146 const { transition, watcher } = setupTransition(t, 'margin-left 100s'); 147 148 await watcher.wait_for([ 'transitionrun', 'transitionstart' ]); 149 150 // Make idle 151 transition.currentTime = 0; 152 transition.timeline = null; 153 const evt = await watcher.wait_for('transitioncancel'); 154 assert_equals(evt.elapsedTime, 0.0); 155 }, 'Active -> Idle, no delay (Animation.timeline = null)'); 156 157 promise_test(async t => { 158 const { transition, watcher, div } = 159 setupTransition(t, 'margin-left 100s 100s'); 160 // Pause so the currentTime is fixed and we can accurately compare the event 161 // time in transition cancel events. 162 transition.pause(); 163 164 // Seek to Active phase. 165 transition.currentTime = 100 * MS_PER_SEC; 166 await watcher.wait_for([ 'transitionrun', 'transitionstart' ]); 167 168 // Make idle 169 div.style.display = 'none'; 170 getComputedStyle(div).marginLeft; 171 const evt = await watcher.wait_for('transitioncancel'); 172 assert_equals(evt.elapsedTime, 0.0); 173 }, 'Active -> Idle, with positive delay (display: none)'); 174 175 promise_test(async t => { 176 const { transition, watcher } = setupTransition(t, 'margin-left 100s 100s'); 177 178 // Seek to Active phase. 179 transition.currentTime = 100 * MS_PER_SEC; 180 await watcher.wait_for([ 'transitionrun', 'transitionstart' ]); 181 182 // Make idle 183 transition.currentTime = 100 * MS_PER_SEC; 184 transition.timeline = null; 185 const evt = await watcher.wait_for('transitioncancel'); 186 assert_equals(evt.elapsedTime, 0.0); 187 }, 'Active -> Idle, with positive delay (Animation.timeline = null)'); 188 189 promise_test(async t => { 190 const { transition, watcher, div } = 191 setupTransition(t, 'margin-left 100s -50s'); 192 193 // Pause so the currentTime is fixed and we can accurately compare the event 194 // time in transition cancel events. 195 transition.pause(); 196 197 await watcher.wait_for([ 'transitionrun', 'transitionstart' ]); 198 199 // Make idle 200 div.style.display = 'none'; 201 getComputedStyle(div).marginLeft; 202 const evt = await watcher.wait_for('transitioncancel'); 203 assert_equals(evt.elapsedTime, 50.0); 204 }, 'Active -> Idle, with negative delay (display: none)'); 205 206 promise_test(async t => { 207 const { transition, watcher } = setupTransition(t, 'margin-left 100s -50s'); 208 209 await watcher.wait_for([ 'transitionrun', 'transitionstart' ]); 210 211 // Make idle 212 transition.currentTime = 50 * MS_PER_SEC; 213 transition.timeline = null; 214 const evt = await watcher.wait_for('transitioncancel'); 215 assert_equals(evt.elapsedTime, 0.0); 216 }, 'Active -> Idle, with negative delay (Animation.timeline = null)'); 217 218 promise_test(async t => { 219 const { transition, watcher } = setupTransition(t, 'margin-left 100s 100s'); 220 221 // Seek to Active phase. 222 transition.currentTime = 100 * MS_PER_SEC; 223 await watcher.wait_for([ 'transitionrun', 'transitionstart' ]); 224 225 // Seek to Before phase. 226 transition.currentTime = 0; 227 const evt = await watcher.wait_for('transitionend'); 228 assert_equals(evt.elapsedTime, 0.0); 229 }, 'Active -> Before'); 230 231 promise_test(async t => { 232 const { transition, watcher } = setupTransition(t, 'margin-left 100s 100s'); 233 234 // Seek to Active phase. 235 transition.currentTime = 100 * MS_PER_SEC; 236 await watcher.wait_for([ 'transitionrun', 'transitionstart' ]); 237 238 // Seek to After phase. 239 transition.currentTime = 200 * MS_PER_SEC; 240 const evt = await watcher.wait_for('transitionend'); 241 assert_equals(evt.elapsedTime, 100.0); 242 }, 'Active -> After'); 243 244 promise_test(async t => { 245 const { transition, watcher } = setupTransition(t, 'margin-left 100s 100s'); 246 247 // Seek to After phase. 248 transition.finish(); 249 await watcher.wait_for([ 'transitionrun', 250 'transitionstart', 251 'transitionend' ]); 252 253 // Seek to Before phase. 254 transition.currentTime = 0; 255 const events = await watcher.wait_for(['transitionstart', 'transitionend'], { 256 record: 'all', 257 }); 258 259 assert_equals(events[0].elapsedTime, 100.0); 260 assert_equals(events[1].elapsedTime, 0.0); 261 }, 'After -> Before'); 262 263 promise_test(async t => { 264 const { transition, watcher } = setupTransition(t, 'margin-left 100s 100s'); 265 266 // Seek to After phase. 267 transition.finish(); 268 await watcher.wait_for([ 'transitionrun', 269 'transitionstart', 270 'transitionend' ]); 271 272 // Seek to Active phase. 273 transition.currentTime = 100 * MS_PER_SEC; 274 const evt = await watcher.wait_for('transitionstart'); 275 assert_equals(evt.elapsedTime, 100.0); 276 }, 'After -> Active'); 277 278 promise_test(async t => { 279 const { transition, watcher } = setupTransition(t, 'margin-left 100s -50s'); 280 281 const events = await watcher.wait_for(['transitionrun', 'transitionstart'], { 282 record: 'all', 283 }); 284 285 assert_equals(events[0].elapsedTime, 50.0); 286 assert_equals(events[1].elapsedTime, 50.0); 287 transition.finish(); 288 289 const evt = await watcher.wait_for('transitionend'); 290 assert_equals(evt.elapsedTime, 100.0); 291 }, 'Calculating the interval start and end time with negative start delay.'); 292 293 promise_test(async t => { 294 const { transition, watcher, div } = setupTransition( 295 t, 296 'margin-left 100s 100s' 297 ); 298 299 await watcher.wait_for('transitionrun'); 300 301 // We can't set the end delay via generated effect timing 302 // because mutating CSS transitions is not specced yet. 303 transition.effect = new KeyframeEffect( 304 div, 305 { marginLeft: ['0px', '100px'] }, 306 { 307 duration: 100 * MS_PER_SEC, 308 endDelay: -50 * MS_PER_SEC, 309 } 310 ); 311 // Seek to Before and play. 312 transition.cancel(); 313 transition.play(); 314 const events = await watcher.wait_for( 315 ['transitioncancel', 'transitionrun', 'transitionstart'], 316 { record: 'all' } 317 ); 318 assert_equals(events[2].elapsedTime, 0.0); 319 320 // Seek to After phase. 321 transition.finish(); 322 const evt = await watcher.wait_for('transitionend'); 323 assert_equals(evt.elapsedTime, 50.0); 324 }, 'Calculating the interval start and end time with negative end delay.'); 325 326 promise_test(async t => { 327 const { transition, watcher, div } = 328 setupTransition(t, 'margin-left 100s 100s'); 329 330 await watcher.wait_for('transitionrun'); 331 332 // Make idle 333 div.style.display = 'none'; 334 getComputedStyle(div).marginLeft; 335 await watcher.wait_for('transitioncancel'); 336 337 transition.cancel(); 338 // Then wait a couple of frames and check that no event was dispatched 339 await waitForAnimationFrames(2); 340 }, 'Call Animation.cancel after canceling transition.'); 341 342 promise_test(async t => { 343 const { transition, watcher, div } = 344 setupTransition(t, 'margin-left 100s 100s'); 345 346 await watcher.wait_for('transitionrun'); 347 348 // Make idle 349 transition.cancel(); 350 transition.play(); 351 await watcher.wait_for([ 'transitioncancel', 352 'transitionrun' ]); 353 }, 'Restart transition after canceling transition immediately'); 354 355 promise_test(async t => { 356 const { transition, watcher, div } = 357 setupTransition(t, 'margin-left 100s 100s'); 358 359 await watcher.wait_for('transitionrun'); 360 361 // Make idle 362 div.style.display = 'none'; 363 getComputedStyle(div).marginLeft; 364 transition.play(); 365 transition.cancel(); 366 await watcher.wait_for('transitioncancel'); 367 368 // Then wait a couple of frames and check that no event was dispatched 369 await waitForAnimationFrames(2); 370 }, 'Call Animation.cancel after restarting transition immediately'); 371 372 promise_test(async t => { 373 const { transition, watcher } = setupTransition(t, 'margin-left 100s'); 374 375 await watcher.wait_for([ 'transitionrun', 'transitionstart' ]); 376 377 // Make idle 378 transition.timeline = null; 379 await watcher.wait_for('transitioncancel'); 380 381 transition.timeline = document.timeline; 382 transition.play(); 383 384 await watcher.wait_for(['transitionrun', 'transitionstart']); 385 }, 'Set timeline and play transition after clear the timeline'); 386 387 promise_test(async t => { 388 const { transition, watcher, div } = 389 setupTransition(t, 'margin-left 100s'); 390 391 await watcher.wait_for([ 'transitionrun', 'transitionstart' ]); 392 393 transition.cancel(); 394 await watcher.wait_for('transitioncancel'); 395 396 // Make After phase 397 transition.effect = null; 398 399 // Then wait a couple of frames and check that no event was dispatched 400 await waitForAnimationFrames(2); 401 }, 'Set null target effect after canceling the transition'); 402 403 promise_test(async t => { 404 const { transition, watcher, div } = setupTransition(t, 'margin-left 100s'); 405 406 await watcher.wait_for([ 'transitionrun', 'transitionstart' ]); 407 408 transition.effect = null; 409 await watcher.wait_for('transitionend'); 410 411 transition.cancel(); 412 413 // Then wait a couple of frames and check that no event was dispatched 414 await waitForAnimationFrames(2); 415 }, 'Cancel the transition after clearing the target effect'); 416 417 promise_test(async t => { 418 const { transition, watcher, div } = setupTransition(t, 'margin-left 100s'); 419 420 // Seek to After phase. 421 transition.finish(); 422 const events = await watcher.wait_for( 423 ['transitionrun', 'transitionstart', 'transitionend'], 424 { 425 record: 'all', 426 } 427 ); 428 429 transition.cancel(); 430 431 // Then wait a couple of frames and check that no event was dispatched 432 await waitForAnimationFrames(2); 433 }, 'Cancel the transition after it finishes'); 434 435 promise_test(async t => { 436 const { transition, watcher, div } = setupTransition(t, 'margin-left 100s'); 437 438 transition.currentTime = 50 * MS_PER_SEC; 439 await watcher.wait_for(['transitionrun', 'transitionstart']); 440 441 // Replace the running transition. 442 div.style.marginLeft = '200px'; 443 444 // transitioncancel event should be fired before transitionrun because we 445 // expect to cancel the running transition first. 446 await watcher.wait_for( 447 ['transitioncancel', 'transitionrun', 'transitionstart'] 448 ); 449 450 // Then wait a couple of frames and check that no event was dispatched 451 await waitForAnimationFrames(2); 452 }, 'Replacing a running transition should get transitioncancel earlier than ' + 453 'transitionrun and transitionstart'); 454 455 promise_test(async t => { 456 const div = 457 addDiv(t, { style: 'transition: margin-left 100s, margin-top 100s' }); 458 const watcher = new EventWatcher(t, div, [ 'transitionrun', 459 'transitioncancel' ], 460 transitionEventsTimeout); 461 getComputedStyle(div).marginLeft; 462 463 div.style.marginLeft = '100px'; 464 div.style.marginTop = '100px'; 465 const transitions = div.getAnimations(); 466 transitions[0].currentTime = 50 * MS_PER_SEC; 467 transitions[1].currentTime = 50 * MS_PER_SEC; 468 469 await watcher.wait_for(['transitionrun', 'transitionrun']); 470 471 // Replace both running transitions. 472 div.style.marginLeft = '200px'; 473 div.style.marginTop = '200px'; 474 475 await watcher.wait_for([ 476 // Cancel events show first because their transition generations are 477 // smaller than the new ones. 478 'transitioncancel', 'transitioncancel', 479 'transitionrun', 'transitionrun' 480 ]); 481 482 // Then wait a couple of frames and check that no event was dispatched 483 await waitForAnimationFrames(2); 484 }, 'Replacing two running transitions on the same target should get two ' + 485 'transitioncancel events earlier than two transitionrun events, per ' + 486 'transition generation'); 487 488 promise_test(async t => { 489 const { transition, watcher, div } = setupTransition(t, 'margin-left 100s'); 490 491 transition.currentTime = 50 * MS_PER_SEC; 492 await watcher.wait_for(['transitionrun', 'transitionstart']); 493 494 // We need to wait for a while to reproduce the potential bug in Gecko. 495 await new Promise(resolve => t.step_timeout(resolve, 100)); 496 497 // Replace the running transition. 498 div.style.marginLeft = '200px'; 499 getComputedStyle(div).marginLeft; 500 501 // transitioncancel event should be fired before transitionrun because we 502 // expect to cancel the running transition first. 503 await watcher.wait_for( 504 ['transitioncancel', 'transitionrun', 'transitionstart'] 505 ); 506 507 // Then wait a couple of frames and check that no event was dispatched 508 await waitForAnimationFrames(2); 509 }, 'Replacing a running transition and forcing to flush the style together ' + 510 'should get the correct event order'); 511 512 </script>