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