Implement fetchLater (#39547)

Allows fetches to be deferred, only in a secure context. It does not yet
implement quota computation, since we don't have a concept of document
quota yet.

Also update the `fetch/api/idlharness` test to run in a secure context,
since this API is only available there.

Positive Mozilla position:
https://github.com/mozilla/standards-positions/issues/703
Positive WebKit position:
https://github.com/WebKit/standards-positions/issues/85

Closes whatwg/fetch#1858

Signed-off-by: Tim van der Lippe <tvanderlippe@gmail.com>
This commit is contained in:
Tim van der Lippe 2025-10-02 09:51:19 +02:00 committed by GitHub
parent 19c498af16
commit 680a780552
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
39 changed files with 403 additions and 228 deletions

View file

@ -140,6 +140,10 @@ skip: true
skip: false
[fetch]
skip: false
[fetch-later]
skip: false
[quota]
skip: true
[FileAPI]
skip: false
[focus]

View file

@ -674692,10 +674692,10 @@
]
]
},
"idlharness.any.js": [
"idlharness.https.any.js": [
"7b3c694e16ac3ec2776398067cf6ddbef949c969",
[
"fetch/api/idlharness.any.html",
"fetch/api/idlharness.https.any.html",
{
"script_metadata": [
[
@ -674719,7 +674719,7 @@
}
],
[
"fetch/api/idlharness.any.serviceworker.html",
"fetch/api/idlharness.https.any.serviceworker.html",
{
"script_metadata": [
[
@ -674743,7 +674743,7 @@
}
],
[
"fetch/api/idlharness.any.sharedworker.html",
"fetch/api/idlharness.https.any.sharedworker.html",
{
"script_metadata": [
[
@ -674767,7 +674767,7 @@
}
],
[
"fetch/api/idlharness.any.worker.html",
"fetch/api/idlharness.https.any.worker.html",
{
"script_metadata": [
[
@ -680157,7 +680157,7 @@
]
],
"basic.https.window.js": [
"f3ed42fe35a078942980ff44f66eee26a71f38cf",
"afedbf5d4c4fb6eb069e650911014a10e0d43b4a",
[
"fetch/fetch-later/basic.https.window.html",
{}

View file

@ -1,4 +1,4 @@
[idlharness.any.worker.html]
[idlharness.https.any.html]
[Request interface: attribute keepalive]
expected: FAIL
@ -24,10 +24,10 @@
expected: FAIL
[idlharness.any.sharedworker.html]
[idlharness.https.any.serviceworker.html]
expected: ERROR
[idlharness.any.html]
[idlharness.https.any.worker.html]
[Request interface: attribute keepalive]
expected: FAIL
@ -52,36 +52,6 @@
[Request interface: new Request('about:blank') must inherit property "duplex" with the proper type]
expected: FAIL
[FetchLaterResult interface: existence and properties of interface object]
expected: FAIL
[FetchLaterResult interface object length]
expected: FAIL
[FetchLaterResult interface object name]
expected: FAIL
[FetchLaterResult interface: existence and properties of interface prototype object]
expected: FAIL
[FetchLaterResult interface: existence and properties of interface prototype object's "constructor" property]
expected: FAIL
[FetchLaterResult interface: existence and properties of interface prototype object's @@unscopables property]
expected: FAIL
[FetchLaterResult interface: attribute activated]
expected: FAIL
[Window interface: operation fetchLater(RequestInfo, optional DeferredRequestInit)]
expected: FAIL
[Window interface: window must inherit property "fetchLater(RequestInfo, optional DeferredRequestInit)" with the proper type]
expected: FAIL
[Window interface: calling fetchLater(RequestInfo, optional DeferredRequestInit) on window with too few arguments must throw TypeError]
expected: FAIL
[idlharness.any.serviceworker.html]
[idlharness.https.any.sharedworker.html]
expected: ERROR

View file

@ -0,0 +1,3 @@
prefs: [
"dom_abort_controller_enabled:true",
]

View file

@ -1,6 +1,4 @@
[activate-after.https.window.html]
[fetchLater() sends out based on activateAfter.]
expected: FAIL
expected: TIMEOUT
[fetchLater() sends out based on activateAfter, even if document is in BFCache.]
expected: FAIL
expected: TIMEOUT

View file

@ -1,69 +1,3 @@
[basic.https.window.html]
[fetchLater() cannot be called without request.]
expected: FAIL
[fetchLater() with same-origin (https) URL does not throw.]
expected: FAIL
[fetchLater() with http://localhost URL does not throw.]
expected: FAIL
[fetchLater() with https://localhost URL does not throw.]
expected: FAIL
[fetchLater() with http://127.0.0.1 URL does not throw.]
expected: FAIL
[fetchLater() with https://127.0.0.1 URL does not throw.]
expected: FAIL
[fetchLater() with http://[::1\] URL does not throw.]
expected: FAIL
[fetchLater() with https://[::1\] URL does not throw.]
expected: FAIL
[fetchLater() with https://example.com URL does not throw.]
expected: FAIL
[fetchLater() throws SecurityError on non-trustworthy http URL.]
expected: FAIL
[fetchLater() throws TypeError on file:// scheme.]
expected: FAIL
[fetchLater() throws TypeError on ftp:// scheme.]
expected: FAIL
[fetchLater() throws TypeError on ssh:// scheme.]
expected: FAIL
[fetchLater() throws TypeError on wss:// scheme.]
expected: FAIL
[fetchLater() throws TypeError on about: scheme.]
expected: FAIL
[fetchLater() throws TypeError on javascript: scheme.]
expected: FAIL
[fetchLater() throws TypeError on data: scheme.]
expected: FAIL
[fetchLater() throws TypeError on blob: scheme.]
expected: FAIL
[fetchLater() throws RangeError on negative activateAfter.]
expected: FAIL
[fetchLater()'s return tells the deferred request is not yet sent.]
expected: FAIL
[fetchLater() throws TypeError when mutating its returned state.]
expected: FAIL
[fetchLater() throws AbortError when its initial abort signal is aborted.]
expected: FAIL
[fetchLater() does not throw error when it is aborted before sending.]
expected: FAIL

View file

@ -1,3 +0,0 @@
[header-referrer-no-referrer-when-downgrade.https.html]
[Test referer header https://web-platform.test:8443]
expected: FAIL

View file

@ -1,3 +0,0 @@
[header-referrer-no-referrer.https.html]
[Test referer header ]
expected: FAIL

View file

@ -1,6 +0,0 @@
[header-referrer-origin-when-cross-origin.https.html]
[Test referer header https://web-platform.test:8443]
expected: FAIL
[Test referer header https://www1.web-platform.test:8443]
expected: FAIL

View file

@ -1,3 +0,0 @@
[header-referrer-origin.https.html]
[Test referer header https://www1.web-platform.test:8443]
expected: FAIL

View file

@ -1,6 +0,0 @@
[header-referrer-same-origin.https.html]
[Test referer header ]
expected: FAIL
[Test referer header https://www1.web-platform.test:8443]
expected: FAIL

View file

@ -1,3 +0,0 @@
[header-referrer-strict-origin-when-cross-origin.https.html]
[Test referer header https://www1.web-platform.test:8443]
expected: FAIL

View file

@ -1,3 +0,0 @@
[header-referrer-strict-origin.https.html]
[Test referer header https://web-platform.test:8443]
expected: FAIL

View file

@ -1,3 +0,0 @@
[header-referrer-unsafe-url.https.html]
[Test referer header https://web-platform.test:8443]
expected: FAIL

View file

@ -1,4 +0,0 @@
[iframe.https.window.html]
expected: ERROR
[A blank iframe can trigger fetchLater.]
expected: FAIL

View file

@ -1,36 +0,0 @@
[new-window.https.window.html]
[A blank window[target=''\][features=''\] can trigger fetchLater.]
expected: FAIL
[A same-origin window[target=''\][features=''\] can trigger fetchLater.]
expected: FAIL
[A cross-origin window[target=''\][features=''\] can trigger fetchLater.]
expected: FAIL
[A blank window[target=''\][features='popup'\] can trigger fetchLater.]
expected: FAIL
[A same-origin window[target=''\][features='popup'\] can trigger fetchLater.]
expected: FAIL
[A cross-origin window[target=''\][features='popup'\] can trigger fetchLater.]
expected: FAIL
[A blank window[target='_blank'\][features=''\] can trigger fetchLater.]
expected: FAIL
[A same-origin window[target='_blank'\][features=''\] can trigger fetchLater.]
expected: FAIL
[A cross-origin window[target='_blank'\][features=''\] can trigger fetchLater.]
expected: FAIL
[A blank window[target='_blank'\][features='popup'\] can trigger fetchLater.]
expected: FAIL
[A same-origin window[target='_blank'\][features='popup'\] can trigger fetchLater.]
expected: FAIL
[A cross-origin window[target='_blank'\][features='popup'\] can trigger fetchLater.]
expected: FAIL

View file

@ -1,12 +1,3 @@
[deferred-fetch-allowed-by-permissions-policy.https.window.html]
[Permissions policy header: "deferred-fetch=*" allows fetchLater() in the top-level document.]
expected: FAIL
[Permissions policy header: "deferred-fetch=*" allows fetchLater() in the same-origin iframe.]
expected: FAIL
[Permissions policy header: "deferred-fetch=*" allows fetchLater() in the cross-origin iframe.]
expected: FAIL
[Permissions policy header: "deferred-fetch=*" allow="deferred-fetch" allows fetchLater() in the cross-origin iframe.]
expected: FAIL

View file

@ -1,9 +0,0 @@
[deferred-fetch-default-permissions-policy.https.window.html]
[Default "deferred-fetch" permissions policy ["self"\] allows fetchLater() in the top-level document.]
expected: FAIL
[Default "deferred-fetch" permissions policy ["self"\] allows fetchLater() in the same-origin iframe.]
expected: FAIL
[Default "deferred-fetch-minimal" permissions policy ["*"\] allows fetchLater() in the cross-origin iframe.]
expected: FAIL

View file

@ -1,3 +0,0 @@
[csp-allowed.https.window.html]
[FetchLater allowed by CSP should succeed]
expected: FAIL

View file

@ -1,3 +0,0 @@
[csp-blocked.https.window.html]
[FetchLater blocked by CSP should reject]
expected: FAIL

View file

@ -1,15 +1,16 @@
[send-on-deactivate.https.window.html]
expected: TIMEOUT
[fetchLater() sends on page entering BFCache if BackgroundSync is off.]
expected: FAIL
expected: TIMEOUT
[Call fetchLater() when BFCached with activateAfter=0 sends immediately.]
expected: FAIL
expected: TIMEOUT
[fetchLater() sends on navigating away a page w/o BFCache.]
expected: FAIL
expected: TIMEOUT
[fetchLater() does not send aborted request on navigating away a page w/o BFCache.]
expected: FAIL
expected: TIMEOUT
[fetchLater() with activateAfter=1m sends on page entering BFCache if BackgroundSync is off.]
expected: FAIL
expected: TIMEOUT

View file

@ -1,3 +0,0 @@
[not-send-after-abort.https.window.html]
[A discarded document does not send an already aborted fetchLater request.]
expected: FAIL

View file

@ -1,3 +0,0 @@
[send-multiple.https.window.html]
[A discarded document sends all its fetchLater requests.]
expected: FAIL

View file

@ -13775,7 +13775,7 @@
]
],
"interfaces.https.html": [
"1397b723a1cb001521ac1b2032c380f1e02cf1f0",
"027e138f4af5e2a13df9bf8c78765dcf57f6b611",
[
null,
{}

View file

@ -92,6 +92,7 @@ test_interfaces([
"Event",
"EventSource",
"EventTarget",
"FetchLaterResult",
"File",
"FileList",
"FileReader",

View file

@ -52,11 +52,8 @@ test(() => {
}, `fetchLater() with https://example.com URL does not throw.`);
test(() => {
const httpUrl = 'http://example.com';
assert_throws_dom(
'SecurityError', () => fetchLater(httpUrl),
`should throw SecurityError for insecure http url ${httpUrl}`);
}, `fetchLater() throws SecurityError on non-trustworthy http URL.`);
assert_throws_js(TypeError, () => fetchLater('http://example.com'));
}, `fetchLater() throws TypeError on non-trustworthy http URL.`);
test(() => {
assert_throws_js(TypeError, () => fetchLater('file://tmp'));