finish-animation.html (14059B)
1 <!DOCTYPE html> 2 <meta charset=utf-8> 3 <title>Finishing an animation</title> 4 <link rel="help" 5 href="https://drafts.csswg.org/web-animations/#finishing-an-animation-section"> 6 <script src="/resources/testharness.js"></script> 7 <script src="/resources/testharnessreport.js"></script> 8 <script src="/web-animations/testcommon.js"></script> 9 <script src="testcommon.js"></script> 10 <style> 11 .scroller { 12 overflow: auto; 13 height: 200px; 14 width: 100px; 15 will-change: transform; 16 } 17 18 .contents { 19 height: 1000px; 20 width: 100%; 21 } 22 </style> 23 <body> 24 <div id="log"></div> 25 <script> 26 'use strict'; 27 28 promise_test(async t => { 29 const animation = createScrollLinkedAnimation(t); 30 animation.play(); 31 animation.playbackRate = 0; 32 33 assert_throws_dom('InvalidStateError', () => { 34 animation.finish(); 35 }); 36 }, 'Finishing an animation with a zero playback rate throws'); 37 38 promise_test(async t => { 39 const animation = createScrollLinkedAnimation(t); 40 animation.play(); 41 animation.finish(); 42 43 assert_percents_equal(animation.currentTime, 100, 44 'After finishing, the currentTime should be set to the end of the' 45 + ' active duration'); 46 }, 'Finishing an animation seeks to the end time'); 47 48 promise_test(async t => { 49 const animation = createScrollLinkedAnimation(t); 50 animation.play(); 51 // 1% past effect end 52 animation.currentTime = CSSNumericValue.parse("101%"); 53 animation.finish(); 54 55 assert_percents_equal(animation.currentTime, 100, 56 'After finishing, the currentTime should be set back to the end of the' 57 + ' active duration'); 58 }, 'Finishing an animation with a current time past the effect end jumps' 59 + ' back to the end'); 60 61 promise_test(async t => { 62 const animation = createScrollLinkedAnimation(t); 63 const scroller = animation.timeline.source; 64 const maxScroll = scroller.scrollHeight - scroller.clientHeight; 65 animation.play(); 66 scroller.scrollTop = maxScroll; 67 await animation.finished; 68 69 animation.playbackRate = -1; 70 animation.finish(); 71 72 assert_percents_equal(animation.currentTime, 0, 73 'After finishing a reversed animation the ' + 74 'currentTime should be set to zero'); 75 }, 'Finishing a reversed animation jumps to zero time'); 76 77 promise_test(async t => { 78 const animation = createScrollLinkedAnimation(t); 79 animation.play(); 80 animation.currentTime = CSSNumericValue.parse("100%"); 81 await animation.finished; 82 83 animation.playbackRate = -1; 84 animation.currentTime = CSSNumericValue.parse("-1000%"); 85 animation.finish(); 86 87 assert_percents_equal(animation.currentTime, 0, 88 'After finishing a reversed animation the ' + 89 'currentTime should be set back to zero'); 90 }, 'Finishing a reversed animation with a current time less than zero' 91 + ' makes it jump back to zero'); 92 93 promise_test(async t => { 94 const animation = createScrollLinkedAnimation(t); 95 animation.play(); 96 animation.playbackRate = 0.5; 97 animation.finish(); 98 99 assert_false(animation.pending); 100 assert_equals(animation.playState, 'finished', 101 'The play state of a play-pending animation should become ' + 102 '"finished"'); 103 assert_percents_equal(animation.startTime, 104 animation.timeline.currentTime.value - 100 / 0.5, 105 'The start time of a play-pending animation should ' + 106 'be set'); 107 }, 'Finishing an animation while play-pending resolves the pending' 108 + ' task immediately'); 109 110 promise_test(async t => { 111 const animation = createScrollLinkedAnimation(t); 112 const scroller = animation.timeline.source; 113 await runAndWaitForFrameUpdate(() => { 114 // Make the scroll timeline inactive. 115 scroller.style.overflow = 'visible'; 116 }); 117 animation.play(); 118 animation.finish(); 119 120 await animation.finished; 121 122 assert_true(animation.pending); 123 assert_equals(animation.playState, 'finished', 124 'The play state of a play-pending animation should become ' + 125 '"finished"'); 126 assert_percents_equal(animation.currentTime, 100, 127 'The current time of a play-pending animation should ' + 128 'be set to the end of the active duration'); 129 assert_equals(animation.startTime, null, 130 'The start time of a finished play-pending animation should ' + 131 'be unresolved'); 132 }, 'Finishing an animation attached to inactive timeline while play-pending ' 133 + 'doesn\'t resolves the pending task'); 134 135 promise_test(async t => { 136 const animation = createScrollLinkedAnimation(t); 137 animation.play(); 138 let resolvedFinished = false; 139 animation.finished.then(() => { 140 resolvedFinished = true; 141 }); 142 143 await animation.ready; 144 145 animation.finish(); 146 await Promise.resolve(); 147 148 assert_true(resolvedFinished, 'finished promise should be resolved'); 149 }, 'Finishing an animation resolves the finished promise synchronously'); 150 151 promise_test(async t => { 152 const animation = createScrollLinkedAnimation(t); 153 animation.play(); 154 const promise = animation.ready; 155 let readyResolved = false; 156 157 animation.finish(); 158 animation.ready.then(() => { readyResolved = true; }); 159 160 const promiseResult = await animation.finished; 161 await animation.ready; 162 163 assert_equals(promiseResult, animation); 164 assert_equals(animation.ready, promise); 165 assert_true(readyResolved); 166 }, 'A pending ready promise is resolved and not replaced when the animation' 167 + ' is finished'); 168 169 promise_test(async t => { 170 const animation = createScrollLinkedAnimation(t); 171 animation.play(); 172 animation.effect.target.remove(); 173 174 const eventWatcher = new EventWatcher(t, animation, 'finish'); 175 176 await animation.ready; 177 animation.finish(); 178 179 await eventWatcher.wait_for('finish'); 180 assert_equals(animation.effect.target.parentNode, null, 181 'finish event should be fired for the animation on an orphaned element'); 182 }, 'Finishing an animation fires finish event on orphaned element'); 183 184 promise_test(async t => { 185 const animation = createScrollLinkedAnimation(t); 186 animation.play(); 187 await animation.ready; 188 189 const originalFinishPromise = animation.finished; 190 191 animation.cancel(); 192 assert_equals(animation.startTime, null); 193 assert_equals(animation.currentTime, null); 194 195 const resolvedFinishPromise = animation.finished; 196 assert_not_equals(originalFinishPromise, resolvedFinishPromise, 197 'Canceling an animation should create a new finished promise'); 198 199 animation.finish(); 200 assert_equals(animation.playState, 'finished', 201 'The play state of a canceled animation should become ' + 202 '"finished"'); 203 assert_percents_equal(animation.startTime, 204 animation.timeline.currentTime.value - 100, 205 'The start time of a finished animation should be set'); 206 assert_percents_equal(animation.currentTime, 100, 207 'Hold time should be set to end boundary of the ' + 208 'animation'); 209 210 }, 'Finishing a canceled animation sets the current and start times'); 211 212 promise_test(async t => { 213 const animation = createScrollLinkedAnimation(t); 214 const scroller = animation.timeline.source; 215 const maxScroll = scroller.scrollHeight - scroller.clientHeight; 216 // Wait for new animation frame which allows the timeline to compute new 217 // current time. 218 await runAndWaitForFrameUpdate(() => { 219 scroller.scrollTop = 0.25 * maxScroll; 220 }); 221 222 const eventWatcher = new EventWatcher(t, animation, 'finish'); 223 animation.finish(); 224 await animation.finished; 225 const finishEvent = await eventWatcher.wait_for('finish'); 226 assert_equals(animation.playState, 'finished', 227 'Animation is finished.'); 228 assert_percents_equal(animation.currentTime, 100, 229 'The current time is the end of the active duration in finished state.'); 230 assert_percents_equal(animation.startTime, -75, 231 'The start time is calculated to match the current time.'); 232 assert_percents_equal(finishEvent.currentTime, 100, 233 'event.currentTime is the animation current time.'); 234 assert_percents_equal(finishEvent.timelineTime, 25, 235 'event.timelineTime is timeline.currentTime'); 236 }, 'Finishing idle animation produces correct state and fires finish event.'); 237 238 promise_test(async t => { 239 const animation = createScrollLinkedAnimation(t); 240 const scroller = animation.timeline.source; 241 // Make the scroll timeline inactive. 242 scroller.style.overflow = 'visible'; 243 await waitForNextFrame(); 244 assert_equals(animation.timeline.currentTime, null, 245 'Sanity check the timeline is inactive.'); 246 animation.finish(); 247 assert_equals(animation.playState, 'paused', 'Animation is paused.'); 248 }, 'Finishing idle animation attached to inactive timeline pauses the ' + 249 'animation.'); 250 251 promise_test(async t => { 252 const animation = createScrollLinkedAnimation(t); 253 const scroller = animation.timeline.source; 254 const maxScroll = scroller.scrollHeight - scroller.clientHeight; 255 scroller.scrollTop = 0.25 * maxScroll; 256 animation.play(); 257 await animation.ready; 258 259 const eventWatcher = new EventWatcher(t, animation, 'finish'); 260 animation.finish(); 261 await animation.finished; 262 const finishEvent = await eventWatcher.wait_for('finish'); 263 assert_equals(animation.playState, 'finished', 264 'Animation is finished.'); 265 assert_percents_equal(animation.currentTime, 100, 266 'The current time is the end of active duration in finished state.'); 267 assert_percents_equal(animation.startTime, -75, 268 'The start time is calculated to match animation current time.'); 269 assert_percents_equal(finishEvent.currentTime, 100, 270 'event.currentTime is the animation current time.'); 271 assert_percents_equal(finishEvent.timelineTime, 25, 272 'event.timelineTime is timeline.currentTime'); 273 }, 'Finishing running animation produces correct state and fires finish event.'); 274 275 promise_test(async t => { 276 const animation = createScrollLinkedAnimation(t); 277 const scroller = animation.timeline.source; 278 animation.play(); 279 await animation.ready; 280 281 await runAndWaitForFrameUpdate(() => { 282 // Make the scroll timeline inactive. 283 scroller.style.overflow = 'visible'; 284 }); 285 assert_equals(animation.timeline.currentTime, null, 286 'Sanity check the timeline is inactive.'); 287 assert_equals(animation.playState, 'running', 288 'Sanity check the animation is running.'); 289 290 animation.finish(); 291 assert_equals(animation.playState, 'paused', 'Animation is paused.'); 292 }, 'Finishing running animation attached to inactive timeline pauses the ' + 293 'animation.'); 294 295 promise_test(async t => { 296 const animation = createScrollLinkedAnimation(t); 297 animation.pause(); 298 await animation.ready; 299 300 animation.finish(); 301 302 assert_equals(animation.playState, 'finished', 303 'The play state of a paused animation should become ' + 304 '"finished"'); 305 assert_percents_equal(animation.startTime, 306 animation.timeline.currentTime.value - 100, 307 'The start time of a paused animation should be set'); 308 }, 'Finishing a paused animation resolves the start time'); 309 310 promise_test(async t => { 311 const animation = createScrollLinkedAnimation(t); 312 animation.play(); 313 // Update playbackRate so we can test that the calculated startTime 314 // respects it 315 animation.playbackRate = 2; 316 animation.pause(); 317 // While animation is still pause-pending call finish() 318 animation.finish(); 319 320 assert_false(animation.pending); 321 assert_equals(animation.playState, 'finished', 322 'The play state of a pause-pending animation should become ' + 323 '"finished"'); 324 assert_percents_equal(animation.startTime, 325 animation.timeline.currentTime.value - 100 / 2, 326 'The start time of a pause-pending animation should ' + 327 'be set'); 328 }, 'Finishing a pause-pending animation resolves the pending task' 329 + ' immediately and update the start time'); 330 331 promise_test(async t => { 332 const animation = createScrollLinkedAnimation(t); 333 animation.play(); 334 animation.playbackRate = -2; 335 animation.pause(); 336 animation.finish(); 337 338 assert_false(animation.pending); 339 assert_equals(animation.playState, 'finished', 340 'The play state of a pause-pending animation should become ' + 341 '"finished"'); 342 assert_percents_equal(animation.startTime, 343 animation.timeline.currentTime, 344 'The start time of a pause-pending animation should ' + 345 'be set'); 346 }, 'Finishing a pause-pending animation with negative playback rate' 347 + ' resolves the pending task immediately'); 348 349 promise_test(async t => { 350 const animation = createScrollLinkedAnimation(t); 351 animation.play(); 352 await animation.ready; 353 354 animation.pause(); 355 animation.play(); 356 // We are now in the unusual situation of being play-pending whilst having 357 // a resolved start time. Check that finish() still triggers a transition 358 // to the finished state immediately. 359 animation.finish(); 360 361 assert_equals(animation.playState, 'finished', 362 'After aborting a pause then finishing an animation its play ' + 363 'state should become "finished" immediately'); 364 }, 'Finishing an animation during an aborted pause makes it finished' 365 + ' immediately'); 366 367 promise_test(async t => { 368 const animation = createScrollLinkedAnimation(t); 369 animation.play(); 370 await animation.ready; 371 372 animation.updatePlaybackRate(2); 373 assert_true(animation.pending); 374 375 animation.finish(); 376 assert_false(animation.pending); 377 assert_equals(animation.playbackRate, 2); 378 assert_percents_equal(animation.currentTime, 100); 379 }, 'A pending playback rate should be applied immediately when an animation' 380 + ' is finished'); 381 382 promise_test(async t => { 383 const animation = createScrollLinkedAnimation(t); 384 animation.play(); 385 await animation.ready; 386 387 animation.updatePlaybackRate(0); 388 389 assert_throws_dom('InvalidStateError', () => { 390 animation.finish(); 391 }); 392 }, 'An exception should be thrown if the effective playback rate is zero'); 393 </script> 394 </body>