Skip to content

Commit

Permalink
Test cases where request body gets used before network fallback (#27325)
Browse files Browse the repository at this point in the history
Tests for whatwg/fetch#1144 and w3c/ServiceWorker#1563.

Co-authored-by: Yutaka Hirano <yhirano@chrmomium.org>
Co-authored-by: Anne van Kesteren <annevk@annevk.nl>
  • Loading branch information
3 people committed Feb 18, 2021
1 parent 8602e9c commit c30545d
Show file tree
Hide file tree
Showing 2 changed files with 192 additions and 0 deletions.
178 changes: 178 additions & 0 deletions service-workers/service-worker/fetch-event.https.html
Expand Up @@ -480,6 +480,137 @@
});
}, 'FetchEvent#body is a string and is passed to network fallback');

// Test that the request body is sent to network upon network fallback,
// for a ReadableStream body.
promise_test(async t => {
const rs = new ReadableStream({start(c) {
c.enqueue('i a');
c.enqueue('m the request');
t.step_timeout(t.step_func(() => {
c.enqueue(' body');
c.close();
}, 10));
}});
// Set page_url to "?ignore" so the service worker falls back to network
// for the main resource request, and add a suffix to avoid colliding
// with other tests.
const page_url = 'resources/?ignore-for-request-body-fallback-string';
const frame = await with_iframe(page_url);
t.add_cleanup(() => { frame.remove(); });
// Add "?ignore" so the service worker falls back to echo-content.py.
const echo_url = '/fetch/api/resources/echo-content.py?ignore';
const response = await frame.contentWindow.fetch(echo_url, {
method: 'POST',
body: rs
});
const text = await response.text();
assert_equals(text,
'i am the request body',
'the network fallback request should include the request body');
}, 'FetchEvent#body is a ReadableStream and is passed to network fallback');

// Test that the request body is sent to network upon network fallback even when
// the request body is used in the service worker, for a string body.
promise_test(async t => {
// Set page_url to "?ignore" so the service worker falls back to network
// for the main resource request, and add a suffix to avoid colliding
// with other tests.
const page_url = 'resources/?ignore-for-request-body-fallback-string';

const frame = await with_iframe(page_url);
t.add_cleanup(() => { frame.remove(); });
// Add "?use-and-ignore" so the service worker falls back to echo-content.py.
const echo_url = '/fetch/api/resources/echo-content.py?use-and-ignore';
const response = await frame.contentWindow.fetch(echo_url, {
method: 'POST',
body: 'i am the request body'
});
const text = await response.text();
assert_equals(
text,
'i am the request body',
'the network fallback request should include the request body');
}, 'FetchEvent#body is a string, used and passed to network fallback');

// When the streaming body is used in the service worker, network fallback
// fails.
promise_test(async t => {
const rs = new ReadableStream({start(c) {
c.enqueue('i a');
c.enqueue('m the request');
t.step_timeout(t.step_func(() => {
c.enqueue(' body');
c.close();
}, 10));
}});
// Set page_url to "?ignore" so the service worker falls back to network
// for the main resource request, and add a suffix to avoid colliding
// with other tests.
const page_url = 'resources/?ignore-for-request-body-fallback-string';
const frame = await with_iframe(page_url);
t.add_cleanup(() => { frame.remove(); });
const echo_url = '/fetch/api/resources/echo-content.py?use-and-ignore';
await promise_rejects_js(t, TypeError, frame.contentWindow.fetch(echo_url, {
method: 'POST',
body: rs
});
}, 'FetchEvent#body is a ReadableStream, used and passed to network fallback');

// Test that the request body is sent to network upon network fallback even when
// the request body is used by clone() in the service worker, for a string body.
promise_test(async t => {
// Set page_url to "?ignore" so the service worker falls back to network
// for the main resource request, and add a suffix to avoid colliding
// with other tests.
const page_url = 'resources/?ignore-for-request-body-fallback-string';

const frame = await with_iframe(page_url);
t.add_cleanup(() => { frame.remove(); });
// Add "?clone-and-ignore" so the service worker falls back to
// echo-content.py.
const echo_url = '/fetch/api/resources/echo-content.py?clone-and-ignore';
const response = await frame.contentWindow.fetch(echo_url, {
method: 'POST',
body: 'i am the request body'
});
const text = await response.text();
assert_equals(
text,
'i am the request body',
'the network fallback request should include the request body');
}, 'FetchEvent#body is a string, cloned and passed to network fallback');

// When the streaming body is used by clone() in the service worker, network
// fallback fails.
promise_test(async t => {
const rs = new ReadableStream({start(c) {
c.enqueue('i a');
c.enqueue('m the request');
t.step_timeout(t.step_func(() => {
c.enqueue(' body');
c.close();
}, 10));
}});
// Set page_url to "?ignore" so the service worker falls back to network
// for the main resource request, and add a suffix to avoid colliding
// with other tests.
const page_url = 'resources/?ignore-for-request-body-fallback-string';
const frame = await with_iframe(page_url);
t.add_cleanup(() => { frame.remove(); });
// Add "?clone-and-ignore" so the service worker falls back to
// echo-content.py.
const echo_url = '/fetch/api/resources/echo-content.py?clone-and-ignore';
const response = await frame.contentWindow.fetch(echo_url, {
method: 'POST',
body: 'i am the request body'
});
const text = await response.text();
assert_equals(
text,
'i am the request body',
'the network fallback request should include the request body');
}, 'FetchEvent#body is a ReadableStream, cloned and passed to network fallback');

// Test that the service worker can read FetchEvent#body when it is a blob.
// It responds with request body it read.
promise_test(t => {
Expand Down Expand Up @@ -851,5 +982,52 @@
assert_equals(frame.contentDocument.body.textContent,
'method = POST, isHistoryNavigation = true');
}, 'FetchEvent#request.isHistoryNavigation is true (POST + history.go(-1))');

// When service worker responds with a Response, no XHR upload progress
// events are delivered.
promise_test(async t => {
const page_url = 'resources/simple.html?ignore-for-request-body-string';
const frame = await with_iframe(page_url);
t.add_cleanup(() => { frame.remove(); });

const xhr = new frame.contentWindow.XMLHttpRequest();
xhr.open('POST', 'simple.html?request-body');
xhr.upload.addEventListener('progress', t.unreached_func('progress'));
xhr.upload.addEventListener('error', t.unreached_func('error'));
xhr.upload.addEventListener('abort', t.unreached_func('abort'));
xhr.upload.addEventListener('timeout', t.unreached_func('timeout'));
xhr.upload.addEventListener('load', t.unreached_func('load'));
xhr.upload.addEventListener('loadend', t.unreached_func('loadend'));
xhr.send('i am the request body');

await new Promise((resolve) => xhr.addEventListener('load', resolve));
}, 'XHR upload progress events for response coming from SW');

// Upload progress events should be delivered for the network fallback case.
promise_test(async t => {
const page_url = 'resources/simple.html?ignore-for-request-body-string';
const frame = await with_iframe(page_url);
t.add_cleanup(() => { frame.remove(); });

let progress = false;
let load = false;
let loadend = false;

const xhr = new frame.contentWindow.XMLHttpRequest();
xhr.open('POST', '/fetch/api/resources/echo-content.py?ignore');
xhr.upload.addEventListener('progress', () => progress = true);
xhr.upload.addEventListener('error', t.unreached_func('error'));
xhr.upload.addEventListener('abort', t.unreached_func('abort'));
xhr.upload.addEventListener('timeout', t.unreached_func('timeout'));
xhr.upload.addEventListener('load', () => load = true);
xhr.upload.addEventListener('loadend', () => loadend = true);
xhr.send('i am the request body');

await new Promise((resolve) => xhr.addEventListener('load', resolve));
assert_true(progress, 'progress');
assert_true(load, 'load');
assert_true(loadend, 'loadend');
}, 'XHR upload progress events for network fallback');

</script>
</body>
Expand Up @@ -155,6 +155,18 @@ function handleIsHistoryNavigation(event) {
event.respondWith(new Response(body));
}

function handleUseAndIgnore(event) {
const request = event.request;
request.text();
return;
}

function handleCloneAndIgnore(event) {
const request = event.request;
request.clone().text();
return;
}

self.addEventListener('fetch', function(event) {
var url = event.request.url;
var handlers = [
Expand All @@ -180,6 +192,8 @@ self.addEventListener('fetch', function(event) {
{ pattern: '?keepalive', fn: handleKeepalive },
{ pattern: '?isReloadNavigation', fn: handleIsReloadNavigation },
{ pattern: '?isHistoryNavigation', fn: handleIsHistoryNavigation },
{ pattern: '?use-and-ignore', fn: handleUseAndIgnore },
{ pattern: '?clone-and-ignore', fn: handleCloneAndIgnore },
];

var handler = null;
Expand Down

0 comments on commit c30545d

Please sign in to comment.