clients-matchall-order.https.html (12103B)
1 <!DOCTYPE html> 2 <title>Service Worker: Clients.matchAll ordering</title> 3 <meta name=timeout content=long> 4 <script src="/resources/testharness.js"></script> 5 <script src="/resources/testharnessreport.js"></script> 6 <script src="resources/test-helpers.sub.js"></script> 7 <script> 8 9 // Utility function for URLs this test will open. 10 function makeURL(name, num, type) { 11 let u = new URL('resources/empty.html', location); 12 u.searchParams.set('name', name); 13 if (num !== undefined) { 14 u.searchParams.set('q', num); 15 } 16 if (type === 'nested') { 17 u.searchParams.set('nested', true); 18 } 19 return u.href; 20 } 21 22 // Non-test URLs that will be open during each test. The harness URLs 23 // are from the WPT harness. The "extra" URL is a final window opened 24 // by the test. 25 const EXTRA_URL = makeURL('extra'); 26 const TEST_HARNESS_URL = location.href; 27 const TOP_HARNESS_URL = new URL('/testharness_runner.html', location).href; 28 29 // Utility function to open an iframe in the target parent window. We 30 // can't just use with_iframe() because it does not support a configurable 31 // parent window. 32 function openFrame(parentWindow, url) { 33 return new Promise(resolve => { 34 let frame = parentWindow.document.createElement('iframe'); 35 frame.src = url; 36 parentWindow.document.body.appendChild(frame); 37 38 frame.contentWindow.addEventListener('load', evt => { 39 resolve(frame); 40 }, { once: true }); 41 }); 42 } 43 44 // Utility function to open a window and wait for it to load. The 45 // window may optionally have a nested iframe as well. Returns 46 // a result like `{ top: <frame ref> nested: <nested frame ref if present> }`. 47 function openFrameConfig(opts) { 48 let url = new URL(opts.url, location.href); 49 return openFrame(window, url.href).then(top => { 50 if (!opts.withNested) { 51 return { top: top }; 52 } 53 54 url.searchParams.set('nested', true); 55 return openFrame(top.contentWindow, url.href).then(nested => { 56 return { top: top, nested: nested }; 57 }); 58 }); 59 } 60 61 // Utility function that takes a list of configurations and opens the 62 // corresponding windows in sequence. An array of results is returned. 63 function openFrameConfigList(optList) { 64 let resultList = []; 65 function openNextWindow(optList, nextWindow) { 66 if (nextWindow >= optList.length) { 67 return resultList; 68 } 69 return openFrameConfig(optList[nextWindow]).then(result => { 70 resultList.push(result); 71 return openNextWindow(optList, nextWindow + 1); 72 }); 73 } 74 return openNextWindow(optList, 0); 75 } 76 77 // Utility function that focuses the given entry in window result list. 78 function executeFocus(frameResultList, opts) { 79 return new Promise(resolve => { 80 let w = frameResultList[opts.index][opts.type]; 81 let target = w.contentWindow ? w.contentWindow : w; 82 target.addEventListener('focus', evt => { 83 resolve(); 84 }, { once: true }); 85 target.focus(); 86 }); 87 } 88 89 // Utility function that performs a list of focus commands in sequence 90 // based on the window result list. 91 function executeFocusList(frameResultList, optList) { 92 function executeNextCommand(frameResultList, optList, nextCommand) { 93 if (nextCommand >= optList.length) { 94 return; 95 } 96 return executeFocus(frameResultList, optList[nextCommand]).then(_ => { 97 return executeNextCommand(frameResultList, optList, nextCommand + 1); 98 }); 99 } 100 return executeNextCommand(frameResultList, optList, 0); 101 } 102 103 // Perform a `clients.matchAll()` in the service worker with the given 104 // options dictionary. 105 function doMatchAll(worker, options) { 106 return new Promise(resolve => { 107 let channel = new MessageChannel(); 108 channel.port1.onmessage = evt => { 109 resolve(evt.data); 110 }; 111 worker.postMessage({ port: channel.port2, options: options, disableSort: true }, 112 [channel.port2]); 113 }); 114 } 115 116 // Function that performs a single test case. It takes a configuration object 117 // describing the windows to open, how to focus them, the matchAll options, 118 // and the resulting expectations. See the test cases for examples of how to 119 // use this. 120 function matchAllOrderTest(t, opts) { 121 let script = 'resources/clients-matchall-worker.js'; 122 let worker; 123 let frameResultList; 124 let extraWindowResult; 125 return service_worker_unregister_and_register(t, script, opts.scope).then(swr => { 126 t.add_cleanup(() => service_worker_unregister(t, opts.scope)); 127 128 worker = swr.installing; 129 return wait_for_state(t, worker, 'activated'); 130 }).then(_ => { 131 return openFrameConfigList(opts.frameConfigList); 132 }).then(results => { 133 frameResultList = results; 134 return openFrameConfig({ url: EXTRA_URL }); 135 }).then(result => { 136 extraWindowResult = result; 137 return executeFocusList(frameResultList, opts.focusConfigList); 138 }).then(_ => { 139 return doMatchAll(worker, opts.matchAllOptions); 140 }).then(data => { 141 assert_equals(data.length, opts.expected.length); 142 for (let i = 0; i < data.length; ++i) { 143 assert_equals(data[i][2], opts.expected[i], 'expected URL index ' + i); 144 } 145 }).then(_ => { 146 frameResultList.forEach(result => result.top.remove()); 147 extraWindowResult.top.remove(); 148 }).catch(e => { 149 if (frameResultList) { 150 frameResultList.forEach(result => result.top.remove()); 151 } 152 if (extraWindowResult) { 153 extraWindowResult.top.remove(); 154 } 155 throw(e); 156 }); 157 } 158 159 // ---------- 160 // Test cases 161 // ---------- 162 163 promise_test(t => { 164 let name = 'no-focus-controlled-windows'; 165 let opts = { 166 scope: makeURL(name), 167 168 frameConfigList: [ 169 { url: makeURL(name, 0), withNested: false }, 170 { url: makeURL(name, 1), withNested: false }, 171 { url: makeURL(name, 2), withNested: false }, 172 ], 173 174 focusConfigList: [ 175 // no focus commands 176 ], 177 178 matchAllOptions: { 179 includeUncontrolled: false 180 }, 181 182 expected: [ 183 makeURL(name, 0), 184 makeURL(name, 1), 185 makeURL(name, 2), 186 ], 187 }; 188 189 return matchAllOrderTest(t, opts); 190 }, 'Clients.matchAll() returns non-focused controlled windows in creation order.'); 191 192 promise_test(t => { 193 let name = 'focus-controlled-windows-1'; 194 let opts = { 195 scope: makeURL(name), 196 197 frameConfigList: [ 198 { url: makeURL(name, 0), withNested: false }, 199 { url: makeURL(name, 1), withNested: false }, 200 { url: makeURL(name, 2), withNested: false }, 201 ], 202 203 focusConfigList: [ 204 { index: 0, type: 'top' }, 205 { index: 1, type: 'top' }, 206 { index: 2, type: 'top' }, 207 ], 208 209 matchAllOptions: { 210 includeUncontrolled: false 211 }, 212 213 expected: [ 214 makeURL(name, 2), 215 makeURL(name, 1), 216 makeURL(name, 0), 217 ], 218 }; 219 220 return matchAllOrderTest(t, opts); 221 }, 'Clients.matchAll() returns controlled windows in focus order. Case 1.'); 222 223 promise_test(t => { 224 let name = 'focus-controlled-windows-2'; 225 let opts = { 226 scope: makeURL(name), 227 228 frameConfigList: [ 229 { url: makeURL(name, 0), withNested: false }, 230 { url: makeURL(name, 1), withNested: false }, 231 { url: makeURL(name, 2), withNested: false }, 232 ], 233 234 focusConfigList: [ 235 { index: 2, type: 'top' }, 236 { index: 1, type: 'top' }, 237 { index: 0, type: 'top' }, 238 ], 239 240 matchAllOptions: { 241 includeUncontrolled: false 242 }, 243 244 expected: [ 245 makeURL(name, 0), 246 makeURL(name, 1), 247 makeURL(name, 2), 248 ], 249 }; 250 251 return matchAllOrderTest(t, opts); 252 }, 'Clients.matchAll() returns controlled windows in focus order. Case 2.'); 253 254 promise_test(t => { 255 let name = 'no-focus-uncontrolled-windows'; 256 let opts = { 257 scope: makeURL(name + '-outofscope'), 258 259 frameConfigList: [ 260 { url: makeURL(name, 0), withNested: false }, 261 { url: makeURL(name, 1), withNested: false }, 262 { url: makeURL(name, 2), withNested: false }, 263 ], 264 265 focusConfigList: [ 266 // no focus commands 267 ], 268 269 matchAllOptions: { 270 includeUncontrolled: true 271 }, 272 273 expected: [ 274 // The harness windows have been focused, so appear first 275 TEST_HARNESS_URL, 276 TOP_HARNESS_URL, 277 278 // Test frames have not been focused, so appear in creation order 279 makeURL(name, 0), 280 makeURL(name, 1), 281 makeURL(name, 2), 282 EXTRA_URL, 283 ], 284 }; 285 286 return matchAllOrderTest(t, opts); 287 }, 'Clients.matchAll() returns non-focused uncontrolled windows in creation order.'); 288 289 promise_test(t => { 290 let name = 'focus-uncontrolled-windows-1'; 291 let opts = { 292 scope: makeURL(name + '-outofscope'), 293 294 frameConfigList: [ 295 { url: makeURL(name, 0), withNested: false }, 296 { url: makeURL(name, 1), withNested: false }, 297 { url: makeURL(name, 2), withNested: false }, 298 ], 299 300 focusConfigList: [ 301 { index: 0, type: 'top' }, 302 { index: 1, type: 'top' }, 303 { index: 2, type: 'top' }, 304 ], 305 306 matchAllOptions: { 307 includeUncontrolled: true 308 }, 309 310 expected: [ 311 // The test harness window is a parent of all test frames. It will 312 // always have the same focus time or later as its frames. So it 313 // appears first. 314 TEST_HARNESS_URL, 315 316 makeURL(name, 2), 317 makeURL(name, 1), 318 makeURL(name, 0), 319 320 // The overall harness has been focused 321 TOP_HARNESS_URL, 322 323 // The extra frame was never focused 324 EXTRA_URL, 325 ], 326 }; 327 328 return matchAllOrderTest(t, opts); 329 }, 'Clients.matchAll() returns uncontrolled windows in focus order. Case 1.'); 330 331 promise_test(t => { 332 let name = 'focus-uncontrolled-windows-2'; 333 let opts = { 334 scope: makeURL(name + '-outofscope'), 335 336 frameConfigList: [ 337 { url: makeURL(name, 0), withNested: false }, 338 { url: makeURL(name, 1), withNested: false }, 339 { url: makeURL(name, 2), withNested: false }, 340 ], 341 342 focusConfigList: [ 343 { index: 2, type: 'top' }, 344 { index: 1, type: 'top' }, 345 { index: 0, type: 'top' }, 346 ], 347 348 matchAllOptions: { 349 includeUncontrolled: true 350 }, 351 352 expected: [ 353 // The test harness window is a parent of all test frames. It will 354 // always have the same focus time or later as its frames. So it 355 // appears first. 356 TEST_HARNESS_URL, 357 358 makeURL(name, 0), 359 makeURL(name, 1), 360 makeURL(name, 2), 361 362 // The overall harness has been focused 363 TOP_HARNESS_URL, 364 365 // The extra frame was never focused 366 EXTRA_URL, 367 ], 368 }; 369 370 return matchAllOrderTest(t, opts); 371 }, 'Clients.matchAll() returns uncontrolled windows in focus order. Case 2.'); 372 373 promise_test(t => { 374 let name = 'focus-controlled-nested-windows'; 375 let opts = { 376 scope: makeURL(name), 377 378 frameConfigList: [ 379 { url: makeURL(name, 0), withNested: true }, 380 { url: makeURL(name, 1), withNested: true }, 381 { url: makeURL(name, 2), withNested: true }, 382 ], 383 384 focusConfigList: [ 385 { index: 0, type: 'top' }, 386 387 // Note, some browsers don't let programmatic focus of a frame unless 388 // an ancestor window is already focused. So focus the window and 389 // then the frame. 390 { index: 1, type: 'top' }, 391 { index: 1, type: 'nested' }, 392 393 { index: 2, type: 'top' }, 394 ], 395 396 matchAllOptions: { 397 includeUncontrolled: false 398 }, 399 400 expected: [ 401 // Focus order for window 2, but not its frame. We only focused 402 // the window. 403 makeURL(name, 2), 404 405 // Window 1 is next via focus order, but the window is always 406 // shown first here. The window gets its last focus time updated 407 // when the frame is focused. Since the times match between the 408 // two it falls back to creation order. The window was created 409 // before the frame. This behavior is being discussed in: 410 // https://github.com/w3c/ServiceWorker/issues/1080 411 makeURL(name, 1), 412 makeURL(name, 1, 'nested'), 413 414 // Focus order for window 0, but not its frame. We only focused 415 // the window. 416 makeURL(name, 0), 417 418 // Creation order of the frames since they are not focused by 419 // default when they are created. 420 makeURL(name, 0, 'nested'), 421 makeURL(name, 2, 'nested'), 422 ], 423 }; 424 425 return matchAllOrderTest(t, opts); 426 }, 'Clients.matchAll() returns controlled windows and frames in focus order.'); 427 </script>