sw.https.window.js (8427B)
1 // META: script=../../../service-workers/service-worker/resources/test-helpers.sub.js 2 // META: script=/common/utils.js 3 // META: script=/common/get-host-info.sub.js 4 // META: script=resources/utils.js 5 6 const { REMOTE_HOST } = get_host_info(); 7 const BASE_SCOPE = 'resources/basic.html?'; 8 9 async function cleanup() { 10 for (const iframe of document.querySelectorAll('.test-iframe')) { 11 iframe.parentNode.removeChild(iframe); 12 } 13 14 for (const reg of await navigator.serviceWorker.getRegistrations()) { 15 await reg.unregister(); 16 } 17 } 18 19 async function setupRegistration(t, scope) { 20 await cleanup(); 21 const reg = await navigator.serviceWorker.register('resources/range-sw.js', { scope }); 22 await wait_for_state(t, reg.installing, 'activated'); 23 return reg; 24 } 25 26 function awaitMessage(obj, id) { 27 return new Promise(resolve => { 28 obj.addEventListener('message', function listener(event) { 29 if (event.data.id !== id) return; 30 obj.removeEventListener('message', listener); 31 resolve(event.data); 32 }); 33 }); 34 } 35 36 promise_test(async t => { 37 const scope = BASE_SCOPE + Math.random(); 38 const reg = await setupRegistration(t, scope); 39 const iframe = await with_iframe(scope); 40 const w = iframe.contentWindow; 41 42 // Trigger a cross-origin range request using media 43 const url = new URL('long-wav.py?action=range-header-filter-test', w.location); 44 url.hostname = REMOTE_HOST; 45 appendAudio(w.document, url); 46 47 // See rangeHeaderFilterTest in resources/range-sw.js 48 await fetch_tests_from_worker(reg.active); 49 }, `Defer range header filter tests to service worker`); 50 51 promise_test(async t => { 52 const scope = BASE_SCOPE + Math.random(); 53 const reg = await setupRegistration(t, scope); 54 const iframe = await with_iframe(scope); 55 const w = iframe.contentWindow; 56 57 // Trigger a cross-origin range request using media 58 const url = new URL('long-wav.py', w.location); 59 url.searchParams.set('action', 'range-header-passthrough-test'); 60 url.searchParams.set('range-received-key', token()); 61 url.hostname = REMOTE_HOST; 62 appendAudio(w.document, url); 63 64 // See rangeHeaderPassthroughTest in resources/range-sw.js 65 await fetch_tests_from_worker(reg.active); 66 }, `Defer range header passthrough tests to service worker`); 67 68 promise_test(async t => { 69 const scope = BASE_SCOPE + Math.random(); 70 await setupRegistration(t, scope); 71 const iframe = await with_iframe(scope); 72 const w = iframe.contentWindow; 73 const id = Math.random() + ''; 74 const storedRangeResponse = awaitMessage(w.navigator.serviceWorker, id); 75 76 // Trigger a cross-origin range request using media 77 const url = new URL('partial-script.py', w.location); 78 url.searchParams.set('require-range', '1'); 79 url.searchParams.set('action', 'store-ranged-response'); 80 url.searchParams.set('id', id); 81 url.hostname = REMOTE_HOST; 82 83 appendAudio(w.document, url); 84 85 await storedRangeResponse; 86 87 // Fetching should reject 88 const fetchPromise = w.fetch('?action=use-stored-ranged-response', { mode: 'no-cors' }); 89 await promise_rejects_js(t, w.TypeError, fetchPromise); 90 91 // Script loading should error too 92 const loadScriptPromise = loadScript('?action=use-stored-ranged-response', { doc: w.document }); 93 await promise_rejects_js(t, Error, loadScriptPromise); 94 95 await loadScriptPromise.catch(() => {}); 96 97 assert_false(!!w.scriptExecuted, `Partial response shouldn't be executed`); 98 }, `Ranged response not allowed following no-cors ranged request`); 99 100 promise_test(async t => { 101 const scope = BASE_SCOPE + Math.random(); 102 await setupRegistration(t, scope); 103 const iframe = await with_iframe(scope); 104 const w = iframe.contentWindow; 105 const id = Math.random() + ''; 106 const storedRangeResponse = awaitMessage(w.navigator.serviceWorker, id); 107 108 // Trigger a range request using media 109 const url = new URL('partial-script.py', w.location); 110 url.searchParams.set('require-range', '1'); 111 url.searchParams.set('action', 'store-ranged-response'); 112 url.searchParams.set('id', id); 113 114 appendAudio(w.document, url); 115 116 await storedRangeResponse; 117 118 // This should not throw 119 await w.fetch('?action=use-stored-ranged-response'); 120 121 // This shouldn't throw either 122 await loadScript('?action=use-stored-ranged-response', { doc: w.document }); 123 124 assert_true(w.scriptExecuted, `Partial response should be executed`); 125 }, `Non-opaque ranged response executed`); 126 127 promise_test(async t => { 128 const scope = BASE_SCOPE + Math.random(); 129 await setupRegistration(t, scope); 130 const iframe = await with_iframe(scope); 131 const w = iframe.contentWindow; 132 const fetchId = Math.random() + ''; 133 const fetchBroadcast = awaitMessage(w.navigator.serviceWorker, fetchId); 134 const audioId = Math.random() + ''; 135 const audioBroadcast = awaitMessage(w.navigator.serviceWorker, audioId); 136 137 const url = new URL('long-wav.py', w.location); 138 url.searchParams.set('action', 'broadcast-accept-encoding'); 139 url.searchParams.set('id', fetchId); 140 141 await w.fetch(url, { 142 headers: { Range: 'bytes=0-10' } 143 }); 144 145 assert_equals((await fetchBroadcast).acceptEncoding, null, "Accept-Encoding should not be set for fetch"); 146 147 url.searchParams.set('id', audioId); 148 appendAudio(w.document, url); 149 150 assert_equals((await audioBroadcast).acceptEncoding, null, "Accept-Encoding should not be set for media"); 151 }, `Accept-Encoding should not appear in a service worker`); 152 153 promise_test(async t => { 154 const scope = BASE_SCOPE + Math.random(); 155 await setupRegistration(t, scope); 156 const iframe = await with_iframe(scope); 157 const w = iframe.contentWindow; 158 const length = 100; 159 const count = 3; 160 const counts = {}; 161 162 // test a single range request size 163 async function testSizedRange(size, partialResponseCode) { 164 const rangeId = Math.random() + ''; 165 const rangeBroadcast = awaitMessage(w.navigator.serviceWorker, rangeId); 166 167 // Create a bogus audio element to trick the browser into sending 168 // cross-origin range requests that can be manipulated by the service worker. 169 const sound_url = new URL('partial-text.py', w.location); 170 sound_url.hostname = REMOTE_HOST; 171 sound_url.searchParams.set('action', 'record-media-range-request'); 172 sound_url.searchParams.set('length', length); 173 sound_url.searchParams.set('size', size); 174 sound_url.searchParams.set('partial', partialResponseCode); 175 sound_url.searchParams.set('id', rangeId); 176 sound_url.searchParams.set('type', 'audio/mp4'); 177 appendAudio(w.document, sound_url); 178 179 // wait for the range requests to happen 180 await rangeBroadcast; 181 182 // Create multiple preload requests and count the number of resource timing 183 // entries that get created to make sure 206 and 416 range responses are treated 184 // the same. 185 const url = new URL('partial-text.py', w.location); 186 url.searchParams.set('action', 'use-media-range-request'); 187 url.searchParams.set('size', size); 188 url.searchParams.set('type', 'audio/mp4'); 189 counts['size' + size] = 0; 190 for (let i = 0; i < count; i++) { 191 await preloadImage(url, { doc: w.document }); 192 } 193 } 194 195 // Test range requests from 1 smaller than the correct size to 1 larger than 196 // the correct size to exercise the various permutations using the default 206 197 // response code for successful range requests. 198 for (let size = length - 1; size <= length + 1; size++) { 199 await testSizedRange(size, '206'); 200 } 201 202 // Test a successful range request using a 200 response. 203 await testSizedRange(length - 2, '200'); 204 205 // Check the resource timing entries and count the reported number of fetches of each type 206 const resources = w.performance.getEntriesByType("resource"); 207 for (const entry of resources) { 208 const url = new URL(entry.name); 209 if (url.searchParams.has('action') && 210 url.searchParams.get('action') == 'use-media-range-request' && 211 url.searchParams.has('size')) { 212 counts['size' + url.searchParams.get('size')]++; 213 } 214 } 215 216 // Make sure there are a non-zero number of preload requests and they are all the same 217 let counts_valid = true; 218 const first = 'size' + (length - 2); 219 for (let size = length - 2; size <= length + 1; size++) { 220 let key = 'size' + size; 221 if (!(key in counts) || counts[key] <= 0 || counts[key] != counts[first]) { 222 counts_valid = false; 223 break; 224 } 225 } 226 227 assert_true(counts_valid, `Opaque range request preloads were different for error and success`); 228 }, `Opaque range preload successes and failures should be indistinguishable`);