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 NewFunctionWithReserved, PromiseState, PromiseUserInputEventHandlingState, RemoveRawValueRoot,
26 SetFunctionNativeReserved,
27};
28use js::jsval::{Int32Value, JSVal, NullValue, ObjectValue, UndefinedValue};
29use js::realm::{AutoRealm, CurrentRealm};
30use js::rust::wrappers::{
31 AddPromiseReactions, CallOriginalPromiseReject, CallOriginalPromiseResolve,
32 GetPromiseIsHandled, GetPromiseState, IsPromiseObject, NewPromiseObject, RejectPromise,
33 ResolvePromise, SetAnyPromiseIsHandled, SetPromiseUserInputEventHandlingState,
34};
35use js::rust::{HandleObject, HandleValue, MutableHandleObject, Runtime};
36use script_bindings::conversions::SafeToJSValConvertible;
37use script_bindings::settings_stack::run_a_script;
38
39use crate::DomTypeHolder;
40use crate::dom::bindings::conversions::root_from_object;
41use crate::dom::bindings::error::{Error, ErrorToJsval};
42use crate::dom::bindings::reflector::{DomGlobal, DomObject, MutDomObject, Reflector};
43use crate::dom::bindings::root::{AsHandleValue, DomRoot};
44use crate::dom::globalscope::GlobalScope;
45use crate::dom::promisenativehandler::{Callback, PromiseNativeHandler};
46use crate::microtask::{Microtask, MicrotaskRunnable};
47use crate::realms::{AlreadyInRealm, InRealm, enter_auto_realm, enter_realm};
48use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
49use crate::script_thread::ScriptThread;
50
51#[dom_struct]
52#[cfg_attr(crown, crown::unrooted_must_root_lint::allow_unrooted_in_rc)]
53pub(crate) struct Promise {
54 reflector: Reflector,
55 #[ignore_malloc_size_of = "SM handles JS values"]
60 permanent_js_root: Heap<JSVal>,
61}
62
63trait PromiseHelper {
65 fn initialize(&self, cx: SafeJSContext);
66}
67
68impl PromiseHelper for Rc<Promise> {
69 #[expect(unsafe_code)]
70 fn initialize(&self, cx: SafeJSContext) {
71 let obj = self.reflector().get_jsobject();
72 self.permanent_js_root.set(ObjectValue(*obj));
73 unsafe {
74 assert!(AddRawValueRoot(
75 *cx,
76 self.permanent_js_root.get_unsafe(),
77 c"Promise::root".as_ptr(),
78 ));
79 }
80 }
81}
82
83impl Drop for Promise {
87 #[expect(unsafe_code)]
88 fn drop(&mut self) {
89 self.reflector.drop_memory(self);
90 unsafe {
91 let object = self.permanent_js_root.get().to_object();
92 assert!(!object.is_null());
93 if let Some(cx) = Runtime::get() {
94 RemoveRawValueRoot(cx.as_ptr(), self.permanent_js_root.get_unsafe());
95 }
96 }
97 }
98}
99
100impl Promise {
101 pub(crate) fn new(global: &GlobalScope, can_gc: CanGc) -> Rc<Promise> {
102 let realm = enter_realm(global);
103 let comp = InRealm::Entered(&realm);
104 Promise::new_in_current_realm(comp, can_gc)
105 }
106
107 pub(crate) fn new_in_current_realm(_comp: InRealm, can_gc: CanGc) -> Rc<Promise> {
108 let cx = GlobalScope::get_cx();
109 rooted!(in(*cx) let mut obj = ptr::null_mut::<JSObject>());
110 Promise::create_js_promise(cx, obj.handle_mut(), can_gc);
111 Promise::new_with_js_promise(obj.handle(), cx)
112 }
113
114 pub(crate) fn new2(cx: &mut js::context::JSContext, global: &GlobalScope) -> Rc<Promise> {
115 let mut realm = AutoRealm::new(
116 cx,
117 std::ptr::NonNull::new(global.reflector().get_jsobject().get()).unwrap(),
118 );
119 let mut current_realm = realm.current_realm();
120 Promise::new_in_realm(&mut current_realm)
121 }
122
123 pub(crate) fn new_in_realm(current_realm: &mut CurrentRealm) -> Rc<Promise> {
124 let cx = current_realm.deref_mut();
125 rooted!(&in(cx) let mut obj = ptr::null_mut::<JSObject>());
126 Promise::create_js_promise(cx.into(), obj.handle_mut(), CanGc::from_cx(cx));
127 Promise::new_with_js_promise(obj.handle(), cx.into())
128 }
129
130 pub(crate) fn duplicate(&self) -> Rc<Promise> {
131 let cx = GlobalScope::get_cx();
132 Promise::new_with_js_promise(self.reflector().get_jsobject(), cx)
133 }
134
135 #[expect(unsafe_code)]
136 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
137 pub(crate) fn new_with_js_promise(obj: HandleObject, cx: SafeJSContext) -> Rc<Promise> {
138 unsafe {
139 assert!(IsPromiseObject(obj));
140 let promise = Promise {
141 reflector: Reflector::new(),
142 permanent_js_root: Heap::default(),
143 };
144 let promise = Rc::new(promise);
145 promise.init_reflector::<Promise>(obj.get());
146 promise.initialize(cx);
147 promise
148 }
149 }
150
151 #[expect(unsafe_code)]
152 fn create_js_promise(cx: SafeJSContext, mut obj: MutableHandleObject, _can_gc: CanGc) {
155 unsafe {
156 let do_nothing_func = JS_NewFunction(
157 *cx,
158 Some(do_nothing_promise_executor),
159 2,
160 0,
161 ptr::null(),
162 );
163 assert!(!do_nothing_func.is_null());
164 rooted!(in(*cx) let do_nothing_obj = JS_GetFunctionObject(do_nothing_func));
165 assert!(!do_nothing_obj.is_null());
166 obj.set(NewPromiseObject(*cx, do_nothing_obj.handle()));
167 assert!(!obj.is_null());
168 let is_user_interacting = if ScriptThread::is_user_interacting() {
169 PromiseUserInputEventHandlingState::HadUserInteractionAtCreation
170 } else {
171 PromiseUserInputEventHandlingState::DidntHaveUserInteractionAtCreation
172 };
173 SetPromiseUserInputEventHandlingState(obj.handle(), is_user_interacting);
174 }
175 }
176
177 #[expect(unsafe_code)]
178 pub(crate) fn new_resolved(
179 global: &GlobalScope,
180 cx: SafeJSContext,
181 value: impl SafeToJSValConvertible,
182 can_gc: CanGc,
183 ) -> Rc<Promise> {
184 let _ac = JSAutoRealm::new(*cx, global.reflector().get_jsobject().get());
185 rooted!(in(*cx) let mut rval = UndefinedValue());
186 value.safe_to_jsval(cx, rval.handle_mut(), can_gc);
187 unsafe {
188 rooted!(in(*cx) let p = CallOriginalPromiseResolve(*cx, rval.handle()));
189 assert!(!p.handle().is_null());
190 Promise::new_with_js_promise(p.handle(), cx)
191 }
192 }
193
194 #[expect(unsafe_code)]
195 pub(crate) fn new_rejected(
196 global: &GlobalScope,
197 cx: SafeJSContext,
198 value: impl SafeToJSValConvertible,
199 can_gc: CanGc,
200 ) -> Rc<Promise> {
201 let _ac = JSAutoRealm::new(*cx, global.reflector().get_jsobject().get());
202 rooted!(in(*cx) let mut rval = UndefinedValue());
203 value.safe_to_jsval(cx, rval.handle_mut(), can_gc);
204 unsafe {
205 rooted!(in(*cx) let p = CallOriginalPromiseReject(*cx, rval.handle()));
206 assert!(!p.handle().is_null());
207 Promise::new_with_js_promise(p.handle(), cx)
208 }
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 #[expect(unsafe_code)]
223 pub(crate) fn resolve(&self, cx: SafeJSContext, value: HandleValue, _can_gc: CanGc) {
224 unsafe {
225 if !ResolvePromise(*cx, self.promise_obj(), value) {
226 JS_ClearPendingException(*cx);
227 }
228 }
229 }
230
231 pub(crate) fn reject_native<T>(&self, val: &T, can_gc: CanGc)
232 where
233 T: SafeToJSValConvertible,
234 {
235 let cx = GlobalScope::get_cx();
236 let _ac = enter_realm(self);
237 rooted!(in(*cx) let mut v = UndefinedValue());
238 val.safe_to_jsval(cx, v.handle_mut(), can_gc);
239 self.reject(cx, v.handle(), can_gc);
240 }
241
242 pub(crate) fn reject_error(&self, error: Error, can_gc: CanGc) {
243 let cx = GlobalScope::get_cx();
244 let _ac = enter_realm(self);
245 rooted!(in(*cx) let mut v = UndefinedValue());
246 error.to_jsval(cx, &self.global(), v.handle_mut(), can_gc);
247 self.reject(cx, v.handle(), can_gc);
248 }
249
250 #[expect(unsafe_code)]
251 pub(crate) fn reject(&self, cx: SafeJSContext, value: HandleValue, _can_gc: CanGc) {
252 unsafe {
253 if !RejectPromise(*cx, self.promise_obj(), value) {
254 JS_ClearPendingException(*cx);
255 }
256 }
257 }
258
259 #[expect(unsafe_code)]
260 pub(crate) fn is_fulfilled(&self) -> bool {
261 let state = unsafe { GetPromiseState(self.promise_obj()) };
262 matches!(state, PromiseState::Rejected | PromiseState::Fulfilled)
263 }
264
265 #[expect(unsafe_code)]
266 pub(crate) fn is_rejected(&self) -> bool {
267 let state = unsafe { GetPromiseState(self.promise_obj()) };
268 matches!(state, PromiseState::Rejected)
269 }
270
271 #[expect(unsafe_code)]
272 pub(crate) fn is_pending(&self) -> bool {
273 let state = unsafe { GetPromiseState(self.promise_obj()) };
274 matches!(state, PromiseState::Pending)
275 }
276
277 #[expect(unsafe_code)]
278 pub(crate) fn promise_obj(&self) -> HandleObject<'_> {
279 let obj = self.reflector().get_jsobject();
280 unsafe {
281 assert!(IsPromiseObject(obj));
282 }
283 obj
284 }
285
286 #[expect(unsafe_code)]
287 pub(crate) fn append_native_handler(
288 &self,
289 handler: &PromiseNativeHandler,
290 realm: InRealm,
291 can_gc: CanGc,
292 ) {
293 run_a_script::<DomTypeHolder, _>(&handler.global_(realm), || {
294 let cx = GlobalScope::get_cx();
295 rooted!(in(*cx) let resolve_func =
296 create_native_handler_function(*cx,
297 handler.reflector().get_jsobject(),
298 NativeHandlerTask::Resolve,
299 can_gc));
300
301 rooted!(in(*cx) let reject_func =
302 create_native_handler_function(*cx,
303 handler.reflector().get_jsobject(),
304 NativeHandlerTask::Reject,
305 can_gc));
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(
397 cx: *mut RawJSContext,
398 holder: HandleObject,
399 task: NativeHandlerTask,
400 _can_gc: CanGc,
401) -> *mut JSObject {
402 unsafe {
403 let func = NewFunctionWithReserved(cx, Some(native_handler_callback), 1, 0, ptr::null());
404 assert!(!func.is_null());
405
406 rooted!(in(cx) let obj = JS_GetFunctionObject(func));
407 assert!(!obj.is_null());
408 SetFunctionNativeReserved(obj.get(), SLOT_NATIVEHANDLER, &ObjectValue(*holder));
409 SetFunctionNativeReserved(obj.get(), SLOT_NATIVEHANDLER_TASK, &Int32Value(task as i32));
410 obj.get()
411 }
412}
413
414impl FromJSValConvertibleRc for Promise {
415 #[expect(unsafe_code)]
416 unsafe fn from_jsval(
417 cx: *mut RawJSContext,
418 value: HandleValue,
419 ) -> Result<ConversionResult<Rc<Promise>>, ()> {
420 if value.get().is_null() {
421 return Ok(ConversionResult::Failure(c"null not allowed".into()));
422 }
423
424 let cx = unsafe { SafeJSContext::from_ptr(cx) };
425 let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
426 let global_scope =
427 unsafe { GlobalScope::from_context(*cx, InRealm::Already(&in_realm_proof)) };
428
429 let promise = Promise::new_resolved(&global_scope, cx, value, CanGc::note());
430 Ok(ConversionResult::Success(promise))
431 }
432}
433
434type WaitForAllSuccessSteps = Rc<dyn Fn(Vec<HandleValue>)>;
436
437type WaitForAllFailureSteps = Rc<dyn Fn(HandleValue)>;
439
440#[derive(JSTraceable, MallocSizeOf)]
443#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
444struct WaitForAllFulfillmentHandler {
445 #[ignore_malloc_size_of = "callbacks are hard"]
447 #[no_trace]
448 success_steps: WaitForAllSuccessSteps,
449
450 #[ignore_malloc_size_of = "mozjs"]
452 #[expect(clippy::vec_box)]
453 result: Rc<RefCell<Vec<Box<Heap<JSVal>>>>>,
454
455 promise_index: usize,
457
458 #[conditional_malloc_size_of]
460 fulfilled_count: Rc<RefCell<usize>>,
461}
462
463impl Callback for WaitForAllFulfillmentHandler {
464 fn callback(&self, _cx: &mut CurrentRealm, v: HandleValue) {
465 let equals_total = {
468 let result = self.result.borrow_mut();
470 result[self.promise_index].set(v.get());
471
472 let mut fulfilled_count = self.fulfilled_count.borrow_mut();
474 *fulfilled_count += 1;
475
476 *fulfilled_count == result.len()
477 };
478
479 if equals_total {
481 let result_ref = self.result.borrow();
482 let result_handles: Vec<HandleValue> =
483 result_ref.iter().map(|v| v.as_handle_value()).collect();
484
485 (self.success_steps)(result_handles);
486 }
487 }
488}
489
490#[derive(Clone, JSTraceable, MallocSizeOf)]
493struct WaitForAllRejectionHandler {
494 #[ignore_malloc_size_of = "callbacks are hard"]
496 #[no_trace]
497 failure_steps: WaitForAllFailureSteps,
498
499 rejected: Cell<bool>,
501}
502
503impl Callback for WaitForAllRejectionHandler {
504 fn callback(&self, _cx: &mut CurrentRealm, v: HandleValue) {
505 if self.rejected.replace(true) {
508 return;
510 }
511
512 (self.failure_steps)(v);
515 }
516}
517
518#[derive(JSTraceable, MallocSizeOf)]
521pub(crate) struct WaitForAllSuccessStepsMicrotask {
522 global: DomRoot<GlobalScope>,
523
524 #[ignore_malloc_size_of = "Closure is hard"]
525 #[no_trace]
526 success_steps: WaitForAllSuccessSteps,
527}
528
529impl MicrotaskRunnable for WaitForAllSuccessStepsMicrotask {
530 fn handler(&self, _cx: &mut JSContext) {
531 (self.success_steps)(vec![]);
532 }
533
534 fn enter_realm<'cx>(&self, cx: &'cx mut JSContext) -> AutoRealm<'cx> {
535 enter_auto_realm(cx, &*self.global)
536 }
537}
538
539#[cfg_attr(crown, expect(crown::unrooted_must_root))]
541pub(crate) fn wait_for_all(
542 cx: SafeJSContext,
543 global: &GlobalScope,
544 promises: Vec<Rc<Promise>>,
545 success_steps: WaitForAllSuccessSteps,
546 failure_steps: WaitForAllFailureSteps,
547 realm: InRealm,
548 can_gc: CanGc,
549) {
550 let fulfilled_count: Rc<RefCell<usize>> = Default::default();
552
553 let rejection_handler = WaitForAllRejectionHandler {
562 failure_steps,
563 rejected: Default::default(),
564 };
565
566 if promises.is_empty() {
571 global.enqueue_microtask(Microtask::WaitForAllSuccessSteps(
573 WaitForAllSuccessStepsMicrotask {
574 global: DomRoot::from_ref(global),
575 success_steps,
576 },
577 ));
578
579 return;
581 }
582
583 let result: Rc<RefCell<Vec<Box<Heap<JSVal>>>>> = Default::default();
588
589 for (promise_index, promise) in promises.into_iter().enumerate() {
591 let result = result.clone();
592
593 {
594 let mut result_list = result.borrow_mut();
596 rooted!(in(*cx) let null_value = NullValue());
597 result_list.push(Heap::boxed(null_value.get()));
598 }
599
600 let handler = PromiseNativeHandler::new(
611 global,
612 Some(Box::new(WaitForAllFulfillmentHandler {
613 success_steps: success_steps.clone(),
614 result,
615 promise_index,
616 fulfilled_count: fulfilled_count.clone(),
617 })),
618 Some(Box::new(rejection_handler.clone())),
619 can_gc,
620 );
621 promise.append_native_handler(&handler, realm, can_gc);
622
623 }
626}
627
628pub(crate) fn wait_for_all_promise(
630 cx: SafeJSContext,
631 global: &GlobalScope,
632 promises: Vec<Rc<Promise>>,
633 realm: InRealm,
634 can_gc: CanGc,
635) -> Rc<Promise> {
636 let promise = Promise::new(global, can_gc);
638 let success_promise = promise.clone();
639 let failure_promise = promise.clone();
640
641 let success_steps = Rc::new(move |results: Vec<HandleValue>| {
643 success_promise.resolve_native(&results, can_gc);
645 });
646
647 let failure_steps = Rc::new(move |reason: HandleValue| {
649 failure_promise.reject_native(&reason, can_gc);
651 });
652
653 wait_for_all(
655 cx,
656 global,
657 promises,
658 success_steps,
659 failure_steps,
660 realm,
661 can_gc,
662 );
663
664 promise
666}