script: Do not start Fetch operations if they have been aborted by the AbortController (#39295)

The first step for aborting fetch calls. It only
has the case where the signal was already aborted
prior to fetch starting.

Part of #34866

Signed-off-by: Tim van der Lippe <tvanderlippe@gmail.com>
This commit is contained in:
Tim van der Lippe 2025-09-17 10:49:27 +02:00 committed by GitHub
parent 6deb42dbd5
commit 6cba44e0e3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 49 additions and 92 deletions

View file

@ -7,6 +7,8 @@ use std::sync::{Arc, Mutex};
use base::id::WebViewId; use base::id::WebViewId;
use ipc_channel::ipc; use ipc_channel::ipc;
use js::jsval::UndefinedValue;
use js::rust::HandleValue;
use net_traits::policy_container::{PolicyContainer, RequestPolicyContainer}; use net_traits::policy_container::{PolicyContainer, RequestPolicyContainer};
use net_traits::request::{ use net_traits::request::{
CorsSettings, CredentialsMode, Destination, InsecureRequestsPolicy, Referrer, CorsSettings, CredentialsMode, Destination, InsecureRequestsPolicy, Referrer,
@ -19,12 +21,14 @@ use net_traits::{
}; };
use servo_url::ServoUrl; use servo_url::ServoUrl;
use crate::dom::bindings::codegen::Bindings::AbortSignalBinding::AbortSignalMethods;
use crate::dom::bindings::codegen::Bindings::RequestBinding::{ use crate::dom::bindings::codegen::Bindings::RequestBinding::{
RequestInfo, RequestInit, RequestMethods, RequestInfo, RequestInit, RequestMethods,
}; };
use crate::dom::bindings::codegen::Bindings::ResponseBinding::Response_Binding::ResponseMethods; use crate::dom::bindings::codegen::Bindings::ResponseBinding::Response_Binding::ResponseMethods;
use crate::dom::bindings::codegen::Bindings::ResponseBinding::ResponseType as DOMResponseType; use crate::dom::bindings::codegen::Bindings::ResponseBinding::ResponseType as DOMResponseType;
use crate::dom::bindings::error::Error; use crate::dom::bindings::error::Error;
use crate::dom::bindings::import::module::SafeJSContext;
use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::refcounted::{Trusted, TrustedPromise}; use crate::dom::bindings::refcounted::{Trusted, TrustedPromise};
use crate::dom::bindings::reflector::DomGlobal; use crate::dom::bindings::reflector::DomGlobal;
@ -139,6 +143,27 @@ fn request_init_from_request(request: NetTraitsRequest) -> RequestBuilder {
} }
} }
/// <https://fetch.spec.whatwg.org/#abort-fetch>
fn abort_fetch_call(
promise: Rc<Promise>,
_request: &NetTraitsRequest,
_response_object: Option<&Response>,
abort_reason: HandleValue,
cx: SafeJSContext,
can_gc: CanGc,
) {
// Step 1. Reject promise with error.
promise.reject(cx, abort_reason, can_gc);
// Step 2. If requests body is non-null and is readable, then cancel requests body with error.
// TODO
// Step 3. If responseObject is null, then return.
// TODO
// Step 4. Let response be responseObjects response.
// TODO
// Step 5. If responses body is non-null and is readable, then error responses body with error.
// TODO
}
/// <https://fetch.spec.whatwg.org/#dom-global-fetch> /// <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))]
@ -151,6 +176,7 @@ pub(crate) fn Fetch(
) -> Rc<Promise> { ) -> Rc<Promise> {
// Step 1. Let p be a new promise. // Step 1. Let p be a new promise.
let promise = Promise::new_in_current_realm(comp, can_gc); let promise = Promise::new_in_current_realm(comp, can_gc);
let cx = GlobalScope::get_cx();
// Step 7. Let responseObject be null. // Step 7. Let responseObject be null.
// NOTE: We do initialize the object earlier earlier so we can use it to track errors // NOTE: We do initialize the object earlier earlier so we can use it to track errors
@ -159,32 +185,41 @@ pub(crate) fn Fetch(
// Step 2. Let requestObject be the result of invoking the initial value of Request as constructor // Step 2. Let requestObject be the result of invoking the initial value of Request as constructor
// with input and init as arguments. If this throws an exception, reject p with it and return p. // with input and init as arguments. If this throws an exception, reject p with it and return p.
let request = match Request::Constructor(global, None, can_gc, input, init) { let request_object = match Request::Constructor(global, None, can_gc, input, init) {
Err(e) => { Err(e) => {
response.error_stream(e.clone(), can_gc); response.error_stream(e.clone(), can_gc);
promise.reject_error(e, can_gc); promise.reject_error(e, can_gc);
return promise; return promise;
}, },
Ok(r) => { Ok(r) => r,
// Step 3. Let request be requestObjects request.
r.get_request()
},
}; };
// Step 3. Let request be requestObjects request.
let request = request_object.get_request();
let timing_type = request.timing_type(); let timing_type = request.timing_type();
let mut request_init = request_init_from_request(request);
request_init.policy_container =
RequestPolicyContainer::PolicyContainer(global.policy_container());
// Step 4. If requestObjects signal is aborted, then: // Step 4. If requestObjects signal is aborted, then:
// TODO let signal = request_object.Signal();
if signal.aborted() {
// Step 4.1. Abort the fetch() call with p, request, null, and requestObjects signals abort reason. // Step 4.1. Abort the fetch() call with p, request, null, and requestObjects signals abort reason.
// TODO rooted!(in(*cx) let mut abort_reason = UndefinedValue());
signal.Reason(cx, abort_reason.handle_mut());
abort_fetch_call(
promise.clone(),
&request,
None,
abort_reason.handle(),
cx,
can_gc,
);
// Step 4.2. Return p. // Step 4.2. Return p.
// TODO return promise;
}
// 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
let mut request_init = request_init_from_request(request);
request_init.policy_container =
RequestPolicyContainer::PolicyContainer(global.policy_container());
// Step 6. If globalObject is a ServiceWorkerGlobalScope object, then set requests // Step 6. If globalObject is a ServiceWorkerGlobalScope object, then set requests
// service-workers mode to "none". // service-workers mode to "none".

View file

@ -6,30 +6,6 @@
[general.any.html] [general.any.html]
expected: TIMEOUT expected: TIMEOUT
[Aborting rejects with AbortError]
expected: FAIL
[Aborting rejects with AbortError - no-cors]
expected: FAIL
[Signal on request object]
expected: FAIL
[Signal on request object created from request object]
expected: FAIL
[Signal on request object created from request object, with signal on second request]
expected: FAIL
[Signal on request object created from request object, with signal on second request overriding another]
expected: FAIL
[Signal retained after unrelated properties are overridden by fetch]
expected: FAIL
[Already aborted signal rejects immediately]
expected: FAIL
[Request is still 'used' if signal is aborted before fetching] [Request is still 'used' if signal is aborted before fetching]
expected: FAIL expected: FAIL
@ -51,15 +27,6 @@
[Call text() twice on aborted response] [Call text() twice on aborted response]
expected: FAIL expected: FAIL
[Already aborted signal does not make request]
expected: FAIL
[Already aborted signal can be used for many fetches]
expected: FAIL
[Signal can be used to abort other fetches, even if another fetch succeeded before aborting]
expected: FAIL
[Underlying connection is closed when aborting after receiving response] [Underlying connection is closed when aborting after receiving response]
expected: FAIL expected: FAIL
@ -93,12 +60,6 @@
[Readable stream synchronously cancels with AbortError if aborted before reading] [Readable stream synchronously cancels with AbortError if aborted before reading]
expected: NOTRUN expected: NOTRUN
[Aborting rejects with abort reason]
expected: FAIL
[Signal on request object should also have abort reason]
expected: FAIL
[response.bytes() rejects if already aborted] [response.bytes() rejects if already aborted]
expected: FAIL expected: FAIL
@ -108,30 +69,6 @@
[general.any.worker.html] [general.any.worker.html]
expected: TIMEOUT expected: TIMEOUT
[Aborting rejects with AbortError]
expected: FAIL
[Aborting rejects with AbortError - no-cors]
expected: FAIL
[Signal on request object]
expected: FAIL
[Signal on request object created from request object]
expected: FAIL
[Signal on request object created from request object, with signal on second request]
expected: FAIL
[Signal on request object created from request object, with signal on second request overriding another]
expected: FAIL
[Signal retained after unrelated properties are overridden by fetch]
expected: FAIL
[Already aborted signal rejects immediately]
expected: FAIL
[Request is still 'used' if signal is aborted before fetching] [Request is still 'used' if signal is aborted before fetching]
expected: FAIL expected: FAIL
@ -153,15 +90,6 @@
[Call text() twice on aborted response] [Call text() twice on aborted response]
expected: FAIL expected: FAIL
[Already aborted signal does not make request]
expected: FAIL
[Already aborted signal can be used for many fetches]
expected: FAIL
[Signal can be used to abort other fetches, even if another fetch succeeded before aborting]
expected: FAIL
[Underlying connection is closed when aborting after receiving response] [Underlying connection is closed when aborting after receiving response]
expected: FAIL expected: FAIL
@ -195,12 +123,6 @@
[Readable stream synchronously cancels with AbortError if aborted before reading] [Readable stream synchronously cancels with AbortError if aborted before reading]
expected: NOTRUN expected: NOTRUN
[Aborting rejects with abort reason]
expected: FAIL
[Signal on request object should also have abort reason]
expected: FAIL
[response.bytes() rejects if already aborted] [response.bytes() rejects if already aborted]
expected: FAIL expected: FAIL