Skip to main content

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