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