1use 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#[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 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#[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 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 #[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 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 #[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 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 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 #[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 2,
245 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 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 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 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
491type WaitForAllSuccessSteps = Rc<dyn Fn(&mut JSContext, Vec<HandleValue>)>;
493
494type WaitForAllFailureSteps = Rc<dyn Fn(&mut JSContext, HandleValue)>;
496
497#[derive(JSTraceable, MallocSizeOf)]
500#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
501struct WaitForAllFulfillmentHandler {
502 #[ignore_malloc_size_of = "callbacks are hard"]
504 #[no_trace]
505 success_steps: WaitForAllSuccessSteps,
506
507 #[ignore_malloc_size_of = "mozjs"]
509 #[expect(clippy::vec_box)]
510 result: Rc<RefCell<Vec<Box<Heap<JSVal>>>>>,
511
512 promise_index: usize,
514
515 #[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 equals_total = {
525 let result = self.result.borrow_mut();
527 result[self.promise_index].set(v.get());
528
529 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 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#[derive(Clone, JSTraceable, MallocSizeOf)]
551struct WaitForAllRejectionHandler {
552 #[ignore_malloc_size_of = "callbacks are hard"]
554 #[no_trace]
555 failure_steps: WaitForAllFailureSteps,
556
557 rejected: Cell<bool>,
559}
560
561impl Callback for WaitForAllRejectionHandler {
562 fn callback(&self, cx: &mut CurrentRealm, v: HandleValue) {
563 if self.rejected.replace(true) {
566 return;
568 }
569
570 (self.failure_steps)(cx, v);
573 }
574}
575
576#[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#[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 fulfilled_count: Rc<Cell<usize>> = Default::default();
606
607 let rejection_handler = WaitForAllRejectionHandler {
616 failure_steps,
617 rejected: Default::default(),
618 };
619
620 if promises.is_empty() {
625 global.enqueue_microtask(
627 cx,
628 Box::new(WaitForAllSuccessStepsMicrotask {
629 global: Dom::from_ref(global),
630 success_steps,
631 }),
632 );
633
634 return;
636 }
637
638 let result: Rc<RefCell<Vec<Box<Heap<JSVal>>>>> = Default::default();
643
644 for (promise_index, promise) in promises.into_iter().enumerate() {
646 let result = result.clone();
647
648 {
649 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 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 }
681}
682
683pub(crate) fn wait_for_all_promise(
685 cx: &mut CurrentRealm,
686 global: &GlobalScope,
687 promises: Vec<RootedPromise>,
688) -> RootedPromise {
689 let promise = Promise::new(cx, global);
691 let success_promise = promise.clone();
692 let failure_promise = promise.clone();
693
694 let success_steps = Rc::new(move |cx: &mut JSContext, results: Vec<HandleValue>| {
696 success_promise.resolve_native(cx, &results);
698 });
699
700 let failure_steps = Rc::new(move |cx: &mut JSContext, reason: HandleValue| {
702 failure_promise.reject_native(cx, &reason);
704 });
705
706 wait_for_all(cx, global, promises, success_steps, failure_steps);
708
709 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}