finished.html (15169B)
1 <!DOCTYPE html> 2 <meta charset=utf-8> 3 <title>Animation.finished</title> 4 <link rel="help" href="https://drafts.csswg.org/web-animations/#dom-animation-finished"> 5 <script src="/resources/testharness.js"></script> 6 <script src="/resources/testharnessreport.js"></script> 7 <script src="../../testcommon.js"></script> 8 <body> 9 <div id="log"></div> 10 <script> 11 'use strict'; 12 13 promise_test(t => { 14 const div = createDiv(t); 15 const animation = div.animate({}, 100 * MS_PER_SEC); 16 const previousFinishedPromise = animation.finished; 17 return animation.ready.then(() => { 18 assert_equals(animation.finished, previousFinishedPromise, 19 'Finished promise is the same object when playing starts'); 20 animation.pause(); 21 assert_equals(animation.finished, previousFinishedPromise, 22 'Finished promise does not change when pausing'); 23 animation.play(); 24 assert_equals(animation.finished, previousFinishedPromise, 25 'Finished promise does not change when play() unpauses'); 26 27 animation.currentTime = 100 * MS_PER_SEC; 28 29 return animation.finished; 30 }).then(() => { 31 assert_equals(animation.finished, previousFinishedPromise, 32 'Finished promise is the same object when playing completes'); 33 }); 34 }, 'Test pausing then playing does not change the finished promise'); 35 36 promise_test(t => { 37 const div = createDiv(t); 38 const animation = div.animate({}, 100 * MS_PER_SEC); 39 let previousFinishedPromise = animation.finished; 40 animation.finish(); 41 return animation.finished.then(() => { 42 assert_equals(animation.finished, previousFinishedPromise, 43 'Finished promise is the same object when playing completes'); 44 animation.play(); 45 assert_not_equals(animation.finished, previousFinishedPromise, 46 'Finished promise changes when replaying animation'); 47 48 previousFinishedPromise = animation.finished; 49 animation.play(); 50 assert_equals(animation.finished, previousFinishedPromise, 51 'Finished promise is the same after redundant play() call'); 52 53 }); 54 }, 'Test restarting a finished animation'); 55 56 promise_test(t => { 57 const div = createDiv(t); 58 const animation = div.animate({}, 100 * MS_PER_SEC); 59 let previousFinishedPromise; 60 animation.finish(); 61 return animation.finished.then(() => { 62 previousFinishedPromise = animation.finished; 63 animation.playbackRate = -1; 64 assert_not_equals(animation.finished, previousFinishedPromise, 65 'Finished promise should be replaced when reversing a ' + 66 'finished promise'); 67 animation.currentTime = 0; 68 return animation.finished; 69 }).then(() => { 70 previousFinishedPromise = animation.finished; 71 animation.play(); 72 assert_not_equals(animation.finished, previousFinishedPromise, 73 'Finished promise is replaced after play() call on ' + 74 'finished, reversed animation'); 75 }); 76 }, 'Test restarting a reversed finished animation'); 77 78 promise_test(t => { 79 const div = createDiv(t); 80 const animation = div.animate({}, 100 * MS_PER_SEC); 81 const previousFinishedPromise = animation.finished; 82 animation.finish(); 83 return animation.finished.then(() => { 84 animation.currentTime = 100 * MS_PER_SEC + 1000; 85 assert_equals(animation.finished, previousFinishedPromise, 86 'Finished promise is unchanged jumping past end of ' + 87 'finished animation'); 88 }); 89 }, 'Test redundant finishing of animation'); 90 91 promise_test(t => { 92 const div = createDiv(t); 93 const animation = div.animate({}, 100 * MS_PER_SEC); 94 // Setup callback to run if finished promise is resolved 95 let finishPromiseResolved = false; 96 animation.finished.then(() => { 97 finishPromiseResolved = true; 98 }); 99 return animation.ready.then(() => { 100 // Jump to mid-way in interval and pause 101 animation.currentTime = 100 * MS_PER_SEC / 2; 102 animation.pause(); 103 return animation.ready; 104 }).then(() => { 105 // Jump to the end 106 // (But don't use finish() since that should unpause as well) 107 animation.currentTime = 100 * MS_PER_SEC; 108 return waitForAnimationFrames(2); 109 }).then(() => { 110 assert_false(finishPromiseResolved, 111 'Finished promise should not resolve when paused'); 112 }); 113 }, 'Finished promise does not resolve when paused'); 114 115 promise_test(t => { 116 const div = createDiv(t); 117 const animation = div.animate({}, 100 * MS_PER_SEC); 118 // Setup callback to run if finished promise is resolved 119 let finishPromiseResolved = false; 120 animation.finished.then(() => { 121 finishPromiseResolved = true; 122 }); 123 return animation.ready.then(() => { 124 // Jump to mid-way in interval and pause 125 animation.currentTime = 100 * MS_PER_SEC / 2; 126 animation.pause(); 127 // Jump to the end 128 animation.currentTime = 100 * MS_PER_SEC; 129 return waitForAnimationFrames(2); 130 }).then(() => { 131 assert_false(finishPromiseResolved, 132 'Finished promise should not resolve when pause-pending'); 133 }); 134 }, 'Finished promise does not resolve when pause-pending'); 135 136 promise_test(t => { 137 const div = createDiv(t); 138 const animation = div.animate({}, 100 * MS_PER_SEC); 139 animation.finish(); 140 return animation.finished.then(resolvedAnimation => { 141 assert_equals(resolvedAnimation, animation, 142 'Object identity of animation passed to Promise callback' 143 + ' matches the animation object owning the Promise'); 144 }); 145 }, 'The finished promise is fulfilled with its Animation'); 146 147 promise_test(t => { 148 const div = createDiv(t); 149 const animation = div.animate({}, 100 * MS_PER_SEC); 150 const previousFinishedPromise = animation.finished; 151 152 // Set up listeners on finished promise 153 const retPromise = animation.finished.then(() => { 154 assert_unreached('finished promise was fulfilled'); 155 }).catch(err => { 156 assert_equals(err.name, 'AbortError', 157 'finished promise is rejected with AbortError'); 158 assert_not_equals(animation.finished, previousFinishedPromise, 159 'Finished promise should change after the original is ' + 160 'rejected'); 161 }); 162 163 animation.cancel(); 164 165 return retPromise; 166 }, 'finished promise is rejected when an animation is canceled by calling ' + 167 'cancel()'); 168 169 promise_test(t => { 170 const div = createDiv(t); 171 const animation = div.animate({}, 100 * MS_PER_SEC); 172 const previousFinishedPromise = animation.finished; 173 animation.finish(); 174 return animation.finished.then(() => { 175 animation.cancel(); 176 assert_not_equals(animation.finished, previousFinishedPromise, 177 'A new finished promise should be created when' 178 + ' canceling a finished animation'); 179 }); 180 }, 'canceling an already-finished animation replaces the finished promise'); 181 182 promise_test(t => { 183 const div = createDiv(t); 184 const animation = div.animate({}, 100 * MS_PER_SEC); 185 const HALF_DUR = 100 * MS_PER_SEC / 2; 186 const QUARTER_DUR = 100 * MS_PER_SEC / 4; 187 let gotNextFrame = false; 188 let currentTimeBeforeShortening; 189 animation.currentTime = HALF_DUR; 190 return animation.ready.then(() => { 191 currentTimeBeforeShortening = animation.currentTime; 192 animation.effect.updateTiming({ duration: QUARTER_DUR }); 193 // Below we use gotNextFrame to check that shortening of the animation 194 // duration causes the finished promise to resolve, rather than it just 195 // getting resolved on the next animation frame. This relies on the fact 196 // that the promises are resolved as a micro-task before the next frame 197 // happens. 198 waitForAnimationFrames(1).then(() => { 199 gotNextFrame = true; 200 }); 201 202 return animation.finished; 203 }).then(() => { 204 assert_false(gotNextFrame, 'shortening of the animation duration should ' + 205 'resolve the finished promise'); 206 assert_equals(animation.currentTime, currentTimeBeforeShortening, 207 'currentTime should be unchanged when duration shortened'); 208 const previousFinishedPromise = animation.finished; 209 animation.effect.updateTiming({ duration: 100 * MS_PER_SEC }); 210 assert_not_equals(animation.finished, previousFinishedPromise, 211 'Finished promise should change after lengthening the ' + 212 'duration causes the animation to become active'); 213 }); 214 }, 'Test finished promise changes for animation duration changes'); 215 216 promise_test(t => { 217 const div = createDiv(t); 218 const animation = div.animate({}, 100 * MS_PER_SEC); 219 const retPromise = animation.ready.then(() => { 220 animation.playbackRate = 0; 221 animation.currentTime = 100 * MS_PER_SEC + 1000; 222 return waitForAnimationFrames(2); 223 }); 224 225 animation.finished.then(t.step_func(() => { 226 assert_unreached('finished promise should not resolve when playbackRate ' + 227 'is zero'); 228 })); 229 230 return retPromise; 231 }, 'Test finished promise changes when playbackRate == 0'); 232 233 promise_test(t => { 234 const div = createDiv(t); 235 const animation = div.animate({}, 100 * MS_PER_SEC); 236 return animation.ready.then(() => { 237 animation.playbackRate = -1; 238 return animation.finished; 239 }); 240 }, 'Test finished promise resolves when reaching to the natural boundary.'); 241 242 promise_test(t => { 243 const div = createDiv(t); 244 const animation = div.animate({}, 100 * MS_PER_SEC); 245 const previousFinishedPromise = animation.finished; 246 animation.finish(); 247 return animation.finished.then(() => { 248 animation.currentTime = 0; 249 assert_not_equals(animation.finished, previousFinishedPromise, 250 'Finished promise should change once a prior ' + 251 'finished promise resolved and the animation ' + 252 'falls out finished state'); 253 }); 254 }, 'Test finished promise changes when a prior finished promise resolved ' + 255 'and the animation falls out finished state'); 256 257 test(t => { 258 const div = createDiv(t); 259 const animation = div.animate({}, 100 * MS_PER_SEC); 260 const previousFinishedPromise = animation.finished; 261 animation.currentTime = 100 * MS_PER_SEC; 262 animation.currentTime = 100 * MS_PER_SEC / 2; 263 assert_equals(animation.finished, previousFinishedPromise, 264 'No new finished promise generated when finished state ' + 265 'is checked asynchronously'); 266 }, 'Test no new finished promise generated when finished state ' + 267 'is checked asynchronously'); 268 269 test(t => { 270 const div = createDiv(t); 271 const animation = div.animate({}, 100 * MS_PER_SEC); 272 const previousFinishedPromise = animation.finished; 273 animation.finish(); 274 animation.currentTime = 100 * MS_PER_SEC / 2; 275 assert_not_equals(animation.finished, previousFinishedPromise, 276 'New finished promise generated when finished state ' + 277 'is checked synchronously'); 278 }, 'Test new finished promise generated when finished state ' + 279 'is checked synchronously'); 280 281 promise_test(t => { 282 const div = createDiv(t); 283 const animation = div.animate({}, 100 * MS_PER_SEC); 284 let resolvedFinished = false; 285 animation.finished.then(() => { 286 resolvedFinished = true; 287 }); 288 return animation.ready.then(() => { 289 animation.finish(); 290 animation.currentTime = 100 * MS_PER_SEC / 2; 291 }).then(() => { 292 assert_true(resolvedFinished, 293 'Animation.finished should be resolved even if ' + 294 'the finished state is changed soon'); 295 }); 296 297 }, 'Test synchronous finished promise resolved even if finished state ' + 298 'is changed soon'); 299 300 promise_test(t => { 301 const div = createDiv(t); 302 const animation = div.animate({}, 100 * MS_PER_SEC); 303 let resolvedFinished = false; 304 animation.finished.then(() => { 305 resolvedFinished = true; 306 }); 307 308 return animation.ready.then(() => { 309 animation.currentTime = 100 * MS_PER_SEC; 310 animation.finish(); 311 }).then(() => { 312 assert_true(resolvedFinished, 313 'Animation.finished should be resolved soon after finish() is ' + 314 'called even if there are other asynchronous promises just before it'); 315 }); 316 }, 'Test synchronous finished promise resolved even if asynchronous ' + 317 'finished promise happens just before synchronous promise'); 318 319 promise_test(t => { 320 const div = createDiv(t); 321 const animation = div.animate({}, 100 * MS_PER_SEC); 322 animation.finished.then(t.step_func(() => { 323 assert_unreached('Animation.finished should not be resolved'); 324 })); 325 326 return animation.ready.then(() => { 327 animation.currentTime = 100 * MS_PER_SEC; 328 animation.currentTime = 100 * MS_PER_SEC / 2; 329 }); 330 }, 'Test finished promise is not resolved when the animation ' + 331 'falls out finished state immediately'); 332 333 promise_test(t => { 334 const div = createDiv(t); 335 const animation = div.animate({}, 100 * MS_PER_SEC); 336 return animation.ready.then(() => { 337 animation.currentTime = 100 * MS_PER_SEC; 338 animation.finished.then(t.step_func(() => { 339 assert_unreached('Animation.finished should not be resolved'); 340 })); 341 animation.currentTime = 0; 342 }); 343 344 }, 'Test finished promise is not resolved once the animation ' + 345 'falls out finished state even though the current finished ' + 346 'promise is generated soon after animation state became finished'); 347 348 promise_test(t => { 349 const animation = createDiv(t).animate(null, 100 * MS_PER_SEC); 350 let ready = false; 351 animation.ready.then( 352 t.step_func(() => { 353 ready = true; 354 }), 355 t.unreached_func('Ready promise must not be rejected') 356 ); 357 358 const testSuccess = animation.finished.then( 359 t.step_func(() => { 360 assert_true(ready, 'Ready promise has resolved'); 361 }), 362 t.unreached_func('Finished promise must not be rejected') 363 ); 364 365 const timeout = waitForAnimationFrames(3).then(() => { 366 return Promise.reject('Finished promise did not arrive in time'); 367 }); 368 369 animation.finish(); 370 return Promise.race([timeout, testSuccess]); 371 }, 'Finished promise should be resolved after the ready promise is resolved'); 372 373 promise_test(t => { 374 const animation = createDiv(t).animate(null, 100 * MS_PER_SEC); 375 let caught = false; 376 animation.ready.then( 377 t.unreached_func('Ready promise must not be resolved'), 378 t.step_func(() => { 379 caught = true; 380 }) 381 ); 382 383 const testSuccess = animation.finished.then( 384 t.unreached_func('Finished promise must not be resolved'), 385 t.step_func(() => { 386 assert_true(caught, 'Ready promise has been rejected'); 387 }) 388 ); 389 390 const timeout = waitForAnimationFrames(3).then(() => { 391 return Promise.reject('Finished promise was not rejected in time'); 392 }); 393 394 animation.cancel(); 395 return Promise.race([timeout, testSuccess]); 396 }, 'Finished promise should be rejected after the ready promise is rejected'); 397 398 promise_test(async t => { 399 const animation = createDiv(t).animate(null, 100 * MS_PER_SEC); 400 401 // Ensure the finished promise is created 402 const finished = animation.finished; 403 404 window.addEventListener( 405 'unhandledrejection', 406 t.unreached_func('Should not get an unhandled rejection') 407 ); 408 409 animation.cancel(); 410 411 // Wait a moment to allow a chance for the event to be dispatched. 412 await waitForAnimationFrames(2); 413 }, 'Finished promise does not report an unhandledrejection when rejected'); 414 415 </script> 416 </body>