animate.html (13735B)
1 <!DOCTYPE html> 2 <meta charset=utf-8> 3 <title>Animatable.animate</title> 4 <link rel="help" href="https://drafts.csswg.org/web-animations/#dom-animatable-animate"> 5 <script src="/resources/testharness.js"></script> 6 <script src="/resources/testharnessreport.js"></script> 7 <script src="../../testcommon.js"></script> 8 <script src="../../resources/easing-tests.js"></script> 9 <script src="../../resources/keyframe-utils.js"></script> 10 <script src="../../resources/keyframe-tests.js"></script> 11 <script src="../../resources/timing-utils.js"></script> 12 <script src="../../resources/timing-tests.js"></script> 13 <style> 14 .pseudo::before {content: '';} 15 .pseudo::after {content: '';} 16 .pseudo::marker {content: '';} 17 </style> 18 <body> 19 <div id="log"></div> 20 <iframe width="10" height="10" id="iframe"></iframe> 21 <script> 22 'use strict'; 23 24 // Tests on Element 25 26 test(t => { 27 const anim = createDiv(t).animate(null); 28 assert_class_string(anim, 'Animation', 'Returned object is an Animation'); 29 }, 'Element.animate() creates an Animation object'); 30 31 test(t => { 32 const iframe = window.frames[0]; 33 const div = createDiv(t, iframe.document); 34 const anim = Element.prototype.animate.call(div, null); 35 assert_equals(Object.getPrototypeOf(anim), iframe.Animation.prototype, 36 'The prototype of the created Animation is that defined on' 37 + ' the relevant global for the target element'); 38 assert_not_equals(Object.getPrototypeOf(anim), Animation.prototype, 39 'The prototype of the created Animation is NOT that of' 40 + ' the current global'); 41 }, 'Element.animate() creates an Animation object in the relevant realm of' 42 + ' the target element'); 43 44 test(t => { 45 const div = createDiv(t); 46 const anim = Element.prototype.animate.call(div, null); 47 assert_class_string(anim.effect, 'KeyframeEffect', 48 'Returned Animation has a KeyframeEffect'); 49 }, 'Element.animate() creates an Animation object with a KeyframeEffect'); 50 51 test(t => { 52 const iframe = window.frames[0]; 53 const div = createDiv(t, iframe.document); 54 const anim = Element.prototype.animate.call(div, null); 55 assert_equals(Object.getPrototypeOf(anim.effect), 56 iframe.KeyframeEffect.prototype, 57 'The prototype of the created KeyframeEffect is that defined on' 58 + ' the relevant global for the target element'); 59 assert_not_equals(Object.getPrototypeOf(anim.effect), 60 KeyframeEffect.prototype, 61 'The prototype of the created KeyframeEffect is NOT that of' 62 + ' the current global'); 63 }, 'Element.animate() creates an Animation object with a KeyframeEffect' 64 + ' that is created in the relevant realm of the target element'); 65 66 for (const subtest of gEmptyKeyframeListTests) { 67 test(t => { 68 const anim = createDiv(t).animate(subtest, 2000); 69 assert_not_equals(anim, null); 70 }, 'Element.animate() accepts empty keyframe lists ' + 71 `(input: ${JSON.stringify(subtest)})`); 72 } 73 74 for (const subtest of gKeyframesTests) { 75 test(t => { 76 const anim = createDiv(t).animate(subtest.input, 2000); 77 assert_frame_lists_equal(anim.effect.getKeyframes(), subtest.output); 78 }, `Element.animate() accepts ${subtest.desc}`); 79 } 80 81 for (const subtest of gInvalidKeyframesTests) { 82 test(t => { 83 const div = createDiv(t); 84 assert_throws_js(TypeError, () => { 85 div.animate(subtest.input, 2000); 86 }); 87 }, `Element.animate() does not accept ${subtest.desc}`); 88 } 89 90 test(t => { 91 const anim = createDiv(t).animate(null, 2000); 92 assert_equals(anim.effect.getTiming().duration, 2000); 93 assert_default_timing_except(anim.effect, ['duration']); 94 }, 'Element.animate() accepts a double as an options argument'); 95 96 test(t => { 97 const anim = createDiv(t).animate(null, 98 { duration: Infinity, fill: 'forwards' }); 99 assert_equals(anim.effect.getTiming().duration, Infinity); 100 assert_equals(anim.effect.getTiming().fill, 'forwards'); 101 assert_default_timing_except(anim.effect, ['duration', 'fill']); 102 }, 'Element.animate() accepts a KeyframeAnimationOptions argument'); 103 104 test(t => { 105 const anim = createDiv(t).animate(null); 106 assert_default_timing_except(anim.effect, []); 107 }, 'Element.animate() accepts an absent options argument'); 108 109 for (const invalid of gBadDelayValues) { 110 test(t => { 111 assert_throws_js(TypeError, () => { 112 createDiv(t).animate(null, { delay: invalid }); 113 }); 114 }, `Element.animate() does not accept invalid delay value: ${invalid}`); 115 } 116 117 test(t => { 118 const anim = createDiv(t).animate(null, { duration: 'auto' }); 119 assert_equals(anim.effect.getTiming().duration, 'auto', 'set duration \'auto\''); 120 assert_equals(anim.effect.getComputedTiming().duration, 0, 121 'getComputedTiming() after set duration \'auto\''); 122 }, 'Element.animate() accepts a duration of \'auto\' using a dictionary' 123 + ' object'); 124 125 for (const invalid of gBadDurationValues) { 126 if (typeof invalid === 'string' && !isNaN(parseFloat(invalid))) { 127 continue; 128 } 129 test(t => { 130 assert_throws_js(TypeError, () => { 131 createDiv(t).animate(null, invalid); 132 }); 133 }, 'Element.animate() does not accept invalid duration value: ' 134 + (typeof invalid === 'string' ? `"${invalid}"` : invalid)); 135 } 136 137 for (const invalid of gBadDurationValues) { 138 test(t => { 139 assert_throws_js(TypeError, () => { 140 createDiv(t).animate(null, { duration: invalid }); 141 }); 142 }, 'Element.animate() does not accept invalid duration value: ' 143 + (typeof invalid === 'string' ? `"${invalid}"` : invalid) 144 + ' using a dictionary object'); 145 } 146 147 for (const invalidEasing of gInvalidEasings) { 148 test(t => { 149 assert_throws_js(TypeError, () => { 150 createDiv(t).animate({ easing: invalidEasing }, 2000); 151 }); 152 }, `Element.animate() does not accept invalid easing: '${invalidEasing}'`); 153 } 154 155 for (const invalid of gBadIterationStartValues) { 156 test(t => { 157 assert_throws_js(TypeError, () => { 158 createDiv(t).animate(null, { iterationStart: invalid }); 159 }); 160 }, 'Element.animate() does not accept invalid iterationStart value: ' + 161 invalid); 162 } 163 164 for (const invalid of gBadIterationsValues) { 165 test(t => { 166 assert_throws_js(TypeError, () => { 167 createDiv(t).animate(null, { iterations: invalid }); 168 }); 169 }, 'Element.animate() does not accept invalid iterations value: ' + 170 invalid); 171 } 172 173 test(t => { 174 const anim = createDiv(t).animate(null, 2000); 175 assert_equals(anim.id, ''); 176 }, 'Element.animate() correctly sets the id attribute when no id is specified'); 177 178 test(t => { 179 const anim = createDiv(t).animate(null, { id: 'test' }); 180 assert_equals(anim.id, 'test'); 181 }, 'Element.animate() correctly sets the id attribute'); 182 183 test(t => { 184 const anim = createDiv(t).animate(null, 2000); 185 assert_equals(anim.timeline, document.timeline); 186 }, 'Element.animate() correctly sets the Animation\'s timeline'); 187 188 async_test(t => { 189 const iframe = document.createElement('iframe'); 190 iframe.width = 10; 191 iframe.height = 10; 192 193 iframe.addEventListener('load', t.step_func(() => { 194 const div = createDiv(t, iframe.contentDocument); 195 const anim = div.animate(null, 2000); 196 assert_equals(anim.timeline, iframe.contentDocument.timeline); 197 iframe.remove(); 198 t.done(); 199 })); 200 201 document.body.appendChild(iframe); 202 }, 'Element.animate() correctly sets the Animation\'s timeline when ' + 203 'triggered on an element in a different document'); 204 205 for (const subtest of gAnimationTimelineTests) { 206 test(t => { 207 const anim = createDiv(t).animate(null, { timeline: subtest.timeline }); 208 assert_not_equals(anim, null, 209 'An animation sohuld be created'); 210 assert_equals(anim.timeline, subtest.expectedTimeline, 211 'Animation timeline should be '+ 212 subtest.expectedTimelineDescription); 213 }, 'Element.animate() correctly sets the Animation\'s timeline ' 214 + subtest.description + ' in KeyframeAnimationOptions.'); 215 } 216 217 test(t => { 218 const anim = createDiv(t).animate(null, 2000); 219 assert_equals(anim.playState, 'running'); 220 }, 'Element.animate() calls play on the Animation'); 221 222 promise_test(async t => { 223 const div = createDiv(t); 224 225 let gotTransition = false; 226 div.addEventListener('transitionrun', () => { 227 gotTransition = true; 228 }); 229 230 // Setup transition start point. 231 div.style.transition = 'opacity 100s'; 232 getComputedStyle(div).opacity; 233 234 // Update specified style but don't flush style. 235 div.style.opacity = '0.5'; 236 237 // Trigger a new animation at the same time. 238 const anim = div.animate({ opacity: [0, 1] }, 100 * MS_PER_SEC); 239 240 // If Element.animate() produces a style change event it will have triggered 241 // a transition. 242 // 243 // If it does NOT produce a style change event, the animation will override 244 // the before-change style and after-change style such that a transition is 245 // never triggered. 246 247 // Wait for the animation to start and then for one more animation 248 // frame to give the transitionrun event a chance to be dispatched. 249 await anim.ready; 250 await waitForAnimationFrames(1); 251 252 assert_false(gotTransition, 'A transition should NOT have been triggered'); 253 }, 'Element.animate() does NOT trigger a style change event'); 254 255 // Tests on pseudo-elements 256 // Some tests occur twice (on pseudo-elements with and without content) 257 // in order to test both code paths for tree-abiding pseudo-elements in blink. 258 259 test(t => { 260 const div = createDiv(t); 261 div.classList.add('pseudo'); 262 const anim = div.animate(null, {pseudoElement: '::before'}); 263 assert_class_string(anim, 'Animation', 'The returned object is an Animation'); 264 }, 'animate() with pseudoElement parameter creates an Animation object'); 265 266 test(t => { 267 const div = createDiv(t); 268 const anim = div.animate(null, {pseudoElement: '::before'}); 269 assert_class_string(anim, 'Animation', 'The returned object is an Animation'); 270 }, 'animate() with pseudoElement parameter without content creates an Animation object'); 271 272 test(t => { 273 const div = createDiv(t); 274 div.classList.add('pseudo'); 275 div.style.display = 'list-item'; 276 const anim = div.animate(null, {pseudoElement: '::marker'}); 277 assert_class_string(anim, 'Animation', 'The returned object is an Animation for ::marker'); 278 }, 'animate() with pseudoElement parameter creates an Animation object for ::marker'); 279 280 test(t => { 281 const div = createDiv(t); 282 div.classList.add('pseudo'); 283 div.textContent = 'foo'; 284 const anim = div.animate(null, {pseudoElement: '::first-line'}); 285 assert_class_string(anim, 'Animation', 'The returned object is an Animation for ::first-line'); 286 }, 'animate() with pseudoElement parameter creates an Animation object for ::first-line'); 287 288 test(t => { 289 const div = createDiv(t); 290 div.classList.add('pseudo'); 291 const anim = div.animate(null, {pseudoElement: '::before'}); 292 assert_equals(anim.effect.target, div, 'The returned element has the correct target element'); 293 assert_equals(anim.effect.pseudoElement, '::before', 294 'The returned Animation targets the correct selector'); 295 }, 'animate() with pseudoElement an Animation object targeting ' + 296 'the correct pseudo-element'); 297 298 test(t => { 299 const div = createDiv(t); 300 const anim = div.animate(null, {pseudoElement: '::before'}); 301 assert_equals(anim.effect.target, div, 'The returned element has the correct target element'); 302 assert_equals(anim.effect.pseudoElement, '::before', 303 'The returned Animation targets the correct selector'); 304 }, 'animate() with pseudoElement without content creates an Animation object targeting ' + 305 'the correct pseudo-element'); 306 307 test(t => { 308 const div = createDiv(t); 309 div.classList.add('pseudo'); 310 div.style.display = 'list-item'; 311 const anim = div.animate(null, {pseudoElement: '::marker'}); 312 assert_equals(anim.effect.target, div, 'The returned element has the correct target element'); 313 assert_equals(anim.effect.pseudoElement, '::marker', 314 'The returned Animation targets the correct selector'); 315 }, 'animate() with pseudoElement an Animation object targeting ' + 316 'the correct pseudo-element for ::marker'); 317 318 test(t => { 319 const div = createDiv(t); 320 div.classList.add('pseudo'); 321 div.textContent = 'foo'; 322 const anim = div.animate(null, {pseudoElement: '::first-line'}); 323 assert_equals(anim.effect.target, div, 'The returned element has the correct target element'); 324 assert_equals(anim.effect.pseudoElement, '::first-line', 325 'The returned Animation targets the correct selector'); 326 }, 'animate() with pseudoElement an Animation object targeting ' + 327 'the correct pseudo-element for ::first-line'); 328 329 for (const pseudo of [ 330 '', 331 'before', 332 ':abc', 333 '::abc', 334 ]) { 335 test(t => { 336 const div = createDiv(t); 337 assert_throws_dom("SyntaxError", () => { 338 div.animate(null, {pseudoElement: pseudo}); 339 }); 340 }, `animate() with a non-null invalid pseudoElement '${pseudo}' throws a ` + 341 `SyntaxError`); 342 } 343 344 test(t => { 345 const div = createDiv(t); 346 div.animate(null, { pseudoElement: '::placeHOLDER' }); 347 }, `animate() with pseudoElement ::placeholder does not throw`); 348 349 promise_test(async t => { 350 const div = createDiv(t); 351 div.classList.add('pseudo'); 352 let animBefore = div.animate({opacity: [1, 0]}, {duration: 1, pseudoElement: '::before', fill: 'both'}); 353 let animAfter = div.animate({opacity: [1, 0]}, {duration: 1, pseudoElement: '::after', fill: 'both'}); 354 await animBefore.finished; 355 await animAfter.finished; 356 // The animation on ::before should not be replaced as it targets a different 357 // pseudo-element. 358 assert_equals(animBefore.replaceState, 'active'); 359 assert_equals(animAfter.replaceState, 'active'); 360 }, 'Finished fill animation doesn\'t replace animation on a different pseudoElement'); 361 362 </script> 363 </body>