Skip to main content

script/dom/
promise.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! Native representation of JS Promise values.
6//!
7//! This implementation differs from the traditional Rust DOM object, because the reflector
8//! is provided by SpiderMonkey and has no knowledge of an associated native representation
9//! (ie. dom::Promise). This means that native instances use native reference counting (Rc)
10//! to ensure that no memory is leaked, which means that there can be multiple instances of
11//! native Promise values that refer to the same JS value yet are distinct native objects
12//! (ie. address equality for the native objects is meaningless).
13
14use std::cell::{Cell, RefCell};
15use std::ops::DerefMut;
16use std::ptr;
17use std::rc::Rc;
18
19use dom_struct::dom_struct;
20use js::context::JSContext;
21use js::conversions::{ConversionResult, FromJSValConvertibleRc};
22use js::jsapi::{
23    AddRawValueRoot, CallArgs, GetFunctionNativeReserved, Heap, JS_GetFunctionObject,
24    JS_NewFunction, JSAutoRealm, JSContext as RawJSContext, JSObject, PromiseState,
25    PromiseUserInputEventHandlingState, RemoveRawValueRoot, SetFunctionNativeReserved,
26};
27use js::jsval::{Int32Value, JSVal, NullValue, ObjectValue, UndefinedValue};
28use js::realm::{AutoRealm, CurrentRealm};
29use js::rust::wrappers::{
30    CallOriginalPromiseReject, CallOriginalPromiseResolve, GetPromiseIsHandled, GetPromiseState,
31    IsPromiseObject, NewPromiseObject, SetAnyPromiseIsHandled,
32    SetPromiseUserInputEventHandlingState,
33};
34use js::rust::wrappers2::{
35    AddPromiseReactions, JS_ClearPendingException, NewFunctionWithReserved, RejectPromise,
36    ResolvePromise,
37};
38use js::rust::{HandleObject, HandleValue, MutableHandleObject, Runtime};
39use script_bindings::conversions::SafeToJSValConvertible;
40use script_bindings::reflector::{DomObject, MutDomObject, Reflector};
41use script_bindings::settings_stack::run_a_script;
42
43use crate::DomTypeHolder;
44use crate::dom::bindings::conversions::root_from_object;
45use crate::dom::bindings::error::{Error, ErrorToJsval};
46use crate::dom::bindings::reflector::DomGlobal;
47use crate::dom::bindings::root::{AsHandleValue, DomRoot};
48use crate::dom::globalscope::GlobalScope;
49use crate::dom::promisenativehandler::{Callback, PromiseNativeHandler};
50use crate::microtask::{Microtask, MicrotaskRunnable};
51use crate::realms::{InRealm, enter_auto_realm, enter_realm};
52use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
53use crate::script_thread::ScriptThread;
54
55#[dom_struct]
56#[cfg_attr(crown, crown::unrooted_must_root_lint::allow_unrooted_in_rc)]
57pub(crate) struct Promise {
58    reflector: Reflector,
59    /// Since Promise values are natively reference counted without the knowledge of
60    /// the SpiderMonkey GC, an explicit root for the reflector is stored while any
61    /// native instance exists. This ensures that the reflector will never be GCed
62    /// while native code could still interact with its native representation.
63    #[ignore_malloc_size_of = "SM handles JS values"]
64    permanent_js_root: Heap<JSVal>,
65}
66
67/// Private helper to enable adding new methods to `Rc<Promise>`.
68trait PromiseHelper {
69    fn initialize(&self, cx: SafeJSContext);
70}
71
72impl PromiseHelper for Rc<Promise> {
73    #[expect(unsafe_code)]
74    fn initialize(&self, cx: SafeJSContext) {
75        let obj = self.reflector().get_jsobject();
76        self.permanent_js_root.set(ObjectValue(*obj));
77        unsafe {
78            assert!(AddRawValueRoot(
79                *cx,
80                self.permanent_js_root.get_unsafe(),
81                c"Promise::root".as_ptr(),
82            ));
83        }
84    }
85}
86
87// Promise objects are stored inside Rc values, so Drop is run when the last Rc is dropped,
88// rather than when SpiderMonkey runs a GC. This makes it safe to interact with the JS engine unlike
89// Drop implementations for other DOM types.
90impl Drop for Promise {
91    #[expect(unsafe_code)]
92    fn drop(&mut self) {
93        unsafe {
94            let object = self.permanent_js_root.get().to_object();
95            assert!(!object.is_null());
96            if let Some(cx) = Runtime::get() {
97                RemoveRawValueRoot(cx.as_ptr(), self.permanent_js_root.get_unsafe());
98            }
99        }
100    }
101}
102
103impl Promise {
104    pub(crate) fn new(global: &GlobalScope, can_gc: CanGc) -> Rc<Promise> {
105        let realm = enter_realm(global);
106        let comp = InRealm::Entered(&realm);
107        Promise::new_in_current_realm(comp, can_gc)
108    }
109
110    pub(crate) fn new_in_current_realm(_comp: InRealm, can_gc: CanGc) -> Rc<Promise> {
111        let cx = GlobalScope::get_cx();
112        rooted!(in(*cx) let mut obj = ptr::null_mut::<JSObject>());
113        Promise::create_js_promise(cx, obj.handle_mut(), can_gc);
114        Promise::new_with_js_promise(obj.handle(), cx)
115    }
116
117    pub(crate) fn new2(cx: &mut js::context::JSContext, global: &GlobalScope) -> Rc<Promise> {
118        let mut realm = AutoRealm::new(
119            cx,
120            std::ptr::NonNull::new(global.reflector().get_jsobject().get()).unwrap(),
121        );
122        let mut current_realm = realm.current_realm();
123        Promise::new_in_realm(&mut current_realm)
124    }
125
126    pub(crate) fn new_in_realm(current_realm: &mut CurrentRealm) -> Rc<Promise> {
127        let cx = current_realm.deref_mut();
128        rooted!(&in(cx) let mut obj = ptr::null_mut::<JSObject>());
129        Promise::create_js_promise(cx.into(), obj.handle_mut(), CanGc::from_cx(cx));
130        Promise::new_with_js_promise(obj.handle(), cx.into())
131    }
132
133    pub(crate) fn duplicate(&self) -> Rc<Promise> {
134        let cx = GlobalScope::get_cx();
135        Promise::new_with_js_promise(self.reflector().get_jsobject(), cx)
136    }
137
138    #[expect(unsafe_code)]
139    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
140    pub(crate) fn new_with_js_promise(obj: HandleObject, cx: SafeJSContext) -> Rc<Promise> {
141        unsafe {
142            assert!(IsPromiseObject(obj));
143            let promise = Promise {
144                reflector: Reflector::new(),
145                permanent_js_root: Heap::default(),
146            };
147            let promise = Rc::new(promise);
148            promise.init_reflector_without_associated_memory(obj.get());
149            promise.initialize(cx);
150            promise
151        }
152    }
153
154    #[expect(unsafe_code)]
155    // The apparently-unused CanGc parameter reflects the fact that the JS API calls
156    // like JS_NewFunction can trigger a GC.
157    fn create_js_promise(cx: SafeJSContext, mut obj: MutableHandleObject, _can_gc: CanGc) {
158        unsafe {
159            let do_nothing_func = JS_NewFunction(
160                *cx,
161                Some(do_nothing_promise_executor),
162                /* nargs = */ 2,
163                /* flags = */ 0,
164                ptr::null(),
165            );
166            assert!(!do_nothing_func.is_null());
167            rooted!(in(*cx) let do_nothing_obj = JS_GetFunctionObject(do_nothing_func));
168            assert!(!do_nothing_obj.is_null());
169            obj.set(NewPromiseObject(*cx, do_nothing_obj.handle()));
170            assert!(!obj.is_null());
171            let is_user_interacting = if ScriptThread::is_user_interacting() {
172                PromiseUserInputEventHandlingState::HadUserInteractionAtCreation
173            } else {
174                PromiseUserInputEventHandlingState::DidntHaveUserInteractionAtCreation
175            };
176            SetPromiseUserInputEventHandlingState(obj.handle(), is_user_interacting);
177        }
178    }
179
180    #[expect(unsafe_code)]
181    pub(crate) fn new_resolved(
182        global: &GlobalScope,
183        cx: SafeJSContext,
184        value: impl SafeToJSValConvertible,
185        can_gc: CanGc,
186    ) -> Rc<Promise> {
187        let _ac = JSAutoRealm::new(*cx, global.reflector().get_jsobject().get());
188        rooted!(in(*cx) let mut rval = UndefinedValue());
189        value.safe_to_jsval(cx, rval.handle_mut(), can_gc);
190        unsafe {
191            rooted!(in(*cx) let p = CallOriginalPromiseResolve(*cx, rval.handle()));
192            assert!(!p.handle().is_null());
193            Promise::new_with_js_promise(p.handle(), cx)
194        }
195    }
196
197    #[expect(unsafe_code)]
198    pub(crate) fn new_rejected(
199        global: &GlobalScope,
200        cx: SafeJSContext,
201        value: impl SafeToJSValConvertible,
202        can_gc: CanGc,
203    ) -> Rc<Promise> {
204        let _ac = JSAutoRealm::new(*cx, global.reflector().get_jsobject().get());
205        rooted!(in(*cx) let mut rval = UndefinedValue());
206        value.safe_to_jsval(cx, rval.handle_mut(), can_gc);
207        unsafe {
208            rooted!(in(*cx) let p = CallOriginalPromiseReject(*cx, rval.handle()));
209            assert!(!p.handle().is_null());
210            Promise::new_with_js_promise(p.handle(), cx)
211        }
212    }
213
214    pub(crate) fn resolve_native<T>(&self, val: &T, can_gc: CanGc)
215    where
216        T: SafeToJSValConvertible,
217    {
218        let cx = GlobalScope::get_cx();
219        let _ac = enter_realm(self);
220        rooted!(in(*cx) let mut v = UndefinedValue());
221        val.safe_to_jsval(cx, v.handle_mut(), can_gc);
222        self.resolve(cx, v.handle(), can_gc);
223    }
224
225    pub(crate) fn resolve_native_with_cx<T>(&self, cx: &mut JSContext, val: &T)
226    where
227        T: SafeToJSValConvertible,
228    {
229        let mut realm = enter_auto_realm(cx, self);
230        let cx = &mut realm.current_realm();
231        rooted!(&in(cx) let mut v = UndefinedValue());
232        val.safe_to_jsval(cx.into(), v.handle_mut(), CanGc::from_cx(cx));
233        self.resolve_with_cx(cx, v.handle());
234    }
235
236    #[expect(unsafe_code)]
237    pub(crate) fn resolve(&self, cx: SafeJSContext, value: HandleValue, _can_gc: CanGc) {
238        unsafe {
239            if !js::rust::wrappers::ResolvePromise(*cx, self.promise_obj(), value) {
240                js::jsapi::JS_ClearPendingException(*cx);
241            }
242        }
243    }
244
245    #[expect(unsafe_code)]
246    pub(crate) fn resolve_with_cx(&self, cx: &mut JSContext, value: HandleValue) {
247        unsafe {
248            if !ResolvePromise(cx, self.promise_obj(), value) {
249                JS_ClearPendingException(cx);
250            }
251        }
252    }
253
254    pub(crate) fn reject_native<T>(&self, val: &T, can_gc: CanGc)
255    where
256        T: SafeToJSValConvertible,
257    {
258        let cx = GlobalScope::get_cx();
259        let _ac = enter_realm(self);
260        rooted!(in(*cx) let mut v = UndefinedValue());
261        val.safe_to_jsval(cx, v.handle_mut(), can_gc);
262        self.reject(cx, v.handle(), can_gc);
263    }
264
265    pub(crate) fn reject_native_with_cx<T>(&self, cx: &mut JSContext, val: &T)
266    where
267        T: SafeToJSValConvertible,
268    {
269        let mut realm = enter_auto_realm(cx, self);
270        let cx = &mut realm.current_realm();
271        rooted!(&in(cx) let mut v = UndefinedValue());
272        val.safe_to_jsval(cx.into(), v.handle_mut(), CanGc::from_cx(cx));
273        self.reject_with_cx(cx, v.handle());
274    }
275
276    pub(crate) fn reject_error(&self, error: Error, can_gc: CanGc) {
277        let cx = GlobalScope::get_cx();
278        let _ac = enter_realm(self);
279        rooted!(in(*cx) let mut v = UndefinedValue());
280        error.to_jsval(cx, &self.global(), v.handle_mut(), can_gc);
281        self.reject(cx, v.handle(), can_gc);
282    }
283
284    pub(crate) fn reject_error_with_cx(&self, cx: &mut JSContext, error: Error) {
285        let mut realm = enter_auto_realm(cx, self);
286        let cx = &mut realm.current_realm();
287        rooted!(&in(cx) let mut v = UndefinedValue());
288        error.to_jsval(
289            cx.into(),
290            &self.global(),
291            v.handle_mut(),
292            CanGc::from_cx(cx),
293        );
294        self.reject_with_cx(cx, v.handle());
295    }
296
297    #[expect(unsafe_code)]
298    pub(crate) fn reject(&self, cx: SafeJSContext, value: HandleValue, _can_gc: CanGc) {
299        unsafe {
300            if !js::rust::wrappers::RejectPromise(*cx, self.promise_obj(), value) {
301                js::jsapi::JS_ClearPendingException(*cx);
302            }
303        }
304    }
305
306    #[expect(unsafe_code)]
307    pub(crate) fn reject_with_cx(&self, cx: &mut JSContext, value: HandleValue) {
308        unsafe {
309            if !RejectPromise(cx, self.promise_obj(), value) {
310                JS_ClearPendingException(cx);
311            }
312        }
313    }
314
315    #[expect(unsafe_code)]
316    pub(crate) fn is_fulfilled(&self) -> bool {
317        let state = unsafe { GetPromiseState(self.promise_obj()) };
318        matches!(state, PromiseState::Rejected | PromiseState::Fulfilled)
319    }
320
321    #[expect(unsafe_code)]
322    pub(crate) fn is_rejected(&self) -> bool {
323        let state = unsafe { GetPromiseState(self.promise_obj()) };
324        matches!(state, PromiseState::Rejected)
325    }
326
327    #[expect(unsafe_code)]
328    pub(crate) fn is_pending(&self) -> bool {
329        let state = unsafe { GetPromiseState(self.promise_obj()) };
330        matches!(state, PromiseState::Pending)
331    }
332
333    #[expect(unsafe_code)]
334    pub(crate) fn promise_obj(&self) -> HandleObject<'_> {
335        let obj = self.reflector().get_jsobject();
336        unsafe {
337            assert!(IsPromiseObject(obj));
338        }
339        obj
340    }
341
342    #[expect(unsafe_code)]
343    pub(crate) fn append_native_handler(
344        &self,
345        cx: &mut CurrentRealm,
346        handler: &PromiseNativeHandler,
347    ) {
348        let in_realm_proof = cx.into();
349        let realm = InRealm::Already(&in_realm_proof);
350
351        run_a_script::<DomTypeHolder, _>(&handler.global_(realm), || {
352            rooted!(&in(cx) let resolve_func =
353                create_native_handler_function(cx,
354                                               handler.reflector().get_jsobject(),
355                                               NativeHandlerTask::Resolve));
356
357            rooted!(&in(cx) let reject_func =
358                create_native_handler_function(cx,
359                                               handler.reflector().get_jsobject(),
360                                               NativeHandlerTask::Reject));
361
362            unsafe {
363                let ok = AddPromiseReactions(
364                    cx,
365                    self.promise_obj(),
366                    resolve_func.handle(),
367                    reject_func.handle(),
368                );
369                assert!(ok);
370            }
371        })
372    }
373
374    #[expect(unsafe_code)]
375    pub(crate) fn get_promise_is_handled(&self) -> bool {
376        unsafe { GetPromiseIsHandled(self.reflector().get_jsobject()) }
377    }
378
379    #[expect(unsafe_code)]
380    pub(crate) fn set_promise_is_handled(&self) -> bool {
381        let cx = GlobalScope::get_cx();
382        unsafe { SetAnyPromiseIsHandled(*cx, self.reflector().get_jsobject()) }
383    }
384}
385
386#[expect(unsafe_code)]
387unsafe extern "C" fn do_nothing_promise_executor(
388    _cx: *mut RawJSContext,
389    argc: u32,
390    vp: *mut JSVal,
391) -> bool {
392    let args = unsafe { CallArgs::from_vp(vp, argc) };
393    args.rval().set(UndefinedValue());
394    true
395}
396
397const SLOT_NATIVEHANDLER: usize = 0;
398const SLOT_NATIVEHANDLER_TASK: usize = 1;
399
400#[derive(PartialEq)]
401enum NativeHandlerTask {
402    Resolve = 0,
403    Reject = 1,
404}
405
406#[expect(unsafe_code)]
407unsafe extern "C" fn native_handler_callback(
408    cx: *mut RawJSContext,
409    argc: u32,
410    vp: *mut JSVal,
411) -> bool {
412    // SAFETY: it is safe to construct a JSContext from engine hook.
413    let mut cx = unsafe { JSContext::from_ptr(ptr::NonNull::new(cx).unwrap()) };
414    let mut cx = CurrentRealm::assert(&mut cx);
415    let cx = &mut cx;
416
417    let args = unsafe { CallArgs::from_vp(vp, argc) };
418    let native_handler_value =
419        unsafe { *GetFunctionNativeReserved(args.callee(), SLOT_NATIVEHANDLER) };
420    rooted!(&in(cx) let native_handler_value = native_handler_value);
421    assert!(native_handler_value.get().is_object());
422
423    let handler = unsafe {
424        root_from_object::<PromiseNativeHandler>(native_handler_value.to_object(), cx.raw_cx())
425    }
426    .expect("unexpected value for native handler in promise native handler callback");
427
428    let native_handler_task_value =
429        unsafe { *GetFunctionNativeReserved(args.callee(), SLOT_NATIVEHANDLER_TASK) };
430    rooted!(&in(cx) let native_handler_task_value = native_handler_task_value);
431    match native_handler_task_value.to_int32() {
432        native_handler_task_value
433            if native_handler_task_value == NativeHandlerTask::Resolve as i32 =>
434        {
435            handler.resolved_callback(cx, unsafe { HandleValue::from_raw(args.get(0)) })
436        },
437        native_handler_task_value
438            if native_handler_task_value == NativeHandlerTask::Reject as i32 =>
439        {
440            handler.rejected_callback(cx, unsafe { HandleValue::from_raw(args.get(0)) })
441        },
442        _ => panic!("unexpected native handler task value"),
443    };
444
445    true
446}
447
448#[expect(unsafe_code)]
449fn create_native_handler_function(
450    cx: &mut JSContext,
451    holder: HandleObject,
452    task: NativeHandlerTask,
453) -> *mut JSObject {
454    unsafe {
455        let func = NewFunctionWithReserved(cx, Some(native_handler_callback), 1, 0, ptr::null());
456        assert!(!func.is_null());
457
458        rooted!(&in(cx) let obj = JS_GetFunctionObject(func));
459        assert!(!obj.is_null());
460        SetFunctionNativeReserved(obj.get(), SLOT_NATIVEHANDLER, &ObjectValue(*holder));
461        SetFunctionNativeReserved(obj.get(), SLOT_NATIVEHANDLER_TASK, &Int32Value(task as i32));
462        obj.get()
463    }
464}
465
466impl FromJSValConvertibleRc for Promise {
467    #[expect(unsafe_code)]
468    unsafe fn from_jsval(
469        _cx: *mut RawJSContext,
470        value: HandleValue,
471    ) -> Result<ConversionResult<Rc<Promise>>, ()> {
472        // TODO https://github.com/servo/mozjs/issues/749
473        let mut cx = unsafe { script_bindings::script_runtime::temp_cx() };
474        Self::safe_from_jsval(&mut cx, value)
475    }
476
477    fn safe_from_jsval(
478        cx: &mut JSContext,
479        value: HandleValue,
480    ) -> Result<ConversionResult<Rc<Promise>>, ()> {
481        if value.get().is_null() {
482            return Ok(ConversionResult::Failure(c"null not allowed".into()));
483        }
484
485        let realm = CurrentRealm::assert(cx);
486        let global_scope = GlobalScope::from_current_realm(&realm);
487
488        let promise = Promise::new_resolved(&global_scope, cx.into(), value, CanGc::from_cx(cx));
489        Ok(ConversionResult::Success(promise))
490    }
491}
492
493/// The success steps of <https://webidl.spec.whatwg.org/#wait-for-all>
494type WaitForAllSuccessSteps = Rc<dyn Fn(&mut JSContext, Vec<HandleValue>)>;
495
496/// The failure steps of <https://webidl.spec.whatwg.org/#wait-for-all>
497type WaitForAllFailureSteps = Rc<dyn Fn(&mut JSContext, HandleValue)>;
498
499/// The fulfillment handler for the list of promises in
500/// <https://webidl.spec.whatwg.org/#wait-for-all>.
501#[derive(JSTraceable, MallocSizeOf)]
502#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
503struct WaitForAllFulfillmentHandler {
504    /// The steps to call when all promises are resolved.
505    #[ignore_malloc_size_of = "callbacks are hard"]
506    #[no_trace]
507    success_steps: WaitForAllSuccessSteps,
508
509    /// The results of the promises.
510    #[ignore_malloc_size_of = "mozjs"]
511    #[expect(clippy::vec_box)]
512    result: Rc<RefCell<Vec<Box<Heap<JSVal>>>>>,
513
514    /// The index identifying which promise this handler is attached to.
515    promise_index: usize,
516
517    /// A count of fulfilled promises.
518    #[conditional_malloc_size_of]
519    fulfilled_count: Rc<RefCell<usize>>,
520}
521
522impl Callback for WaitForAllFulfillmentHandler {
523    fn callback(&self, cx: &mut CurrentRealm, v: HandleValue) {
524        // Let fulfillmentHandler be the following steps given arg:
525
526        let equals_total = {
527            // Set result[promiseIndex] to arg.
528            let result = self.result.borrow_mut();
529            result[self.promise_index].set(v.get());
530
531            // Set fulfilledCount to fulfilledCount + 1.
532            let mut fulfilled_count = self.fulfilled_count.borrow_mut();
533            *fulfilled_count += 1;
534
535            *fulfilled_count == result.len()
536        };
537
538        // If fulfilledCount equals total, then perform successSteps given result.
539        if equals_total {
540            let result_ref = self.result.borrow();
541            let result_handles: Vec<HandleValue> =
542                result_ref.iter().map(|v| v.as_handle_value()).collect();
543
544            (self.success_steps)(cx, result_handles);
545        }
546    }
547}
548
549/// The rejection handler for the list of promises in
550/// <https://webidl.spec.whatwg.org/#wait-for-all>.
551#[derive(Clone, JSTraceable, MallocSizeOf)]
552struct WaitForAllRejectionHandler {
553    /// The steps to call if any promise rejects.
554    #[ignore_malloc_size_of = "callbacks are hard"]
555    #[no_trace]
556    failure_steps: WaitForAllFailureSteps,
557
558    /// Whether any promises have been rejected already.
559    rejected: Cell<bool>,
560}
561
562impl Callback for WaitForAllRejectionHandler {
563    fn callback(&self, cx: &mut CurrentRealm, v: HandleValue) {
564        // Let rejectionHandlerSteps be the following steps given arg:
565
566        if self.rejected.replace(true) {
567            // If rejected is true, abort these steps.
568            return;
569        }
570
571        // Set rejected to true.
572        // Done above with `replace`.
573        (self.failure_steps)(cx, v);
574    }
575}
576
577/// The microtask for performing successSteps given « » in
578/// <https://webidl.spec.whatwg.org/#wait-for-all>.
579#[derive(JSTraceable, MallocSizeOf)]
580pub(crate) struct WaitForAllSuccessStepsMicrotask {
581    global: DomRoot<GlobalScope>,
582
583    #[ignore_malloc_size_of = "Closure is hard"]
584    #[no_trace]
585    success_steps: WaitForAllSuccessSteps,
586}
587
588impl MicrotaskRunnable for WaitForAllSuccessStepsMicrotask {
589    fn handler(&self, cx: &mut JSContext) {
590        (self.success_steps)(cx, vec![]);
591    }
592
593    fn enter_realm<'cx>(&self, cx: &'cx mut JSContext) -> AutoRealm<'cx> {
594        enter_auto_realm(cx, &*self.global)
595    }
596}
597
598/// <https://webidl.spec.whatwg.org/#wait-for-all>
599#[cfg_attr(crown, expect(crown::unrooted_must_root))]
600fn wait_for_all(
601    cx: &mut CurrentRealm,
602    global: &GlobalScope,
603    promises: Vec<Rc<Promise>>,
604    success_steps: WaitForAllSuccessSteps,
605    failure_steps: WaitForAllFailureSteps,
606) {
607    // Let fulfilledCount be 0.
608    let fulfilled_count: Rc<RefCell<usize>> = Default::default();
609
610    // Let rejected be false.
611    // Note: done below when constructing a rejection handler.
612
613    // Let rejectionHandlerSteps be the following steps given arg:
614    // Note: implemented with the `WaitForAllRejectionHandler`.
615
616    // Let rejectionHandler be CreateBuiltinFunction(rejectionHandlerSteps, « »):
617    // Note: done as part of attaching the `WaitForAllRejectionHandler` as native rejection handler.
618    let rejection_handler = WaitForAllRejectionHandler {
619        failure_steps,
620        rejected: Default::default(),
621    };
622
623    // Let total be promises’s size.
624    // Note: done using the len of result.
625
626    // If total is 0, then:
627    if promises.is_empty() {
628        // Queue a microtask to perform successSteps given « ».
629        global.enqueue_microtask(Microtask::WaitForAllSuccessSteps(
630            WaitForAllSuccessStepsMicrotask {
631                global: DomRoot::from_ref(global),
632                success_steps,
633            },
634        ));
635
636        // Return.
637        return;
638    }
639
640    // Let index be 0.
641    // Note: done with `enumerate` below.
642
643    // Let result be a list containing total null values.
644    let result: Rc<RefCell<Vec<Box<Heap<JSVal>>>>> = Default::default();
645
646    // For each promise of promises:
647    for (promise_index, promise) in promises.into_iter().enumerate() {
648        let result = result.clone();
649
650        {
651            // Note: adding a null value for this promise result.
652            let mut result_list = result.borrow_mut();
653            rooted!(&in(cx) let null_value = NullValue());
654            result_list.push(Heap::boxed(null_value.get()));
655        }
656
657        // Let promiseIndex be index.
658        // Note: done with `enumerate` above.
659
660        // Let fulfillmentHandler be the following steps given arg:
661        // Note: implemented with the `WaitForAllFulFillmentHandler`.
662
663        // Let fulfillmentHandler be CreateBuiltinFunction(fulfillmentHandler, « »):
664        // Note: passed below to avoid the need to root it.
665
666        // Perform PerformPromiseThen(promise, fulfillmentHandler, rejectionHandler).
667        let handler = PromiseNativeHandler::new(
668            cx,
669            global,
670            Some(Box::new(WaitForAllFulfillmentHandler {
671                success_steps: success_steps.clone(),
672                result,
673                promise_index,
674                fulfilled_count: fulfilled_count.clone(),
675            })),
676            Some(Box::new(rejection_handler.clone())),
677        );
678        promise.append_native_handler(cx, &handler);
679
680        // Set index to index + 1.
681        // Note: done above with `enumerate`.
682    }
683}
684
685/// <https://webidl.spec.whatwg.org/#waiting-for-all-promise>
686pub(crate) fn wait_for_all_promise(
687    cx: &mut CurrentRealm,
688    global: &GlobalScope,
689    promises: Vec<Rc<Promise>>,
690) -> Rc<Promise> {
691    // Let promise be a new promise of type Promise<sequence<T>> in realm.
692    let promise = Promise::new2(cx, global);
693    let success_promise = promise.clone();
694    let failure_promise = promise.clone();
695
696    // Let successSteps be the following steps, given results:
697    let success_steps = Rc::new(move |cx: &mut JSContext, results: Vec<HandleValue>| {
698        // Resolve promise with results.
699        success_promise.resolve_native_with_cx(cx, &results);
700    });
701
702    // Let failureSteps be the following steps, given reason:
703    let failure_steps = Rc::new(move |cx: &mut JSContext, reason: HandleValue| {
704        // Reject promise with reason.
705        failure_promise.reject_native_with_cx(cx, &reason);
706    });
707
708    // Wait for all with promises, given successSteps and failureSteps.
709    wait_for_all(cx, global, promises, success_steps, failure_steps);
710
711    // Return promise.
712    promise
713}