From 1421a4a976d4d8263bde4864a1ce412eba106d39 Mon Sep 17 00:00:00 2001 From: Jake Archibald Date: Wed, 6 Jun 2018 08:25:17 +0100 Subject: [PATCH] Fetch: test identity encoding for range requests For https://github.com/whatwg/fetch/pull/751. --- fetch/range/general.any.js | 30 +++++++++++++ fetch/range/general.window.js | 29 ++++++++++++ fetch/range/partial-script.window.js | 7 --- fetch/range/resources/long-wav.py | 23 ++++++++-- fetch/range/resources/range-sw.js | 19 +++++++- fetch/range/resources/utils.js | 15 +++++++ fetch/range/sw.https.window.js | 67 +++++++++++++++++++--------- 7 files changed, 157 insertions(+), 33 deletions(-) create mode 100644 fetch/range/general.window.js delete mode 100644 fetch/range/partial-script.window.js diff --git a/fetch/range/general.any.js b/fetch/range/general.any.js index 2e49ed1e0f9c7d..656ac1ceea2511 100644 --- a/fetch/range/general.any.js +++ b/fetch/range/general.any.js @@ -1,3 +1,5 @@ +// META: script=/common/utils.js + // Helpers that return headers objects with a particular guard function headersGuardNone(fill) { if (fill) return new Headers(fill); @@ -58,3 +60,31 @@ test(() => { assert_false(headers.has('Range')); }, `Privileged header not allowed for guard type: request-no-cors`); +promise_test(async () => { + const wavURL = new URL('resources/long-wav.py', location); + const stashTakeURL = new URL('resources/stash-take.py', location); + + function changeToken() { + const stashToken = token(); + wavURL.searchParams.set('accept-encoding-key', stashToken); + stashTakeURL.searchParams.set('key', stashToken); + } + + const rangeHeaders = [ + 'bytes=0-10', + 'foo=0-10', + 'foo', + '' + ]; + + for (const rangeHeader of rangeHeaders) { + changeToken(); + + await fetch(wavURL, { + headers: { Range: rangeHeader } + }); + + const response = await fetch(stashTakeURL); + assert_equals(await response.json(), 'identity', `Expect identity accept-encoding if range header is ${JSON.stringify(rangeHeader)}`); + } +}, `Fetch with range header will be sent with Accept-Encoding: identity`); diff --git a/fetch/range/general.window.js b/fetch/range/general.window.js new file mode 100644 index 00000000000000..afe80d63a6b263 --- /dev/null +++ b/fetch/range/general.window.js @@ -0,0 +1,29 @@ +// META: script=resources/utils.js +// META: script=/common/utils.js + +const onload = new Promise(r => window.addEventListener('load', r)); + +// It's weird that browsers do this, but it should continue to work. +promise_test(async t => { + await loadScript('resources/partial-script.py?pretend-offset=90000'); + assert_true(self.scriptExecuted); +}, `Script executed from partial response`); + +promise_test(async () => { + const wavURL = new URL('resources/long-wav.py', location); + const stashTakeURL = new URL('resources/stash-take.py', location); + const stashToken = token(); + wavURL.searchParams.set('accept-encoding-key', stashToken); + stashTakeURL.searchParams.set('key', stashToken); + + // The testing framework waits for window onload. If the audio element + // is appended before onload, it extends it, and the test times out. + await onload; + + const audio = appendAudio(document, wavURL); + await new Promise(r => audio.addEventListener('progress', r)); + audio.remove(); + + const response = await fetch(stashTakeURL); + assert_equals(await response.json(), 'identity', `Expect identity accept-encoding on media request`); +}, `Fetch with range header will be sent with Accept-Encoding: identity`); diff --git a/fetch/range/partial-script.window.js b/fetch/range/partial-script.window.js deleted file mode 100644 index 36b4bba31aa8bc..00000000000000 --- a/fetch/range/partial-script.window.js +++ /dev/null @@ -1,7 +0,0 @@ -// META: script=resources/utils.js - -// It's weird that browsers do this, but it should continue to work. -promise_test(async t => { - await loadScript('resources/partial-script.py?pretend-offset=90000'); - assert_true(self.scriptExecuted); -}, `Script executed from partial response`); diff --git a/fetch/range/resources/long-wav.py b/fetch/range/resources/long-wav.py index d510767c309b1a..5df28eb741e670 100644 --- a/fetch/range/resources/long-wav.py +++ b/fetch/range/resources/long-wav.py @@ -50,11 +50,28 @@ def main(request, response): response.headers.set("Cache-Control", "no-cache") range_header = request.headers.get('Range', '') + range_header_match = range_header and re.search(r'^bytes=(\d*)-(\d*)$', range_header) range_received_key = request.GET.first('range-received-key', '') + accept_encoding_key = request.GET.first('accept-encoding-key', '') if range_received_key and range_header: + # Remove any current value + request.server.stash.take(range_received_key, '/fetch/range/') # This is later collected using stash-take.py - request.stash.put(range_received_key, 'range-header-received', '/fetch/range/') + request.server.stash.put(range_received_key, 'range-header-received', '/fetch/range/') + + if accept_encoding_key: + # Remove any current value + request.server.stash.take( + accept_encoding_key, + '/fetch/range/' + ) + # This is later collected using stash-take.py + request.server.stash.put( + accept_encoding_key, + request.headers.get('Accept-Encoding', ''), + '/fetch/range/' + ) # Audio details sample_rate = 8000 @@ -66,9 +83,9 @@ def main(request, response): bytes_remaining_to_send = total_length initial_write = '' - if range_header: + if range_header_match: response.status = 206 - start, end = re.search(r'^bytes=(\d*)-(\d*)$', range_header).groups() + start, end = range_header_match.groups() start = int(start) end = int(end) if end else 0 diff --git a/fetch/range/resources/range-sw.js b/fetch/range/resources/range-sw.js index 1dfee58e452aaa..3680c0c471d3d5 100644 --- a/fetch/range/resources/range-sw.js +++ b/fetch/range/resources/range-sw.js @@ -31,6 +31,9 @@ addEventListener('fetch', event => { case 'use-stored-ranged-response': useStoredRangeResponse(event); return; + case 'broadcast-accept-encoding': + broadcastAcceptEncoding(event); + return; } }); @@ -108,7 +111,7 @@ function rangeHeaderPassthroughTest(event) { promise_test(async () => { await fetch(event.request); const response = await fetch('stash-take.py?key=' + key); - assert_equals(await response.json(), '"range-header-received"'); + assert_equals(await response.json(), 'range-header-received'); resolve(); }, `Include range header in network request`); @@ -140,3 +143,17 @@ function useStoredRangeResponse(event) { return response.clone(); }()); } + +function broadcastAcceptEncoding(event) { + /** @type Request */ + const request = event.request; + const id = new URL(request.url).searchParams.get('id'); + + broadcast({ + id, + acceptEncoding: request.headers.get('Accept-Encoding') + }); + + // Just send back any response, it isn't important for the test. + event.respondWith(new Response('')); +} diff --git a/fetch/range/resources/utils.js b/fetch/range/resources/utils.js index 567f231d030aa5..16ed737f63e8ee 100644 --- a/fetch/range/resources/utils.js +++ b/fetch/range/resources/utils.js @@ -7,3 +7,18 @@ function loadScript(url, { doc = document }={}) { doc.body.appendChild(script); }) } + +/** + * + * @param {Document} document + * @param {string|URL} url + * @returns {HTMLAudioElement} + */ +function appendAudio(document, url) { + const audio = document.createElement('audio'); + audio.muted = true; + audio.src = url; + audio.preload = true; + document.body.appendChild(audio); + return audio; +} diff --git a/fetch/range/sw.https.window.js b/fetch/range/sw.https.window.js index aa3d2c478dd5b4..faaee86734e93b 100644 --- a/fetch/range/sw.https.window.js +++ b/fetch/range/sw.https.window.js @@ -4,28 +4,21 @@ // META: script=resources/utils.js const { REMOTE_HOST } = get_host_info(); -const SCOPE = 'resources/basic.html' + Math.random(); - -function appendAudio(document, url) { - const audio = document.createElement('audio'); - audio.muted = true; - audio.src = url; - audio.preload = true; - document.body.appendChild(audio); -} +const BASE_SCOPE = 'resources/basic.html?'; async function cleanup() { for (const iframe of document.querySelectorAll('.test-iframe')) { iframe.parentNode.removeChild(iframe); } - const reg = await navigator.serviceWorker.getRegistration(SCOPE); - if (reg) await reg.unregister(); + for (const reg of await navigator.serviceWorker.getRegistrations()) { + await reg.unregister(); + } } -async function setupRegistration(t) { +async function setupRegistration(t, scope) { await cleanup(); - const reg = await navigator.serviceWorker.register('resources/range-sw.js', { scope: SCOPE }); + const reg = await navigator.serviceWorker.register('resources/range-sw.js', { scope }); await wait_for_state(t, reg.installing, 'activated'); return reg; } @@ -35,14 +28,15 @@ function awaitMessage(obj, id) { obj.addEventListener('message', function listener(event) { if (event.data.id !== id) return; obj.removeEventListener('message', listener); - resolve(); + resolve(event.data); }); }); } promise_test(async t => { - const reg = await setupRegistration(t); - const iframe = await with_iframe(SCOPE); + 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 @@ -55,8 +49,9 @@ promise_test(async t => { }, `Defer range header filter tests to service worker`); promise_test(async t => { - const reg = await setupRegistration(t); - const iframe = await with_iframe(SCOPE); + 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 @@ -71,8 +66,9 @@ promise_test(async t => { }, `Defer range header passthrough tests to service worker`); promise_test(async t => { - await setupRegistration(t); - const iframe = await with_iframe(SCOPE); + 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); @@ -102,8 +98,9 @@ promise_test(async t => { }, `Ranged response not allowed following no-cors ranged request`); promise_test(async t => { - await setupRegistration(t); - const iframe = await with_iframe(SCOPE); + 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); @@ -126,3 +123,29 @@ promise_test(async t => { 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`);