diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /testing/web-platform/tests/fetch/fetch-later | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/fetch/fetch-later')
26 files changed, 975 insertions, 0 deletions
diff --git a/testing/web-platform/tests/fetch/fetch-later/META.yml b/testing/web-platform/tests/fetch/fetch-later/META.yml new file mode 100644 index 0000000000..f8fd46bec3 --- /dev/null +++ b/testing/web-platform/tests/fetch/fetch-later/META.yml @@ -0,0 +1,3 @@ +spec: https://whatpr.org/fetch/1647/094ea69...152d725.html#fetch-later-method +suggested_reviewers: + - mingyc diff --git a/testing/web-platform/tests/fetch/fetch-later/README.md b/testing/web-platform/tests/fetch/fetch-later/README.md new file mode 100644 index 0000000000..661e2b9184 --- /dev/null +++ b/testing/web-platform/tests/fetch/fetch-later/README.md @@ -0,0 +1,3 @@ +# FetchLater Tests + +These tests cover [FetchLater method](https://whatpr.org/fetch/1647/094ea69...152d725.html#fetch-later-method) related behaviors. diff --git a/testing/web-platform/tests/fetch/fetch-later/activate-after.tentative.https.window.js b/testing/web-platform/tests/fetch/fetch-later/activate-after.tentative.https.window.js new file mode 100644 index 0000000000..18b368066b --- /dev/null +++ b/testing/web-platform/tests/fetch/fetch-later/activate-after.tentative.https.window.js @@ -0,0 +1,53 @@ +// META: script=/common/dispatcher/dispatcher.js +// META: script=/common/get-host-info.sub.js +// META: script=/common/utils.js +// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js +// META: script=/html/browsers/browsing-the-web/back-forward-cache/resources/rc-helper.js +// META: script=/pending-beacon/resources/pending_beacon-helper.js + +'use strict'; + +parallelPromiseTest(async t => { + const uuid = token(); + const url = generateSetBeaconURL(uuid); + + // Loads an iframe that creates a fetchLater request w/ short timeout. + const iframe = await loadScriptAsIframe(` + fetchLater("${url}", {activateAfter: 1000}); // 1s + `); + // Deletes the iframe to trigger deferred request sending. + document.body.removeChild(iframe); + + // The iframe should have sent all requests. + await expectBeacon(uuid, {count: 1}); +}, 'fetchLater() sends out based on activateAfter.'); + +parallelPromiseTest(async t => { + const uuid = token(); + const url = generateSetBeaconURL(uuid); + // Sets no option to test the default behavior when a document enters BFCache. + const helper = new RemoteContextHelper(); + // Opens a window with noopener so that BFCache will work. + const rc1 = await helper.addWindow( + /*config=*/ null, /*options=*/ {features: 'noopener'}); + + // Creates a fetchLater request with short timeout. It should be sent out + // even if the document is then put into BFCache. + await rc1.executeScript(url => { + fetchLater(url, {activateAfter: 1000}); // 1s. + // Add a pageshow listener to stash the BFCache event. + window.addEventListener('pageshow', e => { + window.pageshowEvent = e; + }); + }, [url]); + // Navigates away to let page enter BFCache. + const rc2 = await rc1.navigateToNew(); + // Navigate back. + await rc2.historyBack(); + // Verify that the page was BFCached. + assert_true(await rc1.executeScript(() => { + return window.pageshowEvent.persisted; + })); + + await expectBeacon(uuid, {count: 1}); +}, 'fetchLater() sends out based on activateAfter, even if document is in BFCache.'); diff --git a/testing/web-platform/tests/fetch/fetch-later/basic.tentative.https.window.js b/testing/web-platform/tests/fetch/fetch-later/basic.tentative.https.window.js new file mode 100644 index 0000000000..37f72ab89e --- /dev/null +++ b/testing/web-platform/tests/fetch/fetch-later/basic.tentative.https.window.js @@ -0,0 +1,46 @@ +'use strict'; + +test(() => { + assert_throws_js(TypeError, () => fetchLater()); +}, `fetchLater() cannot be called without request.`); + +test(() => { + assert_throws_js(TypeError, () => fetchLater('http://www.google.com')); + assert_throws_js(TypeError, () => fetchLater('file://tmp')); + assert_throws_js(TypeError, () => fetchLater('ssh://example.com')); + assert_throws_js(TypeError, () => fetchLater('wss://example.com')); + assert_throws_js(TypeError, () => fetchLater('about:blank')); + assert_throws_js(TypeError, () => fetchLater(`javascript:alert('');`)); +}, `fetchLater() throws TypeError on non-HTTPS URL.`); + +test(() => { + assert_throws_js( + RangeError, + () => fetchLater('https://www.google.com', {activateAfter: -1})); +}, `fetchLater() throws RangeError on negative activateAfter.`); + +test(() => { + const result = fetchLater('/'); + assert_false(result.activated); +}, `fetchLater()'s return tells the deferred request is not yet sent.`); + +test(() => { + const result = fetchLater('/'); + assert_throws_js(TypeError, () => result.activated = true); +}, `fetchLater() throws TypeError when mutating its returned state.`); + +test(() => { + const controller = new AbortController(); + // Immediately aborts the controller. + controller.abort(); + assert_throws_dom( + 'AbortError', () => fetchLater('/', {signal: controller.signal})); +}, `fetchLater() throws AbortError when its initial abort signal is aborted.`); + +test(() => { + const controller = new AbortController(); + const result = fetchLater('/', {signal: controller.signal}); + assert_false(result.activated); + controller.abort(); + assert_false(result.activated); +}, `fetchLater() does not throw error when it is aborted before sending.`); diff --git a/testing/web-platform/tests/fetch/fetch-later/basic.tentative.https.worker.js b/testing/web-platform/tests/fetch/fetch-later/basic.tentative.https.worker.js new file mode 100644 index 0000000000..17240db354 --- /dev/null +++ b/testing/web-platform/tests/fetch/fetch-later/basic.tentative.https.worker.js @@ -0,0 +1,6 @@ +importScripts('/resources/testharness.js'); + +test(() => { + assert_false('fetchLater' in self); +}, `fetchLater() is not supported in worker.`); +done(); diff --git a/testing/web-platform/tests/fetch/fetch-later/headers/header-referrer-no-referrer-when-downgrade.tentative.https.html b/testing/web-platform/tests/fetch/fetch-later/headers/header-referrer-no-referrer-when-downgrade.tentative.https.html new file mode 100644 index 0000000000..38eada4513 --- /dev/null +++ b/testing/web-platform/tests/fetch/fetch-later/headers/header-referrer-no-referrer-when-downgrade.tentative.https.html @@ -0,0 +1,23 @@ +<!doctype html> +<html> +<head> +<title>FetchLater Referrer Header: No Referrer When Downgrade Policy</title> +<meta name='referrer' content='no-referrer-when-downgrade'> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script src="/fetch/fetch-later/resources/header-referrer-helper.js"></script> +</head> +<body> +<script> + +const { + HTTPS_ORIGIN, +} = get_host_info(); + +testReferrerHeader(token(), HTTPS_ORIGIN, REFERRER_URL); + +</script> +</body> +</html> diff --git a/testing/web-platform/tests/fetch/fetch-later/headers/header-referrer-no-referrer.tentative.https.html b/testing/web-platform/tests/fetch/fetch-later/headers/header-referrer-no-referrer.tentative.https.html new file mode 100644 index 0000000000..75e9ece7ba --- /dev/null +++ b/testing/web-platform/tests/fetch/fetch-later/headers/header-referrer-no-referrer.tentative.https.html @@ -0,0 +1,19 @@ +<!doctype html> +<html> +<head> +<title>FetchLater Referrer Header: No Referrer Policy</title> +<meta name='referrer' content='no-referrer'> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script src="/fetch/fetch-later/resources/header-referrer-helper.js"></script> +</head> +<body> +<script> + +testReferrerHeader(token(), /*host=*/'', /*expectedReferer=*/""); + +</script> +</body> +</html> diff --git a/testing/web-platform/tests/fetch/fetch-later/headers/header-referrer-origin-when-cross-origin.tentative.https.html b/testing/web-platform/tests/fetch/fetch-later/headers/header-referrer-origin-when-cross-origin.tentative.https.html new file mode 100644 index 0000000000..b9f14171ba --- /dev/null +++ b/testing/web-platform/tests/fetch/fetch-later/headers/header-referrer-origin-when-cross-origin.tentative.https.html @@ -0,0 +1,25 @@ +<!doctype html> +<html> +<head> +<title>FetchLater Referrer Header: Origin When Cross Origin Policy</title> +<meta name='referrer' content='origin-when-cross-origin'> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script src="/fetch/fetch-later/resources/header-referrer-helper.js"></script> +</head> +<body> +<script> + +const { + HTTPS_ORIGIN, + HTTPS_REMOTE_ORIGIN +} = get_host_info(); + +testReferrerHeader(token(), HTTPS_ORIGIN, REFERRER_URL); +testReferrerHeader(token(), HTTPS_REMOTE_ORIGIN, REFERRER_ORIGIN); + +</script> +</body> +</html> diff --git a/testing/web-platform/tests/fetch/fetch-later/headers/header-referrer-origin.tentative.https.html b/testing/web-platform/tests/fetch/fetch-later/headers/header-referrer-origin.tentative.https.html new file mode 100644 index 0000000000..ce7abf9203 --- /dev/null +++ b/testing/web-platform/tests/fetch/fetch-later/headers/header-referrer-origin.tentative.https.html @@ -0,0 +1,23 @@ +<!doctype html> +<html> +<head> +<title>FetchLater Referrer Header: Origin Policy</title> +<meta name='referrer' content='origin'> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script src="/fetch/fetch-later/resources/header-referrer-helper.js"></script> +</head> +<body> +<script> + +const { + HTTPS_REMOTE_ORIGIN +} = get_host_info(); + +testReferrerHeader(token(), HTTPS_REMOTE_ORIGIN, REFERRER_ORIGIN); + +</script> +</body> +</html> diff --git a/testing/web-platform/tests/fetch/fetch-later/headers/header-referrer-same-origin.tentative.https.html b/testing/web-platform/tests/fetch/fetch-later/headers/header-referrer-same-origin.tentative.https.html new file mode 100644 index 0000000000..264beddc03 --- /dev/null +++ b/testing/web-platform/tests/fetch/fetch-later/headers/header-referrer-same-origin.tentative.https.html @@ -0,0 +1,24 @@ +<!doctype html> +<html> +<head> +<title>FetchLater Referrer Header: Same Origin Policy</title> +<meta name='referrer' content='same-origin'> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script src="/fetch/fetch-later/resources/header-referrer-helper.js"></script> +</head> +<body> +<script> + +const { + HTTPS_REMOTE_ORIGIN +} = get_host_info(); + +testReferrerHeader(token(), /*host=*/'', REFERRER_URL); +testReferrerHeader(token(), HTTPS_REMOTE_ORIGIN, /*expectedReferrer=*/''); + +</script> +</body> +</html> diff --git a/testing/web-platform/tests/fetch/fetch-later/headers/header-referrer-strict-origin-when-cross-origin.tentative.https.html b/testing/web-platform/tests/fetch/fetch-later/headers/header-referrer-strict-origin-when-cross-origin.tentative.https.html new file mode 100644 index 0000000000..9133f2496f --- /dev/null +++ b/testing/web-platform/tests/fetch/fetch-later/headers/header-referrer-strict-origin-when-cross-origin.tentative.https.html @@ -0,0 +1,24 @@ +<!doctype html> +<html> +<head> +<title>FetchLater Referrer Header: Strict Origin Policy</title> +<meta name='referrer' content='strict-origin'> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script src="/fetch/fetch-later/resources/header-referrer-helper.js"></script> +</head> +<body> +<script> + +const { + HTTPS_REMOTE_ORIGIN +} = get_host_info(); + +testReferrerHeader(token(), HTTPS_REMOTE_ORIGIN, REFERRER_ORIGIN); +// Note: FetchLater cannot be used for non-secure URL. + +</script> +</body> +</html> diff --git a/testing/web-platform/tests/fetch/fetch-later/headers/header-referrer-strict-origin.tentative.https.html b/testing/web-platform/tests/fetch/fetch-later/headers/header-referrer-strict-origin.tentative.https.html new file mode 100644 index 0000000000..943d70bbc5 --- /dev/null +++ b/testing/web-platform/tests/fetch/fetch-later/headers/header-referrer-strict-origin.tentative.https.html @@ -0,0 +1,24 @@ +<!doctype html> +<html> +<head> +<title>FetchLater Referrer Header: Strict Origin Policy</title> +<meta name='referrer' content='strict-origin'> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script src="/fetch/fetch-later/resources/header-referrer-helper.js"></script> +</head> +<body> +<script> + +const { + HTTPS_ORIGIN +} = get_host_info(); + +testReferrerHeader(token(), HTTPS_ORIGIN, REFERRER_ORIGIN); +// Note: FetchLater cannot be used for non-secure URL. + +</script> +</body> +</html> diff --git a/testing/web-platform/tests/fetch/fetch-later/headers/header-referrer-unsafe-url.tentative.https.html b/testing/web-platform/tests/fetch/fetch-later/headers/header-referrer-unsafe-url.tentative.https.html new file mode 100644 index 0000000000..a602e0003a --- /dev/null +++ b/testing/web-platform/tests/fetch/fetch-later/headers/header-referrer-unsafe-url.tentative.https.html @@ -0,0 +1,24 @@ +<!doctype html> +<html> +<head> +<title>FetchLater Referrer Header: Unsafe Url Policy</title> +<meta name='referrer' content='unsafe-url'> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script src="/fetch/fetch-later/resources/header-referrer-helper.js"></script> +</head> +<body> +<script> + +const { + HTTPS_ORIGIN +} = get_host_info(); + +testReferrerHeader(token(), HTTPS_ORIGIN, REFERRER_URL); +// Note: FetchLater cannot be used for non-secure URL. + +</script> +</body> +</html> diff --git a/testing/web-platform/tests/fetch/fetch-later/iframe.tentative.https.window.js b/testing/web-platform/tests/fetch/fetch-later/iframe.tentative.https.window.js new file mode 100644 index 0000000000..1e9fed1117 --- /dev/null +++ b/testing/web-platform/tests/fetch/fetch-later/iframe.tentative.https.window.js @@ -0,0 +1,63 @@ +// META: script=/common/utils.js +// META: script=/common/get-host-info.sub.js +// META: script=/pending-beacon/resources/pending_beacon-helper.js + +'use strict'; + +const { + HTTPS_ORIGIN, + HTTPS_NOTSAMESITE_ORIGIN, +} = get_host_info(); + +async function loadElement(el) { + const loaded = new Promise(resolve => el.onload = resolve); + document.body.appendChild(el); + await loaded; +} + +// `host` may be cross-origin +async function loadFetchLaterIframe(host, targetUrl) { + const url = `${host}/fetch/fetch-later/resources/fetch-later.html?url=${ + encodeURIComponent(targetUrl)}`; + const iframe = document.createElement('iframe'); + iframe.src = url; + await loadElement(iframe); + return iframe; +} + +parallelPromiseTest(async t => { + const uuid = token(); + const url = generateSetBeaconURL(uuid); + + // Loads a blank iframe that fires a fetchLater request. + const iframe = document.createElement('iframe'); + iframe.addEventListener('load', () => { + fetchLater(url, {activateAfter: 0}); + }); + await loadElement(iframe); + + // The iframe should have sent the request. + await expectBeacon(uuid, {count: 1}); +}, 'A blank iframe can trigger fetchLater.'); + +parallelPromiseTest(async t => { + const uuid = token(); + const url = generateSetBeaconURL(uuid); + + // Loads a same-origin iframe that fires a fetchLater request. + await loadFetchLaterIframe(HTTPS_ORIGIN, url); + + // The iframe should have sent the request. + await expectBeacon(uuid, {count: 1}); +}, 'A same-origin iframe can trigger fetchLater.'); + +parallelPromiseTest(async t => { + const uuid = token(); + const url = generateSetBeaconURL(uuid); + + // Loads a same-origin iframe that fires a fetchLater request. + await loadFetchLaterIframe(HTTPS_NOTSAMESITE_ORIGIN, url); + + // The iframe should have sent the request. + await expectBeacon(uuid, {count: 1}); +}, 'A cross-origin iframe can trigger fetchLater.'); diff --git a/testing/web-platform/tests/fetch/fetch-later/new-window.tentative.https.window.js b/testing/web-platform/tests/fetch/fetch-later/new-window.tentative.https.window.js new file mode 100644 index 0000000000..93705418f2 --- /dev/null +++ b/testing/web-platform/tests/fetch/fetch-later/new-window.tentative.https.window.js @@ -0,0 +1,75 @@ +// META: script=/common/utils.js +// META: script=/common/get-host-info.sub.js +// META: script=/pending-beacon/resources/pending_beacon-helper.js + +'use strict'; + +const { + HTTPS_ORIGIN, + HTTPS_NOTSAMESITE_ORIGIN, +} = get_host_info(); + +function fetchLaterPopupUrl(host, targetUrl) { + return `${host}/fetch/fetch-later/resources/fetch-later.html?url=${ + encodeURIComponent(targetUrl)}`; +} + +for (const target of ['', '_blank']) { + for (const features in ['', 'popup', 'popup,noopener']) { + parallelPromiseTest( + async t => { + const uuid = token(); + const url = + generateSetBeaconURL(uuid, {host: HTTPS_NOTSAMESITE_ORIGIN}); + + // Opens a blank popup window that fires a fetchLater request. + const w = window.open( + `javascript: fetchLater("${url}", {activateAfter: 0})`, target, + features); + await new Promise(resolve => w.addEventListener('load', resolve)); + + // The popup should have sent the request. + await expectBeacon(uuid, {count: 1}); + w.close(); + }, + `A blank window[target='${target}'][features='${ + features}'] can trigger fetchLater.`); + + parallelPromiseTest( + async t => { + const uuid = token(); + const popupUrl = + fetchLaterPopupUrl(HTTPS_ORIGIN, generateSetBeaconURL(uuid)); + + // Opens a same-origin popup that fires a fetchLater request. + const w = window.open(popupUrl, target, features); + await new Promise(resolve => w.addEventListener('load', resolve)); + + // The popup should have sent the request. + await expectBeacon(uuid, {count: 1}); + w.close(); + }, + `A same-origin window[target='${target}'][features='${ + features}'] can trigger fetchLater.`); + + parallelPromiseTest( + async t => { + const uuid = token(); + const popupUrl = fetchLaterPopupUrl( + HTTPS_NOTSAMESITE_ORIGIN, generateSetBeaconURL(uuid)); + + // Opens a cross-origin popup that fires a fetchLater request. + const w = window.open(popupUrl, target, features); + // As events from cross-origin window is not accessible, waiting for + // its message instead. + await new Promise( + resolve => window.addEventListener('message', resolve)); + + // The popup should have sent the request. + await expectBeacon(uuid, {count: 1}); + w.close(); + }, + `A cross-origin window[target='${target}'][features='${ + features}'] can trigger fetchLater.`); + } +} diff --git a/testing/web-platform/tests/fetch/fetch-later/non-secure.window.js b/testing/web-platform/tests/fetch/fetch-later/non-secure.window.js new file mode 100644 index 0000000000..c13932e353 --- /dev/null +++ b/testing/web-platform/tests/fetch/fetch-later/non-secure.window.js @@ -0,0 +1,5 @@ +'use strict'; + +test(() => { + assert_false(window.hasOwnProperty('fetchLater')); +}, `fetchLater() is not supported in non-secure context.`); diff --git a/testing/web-platform/tests/fetch/fetch-later/policies/csp-allowed.tentative.https.window.js b/testing/web-platform/tests/fetch/fetch-later/policies/csp-allowed.tentative.https.window.js new file mode 100644 index 0000000000..60730e0242 --- /dev/null +++ b/testing/web-platform/tests/fetch/fetch-later/policies/csp-allowed.tentative.https.window.js @@ -0,0 +1,26 @@ +// META: title=FetchLater: allowed by CSP +// META: script=/common/utils.js +// META: script=/common/get-host-info.sub.js +// META: script=/pending-beacon/resources/pending_beacon-helper.js +'use strict'; + +const { + HTTPS_NOTSAMESITE_ORIGIN, +} = get_host_info(); + +// FetchLater requests allowed by Content Security Policy. +// https://w3c.github.io/webappsec-csp/#should-block-request + +const meta = document.createElement('meta'); +meta.setAttribute('http-equiv', 'Content-Security-Policy'); +meta.setAttribute('content', `connect-src 'self' ${HTTPS_NOTSAMESITE_ORIGIN}`); +document.head.appendChild(meta); + +promise_test(async t => { + const uuid = token(); + const url = generateSetBeaconURL(uuid, {host: HTTPS_NOTSAMESITE_ORIGIN}); + fetchLater(url, {activateAfter: 0}); + + await expectBeacon(uuid, {count: 1}); + t.done(); +}, 'FetchLater allowed by CSP should succeed'); diff --git a/testing/web-platform/tests/fetch/fetch-later/policies/csp-blocked.tentative.https.window.js b/testing/web-platform/tests/fetch/fetch-later/policies/csp-blocked.tentative.https.window.js new file mode 100644 index 0000000000..b32ddaecfc --- /dev/null +++ b/testing/web-platform/tests/fetch/fetch-later/policies/csp-blocked.tentative.https.window.js @@ -0,0 +1,31 @@ +// META: title=FetchLater: blocked by CSP +// META: script=/common/utils.js +// META: script=/common/get-host-info.sub.js +// META: script=/pending-beacon/resources/pending_beacon-helper.js +'use strict'; + +const { + HTTPS_NOTSAMESITE_ORIGIN, +} = get_host_info(); + +// FetchLater requests blocked by Content Security Policy are rejected. +// https://w3c.github.io/webappsec-csp/#should-block-request + +const meta = document.createElement('meta'); +meta.setAttribute('http-equiv', 'Content-Security-Policy'); +meta.setAttribute('content', 'connect-src \'self\''); +document.head.appendChild(meta); + +promise_test(async t => { + const uuid = token(); + const cspViolationUrl = + generateSetBeaconURL(uuid, {host: HTTPS_NOTSAMESITE_ORIGIN}); + fetchLater(cspViolationUrl, {activateAfter: 0}); + + await new Promise( + resolve => window.addEventListener('securitypolicyviolation', e => { + assert_equals(e.violatedDirective, 'connect-src'); + resolve(); + })); + t.done(); +}, 'FetchLater blocked by CSP should reject'); diff --git a/testing/web-platform/tests/fetch/fetch-later/policies/csp-redirect-to-blocked.tentative.https.window.js b/testing/web-platform/tests/fetch/fetch-later/policies/csp-redirect-to-blocked.tentative.https.window.js new file mode 100644 index 0000000000..3c18727156 --- /dev/null +++ b/testing/web-platform/tests/fetch/fetch-later/policies/csp-redirect-to-blocked.tentative.https.window.js @@ -0,0 +1,33 @@ +// META: title=FetchLater: redirect blocked by CSP +// META: script=/common/utils.js +// META: script=/common/get-host-info.sub.js +// META: script=/pending-beacon/resources/pending_beacon-helper.js +// META: timeout=long + +'use strict'; + +const { + HTTPS_NOTSAMESITE_ORIGIN, +} = get_host_info(); + +// FetchLater requests redirect to URL blocked by Content Security Policy. +// https://w3c.github.io/webappsec-csp/#should-block-request + +const meta = document.createElement('meta'); +meta.setAttribute('http-equiv', 'Content-Security-Policy'); +meta.setAttribute('content', 'connect-src \'self\''); +document.head.appendChild(meta); + +promise_test(async t => { + const uuid = token(); + const cspViolationUrl = + generateSetBeaconURL(uuid, {host: HTTPS_NOTSAMESITE_ORIGIN}); + const url = + `/common/redirect.py?location=${encodeURIComponent(cspViolationUrl)}`; + fetchLater(url, {activateAfter: 0}); + + // TODO(crbug.com/1465781): redirect csp check is handled in browser, of which + // result cannot be populated to renderer at this moment. + await expectBeacon(uuid, {count: 0}); + t.done(); +}, 'FetchLater redirect blocked by CSP should reject'); diff --git a/testing/web-platform/tests/fetch/fetch-later/quota.tentative.https.window.js b/testing/web-platform/tests/fetch/fetch-later/quota.tentative.https.window.js new file mode 100644 index 0000000000..1b5b85563d --- /dev/null +++ b/testing/web-platform/tests/fetch/fetch-later/quota.tentative.https.window.js @@ -0,0 +1,128 @@ +// META: script=/common/get-host-info.sub.js +// META: script=/common/utils.js +// META: script=/pending-beacon/resources/pending_beacon-helper.js + +'use strict'; + +const kQuotaPerOrigin = 64 * 1024; // 64 kilobytes per spec. +const {ORIGIN, HTTPS_NOTSAMESITE_ORIGIN} = get_host_info(); + +// Runs a test case that cover a single fetchLater() call with `body` in its +// request payload. The call is not expected to throw any errors. +function fetchLaterPostTest(body, description) { + test(() => { + const controller = new AbortController(); + const result = fetchLater( + '/fetch-later', + {method: 'POST', signal: controller.signal, body: body}); + assert_false(result.activated); + // Release quota taken by the pending request for subsequent tests. + controller.abort(); + }, description); +} + +// Test small payload for each supported data types. +for (const [dataType, skipCharset] of Object.entries( + BeaconDataTypeToSkipCharset)) { + fetchLaterPostTest( + makeBeaconData(generateSequentialData(0, 1024, skipCharset), dataType), + `A fetchLater() call accept small data in POST request of ${dataType}.`); +} + +// Test various size of payloads for the same origin. +for (const dataType in BeaconDataType) { + if (dataType !== BeaconDataType.FormData && + dataType !== BeaconDataType.URLSearchParams) { + // Skips FormData & URLSearchParams, as browser adds extra bytes to them + // in addition to the user-provided content. It is difficult to test a + // request right at the quota limit. + fetchLaterPostTest( + // Generates data that is exactly 64 kilobytes. + makeBeaconData(generatePayload(kQuotaPerOrigin), dataType), + `A single fetchLater() call takes up the per-origin quota for its ` + + `body of ${dataType}.`); + } +} + +// Test empty payload. +for (const dataType in BeaconDataType) { + test( + () => { + assert_throws_js( + TypeError, () => fetchLater('/', {method: 'POST', body: ''})); + }, + `A single fetchLater() call does not accept empty data in POST request ` + + `of ${dataType}.`); +} + +// Test oversized payload. +for (const dataType in BeaconDataType) { + test( + () => { + assert_throws_dom( + 'QuotaExceededError', + () => fetchLater('/fetch-later', { + method: 'POST', + // Generates data that exceeds 64 kilobytes. + body: + makeBeaconData(generatePayload(kQuotaPerOrigin + 1), dataType) + })); + }, + `A single fetchLater() call is not allowed to exceed per-origin quota ` + + `for its body of ${dataType}.`); +} + +// Test accumulated oversized request. +for (const dataType in BeaconDataType) { + test( + () => { + const controller = new AbortController(); + // Makes the 1st call that sends only half of allowed quota. + fetchLater('/fetch-later', { + method: 'POST', + signal: controller.signal, + body: makeBeaconData(generatePayload(kQuotaPerOrigin / 2), dataType) + }); + + // Makes the 2nd call that sends half+1 of allowed quota. + assert_throws_dom('QuotaExceededError', () => { + fetchLater('/fetch-later', { + method: 'POST', + signal: controller.signal, + body: makeBeaconData( + generatePayload(kQuotaPerOrigin / 2 + 1), dataType) + }); + }); + // Release quota taken by the pending requests for subsequent tests. + controller.abort(); + }, + `The 2nd fetchLater() call is not allowed to exceed per-origin quota ` + + `for its body of ${dataType}.`); +} + +// Test various size of payloads across different origins. +for (const dataType in BeaconDataType) { + test( + () => { + const controller = new AbortController(); + // Makes the 1st call that sends only half of allowed quota. + fetchLater('/fetch-later', { + method: 'POST', + signal: controller.signal, + body: makeBeaconData(generatePayload(kQuotaPerOrigin / 2), dataType) + }); + + // Makes the 2nd call that sends half+1 of allowed quota, but to a + // different origin. + fetchLater(`${HTTPS_NOTSAMESITE_ORIGIN}/fetch-later`, { + method: 'POST', + signal: controller.signal, + body: + makeBeaconData(generatePayload(kQuotaPerOrigin / 2 + 1), dataType) + }); + // Release quota taken by the pending requests for subsequent tests. + controller.abort(); + }, + `The 2nd fetchLater() call to another origin does not exceed per-origin` + + ` quota for its body of ${dataType}.`); +} diff --git a/testing/web-platform/tests/fetch/fetch-later/resources/fetch-later.html b/testing/web-platform/tests/fetch/fetch-later/resources/fetch-later.html new file mode 100644 index 0000000000..b569e1a076 --- /dev/null +++ b/testing/web-platform/tests/fetch/fetch-later/resources/fetch-later.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<head> +</head> +<body> + <script> + const PARAMS = new URL(location.href).searchParams; + const TARGET_URL= decodeURIComponent(PARAMS.get('url')) || ''; + + fetchLater(TARGET_URL, {activateAfter: 0}); + if (window.opener) { + window.opener.postMessage("done", "*"); + } + </script> +</body> diff --git a/testing/web-platform/tests/fetch/fetch-later/resources/header-referrer-helper.js b/testing/web-platform/tests/fetch/fetch-later/resources/header-referrer-helper.js new file mode 100644 index 0000000000..374097614a --- /dev/null +++ b/testing/web-platform/tests/fetch/fetch-later/resources/header-referrer-helper.js @@ -0,0 +1,39 @@ +'use strict'; + +// https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer +const REFERRER_ORIGIN = self.location.origin + '/'; +const REFERRER_URL = self.location.href; + +function testReferrerHeader(id, host, expectedReferrer) { + const url = `${ + host}/beacon/resources/inspect-header.py?header=referer&cmd=put&id=${id}`; + + promise_test(t => { + fetchLater(url, {activateAfter: 0}); + return pollResult(expectedReferrer, id).then(result => { + assert_equals(result, expectedReferrer, 'Correct referrer header result'); + }); + }, `Test referer header ${host}`); +} + +function pollResult(expectedReferrer, id) { + const checkUrl = + `/beacon/resources/inspect-header.py?header=referer&cmd=get&id=${id}`; + + return new Promise(resolve => { + function checkResult() { + fetch(checkUrl).then(response => { + assert_equals( + response.status, 200, 'Inspect header response\'s status is 200'); + let result = response.headers.get('x-request-referer'); + + if (result != undefined) { + resolve(result); + } else { + step_timeout(checkResult.bind(this), 100); + } + }); + } + checkResult(); + }); +} diff --git a/testing/web-platform/tests/fetch/fetch-later/send-on-deactivate.tentative.https.window.js b/testing/web-platform/tests/fetch/fetch-later/send-on-deactivate.tentative.https.window.js new file mode 100644 index 0000000000..d91c73580a --- /dev/null +++ b/testing/web-platform/tests/fetch/fetch-later/send-on-deactivate.tentative.https.window.js @@ -0,0 +1,183 @@ +// META: script=/common/dispatcher/dispatcher.js +// META: script=/common/get-host-info.sub.js +// META: script=/common/utils.js +// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js +// META: script=/html/browsers/browsing-the-web/back-forward-cache/resources/rc-helper.js +// META: script=/pending-beacon/resources/pending_beacon-helper.js + +'use strict'; + +// NOTE: Due to the restriction of WPT runner, the following tests are all run +// with BackgroundSync off, which is different from some browsers, +// e.g. Chrome, default behavior, as the testing infra does not support enabling +// it. + +parallelPromiseTest(async t => { + const uuid = token(); + const url = generateSetBeaconURL(uuid); + // Sets no option to test the default behavior when a document enters BFCache. + const helper = new RemoteContextHelper(); + // Opens a window with noopener so that BFCache will work. + const rc1 = await helper.addWindow( + /*config=*/ null, /*options=*/ {features: 'noopener'}); + + // Creates a fetchLater request with default config in remote, which should + // only be sent on page discarded (not on entering BFCache). + await rc1.executeScript(url => { + fetchLater(url); + // Add a pageshow listener to stash the BFCache event. + window.addEventListener('pageshow', e => { + window.pageshowEvent = e; + }); + }, [url]); + // Navigates away to let page enter BFCache. + const rc2 = await rc1.navigateToNew(); + // Navigates back. + await rc2.historyBack(); + // Verifies the page was BFCached. + assert_true(await rc1.executeScript(() => { + return window.pageshowEvent.persisted; + })); + + // Theoretically, the request should still be pending thus 0 request received. + // However, 1 request is sent, as by default the WPT test runner, e.g. + // content_shell in Chromium, does not enable BackgroundSync permission, + // resulting in forcing request sending on every navigation. + await expectBeacon(uuid, {count: 1}); +}, `fetchLater() sends on page entering BFCache if BackgroundSync is off.`); + +parallelPromiseTest(async t => { + const uuid = token(); + const url = generateSetBeaconURL(uuid); + const helper = new RemoteContextHelper(); + // Opens a window with noopener so that BFCache will work. + const rc1 = await helper.addWindow( + /*config=*/ null, /*options=*/ {features: 'noopener'}); + + // When the remote is put into BFCached, creates a fetchLater request w/ + // activateAfter = 0s. It should be sent out immediately. + await rc1.executeScript(url => { + window.addEventListener('pagehide', e => { + if (e.persisted) { + fetchLater(url, {activateAfter: 0}); + } + }); + // Add a pageshow listener to stash the BFCache event. + window.addEventListener('pageshow', e => { + window.pageshowEvent = e; + }); + }, [url]); + // Navigates away to trigger request sending. + const rc2 = await rc1.navigateToNew(); + // Navigates back. + await rc2.historyBack(); + // Verifies the page was BFCached. + assert_true(await rc1.executeScript(() => { + return window.pageshowEvent.persisted; + })); + + // NOTE: In this case, it does not matter if BackgroundSync is on or off. + await expectBeacon(uuid, {count: 1}); +}, `Call fetchLater() when BFCached with activateAfter=0 sends immediately.`); + +parallelPromiseTest(async t => { + const uuid = token(); + const url = generateSetBeaconURL(uuid); + // Sets no option to test the default behavior when a document gets discarded + // on navigated away. + const helper = new RemoteContextHelper(); + // Opens a window without BFCache. + const rc1 = await helper.addWindow(); + + // Creates a fetchLater request in remote which should only be sent on + // navigating away. + await rc1.executeScript(url => { + fetchLater(url); + // Add a pageshow listener to stash the BFCache event. + window.addEventListener('pageshow', e => { + window.pageshowEvent = e; + }); + }, [url]); + // Navigates away to trigger request sending. + const rc2 = await rc1.navigateToNew(); + // Navigates back. + await rc2.historyBack(); + // Verifies the page was NOT BFCached. + assert_equals(undefined, await rc1.executeScript(() => { + return window.pageshowEvent; + })); + + // NOTE: In this case, it does not matter if BackgroundSync is on or off. + await expectBeacon(uuid, {count: 1}); +}, `fetchLater() sends on navigating away a page w/o BFCache.`); + +parallelPromiseTest(async t => { + const uuid = token(); + const url = generateSetBeaconURL(uuid); + // Sets no option to test the default behavior when a document gets discarded + // on navigated away. + const helper = new RemoteContextHelper(); + // Opens a window without BFCache. + const rc1 = await helper.addWindow(); + + // Creates 2 fetchLater requests in remote, and one of them is aborted + // immediately. The other one should only be sent right on navigating away. + await rc1.executeScript(url => { + const controller = new AbortController(); + fetchLater(url, {signal: controller.signal}); + fetchLater(url); + controller.abort(); + // Add a pageshow listener to stash the BFCache event. + window.addEventListener('pageshow', e => { + window.pageshowEvent = e; + }); + }, [url]); + // Navigates away to trigger request sending. + const rc2 = await rc1.navigateToNew(); + // Navigates back. + await rc2.historyBack(); + // Verifies the page was NOT BFCached. + assert_equals(undefined, await rc1.executeScript(() => { + return window.pageshowEvent; + })); + + // NOTE: In this case, it does not matter if BackgroundSync is on or off. + await expectBeacon(uuid, {count: 1}); +}, `fetchLater() does not send aborted request on navigating away a page w/o BFCache.`); + +parallelPromiseTest(async t => { + const uuid = token(); + const url = generateSetBeaconURL(uuid); + const options = {activateAfter: 60000}; + const helper = new RemoteContextHelper(); + // Opens a window with noopener so that BFCache will work. + const rc1 = await helper.addWindow( + /*config=*/ null, /*options=*/ {features: 'noopener'}); + + // Creates a fetchLater request in remote which should only be sent on + // navigating away. + await rc1.executeScript((url) => { + // Sets activateAfter = 1m to indicate the request should NOT be sent out + // immediately. + fetchLater(url, {activateAfter: 60000}); + // Adds a pageshow listener to stash the BFCache event. + window.addEventListener('pageshow', e => { + window.pageshowEvent = e; + }); + }, [url]); + // Navigates away to trigger request sending. + const rc2 = await rc1.navigateToNew(); + // Navigates back. + await rc2.historyBack(); + // Verifies the page was BFCached. + assert_true(await rc1.executeScript(() => { + return window.pageshowEvent.persisted; + })); + + // Theoretically, the request should still be pending thus 0 request received. + // However, 1 request is sent, as by default the WPT test runner, e.g. + // content_shell in Chromium, does not enable BackgroundSync permission, + // resulting in forcing request sending on every navigation, even if page is + // put into BFCache. + await expectBeacon(uuid, {count: 1}); +}, `fetchLater() with activateAfter=1m sends on page entering BFCache if BackgroundSync is off.`); diff --git a/testing/web-platform/tests/fetch/fetch-later/send-on-discard/not-send-after-abort.tentative.https.window.js b/testing/web-platform/tests/fetch/fetch-later/send-on-discard/not-send-after-abort.tentative.https.window.js new file mode 100644 index 0000000000..ff8d9520e0 --- /dev/null +++ b/testing/web-platform/tests/fetch/fetch-later/send-on-discard/not-send-after-abort.tentative.https.window.js @@ -0,0 +1,23 @@ +// META: script=/common/utils.js +// META: script=/pending-beacon/resources/pending_beacon-helper.js + +'use strict'; + +parallelPromiseTest(async t => { + const uuid = token(); + const url = generateSetBeaconURL(uuid); + + // Loads an iframe that creates 2 fetchLater requests. One of them is aborted. + const iframe = await loadScriptAsIframe(` + const url = '${url}'; + const controller = new AbortController(); + fetchLater(url, {signal: controller.signal}); + fetchLater(url, {method: 'POST'}); + controller.abort(); + `); + // Delete the iframe to trigger deferred request sending. + document.body.removeChild(iframe); + + // The iframe should not send the aborted request. + await expectBeacon(uuid, {count: 1}); +}, 'A discarded document does not send an already aborted fetchLater request.'); diff --git a/testing/web-platform/tests/fetch/fetch-later/send-on-discard/send-multiple-with-activate-after.tentative.https.window.js b/testing/web-platform/tests/fetch/fetch-later/send-on-discard/send-multiple-with-activate-after.tentative.https.window.js new file mode 100644 index 0000000000..11e85b31a7 --- /dev/null +++ b/testing/web-platform/tests/fetch/fetch-later/send-on-discard/send-multiple-with-activate-after.tentative.https.window.js @@ -0,0 +1,30 @@ +// META: script=/common/utils.js +// META: script=/pending-beacon/resources/pending_beacon-helper.js +// META: timeout=long + +'use strict'; + +parallelPromiseTest(async t => { + const uuid = token(); + const url = generateSetBeaconURL(uuid); + const numPerMethod = 20; + const total = numPerMethod * 2; + + // Loads an iframe that creates `numPerMethod` GET & POST fetchLater requests. + const iframe = await loadScriptAsIframe(` + const url = '${url}'; + for (let i = 0; i < ${numPerMethod}; i++) { + // Changing the URL of each request to avoid HTTP Cache issue. + // See crbug.com/1498203#c17. + fetchLater(url + "&method=GET&i=" + i, + {method: 'GET', activateAfter: 10000}); // 10s + fetchLater(url + "&method=POST&i=" + i, + {method: 'POST', activateAfter: 8000}); // 8s + } + `); + // Delete the iframe to trigger deferred request sending. + document.body.removeChild(iframe); + + // The iframe should have sent all requests. + await expectBeacon(uuid, {count: total}); +}, 'A discarded document sends all its fetchLater requests, no matter how much their activateAfter timeout remain.'); diff --git a/testing/web-platform/tests/fetch/fetch-later/send-on-discard/send-multiple.tentative.https.window.js b/testing/web-platform/tests/fetch/fetch-later/send-on-discard/send-multiple.tentative.https.window.js new file mode 100644 index 0000000000..df34ec9ac0 --- /dev/null +++ b/testing/web-platform/tests/fetch/fetch-later/send-on-discard/send-multiple.tentative.https.window.js @@ -0,0 +1,28 @@ +// META: script=/common/utils.js +// META: script=/pending-beacon/resources/pending_beacon-helper.js +// META: timeout=long + +'use strict'; + +parallelPromiseTest(async t => { + const uuid = token(); + const url = generateSetBeaconURL(uuid); + const numPerMethod = 20; + const total = numPerMethod * 2; + + // Loads an iframe that creates `numPerMethod` GET & POST fetchLater requests. + const iframe = await loadScriptAsIframe(` + const url = '${url}'; + for (let i = 0; i < ${numPerMethod}; i++) { + // Changing the URL of each request to avoid HTTP Cache issue. + // See crbug.com/1498203#c17. + fetchLater(url + "&method=GET&i=" + i); + fetchLater(url + "&method=POST&i=" + i, {method: 'POST'}); + } + `); + // Delete the iframe to trigger deferred request sending. + document.body.removeChild(iframe); + + // The iframe should have sent all requests. + await expectBeacon(uuid, {count: total}); +}, 'A discarded document sends all its fetchLater requests.'); |