finishing-an-animation.html (11552B)
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="../../testcommon.js"></script> 9 <script src="../../resources/timing-override.js"></script> 10 <body> 11 <div id="log"></div> 12 <script> 13 'use strict'; 14 15 test(t => { 16 const div = createDiv(t); 17 const animation = div.animate(null, 100 * MS_PER_SEC); 18 animation.playbackRate = 0; 19 20 assert_throws_dom('InvalidStateError', () => { 21 animation.finish(); 22 }); 23 }, 'Finishing an animation with a zero playback rate throws'); 24 25 test(t => { 26 const div = createDiv(t); 27 const animation = div.animate(null, 28 { duration : 100 * MS_PER_SEC, 29 iterations : Infinity }); 30 31 assert_throws_dom('InvalidStateError', () => { 32 animation.finish(); 33 }); 34 }, 'Finishing an infinite animation throws'); 35 36 test(t => { 37 const div = createDiv(t); 38 const animation = div.animate(null, 100 * MS_PER_SEC); 39 animation.finish(); 40 41 assert_time_equals_literal(animation.currentTime, 100 * MS_PER_SEC, 42 'After finishing, the currentTime should be set to the end of the' 43 + ' active duration'); 44 }, 'Finishing an animation seeks to the end time'); 45 46 test(t => { 47 const div = createDiv(t); 48 const animation = div.animate(null, 100 * MS_PER_SEC); 49 // 1s past effect end 50 animation.currentTime = 51 animation.effect.getComputedTiming().endTime + 1 * MS_PER_SEC; 52 animation.finish(); 53 54 assert_time_equals_literal(animation.currentTime, 100 * MS_PER_SEC, 55 'After finishing, the currentTime should be set back to the end of the' 56 + ' active duration'); 57 }, 'Finishing an animation with a current time past the effect end jumps' 58 + ' back to the end'); 59 60 promise_test(async t => { 61 const div = createDiv(t); 62 const animation = div.animate(null, 100 * MS_PER_SEC); 63 animation.currentTime = 100 * MS_PER_SEC; 64 await animation.finished; 65 66 animation.playbackRate = -1; 67 animation.finish(); 68 69 assert_equals(animation.currentTime, 0, 70 'After finishing a reversed animation the currentTime ' + 71 'should be set to zero'); 72 }, 'Finishing a reversed animation jumps to zero time'); 73 74 promise_test(async t => { 75 const div = createDiv(t); 76 const animation = div.animate(null, 100 * MS_PER_SEC); 77 animation.currentTime = 100 * MS_PER_SEC; 78 await animation.finished; 79 80 animation.playbackRate = -1; 81 animation.currentTime = -1000; 82 animation.finish(); 83 84 assert_equals(animation.currentTime, 0, 85 'After finishing a reversed animation the currentTime ' + 86 'should be set back to zero'); 87 }, 'Finishing a reversed animation with a current time less than zero' 88 + ' makes it jump back to zero'); 89 90 promise_test(async t => { 91 const div = createDiv(t); 92 const animation = div.animate(null, 100 * MS_PER_SEC); 93 animation.pause(); 94 await animation.ready; 95 96 animation.finish(); 97 98 assert_equals(animation.playState, 'finished', 99 'The play state of a paused animation should become ' + 100 '"finished"'); 101 assert_times_equal(animation.startTime, 102 animation.timeline.currentTime - 100 * MS_PER_SEC, 103 'The start time of a paused animation should be set'); 104 }, 'Finishing a paused animation resolves the start time'); 105 106 test(t => { 107 const div = createDiv(t); 108 const animation = div.animate(null, 100 * MS_PER_SEC); 109 // Update playbackRate so we can test that the calculated startTime 110 // respects it 111 animation.playbackRate = 2; 112 animation.pause(); 113 // While animation is still pause-pending call finish() 114 animation.finish(); 115 116 assert_false(animation.pending); 117 assert_equals(animation.playState, 'finished', 118 'The play state of a pause-pending animation should become ' + 119 '"finished"'); 120 assert_times_equal(animation.startTime, 121 animation.timeline.currentTime - 100 * MS_PER_SEC / 2, 122 'The start time of a pause-pending animation should ' + 123 'be set'); 124 }, 'Finishing a pause-pending animation resolves the pending task' 125 + ' immediately and update the start time'); 126 127 test(t => { 128 const div = createDiv(t); 129 const animation = div.animate(null, 100 * MS_PER_SEC); 130 animation.playbackRate = -2; 131 animation.pause(); 132 animation.finish(); 133 134 assert_false(animation.pending); 135 assert_equals(animation.playState, 'finished', 136 'The play state of a pause-pending animation should become ' + 137 '"finished"'); 138 assert_times_equal(animation.startTime, animation.timeline.currentTime, 139 'The start time of a pause-pending animation should be ' + 140 'set'); 141 }, 'Finishing a pause-pending animation with negative playback rate' 142 + ' resolves the pending task immediately'); 143 144 test(t => { 145 const div = createDiv(t); 146 const animation = div.animate(null, 100 * MS_PER_SEC); 147 animation.playbackRate = 0.5; 148 animation.finish(); 149 150 assert_false(animation.pending); 151 assert_equals(animation.playState, 'finished', 152 'The play state of a play-pending animation should become ' + 153 '"finished"'); 154 assert_times_equal(animation.startTime, 155 animation.timeline.currentTime - 100 * MS_PER_SEC / 0.5, 156 'The start time of a play-pending animation should ' + 157 'be set'); 158 }, 'Finishing an animation while play-pending resolves the pending' 159 + ' task immediately'); 160 161 // FIXME: Add a test for when we are play-pending without an active timeline. 162 // - In that case even after calling finish() we should still be pending but 163 // the current time should be updated 164 165 promise_test(async t => { 166 const div = createDiv(t); 167 const animation = div.animate(null, 100 * MS_PER_SEC); 168 await animation.ready; 169 170 animation.pause(); 171 animation.play(); 172 // We are now in the unusual situation of being play-pending whilst having 173 // a resolved start time. Check that finish() still triggers a transition 174 // to the finished state immediately. 175 animation.finish(); 176 177 assert_equals(animation.playState, 'finished', 178 'After aborting a pause then finishing an animation its play ' + 179 'state should become "finished" immediately'); 180 }, 'Finishing an animation during an aborted pause makes it finished' 181 + ' immediately'); 182 183 promise_test(async t => { 184 const div = createDiv(t); 185 const animation = div.animate(null, 100 * MS_PER_SEC); 186 let resolvedFinished = false; 187 animation.finished.then(() => { 188 resolvedFinished = true; 189 }); 190 191 await animation.ready; 192 193 animation.finish(); 194 await Promise.resolve(); 195 196 assert_true(resolvedFinished, 'finished promise should be resolved'); 197 }, 'Finishing an animation resolves the finished promise synchronously'); 198 199 promise_test(async t => { 200 const effect = new KeyframeEffect(null, null, 100 * MS_PER_SEC); 201 const animation = new Animation(effect, document.timeline); 202 let resolvedFinished = false; 203 animation.finished.then(() => { 204 resolvedFinished = true; 205 }); 206 207 await animation.ready; 208 209 animation.finish(); 210 await Promise.resolve(); 211 212 assert_true(resolvedFinished, 'finished promise should be resolved'); 213 }, 'Finishing an animation without a target resolves the finished promise' 214 + ' synchronously'); 215 216 promise_test(async t => { 217 const animation = createDiv(t).animate(null, 100 * MS_PER_SEC); 218 const promise = animation.ready; 219 let readyResolved = false; 220 221 animation.finish(); 222 animation.ready.then(() => { readyResolved = true; }); 223 224 const promiseResult = await animation.finished; 225 226 assert_equals(promiseResult, animation); 227 assert_equals(animation.ready, promise); 228 assert_true(readyResolved); 229 }, 'A pending ready promise is resolved and not replaced when the animation' 230 + ' is finished'); 231 232 promise_test(async t => { 233 const animation = createDiv(t).animate(null, 100 * MS_PER_SEC); 234 await animation.ready; 235 236 animation.updatePlaybackRate(2); 237 assert_true(animation.pending); 238 239 animation.finish(); 240 assert_false(animation.pending); 241 assert_equals(animation.playbackRate, 2); 242 assert_time_equals_literal(animation.currentTime, 100 * MS_PER_SEC); 243 }, 'A pending playback rate should be applied immediately when an animation' 244 + ' is finished'); 245 246 promise_test(async t => { 247 const animation = createDiv(t).animate(null, 100 * MS_PER_SEC); 248 await animation.ready; 249 250 animation.updatePlaybackRate(0); 251 252 assert_throws_dom('InvalidStateError', () => { 253 animation.finish(); 254 }); 255 }, 'An exception should be thrown if the effective playback rate is zero'); 256 257 promise_test(async t => { 258 const animation = createDiv(t).animate(null, { 259 duration: 100 * MS_PER_SEC, 260 iterations: Infinity 261 }); 262 animation.currentTime = 50 * MS_PER_SEC; 263 animation.playbackRate = -1; 264 await animation.ready; 265 266 animation.updatePlaybackRate(1); 267 268 assert_throws_dom('InvalidStateError', () => { 269 animation.finish(); 270 }); 271 }, 'An exception should be thrown when finishing if the effective playback rate' 272 + ' is positive and the target effect end is infinity'); 273 274 promise_test(async t => { 275 const animation = createDiv(t).animate(null, { 276 duration: 100 * MS_PER_SEC, 277 iterations: Infinity 278 }); 279 await animation.ready; 280 281 animation.updatePlaybackRate(-1); 282 283 animation.finish(); 284 // Should not have thrown 285 }, 'An exception is NOT thrown when finishing if the effective playback rate' 286 + ' is negative and the target effect end is infinity'); 287 288 promise_test(async t => { 289 const div = createDiv(t); 290 const animation = div.animate({}, 100 * MS_PER_SEC); 291 div.remove(); 292 293 const eventWatcher = new EventWatcher(t, animation, 'finish'); 294 295 await animation.ready; 296 animation.finish(); 297 298 await eventWatcher.wait_for('finish'); 299 assert_equals(animation.effect.target.parentNode, null, 300 'finish event should be fired for the animation on an orphaned element'); 301 }, 'Finishing an animation fires finish event on orphaned element'); 302 303 promise_test(async t => { 304 const animation = createDiv(t).animate(null, 100 * MS_PER_SEC); 305 await animation.ready; 306 307 const originalFinishPromise = animation.finished; 308 309 animation.cancel(); 310 assert_equals(animation.startTime, null); 311 assert_equals(animation.currentTime, null); 312 313 const resolvedFinishPromise = animation.finished; 314 assert_not_equals(originalFinishPromise, resolvedFinishPromise, 315 'Canceling an animation should create a new finished promise'); 316 317 animation.finish(); 318 assert_equals(animation.playState, 'finished', 319 'The play state of a canceled animation should become ' + 320 '"finished"'); 321 assert_times_equal(animation.startTime, 322 animation.timeline.currentTime - 100 * MS_PER_SEC, 323 'The start time of a finished animation should be set'); 324 assert_times_equal(animation.currentTime, 100000, 325 'Hold time should be set to end boundary of the animation'); 326 327 }, 'Finishing a canceled animation sets the current and start times'); 328 329 promise_test(async t => { 330 const animation = createDiv(t).animate(null, 1); 331 await animation.finished; 332 const eventWatcher = new EventWatcher(t, animation, 'finish', waitForNextFrame); 333 await eventWatcher.wait_for('finish'); 334 }, 'Finishing an animation fires finish event when a finish event listener is ' 335 + 'added as the finished promise resolves'); 336 337 </script> 338 </body>