mirror of
https://github.com/servo/servo.git
synced 2025-06-19 22:59:03 +01:00
Fixes (partially) #26488 and apply the https://github.com/servo/servo/pull/37324#discussion_r2133989190 comment. Testing: No tests added Fixes: Partially #26488 --------- Signed-off-by: Domenico Rizzo <domenico.rizzo@gmail.com>
629 lines
22 KiB
Rust
629 lines
22 KiB
Rust
/* 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/. */
|
||
|
||
//! Native representation of JS Promise values.
|
||
//!
|
||
//! This implementation differs from the traditional Rust DOM object, because the reflector
|
||
//! is provided by SpiderMonkey and has no knowledge of an associated native representation
|
||
//! (ie. dom::Promise). This means that native instances use native reference counting (Rc)
|
||
//! to ensure that no memory is leaked, which means that there can be multiple instances of
|
||
//! 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;
|
||
|
||
use dom_struct::dom_struct;
|
||
use js::conversions::{ConversionResult, FromJSValConvertibleRc, ToJSValConvertible};
|
||
use js::jsapi::{
|
||
AddRawValueRoot, CallArgs, GetFunctionNativeReserved, Heap, JS_ClearPendingException,
|
||
JS_GetFunctionObject, JS_NewFunction, JSAutoRealm, JSContext, JSObject,
|
||
NewFunctionWithReserved, PromiseState, PromiseUserInputEventHandlingState, RemoveRawValueRoot,
|
||
SetFunctionNativeReserved,
|
||
};
|
||
use js::jsval::{Int32Value, JSVal, NullValue, ObjectValue, UndefinedValue};
|
||
use js::rust::wrappers::{
|
||
AddPromiseReactions, CallOriginalPromiseReject, CallOriginalPromiseResolve,
|
||
GetPromiseIsHandled, GetPromiseState, IsPromiseObject, NewPromiseObject, RejectPromise,
|
||
ResolvePromise, SetAnyPromiseIsHandled, SetPromiseUserInputEventHandlingState,
|
||
};
|
||
use js::rust::{HandleObject, HandleValue, MutableHandleObject, Runtime};
|
||
|
||
use crate::dom::bindings::conversions::root_from_object;
|
||
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::{Callback, PromiseNativeHandler};
|
||
use crate::realms::{AlreadyInRealm, InRealm, enter_realm};
|
||
use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
|
||
use crate::script_thread::ScriptThread;
|
||
|
||
#[dom_struct]
|
||
#[cfg_attr(crown, crown::unrooted_must_root_lint::allow_unrooted_in_rc)]
|
||
pub(crate) struct Promise {
|
||
reflector: Reflector,
|
||
/// Since Promise values are natively reference counted without the knowledge of
|
||
/// the SpiderMonkey GC, an explicit root for the reflector is stored while any
|
||
/// native instance exists. This ensures that the reflector will never be GCed
|
||
/// while native code could still interact with its native representation.
|
||
#[ignore_malloc_size_of = "SM handles JS values"]
|
||
permanent_js_root: Heap<JSVal>,
|
||
}
|
||
|
||
/// Private helper to enable adding new methods to `Rc<Promise>`.
|
||
trait PromiseHelper {
|
||
fn initialize(&self, cx: SafeJSContext);
|
||
}
|
||
|
||
impl PromiseHelper for Rc<Promise> {
|
||
#[allow(unsafe_code)]
|
||
fn initialize(&self, cx: SafeJSContext) {
|
||
let obj = self.reflector().get_jsobject();
|
||
self.permanent_js_root.set(ObjectValue(*obj));
|
||
unsafe {
|
||
assert!(AddRawValueRoot(
|
||
*cx,
|
||
self.permanent_js_root.get_unsafe(),
|
||
c"Promise::root".as_ptr(),
|
||
));
|
||
}
|
||
}
|
||
}
|
||
|
||
// Promise objects are stored inside Rc values, so Drop is run when the last Rc is dropped,
|
||
// rather than when SpiderMonkey runs a GC. This makes it safe to interact with the JS engine unlike
|
||
// Drop implementations for other DOM types.
|
||
impl Drop for Promise {
|
||
#[allow(unsafe_code)]
|
||
fn drop(&mut self) {
|
||
unsafe {
|
||
let object = self.permanent_js_root.get().to_object();
|
||
assert!(!object.is_null());
|
||
if let Some(cx) = Runtime::get() {
|
||
RemoveRawValueRoot(cx.as_ptr(), self.permanent_js_root.get_unsafe());
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
impl Promise {
|
||
pub(crate) fn new(global: &GlobalScope, can_gc: CanGc) -> Rc<Promise> {
|
||
let realm = enter_realm(global);
|
||
let comp = InRealm::Entered(&realm);
|
||
Promise::new_in_current_realm(comp, can_gc)
|
||
}
|
||
|
||
pub(crate) fn new_in_current_realm(_comp: InRealm, can_gc: CanGc) -> Rc<Promise> {
|
||
let cx = GlobalScope::get_cx();
|
||
rooted!(in(*cx) let mut obj = ptr::null_mut::<JSObject>());
|
||
Promise::create_js_promise(cx, obj.handle_mut(), can_gc);
|
||
Promise::new_with_js_promise(obj.handle(), cx)
|
||
}
|
||
|
||
#[allow(unsafe_code)]
|
||
pub(crate) fn duplicate(&self) -> Rc<Promise> {
|
||
let cx = GlobalScope::get_cx();
|
||
Promise::new_with_js_promise(self.reflector().get_jsobject(), cx)
|
||
}
|
||
|
||
#[allow(unsafe_code)]
|
||
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
|
||
pub(crate) fn new_with_js_promise(obj: HandleObject, cx: SafeJSContext) -> Rc<Promise> {
|
||
unsafe {
|
||
assert!(IsPromiseObject(obj));
|
||
let promise = Promise {
|
||
reflector: Reflector::new(),
|
||
permanent_js_root: Heap::default(),
|
||
};
|
||
let promise = Rc::new(promise);
|
||
promise.init_reflector(obj.get());
|
||
promise.initialize(cx);
|
||
promise
|
||
}
|
||
}
|
||
|
||
#[allow(unsafe_code)]
|
||
// The apparently-unused CanGc parameter reflects the fact that the JS API calls
|
||
// like JS_NewFunction can trigger a GC.
|
||
fn create_js_promise(cx: SafeJSContext, mut obj: MutableHandleObject, _can_gc: CanGc) {
|
||
unsafe {
|
||
let do_nothing_func = JS_NewFunction(
|
||
*cx,
|
||
Some(do_nothing_promise_executor),
|
||
/* nargs = */ 2,
|
||
/* flags = */ 0,
|
||
ptr::null(),
|
||
);
|
||
assert!(!do_nothing_func.is_null());
|
||
rooted!(in(*cx) let do_nothing_obj = JS_GetFunctionObject(do_nothing_func));
|
||
assert!(!do_nothing_obj.is_null());
|
||
obj.set(NewPromiseObject(*cx, do_nothing_obj.handle()));
|
||
assert!(!obj.is_null());
|
||
let is_user_interacting = if ScriptThread::is_user_interacting() {
|
||
PromiseUserInputEventHandlingState::HadUserInteractionAtCreation
|
||
} else {
|
||
PromiseUserInputEventHandlingState::DidntHaveUserInteractionAtCreation
|
||
};
|
||
SetPromiseUserInputEventHandlingState(obj.handle(), is_user_interacting);
|
||
}
|
||
}
|
||
|
||
#[allow(unsafe_code)]
|
||
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
|
||
pub(crate) fn new_resolved(
|
||
global: &GlobalScope,
|
||
cx: SafeJSContext,
|
||
value: impl ToJSValConvertible,
|
||
_can_gc: CanGc,
|
||
) -> Rc<Promise> {
|
||
let _ac = JSAutoRealm::new(*cx, global.reflector().get_jsobject().get());
|
||
unsafe {
|
||
rooted!(in(*cx) let mut rval = UndefinedValue());
|
||
value.to_jsval(*cx, rval.handle_mut());
|
||
rooted!(in(*cx) let p = CallOriginalPromiseResolve(*cx, rval.handle()));
|
||
assert!(!p.handle().is_null());
|
||
Promise::new_with_js_promise(p.handle(), cx)
|
||
}
|
||
}
|
||
|
||
#[allow(unsafe_code)]
|
||
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
|
||
pub(crate) fn new_rejected(
|
||
global: &GlobalScope,
|
||
cx: SafeJSContext,
|
||
value: impl ToJSValConvertible,
|
||
_can_gc: CanGc,
|
||
) -> Rc<Promise> {
|
||
let _ac = JSAutoRealm::new(*cx, global.reflector().get_jsobject().get());
|
||
unsafe {
|
||
rooted!(in(*cx) let mut rval = UndefinedValue());
|
||
value.to_jsval(*cx, rval.handle_mut());
|
||
rooted!(in(*cx) let p = CallOriginalPromiseReject(*cx, rval.handle()));
|
||
assert!(!p.handle().is_null());
|
||
Promise::new_with_js_promise(p.handle(), cx)
|
||
}
|
||
}
|
||
|
||
#[allow(unsafe_code)]
|
||
pub(crate) fn resolve_native<T>(&self, val: &T, can_gc: CanGc)
|
||
where
|
||
T: ToJSValConvertible,
|
||
{
|
||
let cx = GlobalScope::get_cx();
|
||
let _ac = enter_realm(self);
|
||
rooted!(in(*cx) let mut v = UndefinedValue());
|
||
unsafe {
|
||
val.to_jsval(*cx, v.handle_mut());
|
||
}
|
||
self.resolve(cx, v.handle(), can_gc);
|
||
}
|
||
|
||
#[allow(unsafe_code)]
|
||
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
|
||
pub(crate) fn resolve(&self, cx: SafeJSContext, value: HandleValue, _can_gc: CanGc) {
|
||
unsafe {
|
||
if !ResolvePromise(*cx, self.promise_obj(), value) {
|
||
JS_ClearPendingException(*cx);
|
||
}
|
||
}
|
||
}
|
||
|
||
#[allow(unsafe_code)]
|
||
pub(crate) fn reject_native<T>(&self, val: &T, can_gc: CanGc)
|
||
where
|
||
T: ToJSValConvertible,
|
||
{
|
||
let cx = GlobalScope::get_cx();
|
||
let _ac = enter_realm(self);
|
||
rooted!(in(*cx) let mut v = UndefinedValue());
|
||
unsafe {
|
||
val.to_jsval(*cx, v.handle_mut());
|
||
}
|
||
self.reject(cx, v.handle(), can_gc);
|
||
}
|
||
|
||
pub(crate) fn reject_error(&self, error: Error, can_gc: CanGc) {
|
||
let cx = GlobalScope::get_cx();
|
||
let _ac = enter_realm(self);
|
||
rooted!(in(*cx) let mut v = UndefinedValue());
|
||
error.to_jsval(cx, &self.global(), v.handle_mut(), can_gc);
|
||
self.reject(cx, v.handle(), can_gc);
|
||
}
|
||
|
||
#[allow(unsafe_code)]
|
||
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
|
||
pub(crate) fn reject(&self, cx: SafeJSContext, value: HandleValue, _can_gc: CanGc) {
|
||
unsafe {
|
||
if !RejectPromise(*cx, self.promise_obj(), value) {
|
||
JS_ClearPendingException(*cx);
|
||
}
|
||
}
|
||
}
|
||
|
||
#[allow(unsafe_code)]
|
||
pub(crate) fn is_fulfilled(&self) -> bool {
|
||
let state = unsafe { GetPromiseState(self.promise_obj()) };
|
||
matches!(state, PromiseState::Rejected | PromiseState::Fulfilled)
|
||
}
|
||
|
||
#[allow(unsafe_code)]
|
||
pub(crate) fn is_rejected(&self) -> bool {
|
||
let state = unsafe { GetPromiseState(self.promise_obj()) };
|
||
matches!(state, PromiseState::Rejected)
|
||
}
|
||
|
||
#[allow(unsafe_code)]
|
||
pub(crate) fn is_pending(&self) -> bool {
|
||
let state = unsafe { GetPromiseState(self.promise_obj()) };
|
||
matches!(state, PromiseState::Pending)
|
||
}
|
||
|
||
#[allow(unsafe_code)]
|
||
pub(crate) fn promise_obj(&self) -> HandleObject {
|
||
let obj = self.reflector().get_jsobject();
|
||
unsafe {
|
||
assert!(IsPromiseObject(obj));
|
||
}
|
||
obj
|
||
}
|
||
|
||
#[allow(unsafe_code)]
|
||
pub(crate) fn append_native_handler(
|
||
&self,
|
||
handler: &PromiseNativeHandler,
|
||
realm: InRealm,
|
||
can_gc: CanGc,
|
||
) {
|
||
let _ais = AutoEntryScript::new(&handler.global_(realm));
|
||
let cx = GlobalScope::get_cx();
|
||
rooted!(in(*cx) let resolve_func =
|
||
create_native_handler_function(*cx,
|
||
handler.reflector().get_jsobject(),
|
||
NativeHandlerTask::Resolve,
|
||
can_gc));
|
||
|
||
rooted!(in(*cx) let reject_func =
|
||
create_native_handler_function(*cx,
|
||
handler.reflector().get_jsobject(),
|
||
NativeHandlerTask::Reject,
|
||
can_gc));
|
||
|
||
unsafe {
|
||
let ok = AddPromiseReactions(
|
||
*cx,
|
||
self.promise_obj(),
|
||
resolve_func.handle(),
|
||
reject_func.handle(),
|
||
);
|
||
assert!(ok);
|
||
}
|
||
}
|
||
|
||
#[allow(unsafe_code)]
|
||
pub(crate) fn get_promise_is_handled(&self) -> bool {
|
||
unsafe { GetPromiseIsHandled(self.reflector().get_jsobject()) }
|
||
}
|
||
|
||
#[allow(unsafe_code)]
|
||
pub(crate) fn set_promise_is_handled(&self) -> bool {
|
||
let cx = GlobalScope::get_cx();
|
||
unsafe { SetAnyPromiseIsHandled(*cx, self.reflector().get_jsobject()) }
|
||
}
|
||
}
|
||
|
||
#[allow(unsafe_code)]
|
||
unsafe extern "C" fn do_nothing_promise_executor(
|
||
_cx: *mut JSContext,
|
||
argc: u32,
|
||
vp: *mut JSVal,
|
||
) -> bool {
|
||
let args = CallArgs::from_vp(vp, argc);
|
||
args.rval().set(UndefinedValue());
|
||
true
|
||
}
|
||
|
||
const SLOT_NATIVEHANDLER: usize = 0;
|
||
const SLOT_NATIVEHANDLER_TASK: usize = 1;
|
||
|
||
#[derive(PartialEq)]
|
||
enum NativeHandlerTask {
|
||
Resolve = 0,
|
||
Reject = 1,
|
||
}
|
||
|
||
#[allow(unsafe_code)]
|
||
unsafe extern "C" fn native_handler_callback(
|
||
cx: *mut JSContext,
|
||
argc: u32,
|
||
vp: *mut JSVal,
|
||
) -> bool {
|
||
let cx = SafeJSContext::from_ptr(cx);
|
||
let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
|
||
|
||
let args = CallArgs::from_vp(vp, argc);
|
||
rooted!(in(*cx) let v = *GetFunctionNativeReserved(args.callee(), SLOT_NATIVEHANDLER));
|
||
assert!(v.get().is_object());
|
||
|
||
let handler = root_from_object::<PromiseNativeHandler>(v.to_object(), *cx)
|
||
.expect("unexpected value for native handler in promise native handler callback");
|
||
|
||
rooted!(in(*cx) let v = *GetFunctionNativeReserved(args.callee(), SLOT_NATIVEHANDLER_TASK));
|
||
match v.to_int32() {
|
||
v if v == NativeHandlerTask::Resolve as i32 => handler.resolved_callback(
|
||
*cx,
|
||
HandleValue::from_raw(args.get(0)),
|
||
InRealm::Already(&in_realm_proof),
|
||
CanGc::note(),
|
||
),
|
||
v if v == NativeHandlerTask::Reject as i32 => handler.rejected_callback(
|
||
*cx,
|
||
HandleValue::from_raw(args.get(0)),
|
||
InRealm::Already(&in_realm_proof),
|
||
CanGc::note(),
|
||
),
|
||
_ => panic!("unexpected native handler task value"),
|
||
};
|
||
|
||
true
|
||
}
|
||
|
||
#[allow(unsafe_code)]
|
||
// The apparently-unused CanGc argument reflects the fact that the JS API calls
|
||
// like NewFunctionWithReserved can trigger a GC.
|
||
fn create_native_handler_function(
|
||
cx: *mut JSContext,
|
||
holder: HandleObject,
|
||
task: NativeHandlerTask,
|
||
_can_gc: CanGc,
|
||
) -> *mut JSObject {
|
||
unsafe {
|
||
let func = NewFunctionWithReserved(cx, Some(native_handler_callback), 1, 0, ptr::null());
|
||
assert!(!func.is_null());
|
||
|
||
rooted!(in(cx) let obj = JS_GetFunctionObject(func));
|
||
assert!(!obj.is_null());
|
||
SetFunctionNativeReserved(obj.get(), SLOT_NATIVEHANDLER, &ObjectValue(*holder));
|
||
SetFunctionNativeReserved(obj.get(), SLOT_NATIVEHANDLER_TASK, &Int32Value(task as i32));
|
||
obj.get()
|
||
}
|
||
}
|
||
|
||
impl FromJSValConvertibleRc for Promise {
|
||
#[allow(unsafe_code)]
|
||
unsafe fn from_jsval(
|
||
cx: *mut JSContext,
|
||
value: HandleValue,
|
||
) -> Result<ConversionResult<Rc<Promise>>, ()> {
|
||
if value.get().is_null() {
|
||
return Ok(ConversionResult::Failure("null not allowed".into()));
|
||
}
|
||
|
||
let cx = SafeJSContext::from_ptr(cx);
|
||
let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
|
||
let global_scope = GlobalScope::from_context(*cx, InRealm::Already(&in_realm_proof));
|
||
|
||
let promise = Promise::new_resolved(&global_scope, cx, value, CanGc::note());
|
||
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 promises’s 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
|
||
}
|