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::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, Dom};
45use crate::dom::globalscope::GlobalScope;
46use crate::dom::promisenativehandler::{Callback, PromiseNativeHandler};
47use crate::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) {
137 unsafe {
138 let do_nothing_func = JS_NewFunction(
139 cx,
140 Some(do_nothing_promise_executor),
141 2,
142 0,
143 ptr::null(),
144 );
145 assert!(!do_nothing_func.is_null());
146 rooted!(&in(cx) let do_nothing_obj = JS_GetFunctionObject(do_nothing_func));
147 assert!(!do_nothing_obj.is_null());
148 obj.set(NewPromiseObject(cx, do_nothing_obj.handle()));
149 assert!(!obj.is_null());
150 let is_user_interacting = if ScriptThread::is_user_interacting() {
151 PromiseUserInputEventHandlingState::HadUserInteractionAtCreation
152 } else {
153 PromiseUserInputEventHandlingState::DidntHaveUserInteractionAtCreation
154 };
155 SetPromiseUserInputEventHandlingState(obj.handle(), is_user_interacting);
156 }
157 }
158
159 #[expect(unsafe_code)]
160 pub(crate) fn new_resolved(
161 cx: &mut JSContext,
162 global: &GlobalScope,
163 value: impl SafeToJSValConvertible,
164 ) -> Rc<Promise> {
165 let mut realm = enter_auto_realm(cx, global);
166 let cx = &mut realm.current_realm();
167 rooted!(&in(cx) let mut rval = UndefinedValue());
168 value.safe_to_jsval(cx, rval.handle_mut());
169 rooted!(&in(cx) let p = unsafe { CallOriginalPromiseResolve(cx, rval.handle()) });
170 assert!(!p.handle().is_null());
171 Promise::new_with_js_promise(cx, p.handle())
172 }
173
174 #[expect(unsafe_code)]
175 pub(crate) fn new_rejected(
176 cx: &mut JSContext,
177 global: &GlobalScope,
178 value: impl SafeToJSValConvertible,
179 ) -> Rc<Promise> {
180 let mut realm = enter_auto_realm(cx, global);
181 let cx = &mut realm.current_realm();
182 rooted!(&in(cx) let mut rval = UndefinedValue());
183 value.safe_to_jsval(cx, rval.handle_mut());
184 rooted!(&in(cx) let p = unsafe { CallOriginalPromiseReject(cx, rval.handle()) });
185 assert!(!p.handle().is_null());
186 Promise::new_with_js_promise(cx, p.handle())
187 }
188
189 pub(crate) fn resolve_native<T>(&self, cx: &mut JSContext, val: &T)
190 where
191 T: SafeToJSValConvertible,
192 {
193 let mut realm = enter_auto_realm(cx, self);
194 let cx = &mut realm.current_realm();
195 rooted!(&in(cx) let mut v = UndefinedValue());
196 val.safe_to_jsval(cx, v.handle_mut());
197 self.resolve(cx, v.handle());
198 }
199
200 #[expect(unsafe_code)]
201 pub(crate) fn resolve(&self, cx: &mut JSContext, value: HandleValue) {
202 unsafe {
203 if !ResolvePromise(cx, self.promise_obj(), value) {
204 JS_ClearPendingException(cx);
205 }
206 }
207 }
208
209 pub(crate) fn reject_native<T>(&self, cx: &mut JSContext, val: &T)
210 where
211 T: SafeToJSValConvertible,
212 {
213 let mut realm = enter_auto_realm(cx, self);
214 let cx = &mut realm.current_realm();
215 rooted!(&in(cx) let mut v = UndefinedValue());
216 val.safe_to_jsval(cx, v.handle_mut());
217 self.reject(cx, v.handle());
218 }
219
220 pub(crate) fn reject_error(&self, cx: &mut JSContext, error: Error) {
221 let mut realm = enter_auto_realm(cx, self);
222 let cx = &mut realm.current_realm();
223 rooted!(&in(cx) let mut v = UndefinedValue());
224 error.to_jsval(cx, &self.global(), v.handle_mut());
225 self.reject(cx, v.handle());
226 }
227
228 #[expect(unsafe_code)]
229 pub(crate) fn reject(&self, cx: &mut JSContext, value: HandleValue) {
230 unsafe {
231 if !RejectPromise(cx, self.promise_obj(), value) {
232 JS_ClearPendingException(cx);
233 }
234 }
235 }
236
237 #[expect(unsafe_code)]
238 pub(crate) fn is_fulfilled(&self) -> bool {
239 let state = unsafe { GetPromiseState(self.promise_obj()) };
240 matches!(state, PromiseState::Rejected | PromiseState::Fulfilled)
241 }
242
243 #[expect(unsafe_code)]
244 pub(crate) fn is_rejected(&self) -> bool {
245 let state = unsafe { GetPromiseState(self.promise_obj()) };
246 matches!(state, PromiseState::Rejected)
247 }
248
249 #[expect(unsafe_code)]
250 pub(crate) fn is_pending(&self) -> bool {
251 let state = unsafe { GetPromiseState(self.promise_obj()) };
252 matches!(state, PromiseState::Pending)
253 }
254
255 #[expect(unsafe_code)]
256 pub(crate) fn promise_obj(&self) -> HandleObject<'_> {
257 let obj = self.reflector().get_jsobject();
258 unsafe {
259 assert!(IsPromiseObject(obj));
260 }
261 obj
262 }
263
264 #[expect(unsafe_code)]
265 pub(crate) fn append_native_handler(
266 &self,
267 cx: &mut CurrentRealm,
268 handler: &PromiseNativeHandler,
269 ) {
270 let global = GlobalScope::from_current_realm(cx);
271 run_a_script::<DomTypeHolder, _, _>(cx, &global, |cx| {
272 rooted!(&in(cx) let resolve_func =
273 create_native_handler_function(cx,
274 handler.reflector().get_jsobject(),
275 NativeHandlerTask::Resolve));
276
277 rooted!(&in(cx) let reject_func =
278 create_native_handler_function(cx,
279 handler.reflector().get_jsobject(),
280 NativeHandlerTask::Reject));
281
282 unsafe {
283 let ok = AddPromiseReactions(
284 cx,
285 self.promise_obj(),
286 resolve_func.handle(),
287 reject_func.handle(),
288 );
289 assert!(ok);
290 }
291 })
292 }
293
294 #[expect(unsafe_code)]
295 pub(crate) fn get_promise_is_handled(&self) -> bool {
296 unsafe { GetPromiseIsHandled(self.reflector().get_jsobject()) }
297 }
298
299 #[expect(unsafe_code)]
300 pub(crate) fn set_promise_is_handled(&self, cx: &mut JSContext) -> bool {
301 unsafe { SetAnyPromiseIsHandled(cx, self.reflector().get_jsobject()) }
302 }
303}
304
305#[expect(unsafe_code)]
306unsafe extern "C" fn do_nothing_promise_executor(
307 _cx: *mut RawJSContext,
308 argc: u32,
309 vp: *mut JSVal,
310) -> bool {
311 let args = unsafe { CallArgs::from_vp(vp, argc) };
312 args.rval().set(UndefinedValue());
313 true
314}
315
316const SLOT_NATIVEHANDLER: usize = 0;
317const SLOT_NATIVEHANDLER_TASK: usize = 1;
318
319#[derive(PartialEq)]
320enum NativeHandlerTask {
321 Resolve = 0,
322 Reject = 1,
323}
324
325#[expect(unsafe_code)]
326unsafe extern "C" fn native_handler_callback(
327 cx: *mut RawJSContext,
328 argc: u32,
329 vp: *mut JSVal,
330) -> bool {
331 let mut cx = unsafe { JSContext::from_ptr(ptr::NonNull::new(cx).unwrap()) };
333 let mut cx = CurrentRealm::assert(&mut cx);
334 let cx = &mut cx;
335
336 let args = unsafe { CallArgs::from_vp(vp, argc) };
337 let native_handler_value =
338 unsafe { *GetFunctionNativeReserved(args.callee(), SLOT_NATIVEHANDLER) };
339 rooted!(&in(cx) let native_handler_value = native_handler_value);
340 assert!(native_handler_value.get().is_object());
341
342 let handler =
343 unsafe { root_from_object::<PromiseNativeHandler>(cx, native_handler_value.to_object()) }
344 .expect("unexpected value for native handler in promise native handler callback");
345
346 let native_handler_task_value =
347 unsafe { *GetFunctionNativeReserved(args.callee(), SLOT_NATIVEHANDLER_TASK) };
348 rooted!(&in(cx) let native_handler_task_value = native_handler_task_value);
349 match native_handler_task_value.to_int32() {
350 native_handler_task_value
351 if native_handler_task_value == NativeHandlerTask::Resolve as i32 =>
352 {
353 handler.resolved_callback(cx, unsafe { HandleValue::from_raw(args.get(0)) })
354 },
355 native_handler_task_value
356 if native_handler_task_value == NativeHandlerTask::Reject as i32 =>
357 {
358 handler.rejected_callback(cx, unsafe { HandleValue::from_raw(args.get(0)) })
359 },
360 _ => panic!("unexpected native handler task value"),
361 };
362
363 true
364}
365
366#[expect(unsafe_code)]
367fn create_native_handler_function(
368 cx: &mut JSContext,
369 holder: HandleObject,
370 task: NativeHandlerTask,
371) -> *mut JSObject {
372 unsafe {
373 let func = NewFunctionWithReserved(cx, Some(native_handler_callback), 1, 0, ptr::null());
374 assert!(!func.is_null());
375
376 rooted!(&in(cx) let obj = JS_GetFunctionObject(func));
377 assert!(!obj.is_null());
378 SetFunctionNativeReserved(obj.get(), SLOT_NATIVEHANDLER, &ObjectValue(*holder));
379 SetFunctionNativeReserved(obj.get(), SLOT_NATIVEHANDLER_TASK, &Int32Value(task as i32));
380 obj.get()
381 }
382}
383
384impl FromJSValConvertibleRc for Promise {
385 fn safe_from_jsval(
386 cx: &mut JSContext,
387 value: HandleValue,
388 ) -> Result<ConversionResult<Rc<Promise>>, ()> {
389 if value.get().is_null() {
390 return Ok(ConversionResult::Failure(c"null not allowed".into()));
391 }
392
393 let mut realm = CurrentRealm::assert(cx);
394 let global_scope = GlobalScope::from_current_realm(&mut realm);
395
396 let promise = Promise::new_resolved(cx, &global_scope, value);
397 Ok(ConversionResult::Success(promise))
398 }
399}
400
401type WaitForAllSuccessSteps = Rc<dyn Fn(&mut JSContext, Vec<HandleValue>)>;
403
404type WaitForAllFailureSteps = Rc<dyn Fn(&mut JSContext, HandleValue)>;
406
407#[derive(JSTraceable, MallocSizeOf)]
410#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
411struct WaitForAllFulfillmentHandler {
412 #[ignore_malloc_size_of = "callbacks are hard"]
414 #[no_trace]
415 success_steps: WaitForAllSuccessSteps,
416
417 #[ignore_malloc_size_of = "mozjs"]
419 #[expect(clippy::vec_box)]
420 result: Rc<RefCell<Vec<Box<Heap<JSVal>>>>>,
421
422 promise_index: usize,
424
425 #[conditional_malloc_size_of]
427 fulfilled_count: Rc<Cell<usize>>,
428}
429
430impl Callback for WaitForAllFulfillmentHandler {
431 fn callback(&self, cx: &mut CurrentRealm, v: HandleValue) {
432 let equals_total = {
435 let result = self.result.borrow_mut();
437 result[self.promise_index].set(v.get());
438
439 let mut fulfilled_count = self.fulfilled_count.get();
441 fulfilled_count += 1;
442 self.fulfilled_count.set(fulfilled_count);
443
444 fulfilled_count == result.len()
445 };
446
447 if equals_total {
449 let result_ref = self.result.borrow();
450 let result_handles: Vec<HandleValue> =
451 result_ref.iter().map(|v| v.as_handle_value()).collect();
452
453 (self.success_steps)(cx, result_handles);
454 }
455 }
456}
457
458#[derive(Clone, JSTraceable, MallocSizeOf)]
461struct WaitForAllRejectionHandler {
462 #[ignore_malloc_size_of = "callbacks are hard"]
464 #[no_trace]
465 failure_steps: WaitForAllFailureSteps,
466
467 rejected: Cell<bool>,
469}
470
471impl Callback for WaitForAllRejectionHandler {
472 fn callback(&self, cx: &mut CurrentRealm, v: HandleValue) {
473 if self.rejected.replace(true) {
476 return;
478 }
479
480 (self.failure_steps)(cx, v);
483 }
484}
485
486#[derive(JSTraceable, MallocSizeOf)]
489#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
490pub(crate) struct WaitForAllSuccessStepsMicrotask {
491 global: Dom<GlobalScope>,
492
493 #[ignore_malloc_size_of = "Closure is hard"]
494 #[no_trace]
495 success_steps: WaitForAllSuccessSteps,
496}
497
498impl MicrotaskRunnable for WaitForAllSuccessStepsMicrotask {
499 fn handler(&self, cx: &mut JSContext) {
500 let mut realm = enter_auto_realm(cx, &*self.global);
501 (self.success_steps)(&mut realm, vec![]);
502 }
503}
504
505#[cfg_attr(crown, expect(crown::unrooted_must_root))]
507fn wait_for_all(
508 cx: &mut CurrentRealm,
509 global: &GlobalScope,
510 promises: Vec<Rc<Promise>>,
511 success_steps: WaitForAllSuccessSteps,
512 failure_steps: WaitForAllFailureSteps,
513) {
514 let fulfilled_count: Rc<Cell<usize>> = Default::default();
516
517 let rejection_handler = WaitForAllRejectionHandler {
526 failure_steps,
527 rejected: Default::default(),
528 };
529
530 if promises.is_empty() {
535 global.enqueue_microtask(
537 cx,
538 Box::new(WaitForAllSuccessStepsMicrotask {
539 global: Dom::from_ref(global),
540 success_steps,
541 }),
542 );
543
544 return;
546 }
547
548 let result: Rc<RefCell<Vec<Box<Heap<JSVal>>>>> = Default::default();
553
554 for (promise_index, promise) in promises.into_iter().enumerate() {
556 let result = result.clone();
557
558 {
559 let mut result_list = result.borrow_mut();
561 rooted!(&in(cx) let null_value = NullValue());
562 result_list.push(Heap::boxed(null_value.get()));
563 }
564
565 let handler = PromiseNativeHandler::new(
576 cx,
577 global,
578 Some(Box::new(WaitForAllFulfillmentHandler {
579 success_steps: success_steps.clone(),
580 result,
581 promise_index,
582 fulfilled_count: fulfilled_count.clone(),
583 })),
584 Some(Box::new(rejection_handler.clone())),
585 );
586 promise.append_native_handler(cx, &handler);
587
588 }
591}
592
593pub(crate) fn wait_for_all_promise(
595 cx: &mut CurrentRealm,
596 global: &GlobalScope,
597 promises: Vec<Rc<Promise>>,
598) -> Rc<Promise> {
599 let promise = Promise::new(cx, global);
601 let success_promise = promise.clone();
602 let failure_promise = promise.clone();
603
604 let success_steps = Rc::new(move |cx: &mut JSContext, results: Vec<HandleValue>| {
606 success_promise.resolve_native(cx, &results);
608 });
609
610 let failure_steps = Rc::new(move |cx: &mut JSContext, reason: HandleValue| {
612 failure_promise.reject_native(cx, &reason);
614 });
615
616 wait_for_all(cx, global, promises, success_steps, failure_steps);
618
619 promise
621}