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