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