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 type ThreadTrusted = TrustedPromise;
67 fn to_traced(&self) -> TracedPromise {
68 RootedPromise::to_traced(self)
69 }
70 fn to_trusted(&self) -> TrustedPromise {
71 TrustedPromise::new(self)
72 }
73}
74
75impl Deref for RootedPromise {
76 type Target = Promise;
77 fn deref(&self) -> &Self::Target {
78 &self.0.0
79 }
80}
81
82impl RootedPromise {
83 pub(crate) fn to_traced(&self) -> TracedPromise {
85 TracedPromise(self.duplicate_unrooted())
86 }
87}
88
89impl From<&'_ RootedPromise> for TrustedPromise {
90 fn from(promise: &'_ RootedPromise) -> Self {
91 TrustedPromise::new(promise)
92 }
93}
94
95impl FromJSValConvertible for RootedPromise {
96 type Config = ();
97
98 fn from_jsval(
99 cx: &mut JSContext,
100 value: HandleValue,
101 _option: Self::Config,
102 ) -> Result<ConversionResult<Self>, ()> {
103 if value.get().is_null() {
104 return Ok(ConversionResult::Failure(c"null not allowed".into()));
105 }
106
107 let mut realm = CurrentRealm::assert(cx);
108 let global_scope = GlobalScope::from_current_realm(&mut realm);
109
110 let promise = Promise::new_resolved(cx, &global_scope, value);
111 Ok(ConversionResult::Success(promise))
112 }
113}
114
115impl ToJSValConvertible for RootedPromise {
116 fn to_jsval(&self, cx: &mut JSContext, rval: MutableHandleValue<'_>) {
117 self.0.0.to_jsval(cx, rval)
118 }
119}
120
121#[derive(Clone, MallocSizeOf, JSTraceable)]
126#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
127pub(crate) struct TracedPromise(#[conditional_malloc_size_of] Rc<Promise>);
128
129impl std::cmp::PartialEq for TracedPromise {
130 fn eq(&self, other: &Self) -> bool {
131 *self.0 == **other
132 }
133}
134
135impl HeapTracedPromiseHelpers<crate::DomTypeHolder> for TracedPromise {
136 type StackRoot = RootedPromise;
137 fn root(&self, cx: &JSContext) -> RootedPromise {
138 TracedPromise::root(self, cx)
139 }
140}
141
142impl js::rust::Rootable for TracedPromise {}
143
144impl TracedPromise {
145 pub(crate) fn root(&self, cx: &JSContext) -> RootedPromise {
147 self.duplicate(cx)
148 }
149}
150
151impl Deref for TracedPromise {
152 type Target = Promise;
153 fn deref(&self) -> &Self::Target {
154 &self.0
155 }
156}
157
158#[dom_struct]
159#[cfg_attr(crown, crown::unrooted_must_root_lint::allow_unrooted_in_rc)]
160pub(crate) struct Promise {
161 reflector: Reflector,
162}
163
164impl Promise {
165 #[expect(unsafe_code)]
167 pub(crate) fn resolve_or_wrap_promise(
168 cx: &mut JSContext,
169 value: HandleValue,
170 global: &GlobalScope,
171 ) -> RootedPromise {
172 rooted!(&in(cx) let mut object = ptr::null_mut::<JSObject>());
173 let is_promise = if value.is_object() {
174 object.set(value.to_object());
175 unsafe { IsPromiseObject(object.handle()) }
177 } else {
178 false
179 };
180 if is_promise {
181 Self::new_with_js_promise(cx, object.handle())
182 } else {
183 Self::new_resolved(cx, global, value.get())
184 }
185 }
186
187 #[expect(clippy::new_ret_no_self)]
189 pub(crate) fn new(cx: &mut JSContext, global: &GlobalScope) -> RootedPromise {
190 let mut realm = enter_auto_realm(cx, global);
191 let cx = &mut realm.current_realm();
192 Promise::new_in_realm(cx)
193 }
194
195 pub(crate) fn new_in_realm(current_realm: &mut CurrentRealm) -> RootedPromise {
197 let cx = current_realm.deref_mut();
198 rooted!(&in(cx) let mut obj = ptr::null_mut::<JSObject>());
199 Promise::create_js_promise(cx, obj.handle_mut());
200 Promise::new_with_js_promise(cx, obj.handle())
201 }
202
203 pub(crate) fn duplicate(&self, cx: &JSContext) -> RootedPromise {
205 Promise::new_with_js_promise(cx, self.reflector().get_jsobject())
206 }
207
208 #[expect(unsafe_code)]
209 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
210 fn duplicate_unrooted(&self) -> Rc<Promise> {
211 let promise = Promise {
212 reflector: Reflector::new(),
213 };
214 let promise = Rc::new(promise);
215 unsafe {
216 promise.init_reflector_without_associated_memory(self.reflector().get_jsobject().get());
217 }
218 promise
219 }
220
221 #[expect(unsafe_code)]
224 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
225 pub(crate) fn new_with_js_promise(cx: &JSContext, obj: HandleObject) -> RootedPromise {
226 unsafe {
227 assert!(IsPromiseObject(obj));
228 }
229 let promise = Promise {
230 reflector: Reflector::new(),
231 };
232 let promise = Rc::new((promise, PermanentRoot::default()));
233 unsafe {
234 promise
235 .0
236 .init_reflector_without_associated_memory(obj.get());
237 promise.1.init(cx, *obj, c"Promise::root");
238 }
239 RootedPromise(promise)
240 }
241
242 #[expect(unsafe_code)]
243 fn create_js_promise(cx: &mut JSContext, mut obj: MutableHandleObject) {
244 unsafe {
245 let do_nothing_func = JS_NewFunction(
246 cx,
247 Some(do_nothing_promise_executor),
248 2,
249 0,
250 ptr::null(),
251 );
252 assert!(!do_nothing_func.is_null());
253 rooted!(&in(cx) let do_nothing_obj = JS_GetFunctionObject(do_nothing_func));
254 assert!(!do_nothing_obj.is_null());
255 obj.set(NewPromiseObject(cx, do_nothing_obj.handle()));
256 assert!(!obj.is_null());
257 let is_user_interacting = if ScriptThread::is_user_interacting() {
258 PromiseUserInputEventHandlingState::HadUserInteractionAtCreation
259 } else {
260 PromiseUserInputEventHandlingState::DidntHaveUserInteractionAtCreation
261 };
262 SetPromiseUserInputEventHandlingState(obj.handle(), is_user_interacting);
263 }
264 }
265
266 #[expect(unsafe_code)]
267 fn new_with_settled_value(
268 cx: &mut JSContext,
269 global: &GlobalScope,
270 value: impl ToJSValConvertible,
271 settle_fn: unsafe fn(&mut JSContext, HandleValue) -> *mut JSObject,
272 ) -> RootedPromise {
273 let mut realm = enter_auto_realm(cx, global);
274 let cx = &mut realm.current_realm();
275 rooted!(&in(cx) let mut rval = UndefinedValue());
276 value.to_jsval(cx, rval.handle_mut());
277 rooted!(&in(cx) let p = unsafe { settle_fn(cx, rval.handle()) });
278 assert!(!p.handle().is_null());
279 Promise::new_with_js_promise(cx, p.handle())
280 }
281
282 pub(crate) fn new_resolved(
285 cx: &mut JSContext,
286 global: &GlobalScope,
287 value: impl ToJSValConvertible,
288 ) -> RootedPromise {
289 Promise::new_with_settled_value(cx, global, value, CallOriginalPromiseResolve)
290 }
291
292 pub(crate) fn new_rejected(
295 cx: &mut JSContext,
296 global: &GlobalScope,
297 value: impl ToJSValConvertible,
298 ) -> RootedPromise {
299 Promise::new_with_settled_value(cx, global, value, CallOriginalPromiseReject)
300 }
301
302 pub(crate) fn resolve_native<T>(&self, cx: &mut JSContext, val: &T)
303 where
304 T: ToJSValConvertible,
305 {
306 let mut realm = enter_auto_realm(cx, self);
307 let cx = &mut realm.current_realm();
308 rooted!(&in(cx) let mut v = UndefinedValue());
309 val.to_jsval(cx, v.handle_mut());
310 self.resolve(cx, v.handle());
311 }
312
313 #[expect(unsafe_code)]
314 pub(crate) fn resolve(&self, cx: &mut JSContext, value: HandleValue) {
315 unsafe {
316 if !ResolvePromise(cx, self.promise_obj(), value) {
317 JS_ClearPendingException(cx);
318 }
319 }
320 }
321
322 pub(crate) fn reject_native<T>(&self, cx: &mut JSContext, val: &T)
323 where
324 T: ToJSValConvertible,
325 {
326 let mut realm = enter_auto_realm(cx, self);
327 let cx = &mut realm.current_realm();
328 rooted!(&in(cx) let mut v = UndefinedValue());
329 val.to_jsval(cx, v.handle_mut());
330 self.reject(cx, v.handle());
331 }
332
333 pub(crate) fn reject_error(&self, cx: &mut JSContext, error: Error) {
334 let mut realm = enter_auto_realm(cx, self);
335 let cx = &mut realm.current_realm();
336 rooted!(&in(cx) let mut v = UndefinedValue());
337 error.to_jsval(cx, &self.global(), v.handle_mut());
338 self.reject(cx, v.handle());
339 }
340
341 #[expect(unsafe_code)]
342 pub(crate) fn reject(&self, cx: &mut JSContext, value: HandleValue) {
343 unsafe {
344 if !RejectPromise(cx, self.promise_obj(), value) {
345 JS_ClearPendingException(cx);
346 }
347 }
348 }
349
350 #[expect(unsafe_code)]
351 pub(crate) fn is_fulfilled(&self) -> bool {
352 let state = unsafe { GetPromiseState(self.promise_obj()) };
353 matches!(state, PromiseState::Rejected | PromiseState::Fulfilled)
354 }
355
356 #[expect(unsafe_code)]
357 pub(crate) fn is_rejected(&self) -> bool {
358 let state = unsafe { GetPromiseState(self.promise_obj()) };
359 matches!(state, PromiseState::Rejected)
360 }
361
362 #[expect(unsafe_code)]
363 pub(crate) fn is_pending(&self) -> bool {
364 let state = unsafe { GetPromiseState(self.promise_obj()) };
365 matches!(state, PromiseState::Pending)
366 }
367
368 #[expect(unsafe_code)]
369 pub(crate) fn promise_obj(&self) -> HandleObject<'_> {
370 let obj = self.reflector().get_jsobject();
371 unsafe {
372 assert!(IsPromiseObject(obj));
373 }
374 obj
375 }
376
377 #[expect(unsafe_code)]
378 pub(crate) fn append_native_handler(
379 &self,
380 cx: &mut CurrentRealm,
381 handler: &PromiseNativeHandler,
382 ) {
383 let global = GlobalScope::from_current_realm(cx);
384 run_a_script::<DomTypeHolder, _, _>(cx, &global, |cx| {
385 rooted!(&in(cx) let resolve_func =
386 create_native_handler_function(cx,
387 handler.reflector().get_jsobject(),
388 NativeHandlerTask::Resolve));
389
390 rooted!(&in(cx) let reject_func =
391 create_native_handler_function(cx,
392 handler.reflector().get_jsobject(),
393 NativeHandlerTask::Reject));
394
395 unsafe {
396 let ok = AddPromiseReactions(
397 cx,
398 self.promise_obj(),
399 resolve_func.handle(),
400 reject_func.handle(),
401 );
402 assert!(ok);
403 }
404 })
405 }
406
407 #[expect(unsafe_code)]
408 pub(crate) fn get_promise_is_handled(&self) -> bool {
409 unsafe { GetPromiseIsHandled(self.reflector().get_jsobject()) }
410 }
411
412 #[expect(unsafe_code)]
413 pub(crate) fn set_promise_is_handled(&self, cx: &mut JSContext) -> bool {
414 unsafe { SetAnyPromiseIsHandled(cx, self.reflector().get_jsobject()) }
415 }
416}
417
418#[expect(unsafe_code)]
419unsafe extern "C" fn do_nothing_promise_executor(
420 _cx: *mut RawJSContext,
421 argc: u32,
422 vp: *mut JSVal,
423) -> bool {
424 let args = unsafe { CallArgs::from_vp(vp, argc) };
425 args.rval().set(UndefinedValue());
426 true
427}
428
429const SLOT_NATIVEHANDLER: usize = 0;
430const SLOT_NATIVEHANDLER_TASK: usize = 1;
431
432#[derive(PartialEq)]
433enum NativeHandlerTask {
434 Resolve = 0,
435 Reject = 1,
436}
437
438#[expect(unsafe_code)]
439unsafe extern "C" fn native_handler_callback(
440 cx: *mut RawJSContext,
441 argc: u32,
442 vp: *mut JSVal,
443) -> bool {
444 let mut cx = unsafe { JSContext::from_ptr(ptr::NonNull::new(cx).unwrap()) };
446 let mut cx = CurrentRealm::assert(&mut cx);
447 let cx = &mut cx;
448
449 let args = unsafe { CallArgs::from_vp(vp, argc) };
450 let native_handler_value =
451 unsafe { *GetFunctionNativeReserved(args.callee(), SLOT_NATIVEHANDLER) };
452 rooted!(&in(cx) let native_handler_value = native_handler_value);
453 assert!(native_handler_value.get().is_object());
454
455 let handler =
456 unsafe { root_from_object::<PromiseNativeHandler>(cx, native_handler_value.to_object()) }
457 .expect("unexpected value for native handler in promise native handler callback");
458
459 let native_handler_task_value =
460 unsafe { *GetFunctionNativeReserved(args.callee(), SLOT_NATIVEHANDLER_TASK) };
461 rooted!(&in(cx) let native_handler_task_value = native_handler_task_value);
462 match native_handler_task_value.to_int32() {
463 native_handler_task_value
464 if native_handler_task_value == NativeHandlerTask::Resolve as i32 =>
465 {
466 handler.resolved_callback(cx, unsafe { HandleValue::from_raw(args.get(0)) })
467 },
468 native_handler_task_value
469 if native_handler_task_value == NativeHandlerTask::Reject as i32 =>
470 {
471 handler.rejected_callback(cx, unsafe { HandleValue::from_raw(args.get(0)) })
472 },
473 _ => panic!("unexpected native handler task value"),
474 };
475
476 true
477}
478
479#[expect(unsafe_code)]
480fn create_native_handler_function(
481 cx: &mut JSContext,
482 holder: HandleObject,
483 task: NativeHandlerTask,
484) -> *mut JSObject {
485 unsafe {
486 let func = NewFunctionWithReserved(cx, Some(native_handler_callback), 1, 0, ptr::null());
487 assert!(!func.is_null());
488
489 rooted!(&in(cx) let obj = JS_GetFunctionObject(func));
490 assert!(!obj.is_null());
491 SetFunctionNativeReserved(obj.get(), SLOT_NATIVEHANDLER, &ObjectValue(*holder));
492 SetFunctionNativeReserved(obj.get(), SLOT_NATIVEHANDLER_TASK, &Int32Value(task as i32));
493 obj.get()
494 }
495}
496
497type WaitForAllSuccessSteps = Rc<dyn Fn(&mut JSContext, Vec<HandleValue>)>;
499
500type WaitForAllFailureSteps = Rc<dyn Fn(&mut JSContext, HandleValue)>;
502
503#[derive(JSTraceable, MallocSizeOf)]
506#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
507struct WaitForAllFulfillmentHandler {
508 #[ignore_malloc_size_of = "callbacks are hard"]
510 #[no_trace]
511 success_steps: WaitForAllSuccessSteps,
512
513 #[ignore_malloc_size_of = "mozjs"]
515 #[expect(clippy::vec_box)]
516 result: Rc<RefCell<Vec<Box<Heap<JSVal>>>>>,
517
518 promise_index: usize,
520
521 #[conditional_malloc_size_of]
523 fulfilled_count: Rc<Cell<usize>>,
524}
525
526impl Callback for WaitForAllFulfillmentHandler {
527 fn callback(&self, cx: &mut CurrentRealm, v: HandleValue) {
528 let equals_total = {
531 let result = self.result.borrow_mut();
533 result[self.promise_index].set(v.get());
534
535 let mut fulfilled_count = self.fulfilled_count.get();
537 fulfilled_count += 1;
538 self.fulfilled_count.set(fulfilled_count);
539
540 fulfilled_count == result.len()
541 };
542
543 if equals_total {
545 let result_ref = self.result.borrow();
546 let result_handles: Vec<HandleValue> =
547 result_ref.iter().map(|v| v.as_handle_value()).collect();
548
549 (self.success_steps)(cx, result_handles);
550 }
551 }
552}
553
554#[derive(Clone, JSTraceable, MallocSizeOf)]
557struct WaitForAllRejectionHandler {
558 #[ignore_malloc_size_of = "callbacks are hard"]
560 #[no_trace]
561 failure_steps: WaitForAllFailureSteps,
562
563 rejected: Cell<bool>,
565}
566
567impl Callback for WaitForAllRejectionHandler {
568 fn callback(&self, cx: &mut CurrentRealm, v: HandleValue) {
569 if self.rejected.replace(true) {
572 return;
574 }
575
576 (self.failure_steps)(cx, v);
579 }
580}
581
582#[derive(JSTraceable, MallocSizeOf)]
585#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
586pub(crate) struct WaitForAllSuccessStepsMicrotask {
587 global: Dom<GlobalScope>,
588
589 #[ignore_malloc_size_of = "Closure is hard"]
590 #[no_trace]
591 success_steps: WaitForAllSuccessSteps,
592}
593
594impl MicrotaskRunnable for WaitForAllSuccessStepsMicrotask {
595 fn handler(&self, cx: &mut JSContext) {
596 let mut realm = enter_auto_realm(cx, &*self.global);
597 (self.success_steps)(&mut realm, vec![]);
598 }
599}
600
601#[cfg_attr(crown, expect(crown::unrooted_must_root))]
603fn wait_for_all(
604 cx: &mut CurrentRealm,
605 global: &GlobalScope,
606 promises: Vec<RootedPromise>,
607 success_steps: WaitForAllSuccessSteps,
608 failure_steps: WaitForAllFailureSteps,
609) {
610 let fulfilled_count: Rc<Cell<usize>> = Default::default();
612
613 let rejection_handler = WaitForAllRejectionHandler {
622 failure_steps,
623 rejected: Default::default(),
624 };
625
626 if promises.is_empty() {
631 global.enqueue_microtask(
633 cx,
634 Box::new(WaitForAllSuccessStepsMicrotask {
635 global: Dom::from_ref(global),
636 success_steps,
637 }),
638 );
639
640 return;
642 }
643
644 let result: Rc<RefCell<Vec<Box<Heap<JSVal>>>>> = Default::default();
649
650 for (promise_index, promise) in promises.into_iter().enumerate() {
652 let result = result.clone();
653
654 {
655 let mut result_list = result.borrow_mut();
657 rooted!(&in(cx) let null_value = NullValue());
658 result_list.push(Heap::boxed(null_value.get()));
659 }
660
661 let handler = PromiseNativeHandler::new(
672 cx,
673 global,
674 Some(Box::new(WaitForAllFulfillmentHandler {
675 success_steps: success_steps.clone(),
676 result,
677 promise_index,
678 fulfilled_count: fulfilled_count.clone(),
679 })),
680 Some(Box::new(rejection_handler.clone())),
681 );
682 promise.append_native_handler(cx, &handler);
683
684 }
687}
688
689pub(crate) fn wait_for_all_promise(
691 cx: &mut CurrentRealm,
692 global: &GlobalScope,
693 promises: Vec<RootedPromise>,
694) -> RootedPromise {
695 let promise = Promise::new(cx, global);
697 let success_promise = promise.clone();
698 let failure_promise = promise.clone();
699
700 let success_steps = Rc::new(move |cx: &mut JSContext, results: Vec<HandleValue>| {
702 success_promise.resolve_native(cx, &results);
704 });
705
706 let failure_steps = Rc::new(move |cx: &mut JSContext, reason: HandleValue| {
708 failure_promise.reject_native(cx, &reason);
710 });
711
712 wait_for_all(cx, global, promises, success_steps, failure_steps);
714
715 promise
717}
718
719impl PromiseHelpers<crate::DomTypeHolder> for Promise {
720 type StackRoot = RootedPromise;
721 type HeapTraced = TracedPromise;
722 type ThreadTrusted = TrustedPromise;
723
724 fn new_in_realm(cx: &mut CurrentRealm) -> RootedPromise {
725 Promise::new_in_realm(cx)
726 }
727
728 fn reject_error(&self, cx: &mut js::context::JSContext, error: script_bindings::error::Error) {
729 Promise::reject_error(self, cx, error);
730 }
731
732 fn is_rejected(&self) -> bool {
733 self.is_rejected()
734 }
735
736 fn is_pending(&self) -> bool {
737 self.is_pending()
738 }
739
740 fn resolve_native<T: ToJSValConvertible>(&self, cx: &mut JSContext, val: &T) {
741 self.resolve_native(cx, val);
742 }
743
744 fn reject_native<T: ToJSValConvertible>(&self, cx: &mut JSContext, val: &T) {
745 self.reject_native(cx, val);
746 }
747
748 fn is_fulfilled(&self) -> bool {
749 self.is_fulfilled()
750 }
751
752 fn new(cx: &mut JSContext, global: &GlobalScope) -> RootedPromise {
753 Promise::new(cx, global)
754 }
755}