1use std::cell::{Cell, RefCell};
15use std::ops::DerefMut;
16use std::ptr;
17use std::rc::Rc;
18
19use dom_struct::dom_struct;
20use js::context::JSContext;
21use js::conversions::{ConversionResult, FromJSValConvertibleRc};
22use js::jsapi::{
23 AddRawValueRoot, CallArgs, GetFunctionNativeReserved, Heap, JS_GetFunctionObject,
24 JS_NewFunction, JSAutoRealm, JSContext as RawJSContext, JSObject, PromiseState,
25 PromiseUserInputEventHandlingState, RemoveRawValueRoot, SetFunctionNativeReserved,
26};
27use js::jsval::{Int32Value, JSVal, NullValue, ObjectValue, UndefinedValue};
28use js::realm::{AutoRealm, CurrentRealm};
29use js::rust::wrappers::{
30 CallOriginalPromiseResolve, GetPromiseIsHandled, GetPromiseState, IsPromiseObject,
31 NewPromiseObject, SetAnyPromiseIsHandled, SetPromiseUserInputEventHandlingState,
32};
33use js::rust::wrappers2::{
34 AddPromiseReactions, CallOriginalPromiseReject, JS_ClearPendingException,
35 NewFunctionWithReserved, RejectPromise, ResolvePromise,
36};
37use js::rust::{HandleObject, HandleValue, MutableHandleObject, Runtime};
38use script_bindings::conversions::SafeToJSValConvertible;
39use script_bindings::reflector::{DomObject, MutDomObject, Reflector};
40use script_bindings::settings_stack::run_a_script;
41
42use crate::DomTypeHolder;
43use crate::dom::bindings::conversions::root_from_object;
44use crate::dom::bindings::error::{Error, ErrorToJsval};
45use crate::dom::bindings::reflector::DomGlobal;
46use crate::dom::bindings::root::{AsHandleValue, DomRoot};
47use crate::dom::globalscope::GlobalScope;
48use crate::dom::promisenativehandler::{Callback, PromiseNativeHandler};
49use crate::microtask::{Microtask, MicrotaskRunnable};
50use crate::realms::{InRealm, enter_auto_realm, enter_realm};
51use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
52use crate::script_thread::ScriptThread;
53
54#[dom_struct]
55#[cfg_attr(crown, crown::unrooted_must_root_lint::allow_unrooted_in_rc)]
56pub(crate) struct Promise {
57 reflector: Reflector,
58 #[ignore_malloc_size_of = "SM handles JS values"]
63 permanent_js_root: Heap<JSVal>,
64}
65
66trait PromiseHelper {
68 fn initialize(&self, cx: SafeJSContext);
69}
70
71impl PromiseHelper for Rc<Promise> {
72 #[expect(unsafe_code)]
73 fn initialize(&self, cx: SafeJSContext) {
74 let obj = self.reflector().get_jsobject();
75 self.permanent_js_root.set(ObjectValue(*obj));
76 unsafe {
77 assert!(AddRawValueRoot(
78 *cx,
79 self.permanent_js_root.get_unsafe(),
80 c"Promise::root".as_ptr(),
81 ));
82 }
83 }
84}
85
86impl Drop for Promise {
90 #[expect(unsafe_code)]
91 fn drop(&mut self) {
92 unsafe {
93 let object = self.permanent_js_root.get().to_object();
94 assert!(!object.is_null());
95 if let Some(cx) = Runtime::get() {
96 RemoveRawValueRoot(cx.as_ptr(), self.permanent_js_root.get_unsafe());
97 }
98 }
99 }
100}
101
102impl Promise {
103 pub(crate) fn new(global: &GlobalScope, can_gc: CanGc) -> Rc<Promise> {
104 let realm = enter_realm(global);
105 let comp = InRealm::Entered(&realm);
106 Promise::new_in_current_realm(comp, can_gc)
107 }
108
109 pub(crate) fn new_in_current_realm(_comp: InRealm, can_gc: CanGc) -> Rc<Promise> {
110 let cx = GlobalScope::get_cx();
111 rooted!(in(*cx) let mut obj = ptr::null_mut::<JSObject>());
112 Promise::create_js_promise(cx, obj.handle_mut(), can_gc);
113 Promise::new_with_js_promise(obj.handle(), cx)
114 }
115
116 pub(crate) fn new2(cx: &mut js::context::JSContext, global: &GlobalScope) -> Rc<Promise> {
117 let mut realm = AutoRealm::new(
118 cx,
119 std::ptr::NonNull::new(global.reflector().get_jsobject().get()).unwrap(),
120 );
121 let mut current_realm = realm.current_realm();
122 Promise::new_in_realm(&mut current_realm)
123 }
124
125 pub(crate) fn new_in_realm(current_realm: &mut CurrentRealm) -> Rc<Promise> {
126 let cx = current_realm.deref_mut();
127 rooted!(&in(cx) let mut obj = ptr::null_mut::<JSObject>());
128 Promise::create_js_promise(cx.into(), obj.handle_mut(), CanGc::from_cx(cx));
129 Promise::new_with_js_promise(obj.handle(), cx.into())
130 }
131
132 pub(crate) fn duplicate(&self) -> Rc<Promise> {
133 let cx = GlobalScope::get_cx();
134 Promise::new_with_js_promise(self.reflector().get_jsobject(), cx)
135 }
136
137 #[expect(unsafe_code)]
138 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
139 pub(crate) fn new_with_js_promise(obj: HandleObject, cx: SafeJSContext) -> Rc<Promise> {
140 unsafe {
141 assert!(IsPromiseObject(obj));
142 let promise = Promise {
143 reflector: Reflector::new(),
144 permanent_js_root: Heap::default(),
145 };
146 let promise = Rc::new(promise);
147 promise.init_reflector_without_associated_memory(obj.get());
148 promise.initialize(cx);
149 promise
150 }
151 }
152
153 #[expect(unsafe_code)]
154 fn create_js_promise(cx: SafeJSContext, mut obj: MutableHandleObject, _can_gc: CanGc) {
157 unsafe {
158 let do_nothing_func = JS_NewFunction(
159 *cx,
160 Some(do_nothing_promise_executor),
161 2,
162 0,
163 ptr::null(),
164 );
165 assert!(!do_nothing_func.is_null());
166 rooted!(in(*cx) let do_nothing_obj = JS_GetFunctionObject(do_nothing_func));
167 assert!(!do_nothing_obj.is_null());
168 obj.set(NewPromiseObject(*cx, do_nothing_obj.handle()));
169 assert!(!obj.is_null());
170 let is_user_interacting = if ScriptThread::is_user_interacting() {
171 PromiseUserInputEventHandlingState::HadUserInteractionAtCreation
172 } else {
173 PromiseUserInputEventHandlingState::DidntHaveUserInteractionAtCreation
174 };
175 SetPromiseUserInputEventHandlingState(obj.handle(), is_user_interacting);
176 }
177 }
178
179 #[expect(unsafe_code)]
180 pub(crate) fn new_resolved(
181 global: &GlobalScope,
182 cx: SafeJSContext,
183 value: impl SafeToJSValConvertible,
184 can_gc: CanGc,
185 ) -> Rc<Promise> {
186 let _ac = JSAutoRealm::new(*cx, global.reflector().get_jsobject().get());
187 rooted!(in(*cx) let mut rval = UndefinedValue());
188 value.safe_to_jsval(cx, rval.handle_mut(), can_gc);
189 unsafe {
190 rooted!(in(*cx) let p = CallOriginalPromiseResolve(*cx, rval.handle()));
191 assert!(!p.handle().is_null());
192 Promise::new_with_js_promise(p.handle(), cx)
193 }
194 }
195
196 #[expect(unsafe_code)]
197 pub(crate) fn new_rejected(
198 cx: &mut js::context::JSContext,
199 global: &GlobalScope,
200 value: impl SafeToJSValConvertible,
201 ) -> Rc<Promise> {
202 let mut realm = enter_auto_realm(cx, global);
203 let cx = &mut realm.current_realm();
204 rooted!(&in(cx) let mut rval = UndefinedValue());
205 value.safe_to_jsval(cx.into(), rval.handle_mut(), CanGc::from_cx(cx));
206 rooted!(&in(cx) let p = unsafe { CallOriginalPromiseReject(cx, rval.handle()) });
207 assert!(!p.handle().is_null());
208 Promise::new_with_js_promise(p.handle(), cx.into())
209 }
210
211 pub(crate) fn resolve_native<T>(&self, val: &T, can_gc: CanGc)
212 where
213 T: SafeToJSValConvertible,
214 {
215 let cx = GlobalScope::get_cx();
216 let _ac = enter_realm(self);
217 rooted!(in(*cx) let mut v = UndefinedValue());
218 val.safe_to_jsval(cx, v.handle_mut(), can_gc);
219 self.resolve(cx, v.handle(), can_gc);
220 }
221
222 pub(crate) fn resolve_native_with_cx<T>(&self, cx: &mut JSContext, val: &T)
223 where
224 T: SafeToJSValConvertible,
225 {
226 let mut realm = enter_auto_realm(cx, self);
227 let cx = &mut realm.current_realm();
228 rooted!(&in(cx) let mut v = UndefinedValue());
229 val.safe_to_jsval(cx.into(), v.handle_mut(), CanGc::from_cx(cx));
230 self.resolve_with_cx(cx, v.handle());
231 }
232
233 #[expect(unsafe_code)]
234 pub(crate) fn resolve(&self, cx: SafeJSContext, value: HandleValue, _can_gc: CanGc) {
235 unsafe {
236 if !js::rust::wrappers::ResolvePromise(*cx, self.promise_obj(), value) {
237 js::jsapi::JS_ClearPendingException(*cx);
238 }
239 }
240 }
241
242 #[expect(unsafe_code)]
243 pub(crate) fn resolve_with_cx(&self, cx: &mut JSContext, value: HandleValue) {
244 unsafe {
245 if !ResolvePromise(cx, self.promise_obj(), value) {
246 JS_ClearPendingException(cx);
247 }
248 }
249 }
250
251 pub(crate) fn reject_native<T>(&self, val: &T, can_gc: CanGc)
252 where
253 T: SafeToJSValConvertible,
254 {
255 let cx = GlobalScope::get_cx();
256 let _ac = enter_realm(self);
257 rooted!(in(*cx) let mut v = UndefinedValue());
258 val.safe_to_jsval(cx, v.handle_mut(), can_gc);
259 self.reject(cx, v.handle(), can_gc);
260 }
261
262 pub(crate) fn reject_native_with_cx<T>(&self, cx: &mut JSContext, val: &T)
263 where
264 T: SafeToJSValConvertible,
265 {
266 let mut realm = enter_auto_realm(cx, self);
267 let cx = &mut realm.current_realm();
268 rooted!(&in(cx) let mut v = UndefinedValue());
269 val.safe_to_jsval(cx.into(), v.handle_mut(), CanGc::from_cx(cx));
270 self.reject_with_cx(cx, v.handle());
271 }
272
273 pub(crate) fn reject_error(&self, error: Error, can_gc: CanGc) {
274 let cx = GlobalScope::get_cx();
275 let _ac = enter_realm(self);
276 rooted!(in(*cx) let mut v = UndefinedValue());
277 error.to_jsval(cx, &self.global(), v.handle_mut(), can_gc);
278 self.reject(cx, v.handle(), can_gc);
279 }
280
281 pub(crate) fn reject_error_with_cx(&self, cx: &mut JSContext, error: Error) {
282 let mut realm = enter_auto_realm(cx, self);
283 let cx = &mut realm.current_realm();
284 rooted!(&in(cx) let mut v = UndefinedValue());
285 error.to_jsval(
286 cx.into(),
287 &self.global(),
288 v.handle_mut(),
289 CanGc::from_cx(cx),
290 );
291 self.reject_with_cx(cx, v.handle());
292 }
293
294 #[expect(unsafe_code)]
295 pub(crate) fn reject(&self, cx: SafeJSContext, value: HandleValue, _can_gc: CanGc) {
296 unsafe {
297 if !js::rust::wrappers::RejectPromise(*cx, self.promise_obj(), value) {
298 js::jsapi::JS_ClearPendingException(*cx);
299 }
300 }
301 }
302
303 #[expect(unsafe_code)]
304 pub(crate) fn reject_with_cx(&self, cx: &mut JSContext, value: HandleValue) {
305 unsafe {
306 if !RejectPromise(cx, self.promise_obj(), value) {
307 JS_ClearPendingException(cx);
308 }
309 }
310 }
311
312 #[expect(unsafe_code)]
313 pub(crate) fn is_fulfilled(&self) -> bool {
314 let state = unsafe { GetPromiseState(self.promise_obj()) };
315 matches!(state, PromiseState::Rejected | PromiseState::Fulfilled)
316 }
317
318 #[expect(unsafe_code)]
319 pub(crate) fn is_rejected(&self) -> bool {
320 let state = unsafe { GetPromiseState(self.promise_obj()) };
321 matches!(state, PromiseState::Rejected)
322 }
323
324 #[expect(unsafe_code)]
325 pub(crate) fn is_pending(&self) -> bool {
326 let state = unsafe { GetPromiseState(self.promise_obj()) };
327 matches!(state, PromiseState::Pending)
328 }
329
330 #[expect(unsafe_code)]
331 pub(crate) fn promise_obj(&self) -> HandleObject<'_> {
332 let obj = self.reflector().get_jsobject();
333 unsafe {
334 assert!(IsPromiseObject(obj));
335 }
336 obj
337 }
338
339 #[expect(unsafe_code)]
340 pub(crate) fn append_native_handler(
341 &self,
342 cx: &mut CurrentRealm,
343 handler: &PromiseNativeHandler,
344 ) {
345 let in_realm_proof = cx.into();
346 let realm = InRealm::Already(&in_realm_proof);
347
348 run_a_script::<DomTypeHolder, _>(&handler.global_(realm), || {
349 rooted!(&in(cx) let resolve_func =
350 create_native_handler_function(cx,
351 handler.reflector().get_jsobject(),
352 NativeHandlerTask::Resolve));
353
354 rooted!(&in(cx) let reject_func =
355 create_native_handler_function(cx,
356 handler.reflector().get_jsobject(),
357 NativeHandlerTask::Reject));
358
359 unsafe {
360 let ok = AddPromiseReactions(
361 cx,
362 self.promise_obj(),
363 resolve_func.handle(),
364 reject_func.handle(),
365 );
366 assert!(ok);
367 }
368 })
369 }
370
371 #[expect(unsafe_code)]
372 pub(crate) fn get_promise_is_handled(&self) -> bool {
373 unsafe { GetPromiseIsHandled(self.reflector().get_jsobject()) }
374 }
375
376 #[expect(unsafe_code)]
377 pub(crate) fn set_promise_is_handled(&self) -> bool {
378 let cx = GlobalScope::get_cx();
379 unsafe { SetAnyPromiseIsHandled(*cx, self.reflector().get_jsobject()) }
380 }
381}
382
383#[expect(unsafe_code)]
384unsafe extern "C" fn do_nothing_promise_executor(
385 _cx: *mut RawJSContext,
386 argc: u32,
387 vp: *mut JSVal,
388) -> bool {
389 let args = unsafe { CallArgs::from_vp(vp, argc) };
390 args.rval().set(UndefinedValue());
391 true
392}
393
394const SLOT_NATIVEHANDLER: usize = 0;
395const SLOT_NATIVEHANDLER_TASK: usize = 1;
396
397#[derive(PartialEq)]
398enum NativeHandlerTask {
399 Resolve = 0,
400 Reject = 1,
401}
402
403#[expect(unsafe_code)]
404unsafe extern "C" fn native_handler_callback(
405 cx: *mut RawJSContext,
406 argc: u32,
407 vp: *mut JSVal,
408) -> bool {
409 let mut cx = unsafe { JSContext::from_ptr(ptr::NonNull::new(cx).unwrap()) };
411 let mut cx = CurrentRealm::assert(&mut cx);
412 let cx = &mut cx;
413
414 let args = unsafe { CallArgs::from_vp(vp, argc) };
415 let native_handler_value =
416 unsafe { *GetFunctionNativeReserved(args.callee(), SLOT_NATIVEHANDLER) };
417 rooted!(&in(cx) let native_handler_value = native_handler_value);
418 assert!(native_handler_value.get().is_object());
419
420 let handler = unsafe {
421 root_from_object::<PromiseNativeHandler>(native_handler_value.to_object(), cx.raw_cx())
422 }
423 .expect("unexpected value for native handler in promise native handler callback");
424
425 let native_handler_task_value =
426 unsafe { *GetFunctionNativeReserved(args.callee(), SLOT_NATIVEHANDLER_TASK) };
427 rooted!(&in(cx) let native_handler_task_value = native_handler_task_value);
428 match native_handler_task_value.to_int32() {
429 native_handler_task_value
430 if native_handler_task_value == NativeHandlerTask::Resolve as i32 =>
431 {
432 handler.resolved_callback(cx, unsafe { HandleValue::from_raw(args.get(0)) })
433 },
434 native_handler_task_value
435 if native_handler_task_value == NativeHandlerTask::Reject as i32 =>
436 {
437 handler.rejected_callback(cx, unsafe { HandleValue::from_raw(args.get(0)) })
438 },
439 _ => panic!("unexpected native handler task value"),
440 };
441
442 true
443}
444
445#[expect(unsafe_code)]
446fn create_native_handler_function(
447 cx: &mut JSContext,
448 holder: HandleObject,
449 task: NativeHandlerTask,
450) -> *mut JSObject {
451 unsafe {
452 let func = NewFunctionWithReserved(cx, Some(native_handler_callback), 1, 0, ptr::null());
453 assert!(!func.is_null());
454
455 rooted!(&in(cx) let obj = JS_GetFunctionObject(func));
456 assert!(!obj.is_null());
457 SetFunctionNativeReserved(obj.get(), SLOT_NATIVEHANDLER, &ObjectValue(*holder));
458 SetFunctionNativeReserved(obj.get(), SLOT_NATIVEHANDLER_TASK, &Int32Value(task as i32));
459 obj.get()
460 }
461}
462
463impl FromJSValConvertibleRc for Promise {
464 #[expect(unsafe_code)]
465 unsafe fn from_jsval(
466 _cx: *mut RawJSContext,
467 value: HandleValue,
468 ) -> Result<ConversionResult<Rc<Promise>>, ()> {
469 let mut cx = unsafe { script_bindings::script_runtime::temp_cx() };
471 Self::safe_from_jsval(&mut cx, value)
472 }
473
474 fn safe_from_jsval(
475 cx: &mut JSContext,
476 value: HandleValue,
477 ) -> Result<ConversionResult<Rc<Promise>>, ()> {
478 if value.get().is_null() {
479 return Ok(ConversionResult::Failure(c"null not allowed".into()));
480 }
481
482 let realm = CurrentRealm::assert(cx);
483 let global_scope = GlobalScope::from_current_realm(&realm);
484
485 let promise = Promise::new_resolved(&global_scope, cx.into(), value, CanGc::from_cx(cx));
486 Ok(ConversionResult::Success(promise))
487 }
488}
489
490type WaitForAllSuccessSteps = Rc<dyn Fn(&mut JSContext, Vec<HandleValue>)>;
492
493type WaitForAllFailureSteps = Rc<dyn Fn(&mut JSContext, HandleValue)>;
495
496#[derive(JSTraceable, MallocSizeOf)]
499#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
500struct WaitForAllFulfillmentHandler {
501 #[ignore_malloc_size_of = "callbacks are hard"]
503 #[no_trace]
504 success_steps: WaitForAllSuccessSteps,
505
506 #[ignore_malloc_size_of = "mozjs"]
508 #[expect(clippy::vec_box)]
509 result: Rc<RefCell<Vec<Box<Heap<JSVal>>>>>,
510
511 promise_index: usize,
513
514 #[conditional_malloc_size_of]
516 fulfilled_count: Rc<RefCell<usize>>,
517}
518
519impl Callback for WaitForAllFulfillmentHandler {
520 fn callback(&self, cx: &mut CurrentRealm, v: HandleValue) {
521 let equals_total = {
524 let result = self.result.borrow_mut();
526 result[self.promise_index].set(v.get());
527
528 let mut fulfilled_count = self.fulfilled_count.borrow_mut();
530 *fulfilled_count += 1;
531
532 *fulfilled_count == result.len()
533 };
534
535 if equals_total {
537 let result_ref = self.result.borrow();
538 let result_handles: Vec<HandleValue> =
539 result_ref.iter().map(|v| v.as_handle_value()).collect();
540
541 (self.success_steps)(cx, result_handles);
542 }
543 }
544}
545
546#[derive(Clone, JSTraceable, MallocSizeOf)]
549struct WaitForAllRejectionHandler {
550 #[ignore_malloc_size_of = "callbacks are hard"]
552 #[no_trace]
553 failure_steps: WaitForAllFailureSteps,
554
555 rejected: Cell<bool>,
557}
558
559impl Callback for WaitForAllRejectionHandler {
560 fn callback(&self, cx: &mut CurrentRealm, v: HandleValue) {
561 if self.rejected.replace(true) {
564 return;
566 }
567
568 (self.failure_steps)(cx, v);
571 }
572}
573
574#[derive(JSTraceable, MallocSizeOf)]
577pub(crate) struct WaitForAllSuccessStepsMicrotask {
578 global: DomRoot<GlobalScope>,
579
580 #[ignore_malloc_size_of = "Closure is hard"]
581 #[no_trace]
582 success_steps: WaitForAllSuccessSteps,
583}
584
585impl MicrotaskRunnable for WaitForAllSuccessStepsMicrotask {
586 fn handler(&self, cx: &mut JSContext) {
587 (self.success_steps)(cx, vec![]);
588 }
589
590 fn enter_realm<'cx>(&self, cx: &'cx mut JSContext) -> AutoRealm<'cx> {
591 enter_auto_realm(cx, &*self.global)
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<Rc<Promise>>,
601 success_steps: WaitForAllSuccessSteps,
602 failure_steps: WaitForAllFailureSteps,
603) {
604 let fulfilled_count: Rc<RefCell<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(Microtask::WaitForAllSuccessSteps(
627 WaitForAllSuccessStepsMicrotask {
628 global: DomRoot::from_ref(global),
629 success_steps,
630 },
631 ));
632
633 return;
635 }
636
637 let result: Rc<RefCell<Vec<Box<Heap<JSVal>>>>> = Default::default();
642
643 for (promise_index, promise) in promises.into_iter().enumerate() {
645 let result = result.clone();
646
647 {
648 let mut result_list = result.borrow_mut();
650 rooted!(&in(cx) let null_value = NullValue());
651 result_list.push(Heap::boxed(null_value.get()));
652 }
653
654 let handler = PromiseNativeHandler::new(
665 cx,
666 global,
667 Some(Box::new(WaitForAllFulfillmentHandler {
668 success_steps: success_steps.clone(),
669 result,
670 promise_index,
671 fulfilled_count: fulfilled_count.clone(),
672 })),
673 Some(Box::new(rejection_handler.clone())),
674 );
675 promise.append_native_handler(cx, &handler);
676
677 }
680}
681
682pub(crate) fn wait_for_all_promise(
684 cx: &mut CurrentRealm,
685 global: &GlobalScope,
686 promises: Vec<Rc<Promise>>,
687) -> Rc<Promise> {
688 let promise = Promise::new2(cx, global);
690 let success_promise = promise.clone();
691 let failure_promise = promise.clone();
692
693 let success_steps = Rc::new(move |cx: &mut JSContext, results: Vec<HandleValue>| {
695 success_promise.resolve_native_with_cx(cx, &results);
697 });
698
699 let failure_steps = Rc::new(move |cx: &mut JSContext, reason: HandleValue| {
701 failure_promise.reject_native_with_cx(cx, &reason);
703 });
704
705 wait_for_all(cx, global, promises, success_steps, failure_steps);
707
708 promise
710}