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