event-order.tentative.html (8226B)
1 <!doctype html> 2 <meta charset=utf-8> 3 <title>Tests for CSS animation event order</title> 4 <link rel="help" href="https://drafts.csswg.org/css-animations-2/#event-dispatch"/> 5 <script src="/resources/testharness.js"></script> 6 <script src="/resources/testharnessreport.js"></script> 7 <script src="support/testcommon.js"></script> 8 <style> 9 @keyframes anim { 10 from { margin-left: 0px; } 11 to { margin-left: 100px; } 12 } 13 @keyframes color-anim { 14 from { color: red; } 15 to { color: green; } 16 } 17 </style> 18 <div id="log"></div> 19 <script type='text/javascript'> 20 'use strict'; 21 22 /** 23 * Asserts that the set of actual and received events match. 24 * @param actualEvents An array of the received AnimationEvent objects. 25 * @param expectedEvents A series of array objects representing the expected 26 * events, each having the form: 27 * [ event type, target element, [pseudo type], elapsed time ] 28 */ 29 const checkEvents = (actualEvents, ...expectedEvents) => { 30 const actualTypeSummary = actualEvents.map(event => event.type).join(', '); 31 const expectedTypeSummary = expectedEvents.map(event => event[0]).join(', '); 32 33 assert_equals( 34 actualEvents.length, 35 expectedEvents.length, 36 `Number of events received (${actualEvents.length}) \ 37 should match expected number (${expectedEvents.length}) \ 38 (expected: ${expectedTypeSummary}, actual: ${actualTypeSummary})` 39 ); 40 41 for (const [index, actualEvent] of actualEvents.entries()) { 42 const expectedEvent = expectedEvents[index]; 43 const [type, target] = expectedEvent; 44 const pseudoElement = expectedEvent.length === 4 ? expectedEvent[2] : ''; 45 const elapsedTime = expectedEvent[expectedEvent.length - 1]; 46 47 assert_equals( 48 actualEvent.type, 49 type, 50 `Event #${index + 1} types should match \ 51 (expected: ${expectedTypeSummary}, actual: ${actualTypeSummary})` 52 ); 53 assert_equals( 54 actualEvent.target, 55 target, 56 `Event #${index + 1} targets should match` 57 ); 58 assert_equals( 59 actualEvent.pseudoElement, 60 pseudoElement, 61 `Event #${index + 1} pseudoElements should match` 62 ); 63 assert_equals( 64 actualEvent.elapsedTime, 65 elapsedTime, 66 `Event #${index + 1} elapsedTimes should match` 67 ); 68 } 69 }; 70 71 const setupAnimation = (t, animationStyle, receiveEvents) => { 72 const div = addDiv(t, { style: 'animation: ' + animationStyle }); 73 74 for (const name of ['start', 'iteration', 'end']) { 75 div['onanimation' + name] = evt => { 76 receiveEvents.push({ 77 type: evt.type, 78 target: evt.target, 79 pseudoElement: evt.pseudoElement, 80 elapsedTime: evt.elapsedTime, 81 }); 82 }; 83 } 84 85 const watcher = new EventWatcher(t, div, [ 86 'animationstart', 87 'animationiteration', 88 'animationend', 89 ]); 90 91 const animation = div.getAnimations()[0]; 92 93 return [animation, watcher, div]; 94 }; 95 96 promise_test(async t => { 97 let events = []; 98 const [animation1, watcher1, div1] = 99 setupAnimation(t, 'anim 100s 2 paused', events); 100 const [animation2, watcher2, div2] = 101 setupAnimation(t, 'anim 100s 2 paused', events); 102 103 await Promise.all([ watcher1.wait_for('animationstart'), 104 watcher2.wait_for('animationstart') ]); 105 106 checkEvents(events, ['animationstart', div1, 0], 107 ['animationstart', div2, 0]); 108 109 events.length = 0; // Clear received event array 110 111 animation1.currentTime = 100 * MS_PER_SEC; 112 animation2.currentTime = 100 * MS_PER_SEC; 113 114 await Promise.all([ watcher1.wait_for('animationiteration'), 115 watcher2.wait_for('animationiteration') ]); 116 117 checkEvents(events, ['animationiteration', div1, 100], 118 ['animationiteration', div2, 100]); 119 120 events.length = 0; // Clear received event array 121 122 animation1.finish(); 123 animation2.finish(); 124 125 await Promise.all([ watcher1.wait_for('animationend'), 126 watcher2.wait_for('animationend') ]); 127 128 checkEvents(events, ['animationend', div1, 200], 129 ['animationend', div2, 200]); 130 }, 'Same events are ordered by elements'); 131 132 function pseudoTest(description, testMarkerPseudos) { 133 promise_test(async t => { 134 // Setup a hierarchy as follows: 135 // 136 // parent 137 // | 138 // (::marker, ::before, ::after) // ::marker optional 139 // | 140 // child 141 const parentDiv = addDiv(t, { style: 'animation: anim 100s' }); 142 143 parentDiv.id = 'parent-div'; 144 addStyle(t, { 145 '#parent-div::after': "content: ''; animation: anim 100s", 146 '#parent-div::before': "content: ''; animation: anim 100s", 147 }); 148 149 if (testMarkerPseudos) { 150 parentDiv.style.display = 'list-item'; 151 addStyle(t, { 152 '#parent-div::marker': "content: ''; animation: color-anim 100s", 153 }); 154 } 155 156 const childDiv = addDiv(t, { style: 'animation: anim 100s' }); 157 parentDiv.append(childDiv); 158 159 // Setup event handlers 160 let events = []; 161 for (const name of ['start', 'iteration', 'end', 'cancel']) { 162 parentDiv['onanimation' + name] = evt => { 163 events.push({ 164 type: evt.type, 165 target: evt.target, 166 pseudoElement: evt.pseudoElement, 167 elapsedTime: evt.elapsedTime, 168 }); 169 }; 170 } 171 172 // Wait a couple of frames for the events to be dispatched 173 await waitForFrame(); 174 await waitForFrame(); 175 176 const expectedEvents = [ 177 ['animationstart', parentDiv, 0], 178 ['animationstart', parentDiv, '::marker', 0], 179 ['animationstart', parentDiv, '::before', 0], 180 ['animationstart', parentDiv, '::after', 0], 181 ['animationstart', childDiv, 0], 182 ]; 183 if (!testMarkerPseudos) { 184 expectedEvents.splice(1, 1); 185 } 186 187 checkEvents(events, ...expectedEvents); 188 }, description); 189 } 190 191 pseudoTest('Same events on pseudo-elements follow the prescribed order', false); 192 pseudoTest('Same events on pseudo-elements follow the prescribed order ' + 193 '(::marker)', true); 194 195 promise_test(async t => { 196 let events = []; 197 const [animation1, watcher1, div1] = 198 setupAnimation(t, 'anim 200s 400s', events); 199 const [animation2, watcher2, div2] = 200 setupAnimation(t, 'anim 300s 2', events); 201 202 await watcher2.wait_for('animationstart'); 203 204 animation1.currentTime = 400 * MS_PER_SEC; 205 animation2.currentTime = 400 * MS_PER_SEC; 206 207 events.length = 0; // Clear received event array 208 209 await Promise.all([ watcher1.wait_for('animationstart'), 210 watcher2.wait_for('animationiteration') ]); 211 212 checkEvents(events, ['animationiteration', div2, 300], 213 ['animationstart', div1, 0]); 214 }, 'Start and iteration events are ordered by time'); 215 216 promise_test(async t => { 217 let events = []; 218 const [animation1, watcher1, div1] = 219 setupAnimation(t, 'anim 150s', events); 220 const [animation2, watcher2, div2] = 221 setupAnimation(t, 'anim 100s 2', events); 222 223 await Promise.all([ watcher1.wait_for('animationstart'), 224 watcher2.wait_for('animationstart') ]); 225 226 animation1.currentTime = 150 * MS_PER_SEC; 227 animation2.currentTime = 150 * MS_PER_SEC; 228 229 events.length = 0; // Clear received event array 230 231 await Promise.all([ watcher1.wait_for('animationend'), 232 watcher2.wait_for('animationiteration') ]); 233 234 checkEvents(events, ['animationiteration', div2, 100], 235 ['animationend', div1, 150]); 236 }, 'Iteration and end events are ordered by time'); 237 238 promise_test(async t => { 239 let events = []; 240 const [animation1, watcher1, div1] = 241 setupAnimation(t, 'anim 100s 100s', events); 242 const [animation2, watcher2, div2] = 243 setupAnimation(t, 'anim 100s 2', events); 244 245 animation1.finish(); 246 animation2.finish(); 247 248 await Promise.all([ watcher1.wait_for([ 'animationstart', 249 'animationend' ]), 250 watcher2.wait_for([ 'animationstart', 251 'animationend' ]) ]); 252 253 checkEvents(events, ['animationstart', div2, 0], 254 ['animationstart', div1, 0], 255 ['animationend', div1, 100], 256 ['animationend', div2, 200]); 257 }, 'Start and end events are sorted correctly when fired simultaneously'); 258 259 </script>