From 680a780552902844cf2440625f50bcc18a07b95a Mon Sep 17 00:00:00 2001 From: Tim van der Lippe Date: Thu, 2 Oct 2025 09:51:19 +0200 Subject: [PATCH] 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 --- components/script/body.rs | 6 +- components/script/dom/abortsignal.rs | 11 +- components/script/dom/fetchlaterresult.rs | 59 ++++ components/script/dom/mod.rs | 1 + components/script/dom/request.rs | 2 +- components/script/dom/window.rs | 16 +- components/script/fetch.rs | 262 +++++++++++++++++- components/script/task_manager.rs | 1 + components/script/task_source.rs | 3 + .../script_bindings/codegen/Bindings.conf | 2 +- .../webidls/FetchLaterResult.webidl | 9 + .../script_bindings/webidls/Window.webidl | 9 + tests/wpt/include.ini | 4 + tests/wpt/meta/MANIFEST.json | 12 +- ...any.js.ini => idlharness.https.any.js.ini} | 38 +-- tests/wpt/meta/fetch/fetch-later/__dir__.ini | 3 + .../activate-after.https.window.js.ini | 6 +- .../fetch-later/basic.https.window.js.ini | 66 ----- ...-no-referrer-when-downgrade.https.html.ini | 3 - ...header-referrer-no-referrer.https.html.ini | 3 - ...er-origin-when-cross-origin.https.html.ini | 6 - .../header-referrer-origin.https.html.ini | 3 - ...header-referrer-same-origin.https.html.ini | 6 - ...ct-origin-when-cross-origin.https.html.ini | 3 - ...ader-referrer-strict-origin.https.html.ini | 3 - .../header-referrer-unsafe-url.https.html.ini | 3 - .../fetch-later/iframe.https.window.js.ini | 4 - .../new-window.https.window.js.ini | 36 --- ...-by-permissions-policy.https.window.js.ini | 9 - ...ult-permissions-policy.https.window.js.ini | 9 - .../policies/csp-allowed.https.window.js.ini | 3 - .../policies/csp-blocked.https.window.js.ini | 3 - .../send-on-deactivate.https.window.js.ini | 11 +- .../not-send-after-abort.https.window.js.ini | 3 - .../send-multiple.https.window.js.ini | 3 - tests/wpt/mozilla/meta/MANIFEST.json | 2 +- .../tests/mozilla/interfaces.https.html | 1 + ...harness.any.js => idlharness.https.any.js} | 0 .../fetch/fetch-later/basic.https.window.js | 7 +- 39 files changed, 403 insertions(+), 228 deletions(-) create mode 100644 components/script/dom/fetchlaterresult.rs create mode 100644 components/script_bindings/webidls/FetchLaterResult.webidl rename tests/wpt/meta/fetch/api/{idlharness.any.js.ini => idlharness.https.any.js.ini} (57%) create mode 100644 tests/wpt/meta/fetch/fetch-later/__dir__.ini delete mode 100644 tests/wpt/meta/fetch/fetch-later/headers/header-referrer-no-referrer-when-downgrade.https.html.ini delete mode 100644 tests/wpt/meta/fetch/fetch-later/headers/header-referrer-no-referrer.https.html.ini delete mode 100644 tests/wpt/meta/fetch/fetch-later/headers/header-referrer-origin-when-cross-origin.https.html.ini delete mode 100644 tests/wpt/meta/fetch/fetch-later/headers/header-referrer-origin.https.html.ini delete mode 100644 tests/wpt/meta/fetch/fetch-later/headers/header-referrer-same-origin.https.html.ini delete mode 100644 tests/wpt/meta/fetch/fetch-later/headers/header-referrer-strict-origin-when-cross-origin.https.html.ini delete mode 100644 tests/wpt/meta/fetch/fetch-later/headers/header-referrer-strict-origin.https.html.ini delete mode 100644 tests/wpt/meta/fetch/fetch-later/headers/header-referrer-unsafe-url.https.html.ini delete mode 100644 tests/wpt/meta/fetch/fetch-later/iframe.https.window.js.ini delete mode 100644 tests/wpt/meta/fetch/fetch-later/new-window.https.window.js.ini delete mode 100644 tests/wpt/meta/fetch/fetch-later/permissions-policy/deferred-fetch-default-permissions-policy.https.window.js.ini delete mode 100644 tests/wpt/meta/fetch/fetch-later/policies/csp-allowed.https.window.js.ini delete mode 100644 tests/wpt/meta/fetch/fetch-later/policies/csp-blocked.https.window.js.ini delete mode 100644 tests/wpt/meta/fetch/fetch-later/send-on-discard/not-send-after-abort.https.window.js.ini delete mode 100644 tests/wpt/meta/fetch/fetch-later/send-on-discard/send-multiple.https.window.js.ini rename tests/wpt/tests/fetch/api/{idlharness.any.js => idlharness.https.any.js} (100%) diff --git a/components/script/body.rs b/components/script/body.rs index 7a763071d87..33ef7e5d698 100644 --- a/components/script/body.rs +++ b/components/script/body.rs @@ -346,11 +346,15 @@ impl Callback for TransmitBodyPromiseRejectionHandler { } } -/// The result of +/// pub(crate) struct ExtractedBody { + /// pub(crate) stream: DomRoot, + /// pub(crate) source: BodySource, + /// pub(crate) total_bytes: Option, + /// pub(crate) content_type: Option, } diff --git a/components/script/dom/abortsignal.rs b/components/script/dom/abortsignal.rs index a2fc7ce03ef..e659fa99854 100644 --- a/components/script/dom/abortsignal.rs +++ b/components/script/dom/abortsignal.rs @@ -26,7 +26,7 @@ use crate::dom::bindings::str::DOMString; use crate::dom::eventtarget::EventTarget; use crate::dom::globalscope::GlobalScope; use crate::dom::readablestream::PipeTo; -use crate::fetch::FetchContext; +use crate::fetch::{DeferredFetchRecord, FetchContext}; use crate::realms::InRealm; use crate::script_runtime::{CanGc, JSContext as SafeJSContext}; @@ -49,6 +49,12 @@ pub(crate) enum AbortAlgorithm { #[conditional_malloc_size_of] Arc>, ), + /// + FetchLater( + #[no_trace] + #[conditional_malloc_size_of] + Arc>, + ), } #[derive(Clone, JSTraceable, MallocSizeOf)] @@ -190,6 +196,9 @@ impl AbortSignal { .unwrap() .abort_fetch(reason.handle(), cx, can_gc); }, + AbortAlgorithm::FetchLater(deferred_fetch_record) => { + deferred_fetch_record.lock().unwrap().abort(); + }, AbortAlgorithm::DomEventListener(removable_listener) => { removable_listener .event_target diff --git a/components/script/dom/fetchlaterresult.rs b/components/script/dom/fetchlaterresult.rs new file mode 100644 index 00000000000..df87800ff8c --- /dev/null +++ b/components/script/dom/fetchlaterresult.rs @@ -0,0 +1,59 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use std::sync::{Arc, Mutex}; + +use dom_struct::dom_struct; + +use crate::dom::bindings::codegen::Bindings::FetchLaterResultBinding::FetchLaterResultMethods; +use crate::dom::bindings::reflector::{Reflector, reflect_dom_object}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::window::Window; +use crate::fetch::DeferredFetchRecord; +use crate::script_runtime::CanGc; + +/// +#[dom_struct] +pub(crate) struct FetchLaterResult { + reflector_: Reflector, + + /// + #[conditional_malloc_size_of] + #[no_trace] + activated_getter_steps: Arc>, +} + +impl FetchLaterResult { + #[cfg_attr(crown, allow(crown::unrooted_must_root))] + fn new_inherited(activated_getter_steps: Arc>) -> FetchLaterResult { + FetchLaterResult { + reflector_: Reflector::new(), + activated_getter_steps, + } + } + + #[cfg_attr(crown, allow(crown::unrooted_must_root))] + pub(crate) fn new( + window: &Window, + activated_getter_steps: Arc>, + can_gc: CanGc, + ) -> DomRoot { + reflect_dom_object( + Box::new(FetchLaterResult::new_inherited(activated_getter_steps)), + window, + can_gc, + ) + } +} + +impl FetchLaterResultMethods for FetchLaterResult { + /// + fn Activated(&self) -> bool { + // The activated getter steps are to return the result of running this’s activated getter steps. + self.activated_getter_steps + .lock() + .expect("Activated getter not accessible") + .activated_getter_steps() + } +} diff --git a/components/script/dom/mod.rs b/components/script/dom/mod.rs index 5be03ba6d69..006750b7e68 100644 --- a/components/script/dom/mod.rs +++ b/components/script/dom/mod.rs @@ -314,6 +314,7 @@ pub(crate) mod eventsource; pub(crate) mod eventtarget; pub(crate) mod extendableevent; pub(crate) mod extendablemessageevent; +pub(crate) mod fetchlaterresult; pub(crate) mod file; pub(crate) mod filelist; pub(crate) mod filereader; diff --git a/components/script/dom/request.rs b/components/script/dom/request.rs index 3eeeb7fc6ae..96bf356889f 100644 --- a/components/script/dom/request.rs +++ b/components/script/dom/request.rs @@ -92,7 +92,7 @@ impl Request { } // https://fetch.spec.whatwg.org/#dom-request - fn constructor( + pub(crate) fn constructor( global: &GlobalScope, proto: Option, can_gc: CanGc, diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index 26955d6f314..5c4e57b2316 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -111,10 +111,11 @@ use crate::dom::bindings::codegen::Bindings::ImageBitmapBinding::{ }; use crate::dom::bindings::codegen::Bindings::MediaQueryListBinding::MediaQueryList_Binding::MediaQueryListMethods; use crate::dom::bindings::codegen::Bindings::ReportingObserverBinding::Report; -use crate::dom::bindings::codegen::Bindings::RequestBinding::RequestInit; +use crate::dom::bindings::codegen::Bindings::RequestBinding::{RequestInfo, RequestInit}; use crate::dom::bindings::codegen::Bindings::VoidFunctionBinding::VoidFunction; use crate::dom::bindings::codegen::Bindings::WindowBinding::{ - self, FrameRequestCallback, ScrollBehavior, WindowMethods, WindowPostMessageOptions, + self, DeferredRequestInit, FrameRequestCallback, ScrollBehavior, WindowMethods, + WindowPostMessageOptions, }; use crate::dom::bindings::codegen::UnionTypes::{ RequestOrUSVString, TrustedScriptOrString, TrustedScriptOrStringOrFunction, @@ -140,6 +141,7 @@ use crate::dom::document::{AnimationFrameCallback, Document}; use crate::dom::element::Element; use crate::dom::event::{Event, EventBubbles, EventCancelable}; use crate::dom::eventtarget::EventTarget; +use crate::dom::fetchlaterresult::FetchLaterResult; use crate::dom::globalscope::GlobalScope; use crate::dom::hashchangeevent::HashChangeEvent; use crate::dom::history::History; @@ -1821,6 +1823,16 @@ impl WindowMethods for Window { fetch::Fetch(self.upcast(), input, init, comp, can_gc) } + /// + fn FetchLater( + &self, + input: RequestInfo, + init: RootedTraceableBox, + can_gc: CanGc, + ) -> Fallible> { + fetch::FetchLater(self, input, init, can_gc) + } + #[cfg(feature = "bluetooth")] fn TestRunner(&self) -> DomRoot { self.test_runner diff --git a/components/script/fetch.rs b/components/script/fetch.rs index ae9b401f7e6..1776a46fe8b 100644 --- a/components/script/fetch.rs +++ b/components/script/fetch.rs @@ -2,13 +2,17 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +use std::cell::Cell; use std::rc::Rc; use std::sync::{Arc, Mutex}; +use std::time::Duration; use base::id::WebViewId; use ipc_channel::ipc; +use js::jsapi::{ExceptionStackBehavior, JS_IsExceptionPending}; use js::jsval::UndefinedValue; use js::rust::HandleValue; +use js::rust::wrappers::JS_SetPendingException; use net_traits::policy_container::{PolicyContainer, RequestPolicyContainer}; use net_traits::request::{ CorsSettings, CredentialsMode, Destination, InsecureRequestsPolicy, Referrer, @@ -20,6 +24,7 @@ use net_traits::{ ResourceTimingType, cancel_async_fetch, }; use servo_url::ServoUrl; +use timers::TimerEventRequest; use crate::body::BodyMixin; use crate::dom::abortsignal::AbortAlgorithm; @@ -29,14 +34,17 @@ use crate::dom::bindings::codegen::Bindings::RequestBinding::{ }; use crate::dom::bindings::codegen::Bindings::ResponseBinding::Response_Binding::ResponseMethods; use crate::dom::bindings::codegen::Bindings::ResponseBinding::ResponseType as DOMResponseType; -use crate::dom::bindings::error::Error; +use crate::dom::bindings::codegen::Bindings::WindowBinding::{DeferredRequestInit, WindowMethods}; +use crate::dom::bindings::error::{Error, Fallible}; use crate::dom::bindings::import::module::SafeJSContext; use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::num::Finite; use crate::dom::bindings::refcounted::{Trusted, TrustedPromise}; use crate::dom::bindings::reflector::DomGlobal; use crate::dom::bindings::root::DomRoot; use crate::dom::bindings::trace::RootedTraceableBox; use crate::dom::csp::{GlobalCspReporting, Violation}; +use crate::dom::fetchlaterresult::FetchLaterResult; use crate::dom::globalscope::GlobalScope; use crate::dom::headers::Guard; use crate::dom::performanceresourcetiming::InitiatorType; @@ -44,6 +52,7 @@ use crate::dom::promise::Promise; use crate::dom::request::Request; use crate::dom::response::Response; use crate::dom::serviceworkerglobalscope::ServiceWorkerGlobalScope; +use crate::dom::window::Window; use crate::network_listener::{self, PreInvoke, ResourceTimingListener, submit_timing_data}; use crate::realms::{InRealm, enter_realm}; use crate::script_runtime::CanGc; @@ -266,6 +275,189 @@ pub(crate) fn Fetch( promise } +/// +fn queue_deferred_fetch( + request: NetTraitsRequest, + activate_after: Finite, + global: &GlobalScope, +) -> Arc> { + let trusted_global = Trusted::new(global); + // Step 1. Populate request from client given request. + // TODO + // Step 2. Set request’s service-workers mode to "none". + // TODO + // Step 3. Set request’s keepalive to true. + // TODO + // Step 4. Let deferredRecord be a new deferred fetch record whose request is request, and whose notify invoked is onActivatedWithoutTermination. + let deferred_record = Arc::new(Mutex::new(DeferredFetchRecord { + request, + global: trusted_global.clone(), + invoke_state: Cell::new(DeferredFetchRecordInvokeState::Pending), + activated: Cell::new(false), + })); + // Step 5. Append deferredRecord to request’s client’s fetch group’s deferred fetch records. + // TODO + // Step 6. If activateAfter is non-null, then run the following steps in parallel: + let deferred_record_clone = deferred_record.clone(); + global.schedule_timer(TimerEventRequest { + callback: Box::new(move || { + // Step 6.2. Process deferredRecord. + deferred_record_clone.lock().unwrap().process(); + + // Last step of https://fetch.spec.whatwg.org/#process-a-deferred-fetch + // + // Step 4. Queue a global task on the deferred fetch task source with + // deferredRecord’s request’s client’s global object to run deferredRecord’s notify invoked. + let deferred_record_clone = deferred_record_clone.clone(); + trusted_global + .root() + .task_manager() + .deferred_fetch_task_source() + .queue(task!(notify_deferred_record: move || { + deferred_record_clone.lock().unwrap().activate(); + })); + }), + // Step 6.1. The user agent should wait until any of the following conditions is met: + duration: Duration::from_millis(*activate_after as u64), + }); + // Step 7. Return deferredRecord. + deferred_record +} + +/// +#[allow(non_snake_case, unsafe_code)] +pub(crate) fn FetchLater( + window: &Window, + input: RequestInfo, + init: RootedTraceableBox, + can_gc: CanGc, +) -> Fallible> { + let global_scope = window.upcast(); + // Step 1. Let requestObject be the result of invoking the initial value + // of Request as constructor with input and init as arguments. + let request_object = Request::constructor(global_scope, None, can_gc, input, &init.parent)?; + // Step 2. If requestObject’s signal is aborted, then throw signal’s abort reason. + let signal = request_object.Signal(); + if signal.aborted() { + let cx = GlobalScope::get_cx(); + rooted!(in(*cx) let mut abort_reason = UndefinedValue()); + signal.Reason(cx, abort_reason.handle_mut()); + unsafe { + assert!(!JS_IsExceptionPending(*cx)); + JS_SetPendingException(*cx, abort_reason.handle(), ExceptionStackBehavior::Capture); + } + return Err(Error::JSFailed); + } + // Step 3. Let request be requestObject’s request. + let request = request_object.get_request(); + // Step 4. Let activateAfter be null. + let mut activate_after = Finite::wrap(0_f64); + // Step 5. If init is given and init["activateAfter"] exists, then set + // activateAfter to init["activateAfter"]. + if let Some(init_activate_after) = init.activateAfter.as_ref() { + activate_after = *init_activate_after; + } + // Step 6. If activateAfter is less than 0, then throw a RangeError. + if *activate_after < 0.0 { + return Err(Error::Range("activateAfter must be at least 0".to_owned())); + } + // Step 7. If this’s relevant global object’s associated document is not fully active, then throw a TypeError. + if !window.Document().is_fully_active() { + return Err(Error::Type("Document is not fully active".to_owned())); + } + let url = request.url(); + // Step 8. If request’s URL’s scheme is not an HTTP(S) scheme, then throw a TypeError. + if !matches!(url.scheme(), "http" | "https") { + return Err(Error::Type("URL is not http(s)".to_owned())); + } + // Step 9. If request’s URL is not a potentially trustworthy URL, then throw a SecurityError. + if !url.is_potentially_trustworthy() { + return Err(Error::Type("URL is not trustworthy".to_owned())); + } + // Step 10. If request’s body is not null, and request’s body length is null, then throw a TypeError. + if let Some(body) = request.body.as_ref() { + if body.len().is_none() { + return Err(Error::Type("Body is null".to_owned())); + } + } + // Step 11. If the available deferred-fetch quota given request’s client and request’s URL’s + // origin is less than request’s total request length, then throw a "QuotaExceededError" DOMException. + // TODO + // Step 12. Let activated be false. + // Step 13. Let deferredRecord be the result of calling queue a deferred fetch given request, + // activateAfter, and the following step: set activated to true. + let deferred_record = queue_deferred_fetch(request, activate_after, global_scope); + // Step 14. Add the following abort steps to requestObject’s signal: Set deferredRecord’s invoke state to "aborted". + signal.add(&AbortAlgorithm::FetchLater(deferred_record.clone())); + // Step 15. Return a new FetchLaterResult whose activated getter steps are to return activated. + Ok(FetchLaterResult::new(window, deferred_record, can_gc)) +} + +/// +#[derive(Clone, Copy, MallocSizeOf, PartialEq)] +enum DeferredFetchRecordInvokeState { + Pending, + Sent, + Aborted, +} + +/// +#[derive(MallocSizeOf)] +pub(crate) struct DeferredFetchRecord { + /// + request: NetTraitsRequest, + /// + invoke_state: Cell, + global: Trusted, + activated: Cell, +} + +impl DeferredFetchRecord { + /// Part of step 13 of + fn activate(&self) { + // and the following step: set activated to true. + self.activated.set(true); + } + /// Part of step 14 of + pub(crate) fn abort(&self) { + // Set deferredRecord’s invoke state to "aborted". + self.invoke_state + .set(DeferredFetchRecordInvokeState::Aborted); + } + /// Part of step 15 of + pub(crate) fn activated_getter_steps(&self) -> bool { + // whose activated getter steps are to return activated. + self.activated.get() + } + /// + fn process(&self) { + // Step 1. If deferredRecord’s invoke state is not "pending", then return. + if self.invoke_state.get() != DeferredFetchRecordInvokeState::Pending { + return; + } + // Step 2. Set deferredRecord’s invoke state to "sent". + self.invoke_state.set(DeferredFetchRecordInvokeState::Sent); + // Step 3. Fetch deferredRecord’s request. + let url = self.request.url().clone(); + let fetch_later_listener = Arc::new(Mutex::new(FetchLaterListener { + url, + resource_timing: ResourceFetchTiming::new(ResourceTimingType::Resource), + global: self.global.clone(), + })); + let global = self.global.root(); + let _realm = enter_realm(&*global); + let mut request_init = request_init_from_request(self.request.clone()); + request_init.policy_container = + RequestPolicyContainer::PolicyContainer(global.policy_container()); + global.fetch( + request_init, + fetch_later_listener, + global.task_manager().networking_task_source().to_sendable(), + ); + // Step 4 is handled by caller + } +} + #[derive(JSTraceable, MallocSizeOf)] pub(crate) struct FetchContext { #[ignore_malloc_size_of = "unclear ownership semantics"] @@ -453,6 +645,74 @@ impl ResourceTimingListener for FetchContext { } } +struct FetchLaterListener { + /// URL of this request. + url: ServoUrl, + /// Timing data for this resource. + resource_timing: ResourceFetchTiming, + /// The global object fetching the report uri violation + global: Trusted, +} + +impl FetchResponseListener for FetchLaterListener { + fn process_request_body(&mut self, _: RequestId) {} + + fn process_request_eof(&mut self, _: RequestId) {} + + fn process_response( + &mut self, + _: RequestId, + fetch_metadata: Result, + ) { + _ = fetch_metadata; + } + + fn process_response_chunk(&mut self, _: RequestId, chunk: Vec) { + _ = chunk; + } + + fn process_response_eof( + &mut self, + _: RequestId, + response: Result, + ) { + _ = response; + } + + fn resource_timing_mut(&mut self) -> &mut ResourceFetchTiming { + &mut self.resource_timing + } + + fn resource_timing(&self) -> &ResourceFetchTiming { + &self.resource_timing + } + + fn submit_resource_timing(&mut self) { + network_listener::submit_timing(self, CanGc::note()) + } + + fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec) { + let global = self.resource_timing_global(); + global.report_csp_violations(violations, None, None); + } +} + +impl ResourceTimingListener for FetchLaterListener { + fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) { + (InitiatorType::Fetch, self.url.clone()) + } + + fn resource_timing_global(&self) -> DomRoot { + self.global.root() + } +} + +impl PreInvoke for FetchLaterListener { + fn should_invoke(&self) -> bool { + true + } +} + fn fill_headers_with_metadata(r: DomRoot, m: Metadata, can_gc: CanGc) { r.set_headers(m.headers, can_gc); r.set_status(&m.status); diff --git a/components/script/task_manager.rs b/components/script/task_manager.rs index b963082c869..b35f7ec1e24 100644 --- a/components/script/task_manager.rs +++ b/components/script/task_manager.rs @@ -137,6 +137,7 @@ impl TaskManager { task_source_functions!(self, clipboard_task_source, Clipboard); task_source_functions!(self, crypto_task_source, Crypto); task_source_functions!(self, database_access_task_source, DatabaseAccess); + task_source_functions!(self, deferred_fetch_task_source, DeferredFetch); task_source_functions!(self, dom_manipulation_task_source, DOMManipulation); task_source_functions!(self, file_reading_task_source, FileReading); task_source_functions!(self, font_loading_task_source, FontLoading); diff --git a/components/script/task_source.rs b/components/script/task_source.rs index af133f5484e..13b17b9b9a7 100644 --- a/components/script/task_source.rs +++ b/components/script/task_source.rs @@ -29,6 +29,8 @@ pub(crate) enum TaskSourceName { /// Crypto, DatabaseAccess, + /// + DeferredFetch, DOMManipulation, FileReading, /// @@ -62,6 +64,7 @@ impl From for ScriptThreadEventCategory { TaskSourceName::Clipboard => ScriptThreadEventCategory::ScriptEvent, TaskSourceName::Crypto => ScriptThreadEventCategory::ScriptEvent, TaskSourceName::DatabaseAccess => ScriptThreadEventCategory::ScriptEvent, + TaskSourceName::DeferredFetch => ScriptThreadEventCategory::NetworkEvent, TaskSourceName::DOMManipulation => ScriptThreadEventCategory::ScriptEvent, TaskSourceName::FileReading => ScriptThreadEventCategory::FileRead, TaskSourceName::FontLoading => ScriptThreadEventCategory::FontLoading, diff --git a/components/script_bindings/codegen/Bindings.conf b/components/script_bindings/codegen/Bindings.conf index 2b96a6ffa1d..4f50cccb619 100644 --- a/components/script_bindings/codegen/Bindings.conf +++ b/components/script_bindings/codegen/Bindings.conf @@ -659,7 +659,7 @@ DOMInterfaces = { }, 'Window': { - 'canGc': ['CreateImageBitmap', 'CreateImageBitmap_', 'CookieStore', 'Fetch', 'Open', 'SetInterval', 'SetTimeout', 'Stop', 'TrustedTypes', 'WebdriverCallback', 'WebdriverException'], + 'canGc': ['CreateImageBitmap', 'CreateImageBitmap_', 'CookieStore', 'Fetch', 'FetchLater', 'Open', 'SetInterval', 'SetTimeout', 'Stop', 'TrustedTypes', 'WebdriverCallback', 'WebdriverException'], 'inRealms': ['Fetch', 'GetOpener', 'WebdriverCallback', 'WebdriverException'], 'additionalTraits': ['crate::interfaces::WindowHelpers'], }, diff --git a/components/script_bindings/webidls/FetchLaterResult.webidl b/components/script_bindings/webidls/FetchLaterResult.webidl new file mode 100644 index 00000000000..b43685ff437 --- /dev/null +++ b/components/script_bindings/webidls/FetchLaterResult.webidl @@ -0,0 +1,9 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +// https://fetch.spec.whatwg.org/#fetch-method +[Exposed=Window] +interface FetchLaterResult { + readonly attribute boolean activated; +}; diff --git a/components/script_bindings/webidls/Window.webidl b/components/script_bindings/webidls/Window.webidl index f62842a22b6..5c6976ab80d 100644 --- a/components/script_bindings/webidls/Window.webidl +++ b/components/script_bindings/webidls/Window.webidl @@ -189,3 +189,12 @@ partial interface Window { dictionary WindowPostMessageOptions : StructuredSerializeOptions { USVString targetOrigin = "/"; }; + +// https://fetch.spec.whatwg.org/#fetch-method +dictionary DeferredRequestInit : RequestInit { + DOMHighResTimeStamp activateAfter; +}; + +partial interface Window { + [NewObject, SecureContext, Throws] FetchLaterResult fetchLater(RequestInfo input, optional DeferredRequestInit init = {}); +}; diff --git a/tests/wpt/include.ini b/tests/wpt/include.ini index 4c76805a2cd..bfd28f15faa 100644 --- a/tests/wpt/include.ini +++ b/tests/wpt/include.ini @@ -140,6 +140,10 @@ skip: true skip: false [fetch] skip: false + [fetch-later] + skip: false + [quota] + skip: true [FileAPI] skip: false [focus] diff --git a/tests/wpt/meta/MANIFEST.json b/tests/wpt/meta/MANIFEST.json index fe5e2a170a8..2ba1769cc1a 100644 --- a/tests/wpt/meta/MANIFEST.json +++ b/tests/wpt/meta/MANIFEST.json @@ -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", {} diff --git a/tests/wpt/meta/fetch/api/idlharness.any.js.ini b/tests/wpt/meta/fetch/api/idlharness.https.any.js.ini similarity index 57% rename from tests/wpt/meta/fetch/api/idlharness.any.js.ini rename to tests/wpt/meta/fetch/api/idlharness.https.any.js.ini index 4ef69983c44..bf8ae023c8f 100644 --- a/tests/wpt/meta/fetch/api/idlharness.any.js.ini +++ b/tests/wpt/meta/fetch/api/idlharness.https.any.js.ini @@ -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 diff --git a/tests/wpt/meta/fetch/fetch-later/__dir__.ini b/tests/wpt/meta/fetch/fetch-later/__dir__.ini new file mode 100644 index 00000000000..e79af6880d5 --- /dev/null +++ b/tests/wpt/meta/fetch/fetch-later/__dir__.ini @@ -0,0 +1,3 @@ +prefs: [ + "dom_abort_controller_enabled:true", +] diff --git a/tests/wpt/meta/fetch/fetch-later/activate-after.https.window.js.ini b/tests/wpt/meta/fetch/fetch-later/activate-after.https.window.js.ini index 332559376b9..4f22c0aba50 100644 --- a/tests/wpt/meta/fetch/fetch-later/activate-after.https.window.js.ini +++ b/tests/wpt/meta/fetch/fetch-later/activate-after.https.window.js.ini @@ -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 diff --git a/tests/wpt/meta/fetch/fetch-later/basic.https.window.js.ini b/tests/wpt/meta/fetch/fetch-later/basic.https.window.js.ini index ab05bff9379..0a836517db9 100644 --- a/tests/wpt/meta/fetch/fetch-later/basic.https.window.js.ini +++ b/tests/wpt/meta/fetch/fetch-later/basic.https.window.js.ini @@ -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 diff --git a/tests/wpt/meta/fetch/fetch-later/headers/header-referrer-no-referrer-when-downgrade.https.html.ini b/tests/wpt/meta/fetch/fetch-later/headers/header-referrer-no-referrer-when-downgrade.https.html.ini deleted file mode 100644 index a5f1007fef0..00000000000 --- a/tests/wpt/meta/fetch/fetch-later/headers/header-referrer-no-referrer-when-downgrade.https.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[header-referrer-no-referrer-when-downgrade.https.html] - [Test referer header https://web-platform.test:8443] - expected: FAIL diff --git a/tests/wpt/meta/fetch/fetch-later/headers/header-referrer-no-referrer.https.html.ini b/tests/wpt/meta/fetch/fetch-later/headers/header-referrer-no-referrer.https.html.ini deleted file mode 100644 index 2b80d6e8efe..00000000000 --- a/tests/wpt/meta/fetch/fetch-later/headers/header-referrer-no-referrer.https.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[header-referrer-no-referrer.https.html] - [Test referer header ] - expected: FAIL diff --git a/tests/wpt/meta/fetch/fetch-later/headers/header-referrer-origin-when-cross-origin.https.html.ini b/tests/wpt/meta/fetch/fetch-later/headers/header-referrer-origin-when-cross-origin.https.html.ini deleted file mode 100644 index 0c8817e4ed8..00000000000 --- a/tests/wpt/meta/fetch/fetch-later/headers/header-referrer-origin-when-cross-origin.https.html.ini +++ /dev/null @@ -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 diff --git a/tests/wpt/meta/fetch/fetch-later/headers/header-referrer-origin.https.html.ini b/tests/wpt/meta/fetch/fetch-later/headers/header-referrer-origin.https.html.ini deleted file mode 100644 index d90fbd4038f..00000000000 --- a/tests/wpt/meta/fetch/fetch-later/headers/header-referrer-origin.https.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[header-referrer-origin.https.html] - [Test referer header https://www1.web-platform.test:8443] - expected: FAIL diff --git a/tests/wpt/meta/fetch/fetch-later/headers/header-referrer-same-origin.https.html.ini b/tests/wpt/meta/fetch/fetch-later/headers/header-referrer-same-origin.https.html.ini deleted file mode 100644 index 9e066e27b3b..00000000000 --- a/tests/wpt/meta/fetch/fetch-later/headers/header-referrer-same-origin.https.html.ini +++ /dev/null @@ -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 diff --git a/tests/wpt/meta/fetch/fetch-later/headers/header-referrer-strict-origin-when-cross-origin.https.html.ini b/tests/wpt/meta/fetch/fetch-later/headers/header-referrer-strict-origin-when-cross-origin.https.html.ini deleted file mode 100644 index 704c85d9710..00000000000 --- a/tests/wpt/meta/fetch/fetch-later/headers/header-referrer-strict-origin-when-cross-origin.https.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[header-referrer-strict-origin-when-cross-origin.https.html] - [Test referer header https://www1.web-platform.test:8443] - expected: FAIL diff --git a/tests/wpt/meta/fetch/fetch-later/headers/header-referrer-strict-origin.https.html.ini b/tests/wpt/meta/fetch/fetch-later/headers/header-referrer-strict-origin.https.html.ini deleted file mode 100644 index 7a815b6c043..00000000000 --- a/tests/wpt/meta/fetch/fetch-later/headers/header-referrer-strict-origin.https.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[header-referrer-strict-origin.https.html] - [Test referer header https://web-platform.test:8443] - expected: FAIL diff --git a/tests/wpt/meta/fetch/fetch-later/headers/header-referrer-unsafe-url.https.html.ini b/tests/wpt/meta/fetch/fetch-later/headers/header-referrer-unsafe-url.https.html.ini deleted file mode 100644 index 8e1aef08ba1..00000000000 --- a/tests/wpt/meta/fetch/fetch-later/headers/header-referrer-unsafe-url.https.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[header-referrer-unsafe-url.https.html] - [Test referer header https://web-platform.test:8443] - expected: FAIL diff --git a/tests/wpt/meta/fetch/fetch-later/iframe.https.window.js.ini b/tests/wpt/meta/fetch/fetch-later/iframe.https.window.js.ini deleted file mode 100644 index f2646d25393..00000000000 --- a/tests/wpt/meta/fetch/fetch-later/iframe.https.window.js.ini +++ /dev/null @@ -1,4 +0,0 @@ -[iframe.https.window.html] - expected: ERROR - [A blank iframe can trigger fetchLater.] - expected: FAIL diff --git a/tests/wpt/meta/fetch/fetch-later/new-window.https.window.js.ini b/tests/wpt/meta/fetch/fetch-later/new-window.https.window.js.ini deleted file mode 100644 index f85f2bbfcbd..00000000000 --- a/tests/wpt/meta/fetch/fetch-later/new-window.https.window.js.ini +++ /dev/null @@ -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 diff --git a/tests/wpt/meta/fetch/fetch-later/permissions-policy/deferred-fetch-allowed-by-permissions-policy.https.window.js.ini b/tests/wpt/meta/fetch/fetch-later/permissions-policy/deferred-fetch-allowed-by-permissions-policy.https.window.js.ini index c26553b1ab4..9ad6e454489 100644 --- a/tests/wpt/meta/fetch/fetch-later/permissions-policy/deferred-fetch-allowed-by-permissions-policy.https.window.js.ini +++ b/tests/wpt/meta/fetch/fetch-later/permissions-policy/deferred-fetch-allowed-by-permissions-policy.https.window.js.ini @@ -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 diff --git a/tests/wpt/meta/fetch/fetch-later/permissions-policy/deferred-fetch-default-permissions-policy.https.window.js.ini b/tests/wpt/meta/fetch/fetch-later/permissions-policy/deferred-fetch-default-permissions-policy.https.window.js.ini deleted file mode 100644 index 3c0c167c671..00000000000 --- a/tests/wpt/meta/fetch/fetch-later/permissions-policy/deferred-fetch-default-permissions-policy.https.window.js.ini +++ /dev/null @@ -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 diff --git a/tests/wpt/meta/fetch/fetch-later/policies/csp-allowed.https.window.js.ini b/tests/wpt/meta/fetch/fetch-later/policies/csp-allowed.https.window.js.ini deleted file mode 100644 index 3922b6169ce..00000000000 --- a/tests/wpt/meta/fetch/fetch-later/policies/csp-allowed.https.window.js.ini +++ /dev/null @@ -1,3 +0,0 @@ -[csp-allowed.https.window.html] - [FetchLater allowed by CSP should succeed] - expected: FAIL diff --git a/tests/wpt/meta/fetch/fetch-later/policies/csp-blocked.https.window.js.ini b/tests/wpt/meta/fetch/fetch-later/policies/csp-blocked.https.window.js.ini deleted file mode 100644 index 02b75ea17ea..00000000000 --- a/tests/wpt/meta/fetch/fetch-later/policies/csp-blocked.https.window.js.ini +++ /dev/null @@ -1,3 +0,0 @@ -[csp-blocked.https.window.html] - [FetchLater blocked by CSP should reject] - expected: FAIL diff --git a/tests/wpt/meta/fetch/fetch-later/send-on-deactivate.https.window.js.ini b/tests/wpt/meta/fetch/fetch-later/send-on-deactivate.https.window.js.ini index 062e17eb388..b4ccdf8c729 100644 --- a/tests/wpt/meta/fetch/fetch-later/send-on-deactivate.https.window.js.ini +++ b/tests/wpt/meta/fetch/fetch-later/send-on-deactivate.https.window.js.ini @@ -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 diff --git a/tests/wpt/meta/fetch/fetch-later/send-on-discard/not-send-after-abort.https.window.js.ini b/tests/wpt/meta/fetch/fetch-later/send-on-discard/not-send-after-abort.https.window.js.ini deleted file mode 100644 index 5b9a76ac086..00000000000 --- a/tests/wpt/meta/fetch/fetch-later/send-on-discard/not-send-after-abort.https.window.js.ini +++ /dev/null @@ -1,3 +0,0 @@ -[not-send-after-abort.https.window.html] - [A discarded document does not send an already aborted fetchLater request.] - expected: FAIL diff --git a/tests/wpt/meta/fetch/fetch-later/send-on-discard/send-multiple.https.window.js.ini b/tests/wpt/meta/fetch/fetch-later/send-on-discard/send-multiple.https.window.js.ini deleted file mode 100644 index 603c68bef87..00000000000 --- a/tests/wpt/meta/fetch/fetch-later/send-on-discard/send-multiple.https.window.js.ini +++ /dev/null @@ -1,3 +0,0 @@ -[send-multiple.https.window.html] - [A discarded document sends all its fetchLater requests.] - expected: FAIL diff --git a/tests/wpt/mozilla/meta/MANIFEST.json b/tests/wpt/mozilla/meta/MANIFEST.json index faf104a959d..102534b4e13 100644 --- a/tests/wpt/mozilla/meta/MANIFEST.json +++ b/tests/wpt/mozilla/meta/MANIFEST.json @@ -13775,7 +13775,7 @@ ] ], "interfaces.https.html": [ - "1397b723a1cb001521ac1b2032c380f1e02cf1f0", + "027e138f4af5e2a13df9bf8c78765dcf57f6b611", [ null, {} diff --git a/tests/wpt/mozilla/tests/mozilla/interfaces.https.html b/tests/wpt/mozilla/tests/mozilla/interfaces.https.html index 1397b723a1c..027e138f4af 100644 --- a/tests/wpt/mozilla/tests/mozilla/interfaces.https.html +++ b/tests/wpt/mozilla/tests/mozilla/interfaces.https.html @@ -92,6 +92,7 @@ test_interfaces([ "Event", "EventSource", "EventTarget", + "FetchLaterResult", "File", "FileList", "FileReader", diff --git a/tests/wpt/tests/fetch/api/idlharness.any.js b/tests/wpt/tests/fetch/api/idlharness.https.any.js similarity index 100% rename from tests/wpt/tests/fetch/api/idlharness.any.js rename to tests/wpt/tests/fetch/api/idlharness.https.any.js diff --git a/tests/wpt/tests/fetch/fetch-later/basic.https.window.js b/tests/wpt/tests/fetch/fetch-later/basic.https.window.js index f3ed42fe35a..afedbf5d4c4 100644 --- a/tests/wpt/tests/fetch/fetch-later/basic.https.window.js +++ b/tests/wpt/tests/fetch/fetch-later/basic.https.window.js @@ -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'));