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