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, ToJSValConvertible};
22use js::jsapi::{
23 CallArgs, GetFunctionNativeReserved, Heap, JS_GetFunctionObject, JSContext as RawJSContext,
24 JSObject, PromiseState, PromiseUserInputEventHandlingState, RemoveRawValueRoot,
25 SetFunctionNativeReserved,
26};
27use js::jsval::{Int32Value, JSVal, NullValue, ObjectValue, UndefinedValue};
28use js::realm::CurrentRealm;
29use js::rust::wrappers2::{
30 AddPromiseReactions, AddRawValueRoot, 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, Runtime};
36use script_bindings::reflector::{DomObject, MutDomObject, Reflector};
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;
43use crate::dom::bindings::root::{AsHandleValue, Dom};
44use crate::dom::globalscope::GlobalScope;
45use crate::dom::promisenativehandler::{Callback, PromiseNativeHandler};
46use crate::microtask::MicrotaskRunnable;
47use crate::realms::enter_auto_realm;
48use crate::script_thread::ScriptThread;
49
50#[dom_struct]
51#[cfg_attr(crown, crown::unrooted_must_root_lint::allow_unrooted_in_rc)]
52pub(crate) struct Promise {
53 reflector: Reflector,
54 #[ignore_malloc_size_of = "SM handles JS values"]
59 permanent_js_root: Heap<JSVal>,
60}
61
62trait PromiseHelper {
64 fn initialize(&self, cx: &mut JSContext);
65}
66
67impl PromiseHelper for Rc<Promise> {
68 #[expect(unsafe_code)]
69 fn initialize(&self, cx: &mut JSContext) {
70 let obj = self.reflector().get_jsobject();
71 self.permanent_js_root.set(ObjectValue(*obj));
72 unsafe {
73 assert!(AddRawValueRoot(
74 cx,
75 self.permanent_js_root.get_unsafe(),
76 c"Promise::root".as_ptr(),
77 ));
78 }
79 }
80}
81
82impl Drop for Promise {
86 #[expect(unsafe_code)]
87 fn drop(&mut self) {
88 unsafe {
89 let object = self.permanent_js_root.get().to_object();
90 assert!(!object.is_null());
91 if let Some(cx) = Runtime::get() {
92 RemoveRawValueRoot(cx.as_ptr(), self.permanent_js_root.get_unsafe());
93 }
94 }
95 }
96}
97
98impl Promise {
99 pub(crate) fn new(cx: &mut JSContext, global: &GlobalScope) -> Rc<Promise> {
100 let mut realm = enter_auto_realm(cx, global);
101 let cx = &mut realm.current_realm();
102 Promise::new_in_realm(cx)
103 }
104
105 pub(crate) fn new_in_realm(current_realm: &mut CurrentRealm) -> Rc<Promise> {
106 let cx = current_realm.deref_mut();
107 rooted!(&in(cx) let mut obj = ptr::null_mut::<JSObject>());
108 Promise::create_js_promise(cx, obj.handle_mut());
109 Promise::new_with_js_promise(cx, obj.handle())
110 }
111
112 pub(crate) fn duplicate(&self, cx: &mut JSContext) -> Rc<Promise> {
113 Promise::new_with_js_promise(cx, self.reflector().get_jsobject())
114 }
115
116 #[expect(unsafe_code)]
117 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
118 pub(crate) fn new_with_js_promise(cx: &mut JSContext, obj: HandleObject) -> Rc<Promise> {
119 unsafe {
120 assert!(IsPromiseObject(obj));
121 }
122 let promise = Promise {
123 reflector: Reflector::new(),
124 permanent_js_root: Heap::default(),
125 };
126 let promise = Rc::new(promise);
127 unsafe {
128 promise.init_reflector_without_associated_memory(obj.get());
129 }
130 promise.initialize(cx);
131 promise
132 }
133
134 #[expect(unsafe_code)]
135 fn create_js_promise(cx: &mut JSContext, mut obj: MutableHandleObject) {
136 unsafe {
137 let do_nothing_func = JS_NewFunction(
138 cx,
139 Some(do_nothing_promise_executor),
140 2,
141 0,
142 ptr::null(),
143 );
144 assert!(!do_nothing_func.is_null());
145 rooted!(&in(cx) let do_nothing_obj = JS_GetFunctionObject(do_nothing_func));
146 assert!(!do_nothing_obj.is_null());
147 obj.set(NewPromiseObject(cx, do_nothing_obj.handle()));
148 assert!(!obj.is_null());
149 let is_user_interacting = if ScriptThread::is_user_interacting() {
150 PromiseUserInputEventHandlingState::HadUserInteractionAtCreation
151 } else {
152 PromiseUserInputEventHandlingState::DidntHaveUserInteractionAtCreation
153 };
154 SetPromiseUserInputEventHandlingState(obj.handle(), is_user_interacting);
155 }
156 }
157
158 #[expect(unsafe_code)]
159 pub(crate) fn new_resolved(
160 cx: &mut JSContext,
161 global: &GlobalScope,
162 value: impl ToJSValConvertible,
163 ) -> Rc<Promise> {
164 let mut realm = enter_auto_realm(cx, global);
165 let cx = &mut realm.current_realm();
166 rooted!(&in(cx) let mut rval = UndefinedValue());
167 value.safe_to_jsval(cx, rval.handle_mut());
168 rooted!(&in(cx) let p = unsafe { CallOriginalPromiseResolve(cx, rval.handle()) });
169 assert!(!p.handle().is_null());
170 Promise::new_with_js_promise(cx, p.handle())
171 }
172
173 #[expect(unsafe_code)]
174 pub(crate) fn new_rejected(
175 cx: &mut JSContext,
176 global: &GlobalScope,
177 value: impl ToJSValConvertible,
178 ) -> Rc<Promise> {
179 let mut realm = enter_auto_realm(cx, global);
180 let cx = &mut realm.current_realm();
181 rooted!(&in(cx) let mut rval = UndefinedValue());
182 value.safe_to_jsval(cx, rval.handle_mut());
183 rooted!(&in(cx) let p = unsafe { CallOriginalPromiseReject(cx, rval.handle()) });
184 assert!(!p.handle().is_null());
185 Promise::new_with_js_promise(cx, p.handle())
186 }
187
188 pub(crate) fn resolve_native<T>(&self, cx: &mut JSContext, val: &T)
189 where
190 T: ToJSValConvertible,
191 {
192 let mut realm = enter_auto_realm(cx, self);
193 let cx = &mut realm.current_realm();
194 rooted!(&in(cx) let mut v = UndefinedValue());
195 val.safe_to_jsval(cx, v.handle_mut());
196 self.resolve(cx, v.handle());
197 }
198
199 #[expect(unsafe_code)]
200 pub(crate) fn resolve(&self, cx: &mut JSContext, value: HandleValue) {
201 unsafe {
202 if !ResolvePromise(cx, self.promise_obj(), value) {
203 JS_ClearPendingException(cx);
204 }
205 }
206 }
207
208 pub(crate) fn reject_native<T>(&self, cx: &mut JSContext, val: &T)
209 where
210 T: ToJSValConvertible,
211 {
212 let mut realm = enter_auto_realm(cx, self);
213 let cx = &mut realm.current_realm();
214 rooted!(&in(cx) let mut v = UndefinedValue());
215 val.safe_to_jsval(cx, v.handle_mut());
216 self.reject(cx, v.handle());
217 }
218
219 pub(crate) fn reject_error(&self, cx: &mut JSContext, error: Error) {
220 let mut realm = enter_auto_realm(cx, self);
221 let cx = &mut realm.current_realm();
222 rooted!(&in(cx) let mut v = UndefinedValue());
223 error.to_jsval(cx, &self.global(), v.handle_mut());
224 self.reject(cx, v.handle());
225 }
226
227 #[expect(unsafe_code)]
228 pub(crate) fn reject(&self, cx: &mut JSContext, value: HandleValue) {
229 unsafe {
230 if !RejectPromise(cx, self.promise_obj(), value) {
231 JS_ClearPendingException(cx);
232 }
233 }
234 }
235
236 #[expect(unsafe_code)]
237 pub(crate) fn is_fulfilled(&self) -> bool {
238 let state = unsafe { GetPromiseState(self.promise_obj()) };
239 matches!(state, PromiseState::Rejected | PromiseState::Fulfilled)
240 }
241
242 #[expect(unsafe_code)]
243 pub(crate) fn is_rejected(&self) -> bool {
244 let state = unsafe { GetPromiseState(self.promise_obj()) };
245 matches!(state, PromiseState::Rejected)
246 }
247
248 #[expect(unsafe_code)]
249 pub(crate) fn is_pending(&self) -> bool {
250 let state = unsafe { GetPromiseState(self.promise_obj()) };
251 matches!(state, PromiseState::Pending)
252 }
253
254 #[expect(unsafe_code)]
255 pub(crate) fn promise_obj(&self) -> HandleObject<'_> {
256 let obj = self.reflector().get_jsobject();
257 unsafe {
258 assert!(IsPromiseObject(obj));
259 }
260 obj
261 }
262
263 #[expect(unsafe_code)]
264 pub(crate) fn append_native_handler(
265 &self,
266 cx: &mut CurrentRealm,
267 handler: &PromiseNativeHandler,
268 ) {
269 let global = GlobalScope::from_current_realm(cx);
270 run_a_script::<DomTypeHolder, _, _>(cx, &global, |cx| {
271 rooted!(&in(cx) let resolve_func =
272 create_native_handler_function(cx,
273 handler.reflector().get_jsobject(),
274 NativeHandlerTask::Resolve));
275
276 rooted!(&in(cx) let reject_func =
277 create_native_handler_function(cx,
278 handler.reflector().get_jsobject(),
279 NativeHandlerTask::Reject));
280
281 unsafe {
282 let ok = AddPromiseReactions(
283 cx,
284 self.promise_obj(),
285 resolve_func.handle(),
286 reject_func.handle(),
287 );
288 assert!(ok);
289 }
290 })
291 }
292
293 #[expect(unsafe_code)]
294 pub(crate) fn get_promise_is_handled(&self) -> bool {
295 unsafe { GetPromiseIsHandled(self.reflector().get_jsobject()) }
296 }
297
298 #[expect(unsafe_code)]
299 pub(crate) fn set_promise_is_handled(&self, cx: &mut JSContext) -> bool {
300 unsafe { SetAnyPromiseIsHandled(cx, self.reflector().get_jsobject()) }
301 }
302}
303
304#[expect(unsafe_code)]
305unsafe extern "C" fn do_nothing_promise_executor(
306 _cx: *mut RawJSContext,
307 argc: u32,
308 vp: *mut JSVal,
309) -> bool {
310 let args = unsafe { CallArgs::from_vp(vp, argc) };
311 args.rval().set(UndefinedValue());
312 true
313}
314
315const SLOT_NATIVEHANDLER: usize = 0;
316const SLOT_NATIVEHANDLER_TASK: usize = 1;
317
318#[derive(PartialEq)]
319enum NativeHandlerTask {
320 Resolve = 0,
321 Reject = 1,
322}
323
324#[expect(unsafe_code)]
325unsafe extern "C" fn native_handler_callback(
326 cx: *mut RawJSContext,
327 argc: u32,
328 vp: *mut JSVal,
329) -> bool {
330 let mut cx = unsafe { JSContext::from_ptr(ptr::NonNull::new(cx).unwrap()) };
332 let mut cx = CurrentRealm::assert(&mut cx);
333 let cx = &mut cx;
334
335 let args = unsafe { CallArgs::from_vp(vp, argc) };
336 let native_handler_value =
337 unsafe { *GetFunctionNativeReserved(args.callee(), SLOT_NATIVEHANDLER) };
338 rooted!(&in(cx) let native_handler_value = native_handler_value);
339 assert!(native_handler_value.get().is_object());
340
341 let handler =
342 unsafe { root_from_object::<PromiseNativeHandler>(cx, native_handler_value.to_object()) }
343 .expect("unexpected value for native handler in promise native handler callback");
344
345 let native_handler_task_value =
346 unsafe { *GetFunctionNativeReserved(args.callee(), SLOT_NATIVEHANDLER_TASK) };
347 rooted!(&in(cx) let native_handler_task_value = native_handler_task_value);
348 match native_handler_task_value.to_int32() {
349 native_handler_task_value
350 if native_handler_task_value == NativeHandlerTask::Resolve as i32 =>
351 {
352 handler.resolved_callback(cx, unsafe { HandleValue::from_raw(args.get(0)) })
353 },
354 native_handler_task_value
355 if native_handler_task_value == NativeHandlerTask::Reject as i32 =>
356 {
357 handler.rejected_callback(cx, unsafe { HandleValue::from_raw(args.get(0)) })
358 },
359 _ => panic!("unexpected native handler task value"),
360 };
361
362 true
363}
364
365#[expect(unsafe_code)]
366fn create_native_handler_function(
367 cx: &mut JSContext,
368 holder: HandleObject,
369 task: NativeHandlerTask,
370) -> *mut JSObject {
371 unsafe {
372 let func = NewFunctionWithReserved(cx, Some(native_handler_callback), 1, 0, ptr::null());
373 assert!(!func.is_null());
374
375 rooted!(&in(cx) let obj = JS_GetFunctionObject(func));
376 assert!(!obj.is_null());
377 SetFunctionNativeReserved(obj.get(), SLOT_NATIVEHANDLER, &ObjectValue(*holder));
378 SetFunctionNativeReserved(obj.get(), SLOT_NATIVEHANDLER_TASK, &Int32Value(task as i32));
379 obj.get()
380 }
381}
382
383impl FromJSValConvertibleRc for Promise {
384 fn safe_from_jsval(
385 cx: &mut JSContext,
386 value: HandleValue,
387 ) -> Result<ConversionResult<Rc<Promise>>, ()> {
388 if value.get().is_null() {
389 return Ok(ConversionResult::Failure(c"null not allowed".into()));
390 }
391
392 let mut realm = CurrentRealm::assert(cx);
393 let global_scope = GlobalScope::from_current_realm(&mut realm);
394
395 let promise = Promise::new_resolved(cx, &global_scope, value);
396 Ok(ConversionResult::Success(promise))
397 }
398}
399
400type WaitForAllSuccessSteps = Rc<dyn Fn(&mut JSContext, Vec<HandleValue>)>;
402
403type WaitForAllFailureSteps = Rc<dyn Fn(&mut JSContext, HandleValue)>;
405
406#[derive(JSTraceable, MallocSizeOf)]
409#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
410struct WaitForAllFulfillmentHandler {
411 #[ignore_malloc_size_of = "callbacks are hard"]
413 #[no_trace]
414 success_steps: WaitForAllSuccessSteps,
415
416 #[ignore_malloc_size_of = "mozjs"]
418 #[expect(clippy::vec_box)]
419 result: Rc<RefCell<Vec<Box<Heap<JSVal>>>>>,
420
421 promise_index: usize,
423
424 #[conditional_malloc_size_of]
426 fulfilled_count: Rc<Cell<usize>>,
427}
428
429impl Callback for WaitForAllFulfillmentHandler {
430 fn callback(&self, cx: &mut CurrentRealm, v: HandleValue) {
431 let equals_total = {
434 let result = self.result.borrow_mut();
436 result[self.promise_index].set(v.get());
437
438 let mut fulfilled_count = self.fulfilled_count.get();
440 fulfilled_count += 1;
441 self.fulfilled_count.set(fulfilled_count);
442
443 fulfilled_count == result.len()
444 };
445
446 if equals_total {
448 let result_ref = self.result.borrow();
449 let result_handles: Vec<HandleValue> =
450 result_ref.iter().map(|v| v.as_handle_value()).collect();
451
452 (self.success_steps)(cx, result_handles);
453 }
454 }
455}
456
457#[derive(Clone, JSTraceable, MallocSizeOf)]
460struct WaitForAllRejectionHandler {
461 #[ignore_malloc_size_of = "callbacks are hard"]
463 #[no_trace]
464 failure_steps: WaitForAllFailureSteps,
465
466 rejected: Cell<bool>,
468}
469
470impl Callback for WaitForAllRejectionHandler {
471 fn callback(&self, cx: &mut CurrentRealm, v: HandleValue) {
472 if self.rejected.replace(true) {
475 return;
477 }
478
479 (self.failure_steps)(cx, v);
482 }
483}
484
485#[derive(JSTraceable, MallocSizeOf)]
488#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
489pub(crate) struct WaitForAllSuccessStepsMicrotask {
490 global: Dom<GlobalScope>,
491
492 #[ignore_malloc_size_of = "Closure is hard"]
493 #[no_trace]
494 success_steps: WaitForAllSuccessSteps,
495}
496
497impl MicrotaskRunnable for WaitForAllSuccessStepsMicrotask {
498 fn handler(&self, cx: &mut JSContext) {
499 let mut realm = enter_auto_realm(cx, &*self.global);
500 (self.success_steps)(&mut realm, vec![]);
501 }
502}
503
504#[cfg_attr(crown, expect(crown::unrooted_must_root))]
506fn wait_for_all(
507 cx: &mut CurrentRealm,
508 global: &GlobalScope,
509 promises: Vec<Rc<Promise>>,
510 success_steps: WaitForAllSuccessSteps,
511 failure_steps: WaitForAllFailureSteps,
512) {
513 let fulfilled_count: Rc<Cell<usize>> = Default::default();
515
516 let rejection_handler = WaitForAllRejectionHandler {
525 failure_steps,
526 rejected: Default::default(),
527 };
528
529 if promises.is_empty() {
534 global.enqueue_microtask(
536 cx,
537 Box::new(WaitForAllSuccessStepsMicrotask {
538 global: Dom::from_ref(global),
539 success_steps,
540 }),
541 );
542
543 return;
545 }
546
547 let result: Rc<RefCell<Vec<Box<Heap<JSVal>>>>> = Default::default();
552
553 for (promise_index, promise) in promises.into_iter().enumerate() {
555 let result = result.clone();
556
557 {
558 let mut result_list = result.borrow_mut();
560 rooted!(&in(cx) let null_value = NullValue());
561 result_list.push(Heap::boxed(null_value.get()));
562 }
563
564 let handler = PromiseNativeHandler::new(
575 cx,
576 global,
577 Some(Box::new(WaitForAllFulfillmentHandler {
578 success_steps: success_steps.clone(),
579 result,
580 promise_index,
581 fulfilled_count: fulfilled_count.clone(),
582 })),
583 Some(Box::new(rejection_handler.clone())),
584 );
585 promise.append_native_handler(cx, &handler);
586
587 }
590}
591
592pub(crate) fn wait_for_all_promise(
594 cx: &mut CurrentRealm,
595 global: &GlobalScope,
596 promises: Vec<Rc<Promise>>,
597) -> Rc<Promise> {
598 let promise = Promise::new(cx, global);
600 let success_promise = promise.clone();
601 let failure_promise = promise.clone();
602
603 let success_steps = Rc::new(move |cx: &mut JSContext, results: Vec<HandleValue>| {
605 success_promise.resolve_native(cx, &results);
607 });
608
609 let failure_steps = Rc::new(move |cx: &mut JSContext, reason: HandleValue| {
611 failure_promise.reject_native(cx, &reason);
613 });
614
615 wait_for_all(cx, global, promises, success_steps, failure_steps);
617
618 promise
620}