From 8c064e0aa678b115cf88cadffd1319ba36e944fe Mon Sep 17 00:00:00 2001 From: gterzian <2792687+gterzian@users.noreply.github.com> Date: Wed, 4 Jun 2025 17:02:13 +0700 Subject: [PATCH] implement promise wait for all Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com> --- components/script/dom/promise.rs | 166 +++++++++++++++++- .../webidls/AbortController.webidl | 2 +- .../webidls/AbortSignal.webidl | 2 +- 3 files changed, 166 insertions(+), 4 deletions(-) diff --git a/components/script/dom/promise.rs b/components/script/dom/promise.rs index 0efffbe6fe2..89ca26434f4 100644 --- a/components/script/dom/promise.rs +++ b/components/script/dom/promise.rs @@ -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,164 @@ impl FromJSValConvertibleRc for Promise { Ok(ConversionResult::Success(promise)) } } + +#[derive(Clone, JSTraceable, MallocSizeOf)] +/// The fulfillment handler for the list of promises in +/// . +struct WaitAllFulfillmentHandler { + /// The steps to call when all promises are resolved. + #[ignore_malloc_size_of = "Rc is hard"] + #[no_trace] + success_steps: Rc, + + /// The results of the promises. + #[ignore_malloc_size_of = "Rc is hard"] + result: Rc>>>, + + /// 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>, +} + +impl Callback for WaitAllFulfillmentHandler { + #[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 fullfilledCount to fullfilledCount + 1. + let mut fulfilled_count = self.fulfilled_count.borrow_mut(); + *fulfilled_count = *fulfilled_count + 1; + + *fulfilled_count == result.len() + }; + + // If fullfilledCount 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 = unsafe { + self.result + .borrow() + .iter() + .map(|val| HandleValue::from_raw(val.handle())) + .collect() + }; + (self.success_steps)(&result_handles); + } + } +} + +#[derive(Clone, JSTraceable, MallocSizeOf)] +/// The rejection handler for the list of promises in +/// . +struct WaitAllRejectionHandler { + /// The steps to call if any promise rejects. + #[ignore_malloc_size_of = "Rc is hard"] + #[no_trace] + failure_steps: Rc, + + /// Whether any promises have been rejected already. + rejected: Cell, +} + +impl Callback for WaitAllRejectionHandler { + 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); + } +} + +/// +pub(crate) fn wait_for_all( + cx: SafeJSContext, + global: &GlobalScope, + promises: Vec>, + success_steps: Rc, + failure_steps: Rc, + realm: InRealm, + can_gc: CanGc, +) { + // Let fullfilledCount be 0. + // Note: done below when constructing a fulfillment handler. + + // Let rejected be false. + // Note: done below when constructing a rejection handler. + + // Let rejectionHandlerSteps be the following steps given arg: + // Note: implemented with the `WaitAllRejectionHandler`. + + // Let rejectionHandler be CreateBuiltinFunction(rejectionHandlerSteps, « »): + // Note: done as part of attaching the `WaitAllRejectionHandler` as native rejection handler. + let rejection_handler = WaitAllRejectionHandler { + failure_steps, + rejected: Default::default(), + }; + + // Let total be promises’s size. + // Note: done using the len of result. + + // If total is 0, then: + // TODO: Queue a microtask to perform successSteps given « ». + + // Let index be 0. + // Note: done with `enumerate` below. + + // Let result be a list containing total null values. + let result: Rc>>> = 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::default()); + // Note: modifying the heap only after its move. + result_list[promise_index].set(null_value.get()); + } + + // Let promiseIndex be index. + // Note: done with `enumerate` above. + + // Let fulfillmentHandler be the following steps given arg: + // Note: implemented with the `WaitAllFulFillmentHandler`. + + // 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(WaitAllFulfillmentHandler { + success_steps: success_steps.clone(), + result, + promise_index, + fulfilled_count: Default::default(), + })), + 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`. + } +} diff --git a/components/script_bindings/webidls/AbortController.webidl b/components/script_bindings/webidls/AbortController.webidl index bb20ec24955..f3b9636d650 100644 --- a/components/script_bindings/webidls/AbortController.webidl +++ b/components/script_bindings/webidls/AbortController.webidl @@ -3,7 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // https://dom.spec.whatwg.org/#interface-abortcontroller -[Exposed=*, Pref="dom_abort_controller_enabled"] +[Exposed=*] interface AbortController { constructor(); diff --git a/components/script_bindings/webidls/AbortSignal.webidl b/components/script_bindings/webidls/AbortSignal.webidl index 1ec9fbcd3a4..bea70e41739 100644 --- a/components/script_bindings/webidls/AbortSignal.webidl +++ b/components/script_bindings/webidls/AbortSignal.webidl @@ -4,7 +4,7 @@ // https://dom.spec.whatwg.org/#abortsignal -[Exposed=*, Pref="dom_abort_controller_enabled"] +[Exposed=*] interface AbortSignal : EventTarget { readonly attribute boolean aborted; readonly attribute any reason;