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