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, FromJSValConvertibleRc, ToJSValConvertible};
22use js::gc::MutableHandleValue;
23use js::jsapi::{
24    CallArgs, GetFunctionNativeReserved, Heap, JS_GetFunctionObject, JSContext as RawJSContext,
25    JSObject, PromiseState, PromiseUserInputEventHandlingState, RemoveRawValueRoot,
26    SetFunctionNativeReserved,
27};
28use js::jsval::{Int32Value, JSVal, NullValue, ObjectValue, UndefinedValue};
29use js::realm::CurrentRealm;
30use js::rust::wrappers2::{
31    AddPromiseReactions, AddRawValueRoot, CallOriginalPromiseReject, CallOriginalPromiseResolve,
32    GetPromiseIsHandled, GetPromiseState, IsPromiseObject, JS_ClearPendingException,
33    JS_NewFunction, NewFunctionWithReserved, NewPromiseObject, RejectPromise, ResolvePromise,
34    SetAnyPromiseIsHandled, SetPromiseUserInputEventHandlingState,
35};
36use js::rust::{HandleObject, HandleValue, MutableHandleObject, Runtime};
37use script_bindings::interfaces::{
38    HeapTracedPromiseHelpers, PromiseHelpers, StackRootPromiseHelpers,
39};
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.duplicate_unrooted())
88    }
89}
90
91impl js::conversions::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_rooted(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/// A manual GC root that will exist until this PermanentRoot is dropped.
155#[derive(JSTraceable)] // TODO: remove this once this is no longer part of Promise.
156#[derive(Default, MallocSizeOf)]
157#[cfg_attr(crown, crown::unrooted_must_root_lint::allow_unrooted_interior)]
158/// Maintains a GC root for the contained value until this object is dropped.
159///
160/// # Safety
161/// The root (and the contained value) is only valid as long as this value
162/// is never moved after it is initialized. It should only be used inside
163/// of a container like Box or Rc and never extracted from it.
164struct PermanentRoot(#[ignore_malloc_size_of = "mozjs value"] Heap<JSVal>);
165
166impl PermanentRoot {
167    /// Add a GC root for the provided JS object.
168    ///
169    /// # Safety
170    /// - This method must only be called on a `PermanentRoot` that will not
171    ///   move for the remainder of its lifetime (e.g. inside of Box, Rc, etc.)
172    /// - This must only be called once per instance of `PermanentRoot`
173    #[expect(unsafe_code)]
174    unsafe fn init(&self, cx: &JSContext, object: HandleObject) {
175        self.0.set(ObjectValue(*object));
176        unsafe {
177            assert!(AddRawValueRoot(
178                cx,
179                self.0.get_unsafe(),
180                c"Promise::root".as_ptr(),
181            ));
182        }
183    }
184}
185
186impl Drop for PermanentRoot {
187    #[expect(unsafe_code)]
188    fn drop(&mut self) {
189        let js_root = self.0.get();
190        if js_root.is_undefined() {
191            return;
192        }
193        let object = js_root.to_object();
194        assert!(!object.is_null());
195        if let Some(cx) = Runtime::get() {
196            unsafe {
197                RemoveRawValueRoot(cx.as_ptr(), self.0.get_unsafe());
198            }
199        }
200    }
201}
202
203#[dom_struct]
204#[cfg_attr(crown, crown::unrooted_must_root_lint::allow_unrooted_in_rc)]
205pub(crate) struct Promise {
206    reflector: Reflector,
207    /// Since Promise values are natively reference counted without the knowledge of
208    /// the SpiderMonkey GC, an explicit root for the reflector is stored while any
209    /// native instance exists. This ensures that the reflector will never be GCed
210    /// while native code could still interact with its native representation.
211    /// FIXME(#47747) Deprecated and planned for removal.
212    permanent_js_root: Option<PermanentRoot>,
213}
214
215impl Promise {
216    /// Create a new [RootedPromise] associated with the provided global.
217    pub(crate) fn new_rooted(cx: &mut JSContext, global: &GlobalScope) -> RootedPromise {
218        let mut realm = enter_auto_realm(cx, global);
219        let cx = &mut realm.current_realm();
220        Promise::new_in_realm_rooted(cx)
221    }
222
223    /// Create a new [Promise] associated with the provided global.
224    ///
225    /// **Deprecated:** Use [Promise::new_rooted] instead.
226    pub(crate) fn new(cx: &mut JSContext, global: &GlobalScope) -> Rc<Promise> {
227        let mut realm = enter_auto_realm(cx, global);
228        let cx = &mut realm.current_realm();
229        Promise::new_in_realm(cx)
230    }
231
232    /// Create a new [Promise] associated with the provided realm.
233    pub(crate) fn new_in_realm(current_realm: &mut CurrentRealm) -> Rc<Promise> {
234        let cx = current_realm.deref_mut();
235        rooted!(&in(cx) let mut obj = ptr::null_mut::<JSObject>());
236        Promise::create_js_promise(cx, obj.handle_mut());
237        Promise::new_with_js_promise(cx, obj.handle())
238    }
239
240    /// Create a new [RootedPromise] associated with the provided realm.
241    ///
242    /// **Deprecated:** Use [Promise::new_in_realm_rooted] instead.
243    pub(crate) fn new_in_realm_rooted(current_realm: &mut CurrentRealm) -> RootedPromise {
244        let cx = current_realm.deref_mut();
245        rooted!(&in(cx) let mut obj = ptr::null_mut::<JSObject>());
246        Promise::create_js_promise(cx, obj.handle_mut());
247        Promise::new_with_js_promise_rooted(cx, obj.handle())
248    }
249
250    /// Create a new [RootedPromise] wrapping the same underlying [Promise].
251    pub(crate) fn duplicate(&self, cx: &JSContext) -> RootedPromise {
252        Promise::new_with_js_promise_rooted(cx, self.reflector().get_jsobject())
253    }
254
255    #[expect(unsafe_code)]
256    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
257    fn duplicate_unrooted(&self) -> Rc<Promise> {
258        let promise = Promise {
259            reflector: Reflector::new(),
260            permanent_js_root: None,
261        };
262        let promise = Rc::new(promise);
263        unsafe {
264            promise.init_reflector_without_associated_memory(self.reflector().get_jsobject().get());
265        }
266        promise
267    }
268
269    /// Create a new [Promise] wrapping the provided JS object.
270    /// Panics if the provided object is not a JS promise.
271    ///
272    /// **Deprecated:** Use [Promise::new_with_js_promise_rooted] instead.
273    #[expect(unsafe_code)]
274    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
275    pub(crate) fn new_with_js_promise(cx: &JSContext, obj: HandleObject) -> Rc<Promise> {
276        unsafe {
277            assert!(IsPromiseObject(obj));
278        }
279        let promise = Promise {
280            reflector: Reflector::new(),
281            permanent_js_root: Some(PermanentRoot::default()),
282        };
283        let promise = Rc::new(promise);
284        unsafe {
285            promise.init_reflector_without_associated_memory(obj.get());
286            promise.permanent_js_root.as_ref().unwrap().init(cx, obj);
287        }
288        promise
289    }
290
291    /// Create a new [RootedPromise] wrapping the provided JS object.
292    /// Panics if the provided object is not a JS promise.
293    #[expect(unsafe_code)]
294    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
295    pub(crate) fn new_with_js_promise_rooted(cx: &JSContext, obj: HandleObject) -> RootedPromise {
296        unsafe {
297            assert!(IsPromiseObject(obj));
298        }
299        let promise = Promise {
300            reflector: Reflector::new(),
301            permanent_js_root: None,
302        };
303        let promise = Rc::new((promise, PermanentRoot::default()));
304        unsafe {
305            promise
306                .0
307                .init_reflector_without_associated_memory(obj.get());
308            promise.1.init(cx, obj);
309        }
310        RootedPromise(promise)
311    }
312
313    #[expect(unsafe_code)]
314    fn create_js_promise(cx: &mut JSContext, mut obj: MutableHandleObject) {
315        unsafe {
316            let do_nothing_func = JS_NewFunction(
317                cx,
318                Some(do_nothing_promise_executor),
319                /* nargs = */ 2,
320                /* flags = */ 0,
321                ptr::null(),
322            );
323            assert!(!do_nothing_func.is_null());
324            rooted!(&in(cx) let do_nothing_obj = JS_GetFunctionObject(do_nothing_func));
325            assert!(!do_nothing_obj.is_null());
326            obj.set(NewPromiseObject(cx, do_nothing_obj.handle()));
327            assert!(!obj.is_null());
328            let is_user_interacting = if ScriptThread::is_user_interacting() {
329                PromiseUserInputEventHandlingState::HadUserInteractionAtCreation
330            } else {
331                PromiseUserInputEventHandlingState::DidntHaveUserInteractionAtCreation
332            };
333            SetPromiseUserInputEventHandlingState(obj.handle(), is_user_interacting);
334        }
335    }
336
337    #[expect(unsafe_code)]
338    fn new_resolved_shared<F, T>(
339        cx: &mut JSContext,
340        global: &GlobalScope,
341        value: impl ToJSValConvertible,
342        constructor: F,
343    ) -> T
344    where
345        F: for<'a, 'b> Fn(&'a JSContext, HandleObject<'b>) -> T,
346    {
347        let mut realm = enter_auto_realm(cx, global);
348        let cx = &mut realm.current_realm();
349        rooted!(&in(cx) let mut rval = UndefinedValue());
350        value.to_jsval(cx, rval.handle_mut());
351        rooted!(&in(cx) let p = unsafe { CallOriginalPromiseResolve(cx, rval.handle()) });
352        assert!(!p.handle().is_null());
353        constructor(cx, p.handle())
354    }
355
356    /// Create a new [Promise] associated with the provided global,
357    /// resolved with the provided value.
358    ///
359    /// **Deprecated:** Use [Promise::new_resolved_rooted] instead.
360    pub(crate) fn new_resolved(
361        cx: &mut JSContext,
362        global: &GlobalScope,
363        value: impl ToJSValConvertible,
364    ) -> Rc<Promise> {
365        Self::new_resolved_shared(cx, global, value, Promise::new_with_js_promise)
366    }
367
368    /// Create a new [RootedPromise] associated with the provided global,
369    /// resolved with the provided value.
370    pub(crate) fn new_resolved_rooted(
371        cx: &mut JSContext,
372        global: &GlobalScope,
373        value: impl ToJSValConvertible,
374    ) -> RootedPromise {
375        Self::new_resolved_shared(cx, global, value, Promise::new_with_js_promise_rooted)
376    }
377
378    #[expect(unsafe_code)]
379    fn new_rejected_shared<F, T>(
380        cx: &mut JSContext,
381        global: &GlobalScope,
382        value: impl ToJSValConvertible,
383        constructor: F,
384    ) -> T
385    where
386        F: for<'a, 'b> Fn(&'a JSContext, HandleObject<'b>) -> T,
387    {
388        let mut realm = enter_auto_realm(cx, global);
389        let cx = &mut realm.current_realm();
390        rooted!(&in(cx) let mut rval = UndefinedValue());
391        value.to_jsval(cx, rval.handle_mut());
392        rooted!(&in(cx) let p = unsafe { CallOriginalPromiseReject(cx, rval.handle()) });
393        assert!(!p.handle().is_null());
394        constructor(cx, p.handle())
395    }
396
397    /// Create a new [RootedPromise] associated with the provided global,
398    /// rejected with the provided value.
399    pub(crate) fn new_rejected_rooted(
400        cx: &mut JSContext,
401        global: &GlobalScope,
402        value: impl ToJSValConvertible,
403    ) -> RootedPromise {
404        Self::new_rejected_shared(cx, global, value, Promise::new_with_js_promise_rooted)
405    }
406
407    pub(crate) fn resolve_native<T>(&self, cx: &mut JSContext, val: &T)
408    where
409        T: ToJSValConvertible,
410    {
411        let mut realm = enter_auto_realm(cx, self);
412        let cx = &mut realm.current_realm();
413        rooted!(&in(cx) let mut v = UndefinedValue());
414        val.to_jsval(cx, v.handle_mut());
415        self.resolve(cx, v.handle());
416    }
417
418    #[expect(unsafe_code)]
419    pub(crate) fn resolve(&self, cx: &mut JSContext, value: HandleValue) {
420        unsafe {
421            if !ResolvePromise(cx, self.promise_obj(), value) {
422                JS_ClearPendingException(cx);
423            }
424        }
425    }
426
427    pub(crate) fn reject_native<T>(&self, cx: &mut JSContext, val: &T)
428    where
429        T: ToJSValConvertible,
430    {
431        let mut realm = enter_auto_realm(cx, self);
432        let cx = &mut realm.current_realm();
433        rooted!(&in(cx) let mut v = UndefinedValue());
434        val.to_jsval(cx, v.handle_mut());
435        self.reject(cx, v.handle());
436    }
437
438    pub(crate) fn reject_error(&self, cx: &mut JSContext, error: Error) {
439        let mut realm = enter_auto_realm(cx, self);
440        let cx = &mut realm.current_realm();
441        rooted!(&in(cx) let mut v = UndefinedValue());
442        error.to_jsval(cx, &self.global(), v.handle_mut());
443        self.reject(cx, v.handle());
444    }
445
446    #[expect(unsafe_code)]
447    pub(crate) fn reject(&self, cx: &mut JSContext, value: HandleValue) {
448        unsafe {
449            if !RejectPromise(cx, self.promise_obj(), value) {
450                JS_ClearPendingException(cx);
451            }
452        }
453    }
454
455    #[expect(unsafe_code)]
456    pub(crate) fn is_fulfilled(&self) -> bool {
457        let state = unsafe { GetPromiseState(self.promise_obj()) };
458        matches!(state, PromiseState::Rejected | PromiseState::Fulfilled)
459    }
460
461    #[expect(unsafe_code)]
462    pub(crate) fn is_rejected(&self) -> bool {
463        let state = unsafe { GetPromiseState(self.promise_obj()) };
464        matches!(state, PromiseState::Rejected)
465    }
466
467    #[expect(unsafe_code)]
468    pub(crate) fn is_pending(&self) -> bool {
469        let state = unsafe { GetPromiseState(self.promise_obj()) };
470        matches!(state, PromiseState::Pending)
471    }
472
473    #[expect(unsafe_code)]
474    pub(crate) fn promise_obj(&self) -> HandleObject<'_> {
475        let obj = self.reflector().get_jsobject();
476        unsafe {
477            assert!(IsPromiseObject(obj));
478        }
479        obj
480    }
481
482    #[expect(unsafe_code)]
483    pub(crate) fn append_native_handler(
484        &self,
485        cx: &mut CurrentRealm,
486        handler: &PromiseNativeHandler,
487    ) {
488        let global = GlobalScope::from_current_realm(cx);
489        run_a_script::<DomTypeHolder, _, _>(cx, &global, |cx| {
490            rooted!(&in(cx) let resolve_func =
491                create_native_handler_function(cx,
492                                               handler.reflector().get_jsobject(),
493                                               NativeHandlerTask::Resolve));
494
495            rooted!(&in(cx) let reject_func =
496                create_native_handler_function(cx,
497                                               handler.reflector().get_jsobject(),
498                                               NativeHandlerTask::Reject));
499
500            unsafe {
501                let ok = AddPromiseReactions(
502                    cx,
503                    self.promise_obj(),
504                    resolve_func.handle(),
505                    reject_func.handle(),
506                );
507                assert!(ok);
508            }
509        })
510    }
511
512    #[expect(unsafe_code)]
513    pub(crate) fn get_promise_is_handled(&self) -> bool {
514        unsafe { GetPromiseIsHandled(self.reflector().get_jsobject()) }
515    }
516
517    #[expect(unsafe_code)]
518    pub(crate) fn set_promise_is_handled(&self, cx: &mut JSContext) -> bool {
519        unsafe { SetAnyPromiseIsHandled(cx, self.reflector().get_jsobject()) }
520    }
521}
522
523#[expect(unsafe_code)]
524unsafe extern "C" fn do_nothing_promise_executor(
525    _cx: *mut RawJSContext,
526    argc: u32,
527    vp: *mut JSVal,
528) -> bool {
529    let args = unsafe { CallArgs::from_vp(vp, argc) };
530    args.rval().set(UndefinedValue());
531    true
532}
533
534const SLOT_NATIVEHANDLER: usize = 0;
535const SLOT_NATIVEHANDLER_TASK: usize = 1;
536
537#[derive(PartialEq)]
538enum NativeHandlerTask {
539    Resolve = 0,
540    Reject = 1,
541}
542
543#[expect(unsafe_code)]
544unsafe extern "C" fn native_handler_callback(
545    cx: *mut RawJSContext,
546    argc: u32,
547    vp: *mut JSVal,
548) -> bool {
549    // SAFETY: it is safe to construct a JSContext from engine hook.
550    let mut cx = unsafe { JSContext::from_ptr(ptr::NonNull::new(cx).unwrap()) };
551    let mut cx = CurrentRealm::assert(&mut cx);
552    let cx = &mut cx;
553
554    let args = unsafe { CallArgs::from_vp(vp, argc) };
555    let native_handler_value =
556        unsafe { *GetFunctionNativeReserved(args.callee(), SLOT_NATIVEHANDLER) };
557    rooted!(&in(cx) let native_handler_value = native_handler_value);
558    assert!(native_handler_value.get().is_object());
559
560    let handler =
561        unsafe { root_from_object::<PromiseNativeHandler>(cx, native_handler_value.to_object()) }
562            .expect("unexpected value for native handler in promise native handler callback");
563
564    let native_handler_task_value =
565        unsafe { *GetFunctionNativeReserved(args.callee(), SLOT_NATIVEHANDLER_TASK) };
566    rooted!(&in(cx) let native_handler_task_value = native_handler_task_value);
567    match native_handler_task_value.to_int32() {
568        native_handler_task_value
569            if native_handler_task_value == NativeHandlerTask::Resolve as i32 =>
570        {
571            handler.resolved_callback(cx, unsafe { HandleValue::from_raw(args.get(0)) })
572        },
573        native_handler_task_value
574            if native_handler_task_value == NativeHandlerTask::Reject as i32 =>
575        {
576            handler.rejected_callback(cx, unsafe { HandleValue::from_raw(args.get(0)) })
577        },
578        _ => panic!("unexpected native handler task value"),
579    };
580
581    true
582}
583
584#[expect(unsafe_code)]
585fn create_native_handler_function(
586    cx: &mut JSContext,
587    holder: HandleObject,
588    task: NativeHandlerTask,
589) -> *mut JSObject {
590    unsafe {
591        let func = NewFunctionWithReserved(cx, Some(native_handler_callback), 1, 0, ptr::null());
592        assert!(!func.is_null());
593
594        rooted!(&in(cx) let obj = JS_GetFunctionObject(func));
595        assert!(!obj.is_null());
596        SetFunctionNativeReserved(obj.get(), SLOT_NATIVEHANDLER, &ObjectValue(*holder));
597        SetFunctionNativeReserved(obj.get(), SLOT_NATIVEHANDLER_TASK, &Int32Value(task as i32));
598        obj.get()
599    }
600}
601
602impl FromJSValConvertibleRc for Promise {
603    fn from_jsval(
604        cx: &mut JSContext,
605        value: HandleValue,
606    ) -> Result<ConversionResult<Rc<Promise>>, ()> {
607        if value.get().is_null() {
608            return Ok(ConversionResult::Failure(c"null not allowed".into()));
609        }
610
611        let mut realm = CurrentRealm::assert(cx);
612        let global_scope = GlobalScope::from_current_realm(&mut realm);
613
614        let promise = Promise::new_resolved(cx, &global_scope, value);
615        Ok(ConversionResult::Success(promise))
616    }
617}
618
619/// The success steps of <https://webidl.spec.whatwg.org/#wait-for-all>
620type WaitForAllSuccessSteps = Rc<dyn Fn(&mut JSContext, Vec<HandleValue>)>;
621
622/// The failure steps of <https://webidl.spec.whatwg.org/#wait-for-all>
623type WaitForAllFailureSteps = Rc<dyn Fn(&mut JSContext, HandleValue)>;
624
625/// The fulfillment handler for the list of promises in
626/// <https://webidl.spec.whatwg.org/#wait-for-all>.
627#[derive(JSTraceable, MallocSizeOf)]
628#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
629struct WaitForAllFulfillmentHandler {
630    /// The steps to call when all promises are resolved.
631    #[ignore_malloc_size_of = "callbacks are hard"]
632    #[no_trace]
633    success_steps: WaitForAllSuccessSteps,
634
635    /// The results of the promises.
636    #[ignore_malloc_size_of = "mozjs"]
637    #[expect(clippy::vec_box)]
638    result: Rc<RefCell<Vec<Box<Heap<JSVal>>>>>,
639
640    /// The index identifying which promise this handler is attached to.
641    promise_index: usize,
642
643    /// A count of fulfilled promises.
644    #[conditional_malloc_size_of]
645    fulfilled_count: Rc<Cell<usize>>,
646}
647
648impl Callback for WaitForAllFulfillmentHandler {
649    fn callback(&self, cx: &mut CurrentRealm, v: HandleValue) {
650        // Let fulfillmentHandler be the following steps given arg:
651
652        let equals_total = {
653            // Set result[promiseIndex] to arg.
654            let result = self.result.borrow_mut();
655            result[self.promise_index].set(v.get());
656
657            // Set fulfilledCount to fulfilledCount + 1.
658            let mut fulfilled_count = self.fulfilled_count.get();
659            fulfilled_count += 1;
660            self.fulfilled_count.set(fulfilled_count);
661
662            fulfilled_count == result.len()
663        };
664
665        // If fulfilledCount equals total, then perform successSteps given result.
666        if equals_total {
667            let result_ref = self.result.borrow();
668            let result_handles: Vec<HandleValue> =
669                result_ref.iter().map(|v| v.as_handle_value()).collect();
670
671            (self.success_steps)(cx, result_handles);
672        }
673    }
674}
675
676/// The rejection handler for the list of promises in
677/// <https://webidl.spec.whatwg.org/#wait-for-all>.
678#[derive(Clone, JSTraceable, MallocSizeOf)]
679struct WaitForAllRejectionHandler {
680    /// The steps to call if any promise rejects.
681    #[ignore_malloc_size_of = "callbacks are hard"]
682    #[no_trace]
683    failure_steps: WaitForAllFailureSteps,
684
685    /// Whether any promises have been rejected already.
686    rejected: Cell<bool>,
687}
688
689impl Callback for WaitForAllRejectionHandler {
690    fn callback(&self, cx: &mut CurrentRealm, v: HandleValue) {
691        // Let rejectionHandlerSteps be the following steps given arg:
692
693        if self.rejected.replace(true) {
694            // If rejected is true, abort these steps.
695            return;
696        }
697
698        // Set rejected to true.
699        // Done above with `replace`.
700        (self.failure_steps)(cx, v);
701    }
702}
703
704/// The microtask for performing successSteps given « » in
705/// <https://webidl.spec.whatwg.org/#wait-for-all>.
706#[derive(JSTraceable, MallocSizeOf)]
707#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
708pub(crate) struct WaitForAllSuccessStepsMicrotask {
709    global: Dom<GlobalScope>,
710
711    #[ignore_malloc_size_of = "Closure is hard"]
712    #[no_trace]
713    success_steps: WaitForAllSuccessSteps,
714}
715
716impl MicrotaskRunnable for WaitForAllSuccessStepsMicrotask {
717    fn handler(&self, cx: &mut JSContext) {
718        let mut realm = enter_auto_realm(cx, &*self.global);
719        (self.success_steps)(&mut realm, vec![]);
720    }
721}
722
723/// <https://webidl.spec.whatwg.org/#wait-for-all>
724#[cfg_attr(crown, expect(crown::unrooted_must_root))]
725fn wait_for_all(
726    cx: &mut CurrentRealm,
727    global: &GlobalScope,
728    promises: Vec<RootedPromise>,
729    success_steps: WaitForAllSuccessSteps,
730    failure_steps: WaitForAllFailureSteps,
731) {
732    // Let fulfilledCount be 0.
733    let fulfilled_count: Rc<Cell<usize>> = Default::default();
734
735    // Let rejected be false.
736    // Note: done below when constructing a rejection handler.
737
738    // Let rejectionHandlerSteps be the following steps given arg:
739    // Note: implemented with the `WaitForAllRejectionHandler`.
740
741    // Let rejectionHandler be CreateBuiltinFunction(rejectionHandlerSteps, « »):
742    // Note: done as part of attaching the `WaitForAllRejectionHandler` as native rejection handler.
743    let rejection_handler = WaitForAllRejectionHandler {
744        failure_steps,
745        rejected: Default::default(),
746    };
747
748    // Let total be promises’s size.
749    // Note: done using the len of result.
750
751    // If total is 0, then:
752    if promises.is_empty() {
753        // Queue a microtask to perform successSteps given « ».
754        global.enqueue_microtask(
755            cx,
756            Box::new(WaitForAllSuccessStepsMicrotask {
757                global: Dom::from_ref(global),
758                success_steps,
759            }),
760        );
761
762        // Return.
763        return;
764    }
765
766    // Let index be 0.
767    // Note: done with `enumerate` below.
768
769    // Let result be a list containing total null values.
770    let result: Rc<RefCell<Vec<Box<Heap<JSVal>>>>> = Default::default();
771
772    // For each promise of promises:
773    for (promise_index, promise) in promises.into_iter().enumerate() {
774        let result = result.clone();
775
776        {
777            // Note: adding a null value for this promise result.
778            let mut result_list = result.borrow_mut();
779            rooted!(&in(cx) let null_value = NullValue());
780            result_list.push(Heap::boxed(null_value.get()));
781        }
782
783        // Let promiseIndex be index.
784        // Note: done with `enumerate` above.
785
786        // Let fulfillmentHandler be the following steps given arg:
787        // Note: implemented with the `WaitForAllFulFillmentHandler`.
788
789        // Let fulfillmentHandler be CreateBuiltinFunction(fulfillmentHandler, « »):
790        // Note: passed below to avoid the need to root it.
791
792        // Perform PerformPromiseThen(promise, fulfillmentHandler, rejectionHandler).
793        let handler = PromiseNativeHandler::new(
794            cx,
795            global,
796            Some(Box::new(WaitForAllFulfillmentHandler {
797                success_steps: success_steps.clone(),
798                result,
799                promise_index,
800                fulfilled_count: fulfilled_count.clone(),
801            })),
802            Some(Box::new(rejection_handler.clone())),
803        );
804        promise.append_native_handler(cx, &handler);
805
806        // Set index to index + 1.
807        // Note: done above with `enumerate`.
808    }
809}
810
811/// <https://webidl.spec.whatwg.org/#waiting-for-all-promise>
812pub(crate) fn wait_for_all_promise(
813    cx: &mut CurrentRealm,
814    global: &GlobalScope,
815    promises: Vec<RootedPromise>,
816) -> RootedPromise {
817    // Let promise be a new promise of type Promise<sequence<T>> in realm.
818    let promise = Promise::new_rooted(cx, global);
819    let success_promise = promise.clone();
820    let failure_promise = promise.clone();
821
822    // Let successSteps be the following steps, given results:
823    let success_steps = Rc::new(move |cx: &mut JSContext, results: Vec<HandleValue>| {
824        // Resolve promise with results.
825        success_promise.resolve_native(cx, &results);
826    });
827
828    // Let failureSteps be the following steps, given reason:
829    let failure_steps = Rc::new(move |cx: &mut JSContext, reason: HandleValue| {
830        // Reject promise with reason.
831        failure_promise.reject_native(cx, &reason);
832    });
833
834    // Wait for all with promises, given successSteps and failureSteps.
835    wait_for_all(cx, global, promises, success_steps, failure_steps);
836
837    // Return promise.
838    promise
839}
840
841impl PromiseHelpers<crate::DomTypeHolder> for Promise {
842    type StackRoot = RootedPromise;
843    type HeapTraced = TracedPromise;
844
845    fn new_in_realm(
846        cx: &mut CurrentRealm,
847    ) -> Rc<<crate::DomTypeHolder as script_bindings::DomTypes>::Promise> {
848        Promise::new_in_realm(cx)
849    }
850
851    fn new_in_realm_rooted(cx: &mut CurrentRealm) -> RootedPromise {
852        Promise::new_in_realm_rooted(cx)
853    }
854
855    fn reject_error(&self, cx: &mut js::context::JSContext, error: script_bindings::error::Error) {
856        Promise::reject_error(self, cx, error);
857    }
858
859    fn is_rejected(&self) -> bool {
860        self.is_rejected()
861    }
862
863    fn is_pending(&self) -> bool {
864        self.is_pending()
865    }
866
867    fn resolve_native<T: ToJSValConvertible>(&self, cx: &mut JSContext, val: &T) {
868        self.resolve_native(cx, val);
869    }
870}