AbortController: integrate with stream piping. (#37244)

Start using abort signal in Use in
https://streams.spec.whatwg.org/#readablestream-pipe-to-signal

Part of https://github.com/servo/servo/issues/34866

Will also cover https://github.com/servo/servo/issues/37230 and
https://github.com/servo/servo/issues/37232

---------

Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com>
This commit is contained in:
Gregory Terzian 2025-06-13 16:52:38 +07:00 committed by GitHub
parent 099fd10317
commit 730fe35b42
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 508 additions and 263 deletions

View file

@ -11,6 +11,7 @@
//! native Promise values that refer to the same JS value yet are distinct native objects
//! (ie. address equality for the native objects is meaningless).
use std::cell::{Cell, RefCell};
use std::ptr;
use std::rc::Rc;
@ -22,7 +23,7 @@ use js::jsapi::{
NewFunctionWithReserved, PromiseState, PromiseUserInputEventHandlingState, RemoveRawValueRoot,
SetFunctionNativeReserved,
};
use js::jsval::{Int32Value, JSVal, ObjectValue, UndefinedValue};
use js::jsval::{Int32Value, JSVal, NullValue, ObjectValue, UndefinedValue};
use js::rust::wrappers::{
AddPromiseReactions, CallOriginalPromiseReject, CallOriginalPromiseResolve,
GetPromiseIsHandled, GetPromiseState, IsPromiseObject, NewPromiseObject, RejectPromise,
@ -35,7 +36,7 @@ use crate::dom::bindings::error::{Error, ErrorToJsval};
use crate::dom::bindings::reflector::{DomGlobal, DomObject, MutDomObject, Reflector};
use crate::dom::bindings::settings_stack::AutoEntryScript;
use crate::dom::globalscope::GlobalScope;
use crate::dom::promisenativehandler::PromiseNativeHandler;
use crate::dom::promisenativehandler::{Callback, PromiseNativeHandler};
use crate::realms::{AlreadyInRealm, InRealm, enter_realm};
use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
use crate::script_thread::ScriptThread;
@ -405,3 +406,221 @@ impl FromJSValConvertibleRc for Promise {
Ok(ConversionResult::Success(promise))
}
}
/// The success steps of <https://webidl.spec.whatwg.org/#wait-for-all>
type WaitForAllSuccessSteps = Rc<dyn Fn(Vec<HandleValue>)>;
/// The failure steps of <https://webidl.spec.whatwg.org/#wait-for-all>
type WaitForAllFailureSteps = Rc<dyn Fn(HandleValue)>;
/// The fulfillment handler for the list of promises in
/// <https://webidl.spec.whatwg.org/#wait-for-all>.
#[derive(JSTraceable, MallocSizeOf)]
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
struct WaitForAllFulfillmentHandler {
/// The steps to call when all promises are resolved.
#[ignore_malloc_size_of = "Rc is hard"]
#[no_trace]
success_steps: WaitForAllSuccessSteps,
/// The results of the promises.
#[ignore_malloc_size_of = "Rc is hard"]
#[allow(clippy::vec_box)]
result: Rc<RefCell<Vec<Box<Heap<JSVal>>>>>,
/// The index identifying which promise this handler is attached to.
promise_index: usize,
/// A count of fulfilled promises.
#[ignore_malloc_size_of = "Rc is hard"]
fulfilled_count: Rc<RefCell<usize>>,
}
impl Callback for WaitForAllFulfillmentHandler {
#[allow(unsafe_code)]
fn callback(&self, _cx: SafeJSContext, v: HandleValue, _realm: InRealm, _can_gc: CanGc) {
// Let fulfillmentHandler be the following steps given arg:
let equals_total = {
// Set result[promiseIndex] to arg.
let result = self.result.borrow_mut();
result[self.promise_index].set(v.get());
// Set fulfilledCount to fulfilledCount + 1.
let mut fulfilled_count = self.fulfilled_count.borrow_mut();
*fulfilled_count += 1;
*fulfilled_count == result.len()
};
// If fulfilledCount equals total, then perform successSteps given result.
if equals_total {
// Safety: the values are kept alive by the Heap
// while their handles are passed to the the success steps.
let result_handles: Vec<HandleValue> = unsafe {
self.result
.borrow()
.iter()
.map(|val| HandleValue::from_raw(val.handle()))
.collect()
};
(self.success_steps)(result_handles);
}
}
}
/// The rejection handler for the list of promises in
/// <https://webidl.spec.whatwg.org/#wait-for-all>.
#[derive(Clone, JSTraceable, MallocSizeOf)]
struct WaitForAllRejectionHandler {
/// The steps to call if any promise rejects.
#[ignore_malloc_size_of = "Rc is hard"]
#[no_trace]
failure_steps: WaitForAllFailureSteps,
/// Whether any promises have been rejected already.
rejected: Cell<bool>,
}
impl Callback for WaitForAllRejectionHandler {
fn callback(&self, _cx: SafeJSContext, v: HandleValue, _realm: InRealm, _can_gc: CanGc) {
// Let rejectionHandlerSteps be the following steps given arg:
if self.rejected.replace(true) {
// If rejected is true, abort these steps.
return;
}
// Set rejected to true.
// Done above with `replace`.
(self.failure_steps)(v);
}
}
/// <https://webidl.spec.whatwg.org/#wait-for-all>
pub(crate) fn wait_for_all(
cx: SafeJSContext,
global: &GlobalScope,
promises: Vec<Rc<Promise>>,
success_steps: WaitForAllSuccessSteps,
failure_steps: WaitForAllFailureSteps,
realm: InRealm,
can_gc: CanGc,
) {
// Let fulfilledCount be 0.
let fulfilled_count: Rc<RefCell<usize>> = Default::default();
// Let rejected be false.
// Note: done below when constructing a rejection handler.
// Let rejectionHandlerSteps be the following steps given arg:
// Note: implemented with the `WaitForAllRejectionHandler`.
// Let rejectionHandler be CreateBuiltinFunction(rejectionHandlerSteps, « »):
// Note: done as part of attaching the `WaitForAllRejectionHandler` as native rejection handler.
let rejection_handler = WaitForAllRejectionHandler {
failure_steps,
rejected: Default::default(),
};
// Let total be promisess size.
// Note: done using the len of result.
// If total is 0, then:
// Queue a microtask to perform successSteps given « ».
// TODO: #37259
// Let index be 0.
// Note: done with `enumerate` below.
// Let result be a list containing total null values.
let result: Rc<RefCell<Vec<Box<Heap<JSVal>>>>> = Default::default();
// For each promise of promises:
for (promise_index, promise) in promises.into_iter().enumerate() {
let result = result.clone();
{
// Note: adding a null value for this promise result.
let mut result_list = result.borrow_mut();
rooted!(in(*cx) let null_value = NullValue());
result_list.push(Heap::boxed(null_value.get()));
}
// Let promiseIndex be index.
// Note: done with `enumerate` above.
// Let fulfillmentHandler be the following steps given arg:
// Note: implemented with the `WaitForAllFulFillmentHandler`.
// Let fulfillmentHandler be CreateBuiltinFunction(fulfillmentHandler, « »):
// Note: passed below to avoid the need to root it.
// Perform PerformPromiseThen(promise, fulfillmentHandler, rejectionHandler).
let handler = PromiseNativeHandler::new(
global,
Some(Box::new(WaitForAllFulfillmentHandler {
success_steps: success_steps.clone(),
result,
promise_index,
fulfilled_count: fulfilled_count.clone(),
})),
Some(Box::new(rejection_handler.clone())),
can_gc,
);
promise.append_native_handler(&handler, realm, can_gc);
// Set index to index + 1.
// Note: done above with `enumerate`.
}
}
/// <https://webidl.spec.whatwg.org/#waiting-for-all-promise>
pub(crate) fn wait_for_all_promise(
cx: SafeJSContext,
global: &GlobalScope,
promises: Vec<Rc<Promise>>,
realm: InRealm,
can_gc: CanGc,
) -> Rc<Promise> {
// Let promise be a new promise of type Promise<sequence<T>> in realm.
let promise = Promise::new(global, can_gc);
let success_promise = promise.clone();
let failure_promise = promise.clone();
// Let successSteps be the following steps, given results:
let success_steps = Rc::new(move |results: Vec<HandleValue>| {
// Resolve promise with results.
success_promise.resolve_native(&results, can_gc);
});
// Let failureSteps be the following steps, given reason:
let failure_steps = Rc::new(move |reason: HandleValue| {
// Reject promise with reason.
failure_promise.reject_native(&reason, can_gc);
});
if promises.is_empty() {
// Note: part of `wait_for_all`.
// Done here by using `resolve_native`.
// TODO: #37259
// If total is 0, then:
// Queue a microtask to perform successSteps given « ».
let empty_list: Vec<HandleValue> = vec![];
promise.resolve_native(&empty_list, can_gc);
} else {
// Wait for all with promises, given successSteps and failureSteps.
wait_for_all(
cx,
global,
promises,
success_steps,
failure_steps,
realm,
can_gc,
);
}
// Return promise.
promise
}