Add spec steps and comments for fetch abort steps (#39283)

While trying to figure out what the status of this implementation was, I
added steps and comments to
see what we are missing. Also updated some links,
since I couldn't find an implementation of
`window.fetch`, since the spec URL was pointing
to the chapter instead of the algorithm.

Part of #34866

Signed-off-by: Tim van der Lippe <tvanderlippe@gmail.com>
This commit is contained in:
Tim van der Lippe 2025-09-13 20:34:14 +02:00 committed by GitHub
parent 2f252c9b78
commit 3ef3ba9378
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 78 additions and 41 deletions

View file

@ -18,7 +18,7 @@ use crate::script_runtime::{CanGc, JSContext};
pub(crate) struct AbortController { pub(crate) struct AbortController {
reflector_: Reflector, reflector_: Reflector,
/// An AbortController object has an associated signal (an AbortSignal object). /// <https://dom.spec.whatwg.org/#dom-abortcontroller-signal>
signal: Dom<AbortSignal>, signal: Dom<AbortSignal>,
} }
@ -27,7 +27,7 @@ impl AbortController {
fn new_inherited(signal: &AbortSignal) -> AbortController { fn new_inherited(signal: &AbortSignal) -> AbortController {
// Note: continuation of the constructor steps. // Note: continuation of the constructor steps.
// Set thiss signal to signal. // Step 2. Set thiss signal to signal.
AbortController { AbortController {
reflector_: Reflector::new(), reflector_: Reflector::new(),
signal: Dom::from_ref(signal), signal: Dom::from_ref(signal),
@ -40,9 +40,9 @@ impl AbortController {
proto: Option<HandleObject>, proto: Option<HandleObject>,
can_gc: CanGc, can_gc: CanGc,
) -> DomRoot<AbortController> { ) -> DomRoot<AbortController> {
// The new AbortController() constructor steps are: // Step 1. Let signal be a new AbortSignal object.
// Let signal be a new AbortSignal object.
let signal = AbortSignal::new_with_proto(global, None, can_gc); let signal = AbortSignal::new_with_proto(global, None, can_gc);
// Step 2. Set thiss signal to signal.
reflect_dom_object_with_proto( reflect_dom_object_with_proto(
Box::new(AbortController::new_inherited(&signal)), Box::new(AbortController::new_inherited(&signal)),
global, global,
@ -59,6 +59,7 @@ impl AbortController {
realm: InRealm, realm: InRealm,
can_gc: CanGc, can_gc: CanGc,
) { ) {
// To signal abort on an AbortController controller with an optional reason,
// signal abort on controllers signal with reason if it is given. // signal abort on controllers signal with reason if it is given.
self.signal.signal_abort(cx, reason, realm, can_gc); self.signal.signal_abort(cx, reason, realm, can_gc);
} }

View file

@ -82,42 +82,48 @@ impl AbortSignal {
) { ) {
let global = self.global(); let global = self.global();
// If signal is aborted, then return. // Step 1. If signal is aborted, then return.
if self.Aborted() { if self.Aborted() {
return; return;
} }
// Step 2. Set signals abort reason to reason if it is given;
// otherwise to a new "AbortError" DOMException.
let abort_reason = reason.get(); let abort_reason = reason.get();
// Set signals abort reason to reason if it is given;
if !abort_reason.is_undefined() { if !abort_reason.is_undefined() {
self.abort_reason.set(abort_reason); self.abort_reason.set(abort_reason);
} else { } else {
// otherwise to a new "AbortError" DOMException.
rooted!(in(*cx) let mut rooted_error = UndefinedValue()); rooted!(in(*cx) let mut rooted_error = UndefinedValue());
Error::Abort.to_jsval(cx, &global, rooted_error.handle_mut(), can_gc); Error::Abort.to_jsval(cx, &global, rooted_error.handle_mut(), can_gc);
self.abort_reason.set(rooted_error.get()) self.abort_reason.set(rooted_error.get())
} }
// Let dependentSignalsToAbort be a new list. // Step 3. Let dependentSignalsToAbort be a new list.
// For each dependentSignal of signals dependent signals: // TODO
// TODO: #36936 // Step 4. For each dependentSignal of signals dependent signals:
// TODO
// Step 4.1. If dependentSignal is not aborted:
// TODO
// Step 4.1.1. Set dependentSignals abort reason to signals abort reason.
// TODO
// Step 4.1.2. Append dependentSignal to dependentSignalsToAbort.
// TODO
// Run the abort steps for signal. // Step 5. Run the abort steps for signal.
self.run_the_abort_steps(cx, &global, realm, can_gc); self.run_the_abort_steps(cx, &global, realm, can_gc);
// For each dependentSignal of dependentSignalsToAbort, run the abort steps for dependentSignal. // Step 6. For each dependentSignal of dependentSignalsToAbort, run the abort steps for dependentSignal.
// TODO: #36936 // TODO
} }
/// <https://dom.spec.whatwg.org/#abortsignal-add> /// <https://dom.spec.whatwg.org/#abortsignal-add>
pub(crate) fn add(&self, algorithm: &AbortAlgorithm) { pub(crate) fn add(&self, algorithm: &AbortAlgorithm) {
// If signal is aborted, then return. // Step 1. If signal is aborted, then return.
if self.aborted() { if self.aborted() {
return; return;
} }
// Append algorithm to signals abort algorithms. // Step 2. Append algorithm to signals abort algorithms.
self.abort_algorithms.borrow_mut().push(algorithm.clone()); self.abort_algorithms.borrow_mut().push(algorithm.clone());
} }
@ -151,15 +157,14 @@ impl AbortSignal {
realm: InRealm, realm: InRealm,
can_gc: CanGc, can_gc: CanGc,
) { ) {
// For each algorithm of signals abort algorithms: run algorithm. // Step 1. For each algorithm of signals abort algorithms: run algorithm.
for algo in self.abort_algorithms.borrow().iter() { for algo in self.abort_algorithms.borrow().iter() {
self.run_abort_algorithm(cx, global, algo, realm, can_gc); self.run_abort_algorithm(cx, global, algo, realm, can_gc);
} }
// Step 2. Empty signals abort algorithms.
// Empty signals abort algorithms.
self.abort_algorithms.borrow_mut().clear(); self.abort_algorithms.borrow_mut().clear();
// Fire an event named abort at signal. // Step 3. Fire an event named abort at signal.
self.upcast::<EventTarget>() self.upcast::<EventTarget>()
.fire_event(atom!("abort"), can_gc); .fire_event(atom!("abort"), can_gc);
} }
@ -185,21 +190,21 @@ impl AbortSignalMethods<crate::DomTypeHolder> for AbortSignal {
reason: HandleValue, reason: HandleValue,
can_gc: CanGc, can_gc: CanGc,
) -> DomRoot<AbortSignal> { ) -> DomRoot<AbortSignal> {
// Let signal be a new AbortSignal object. // Step 1. Let signal be a new AbortSignal object.
let signal = AbortSignal::new_with_proto(global, None, can_gc); let signal = AbortSignal::new_with_proto(global, None, can_gc);
// Step 2. Set signals abort reason to reason if it is given;
// otherwise to a new "AbortError" DOMException.
let abort_reason = reason.get(); let abort_reason = reason.get();
// Set signals abort reason to reason if it is given;
if !abort_reason.is_undefined() { if !abort_reason.is_undefined() {
signal.abort_reason.set(abort_reason); signal.abort_reason.set(abort_reason);
} else { } else {
// otherwise to a new "AbortError" DOMException.
rooted!(in(*cx) let mut rooted_error = UndefinedValue()); rooted!(in(*cx) let mut rooted_error = UndefinedValue());
Error::Abort.to_jsval(cx, global, rooted_error.handle_mut(), can_gc); Error::Abort.to_jsval(cx, global, rooted_error.handle_mut(), can_gc);
signal.abort_reason.set(rooted_error.get()) signal.abort_reason.set(rooted_error.get())
} }
// Return signal. // Step 3. Return signal.
signal signal
} }

View file

@ -1798,7 +1798,7 @@ impl WindowMethods<crate::DomTypeHolder> for Window {
mql mql
} }
// https://fetch.spec.whatwg.org/#fetch-method /// <https://fetch.spec.whatwg.org/#dom-global-fetch>
fn Fetch( fn Fetch(
&self, &self,
input: RequestOrUSVString, input: RequestOrUSVString,

View file

@ -590,7 +590,7 @@ impl WorkerGlobalScopeMethods<crate::DomTypeHolder> for WorkerGlobalScope {
} }
#[cfg_attr(crown, allow(crown::unrooted_must_root))] #[cfg_attr(crown, allow(crown::unrooted_must_root))]
// https://fetch.spec.whatwg.org/#fetch-method /// <https://fetch.spec.whatwg.org/#dom-global-fetch>
fn Fetch( fn Fetch(
&self, &self,
input: RequestOrUSVString, input: RequestOrUSVString,

View file

@ -139,7 +139,7 @@ fn request_init_from_request(request: NetTraitsRequest) -> RequestBuilder {
} }
} }
/// <https://fetch.spec.whatwg.org/#fetch-method> /// <https://fetch.spec.whatwg.org/#dom-global-fetch>
#[allow(non_snake_case)] #[allow(non_snake_case)]
#[cfg_attr(crown, allow(crown::unrooted_must_root))] #[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn Fetch( pub(crate) fn Fetch(
@ -176,7 +176,12 @@ pub(crate) fn Fetch(
request_init.policy_container = request_init.policy_container =
RequestPolicyContainer::PolicyContainer(global.policy_container()); RequestPolicyContainer::PolicyContainer(global.policy_container());
// TODO: Step 4. If requestObjects signal is aborted, then: [..] // Step 4. If requestObjects signal is aborted, then:
// TODO
// Step 4.1. Abort the fetch() call with p, request, null, and requestObjects signals abort reason.
// TODO
// Step 4.2. Return p.
// TODO
// Step 5. Let globalObject be requests clients global object. // Step 5. Let globalObject be requests clients global object.
// NOTE: We already get the global object as an argument // NOTE: We already get the global object as an argument
@ -187,16 +192,48 @@ pub(crate) fn Fetch(
request_init.service_workers_mode = ServiceWorkersMode::None; request_init.service_workers_mode = ServiceWorkersMode::None;
} }
// TODO: Steps 8-11, abortcontroller stuff // Step 8. Let relevantRealm be thiss relevant realm.
//
// Is `comp` as argument
// Step 9. Let locallyAborted be false.
// TODO
// Step 10. Let controller be null.
// TODO
// Step 11. Add the following abort steps to requestObjects signal:
// TODO
// Step 11.1. Set locallyAborted to true.
// TODO
// Step 11.2. Assert: controller is non-null.
// TODO
// Step 11.3. Abort controller with requestObjects signals abort reason.
// TODO
// Step 11.4. Abort the fetch() call with p, request, responseObject, and requestObjects signals abort reason.
// TODO
// Step 12. Set controller to the result of calling fetch given request and // Step 12. Set controller to the result of calling fetch given request and
// processResponse given response being these steps: [..] // processResponse given response being these steps:
let fetch_context = Arc::new(Mutex::new(FetchContext { let fetch_context = Arc::new(Mutex::new(FetchContext {
fetch_promise: Some(TrustedPromise::new(promise.clone())), fetch_promise: Some(TrustedPromise::new(promise.clone())),
response_object: Trusted::new(&*response), response_object: Trusted::new(&*response),
resource_timing: ResourceFetchTiming::new(timing_type), resource_timing: ResourceFetchTiming::new(timing_type),
})); }));
// Step 12.1. If locallyAborted is true, then abort these steps.
// TODO
// Step 12.2. If responses aborted flag is set, then:
// TODO
// Step 12.2.1. Let deserializedError be the result of deserialize a serialized
// abort reason given controllers serialized abort reason and relevantRealm.
// TODO
// Step 12.2.2. Abort the fetch() call with p, request, responseObject, and deserializedError.
// TODO
// Step 12.2.3. Abort these steps.
// TODO
// Step 12.3. If response is a network error, then reject p with a TypeError and abort these steps.
// Step 12.4. Set responseObject to the result of creating a Response object, given response, "immutable", and relevantRealm.
// Step 12.5. Resolve p with responseObject.
global.fetch( global.fetch(
request_init, request_init,
fetch_context, fetch_context,

View file

@ -1,11 +0,0 @@
/* 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,Worker)]
partial interface mixin WindowOrWorkerGlobalScope {
[NewObject] Promise<Response> fetch(RequestInfo input, optional RequestInit init = {});
};

View file

@ -49,5 +49,10 @@ partial interface mixin WindowOrWorkerGlobalScope {
readonly attribute TrustedTypePolicyFactory trustedTypes; readonly attribute TrustedTypePolicyFactory trustedTypes;
}; };
// https://fetch.spec.whatwg.org/#fetch-method
partial interface mixin WindowOrWorkerGlobalScope {
[NewObject] Promise<Response> fetch(RequestInfo input, optional RequestInit init = {});
};
Window includes WindowOrWorkerGlobalScope; Window includes WindowOrWorkerGlobalScope;
WorkerGlobalScope includes WindowOrWorkerGlobalScope; WorkerGlobalScope includes WindowOrWorkerGlobalScope;