1use std::cell::{Cell, RefCell};
15use std::ptr;
16use std::rc::Rc;
17
18use dom_struct::dom_struct;
19use js::conversions::{ConversionResult, FromJSValConvertibleRc};
20use js::jsapi::{
21 AddRawValueRoot, CallArgs, GetFunctionNativeReserved, Heap, JS_ClearPendingException,
22 JS_GetFunctionObject, JS_NewFunction, JSAutoRealm, JSContext, JSObject,
23 NewFunctionWithReserved, PromiseState, PromiseUserInputEventHandlingState, RemoveRawValueRoot,
24 SetFunctionNativeReserved,
25};
26use js::jsval::{Int32Value, JSVal, NullValue, ObjectValue, UndefinedValue};
27use js::rust::wrappers::{
28 AddPromiseReactions, CallOriginalPromiseReject, CallOriginalPromiseResolve,
29 GetPromiseIsHandled, GetPromiseState, IsPromiseObject, NewPromiseObject, RejectPromise,
30 ResolvePromise, SetAnyPromiseIsHandled, SetPromiseUserInputEventHandlingState,
31};
32use js::rust::{HandleObject, HandleValue, MutableHandleObject, Runtime};
33use script_bindings::conversions::SafeToJSValConvertible;
34
35use crate::dom::bindings::conversions::root_from_object;
36use crate::dom::bindings::error::{Error, ErrorToJsval};
37use crate::dom::bindings::reflector::{DomGlobal, DomObject, MutDomObject, Reflector};
38use crate::dom::bindings::root::{AsHandleValue, DomRoot};
39use crate::dom::bindings::settings_stack::AutoEntryScript;
40use crate::dom::globalscope::GlobalScope;
41use crate::dom::promisenativehandler::{Callback, PromiseNativeHandler};
42use crate::microtask::{Microtask, MicrotaskRunnable};
43use crate::realms::{AlreadyInRealm, InRealm, enter_realm};
44use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
45use crate::script_thread::ScriptThread;
46
47#[dom_struct]
48#[cfg_attr(crown, crown::unrooted_must_root_lint::allow_unrooted_in_rc)]
49pub(crate) struct Promise {
50 reflector: Reflector,
51 #[ignore_malloc_size_of = "SM handles JS values"]
56 permanent_js_root: Heap<JSVal>,
57}
58
59trait PromiseHelper {
61 fn initialize(&self, cx: SafeJSContext);
62}
63
64impl PromiseHelper for Rc<Promise> {
65 #[expect(unsafe_code)]
66 fn initialize(&self, cx: SafeJSContext) {
67 let obj = self.reflector().get_jsobject();
68 self.permanent_js_root.set(ObjectValue(*obj));
69 unsafe {
70 assert!(AddRawValueRoot(
71 *cx,
72 self.permanent_js_root.get_unsafe(),
73 c"Promise::root".as_ptr(),
74 ));
75 }
76 }
77}
78
79impl Drop for Promise {
83 #[expect(unsafe_code)]
84 fn drop(&mut self) {
85 unsafe {
86 let object = self.permanent_js_root.get().to_object();
87 assert!(!object.is_null());
88 if let Some(cx) = Runtime::get() {
89 RemoveRawValueRoot(cx.as_ptr(), self.permanent_js_root.get_unsafe());
90 }
91 }
92 }
93}
94
95impl Promise {
96 pub(crate) fn new(global: &GlobalScope, can_gc: CanGc) -> Rc<Promise> {
97 let realm = enter_realm(global);
98 let comp = InRealm::Entered(&realm);
99 Promise::new_in_current_realm(comp, can_gc)
100 }
101
102 pub(crate) fn new_in_current_realm(_comp: InRealm, can_gc: CanGc) -> Rc<Promise> {
103 let cx = GlobalScope::get_cx();
104 rooted!(in(*cx) let mut obj = ptr::null_mut::<JSObject>());
105 Promise::create_js_promise(cx, obj.handle_mut(), can_gc);
106 Promise::new_with_js_promise(obj.handle(), cx)
107 }
108
109 pub(crate) fn duplicate(&self) -> Rc<Promise> {
110 let cx = GlobalScope::get_cx();
111 Promise::new_with_js_promise(self.reflector().get_jsobject(), cx)
112 }
113
114 #[expect(unsafe_code)]
115 #[cfg_attr(crown, allow(crown::unrooted_must_root))]
116 pub(crate) fn new_with_js_promise(obj: HandleObject, cx: SafeJSContext) -> Rc<Promise> {
117 unsafe {
118 assert!(IsPromiseObject(obj));
119 let promise = Promise {
120 reflector: Reflector::new(),
121 permanent_js_root: Heap::default(),
122 };
123 let promise = Rc::new(promise);
124 promise.init_reflector(obj.get());
125 promise.initialize(cx);
126 promise
127 }
128 }
129
130 #[expect(unsafe_code)]
131 fn create_js_promise(cx: SafeJSContext, mut obj: MutableHandleObject, _can_gc: CanGc) {
134 unsafe {
135 let do_nothing_func = JS_NewFunction(
136 *cx,
137 Some(do_nothing_promise_executor),
138 2,
139 0,
140 ptr::null(),
141 );
142 assert!(!do_nothing_func.is_null());
143 rooted!(in(*cx) let do_nothing_obj = JS_GetFunctionObject(do_nothing_func));
144 assert!(!do_nothing_obj.is_null());
145 obj.set(NewPromiseObject(*cx, do_nothing_obj.handle()));
146 assert!(!obj.is_null());
147 let is_user_interacting = if ScriptThread::is_user_interacting() {
148 PromiseUserInputEventHandlingState::HadUserInteractionAtCreation
149 } else {
150 PromiseUserInputEventHandlingState::DidntHaveUserInteractionAtCreation
151 };
152 SetPromiseUserInputEventHandlingState(obj.handle(), is_user_interacting);
153 }
154 }
155
156 #[expect(unsafe_code)]
157 #[cfg_attr(crown, allow(crown::unrooted_must_root))]
158 pub(crate) fn new_resolved(
159 global: &GlobalScope,
160 cx: SafeJSContext,
161 value: impl SafeToJSValConvertible,
162 can_gc: CanGc,
163 ) -> Rc<Promise> {
164 let _ac = JSAutoRealm::new(*cx, global.reflector().get_jsobject().get());
165 rooted!(in(*cx) let mut rval = UndefinedValue());
166 value.safe_to_jsval(cx, rval.handle_mut(), can_gc);
167 unsafe {
168 rooted!(in(*cx) let p = CallOriginalPromiseResolve(*cx, rval.handle()));
169 assert!(!p.handle().is_null());
170 Promise::new_with_js_promise(p.handle(), cx)
171 }
172 }
173
174 #[expect(unsafe_code)]
175 #[cfg_attr(crown, allow(crown::unrooted_must_root))]
176 pub(crate) fn new_rejected(
177 global: &GlobalScope,
178 cx: SafeJSContext,
179 value: impl SafeToJSValConvertible,
180 can_gc: CanGc,
181 ) -> Rc<Promise> {
182 let _ac = JSAutoRealm::new(*cx, global.reflector().get_jsobject().get());
183 rooted!(in(*cx) let mut rval = UndefinedValue());
184 value.safe_to_jsval(cx, rval.handle_mut(), can_gc);
185 unsafe {
186 rooted!(in(*cx) let p = CallOriginalPromiseReject(*cx, rval.handle()));
187 assert!(!p.handle().is_null());
188 Promise::new_with_js_promise(p.handle(), cx)
189 }
190 }
191
192 pub(crate) fn resolve_native<T>(&self, val: &T, can_gc: CanGc)
193 where
194 T: SafeToJSValConvertible,
195 {
196 let cx = GlobalScope::get_cx();
197 let _ac = enter_realm(self);
198 rooted!(in(*cx) let mut v = UndefinedValue());
199 val.safe_to_jsval(cx, v.handle_mut(), can_gc);
200 self.resolve(cx, v.handle(), can_gc);
201 }
202
203 #[expect(unsafe_code)]
204 #[cfg_attr(crown, allow(crown::unrooted_must_root))]
205 pub(crate) fn resolve(&self, cx: SafeJSContext, value: HandleValue, _can_gc: CanGc) {
206 unsafe {
207 if !ResolvePromise(*cx, self.promise_obj(), value) {
208 JS_ClearPendingException(*cx);
209 }
210 }
211 }
212
213 pub(crate) fn reject_native<T>(&self, val: &T, can_gc: CanGc)
214 where
215 T: SafeToJSValConvertible,
216 {
217 let cx = GlobalScope::get_cx();
218 let _ac = enter_realm(self);
219 rooted!(in(*cx) let mut v = UndefinedValue());
220 val.safe_to_jsval(cx, v.handle_mut(), can_gc);
221 self.reject(cx, v.handle(), can_gc);
222 }
223
224 pub(crate) fn reject_error(&self, error: Error, can_gc: CanGc) {
225 let cx = GlobalScope::get_cx();
226 let _ac = enter_realm(self);
227 rooted!(in(*cx) let mut v = UndefinedValue());
228 error.to_jsval(cx, &self.global(), v.handle_mut(), can_gc);
229 self.reject(cx, v.handle(), can_gc);
230 }
231
232 #[expect(unsafe_code)]
233 #[cfg_attr(crown, allow(crown::unrooted_must_root))]
234 pub(crate) fn reject(&self, cx: SafeJSContext, value: HandleValue, _can_gc: CanGc) {
235 unsafe {
236 if !RejectPromise(*cx, self.promise_obj(), value) {
237 JS_ClearPendingException(*cx);
238 }
239 }
240 }
241
242 #[expect(unsafe_code)]
243 pub(crate) fn is_fulfilled(&self) -> bool {
244 let state = unsafe { GetPromiseState(self.promise_obj()) };
245 matches!(state, PromiseState::Rejected | PromiseState::Fulfilled)
246 }
247
248 #[expect(unsafe_code)]
249 pub(crate) fn is_rejected(&self) -> bool {
250 let state = unsafe { GetPromiseState(self.promise_obj()) };
251 matches!(state, PromiseState::Rejected)
252 }
253
254 #[expect(unsafe_code)]
255 pub(crate) fn is_pending(&self) -> bool {
256 let state = unsafe { GetPromiseState(self.promise_obj()) };
257 matches!(state, PromiseState::Pending)
258 }
259
260 #[expect(unsafe_code)]
261 pub(crate) fn promise_obj(&self) -> HandleObject<'_> {
262 let obj = self.reflector().get_jsobject();
263 unsafe {
264 assert!(IsPromiseObject(obj));
265 }
266 obj
267 }
268
269 #[expect(unsafe_code)]
270 pub(crate) fn append_native_handler(
271 &self,
272 handler: &PromiseNativeHandler,
273 realm: InRealm,
274 can_gc: CanGc,
275 ) {
276 let _ais = AutoEntryScript::new(&handler.global_(realm));
277 let cx = GlobalScope::get_cx();
278 rooted!(in(*cx) let resolve_func =
279 create_native_handler_function(*cx,
280 handler.reflector().get_jsobject(),
281 NativeHandlerTask::Resolve,
282 can_gc));
283
284 rooted!(in(*cx) let reject_func =
285 create_native_handler_function(*cx,
286 handler.reflector().get_jsobject(),
287 NativeHandlerTask::Reject,
288 can_gc));
289
290 unsafe {
291 let ok = AddPromiseReactions(
292 *cx,
293 self.promise_obj(),
294 resolve_func.handle(),
295 reject_func.handle(),
296 );
297 assert!(ok);
298 }
299 }
300
301 #[expect(unsafe_code)]
302 pub(crate) fn get_promise_is_handled(&self) -> bool {
303 unsafe { GetPromiseIsHandled(self.reflector().get_jsobject()) }
304 }
305
306 #[expect(unsafe_code)]
307 pub(crate) fn set_promise_is_handled(&self) -> bool {
308 let cx = GlobalScope::get_cx();
309 unsafe { SetAnyPromiseIsHandled(*cx, self.reflector().get_jsobject()) }
310 }
311}
312
313#[expect(unsafe_code)]
314unsafe extern "C" fn do_nothing_promise_executor(
315 _cx: *mut JSContext,
316 argc: u32,
317 vp: *mut JSVal,
318) -> bool {
319 let args = unsafe { CallArgs::from_vp(vp, argc) };
320 args.rval().set(UndefinedValue());
321 true
322}
323
324const SLOT_NATIVEHANDLER: usize = 0;
325const SLOT_NATIVEHANDLER_TASK: usize = 1;
326
327#[derive(PartialEq)]
328enum NativeHandlerTask {
329 Resolve = 0,
330 Reject = 1,
331}
332
333#[expect(unsafe_code)]
334unsafe extern "C" fn native_handler_callback(
335 cx: *mut JSContext,
336 argc: u32,
337 vp: *mut JSVal,
338) -> bool {
339 let cx = unsafe { SafeJSContext::from_ptr(cx) };
340 let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
341
342 let args = unsafe { CallArgs::from_vp(vp, argc) };
343 let native_handler_value =
344 unsafe { *GetFunctionNativeReserved(args.callee(), SLOT_NATIVEHANDLER) };
345 rooted!(in(*cx) let native_handler_value = native_handler_value);
346 assert!(native_handler_value.get().is_object());
347
348 let handler =
349 unsafe { root_from_object::<PromiseNativeHandler>(native_handler_value.to_object(), *cx) }
350 .expect("unexpected value for native handler in promise native handler callback");
351
352 let native_handler_task_value =
353 unsafe { *GetFunctionNativeReserved(args.callee(), SLOT_NATIVEHANDLER_TASK) };
354 rooted!(in(*cx) let native_handler_task_value = native_handler_task_value);
355 match native_handler_task_value.to_int32() {
356 native_handler_task_value
357 if native_handler_task_value == NativeHandlerTask::Resolve as i32 =>
358 {
359 handler.resolved_callback(
360 *cx,
361 unsafe { HandleValue::from_raw(args.get(0)) },
362 InRealm::Already(&in_realm_proof),
363 CanGc::note(),
364 )
365 },
366 native_handler_task_value
367 if native_handler_task_value == NativeHandlerTask::Reject as i32 =>
368 {
369 handler.rejected_callback(
370 *cx,
371 unsafe { HandleValue::from_raw(args.get(0)) },
372 InRealm::Already(&in_realm_proof),
373 CanGc::note(),
374 )
375 },
376 _ => panic!("unexpected native handler task value"),
377 };
378
379 true
380}
381
382#[expect(unsafe_code)]
383fn create_native_handler_function(
386 cx: *mut JSContext,
387 holder: HandleObject,
388 task: NativeHandlerTask,
389 _can_gc: CanGc,
390) -> *mut JSObject {
391 unsafe {
392 let func = NewFunctionWithReserved(cx, Some(native_handler_callback), 1, 0, ptr::null());
393 assert!(!func.is_null());
394
395 rooted!(in(cx) let obj = JS_GetFunctionObject(func));
396 assert!(!obj.is_null());
397 SetFunctionNativeReserved(obj.get(), SLOT_NATIVEHANDLER, &ObjectValue(*holder));
398 SetFunctionNativeReserved(obj.get(), SLOT_NATIVEHANDLER_TASK, &Int32Value(task as i32));
399 obj.get()
400 }
401}
402
403impl FromJSValConvertibleRc for Promise {
404 #[expect(unsafe_code)]
405 unsafe fn from_jsval(
406 cx: *mut JSContext,
407 value: HandleValue,
408 ) -> Result<ConversionResult<Rc<Promise>>, ()> {
409 if value.get().is_null() {
410 return Ok(ConversionResult::Failure("null not allowed".into()));
411 }
412
413 let cx = unsafe { SafeJSContext::from_ptr(cx) };
414 let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
415 let global_scope =
416 unsafe { GlobalScope::from_context(*cx, InRealm::Already(&in_realm_proof)) };
417
418 let promise = Promise::new_resolved(&global_scope, cx, value, CanGc::note());
419 Ok(ConversionResult::Success(promise))
420 }
421}
422
423type WaitForAllSuccessSteps = Rc<dyn Fn(Vec<HandleValue>)>;
425
426type WaitForAllFailureSteps = Rc<dyn Fn(HandleValue)>;
428
429#[derive(JSTraceable, MallocSizeOf)]
432#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
433struct WaitForAllFulfillmentHandler {
434 #[ignore_malloc_size_of = "callbacks are hard"]
436 #[no_trace]
437 success_steps: WaitForAllSuccessSteps,
438
439 #[ignore_malloc_size_of = "mozjs"]
441 #[allow(clippy::vec_box)]
442 result: Rc<RefCell<Vec<Box<Heap<JSVal>>>>>,
443
444 promise_index: usize,
446
447 #[conditional_malloc_size_of]
449 fulfilled_count: Rc<RefCell<usize>>,
450}
451
452impl Callback for WaitForAllFulfillmentHandler {
453 fn callback(&self, _cx: SafeJSContext, v: HandleValue, _realm: InRealm, _can_gc: CanGc) {
454 let equals_total = {
457 let result = self.result.borrow_mut();
459 result[self.promise_index].set(v.get());
460
461 let mut fulfilled_count = self.fulfilled_count.borrow_mut();
463 *fulfilled_count += 1;
464
465 *fulfilled_count == result.len()
466 };
467
468 if equals_total {
470 let result_ref = self.result.borrow();
471 let result_handles: Vec<HandleValue> =
472 result_ref.iter().map(|v| v.as_handle_value()).collect();
473
474 (self.success_steps)(result_handles);
475 }
476 }
477}
478
479#[derive(Clone, JSTraceable, MallocSizeOf)]
482struct WaitForAllRejectionHandler {
483 #[ignore_malloc_size_of = "callbacks are hard"]
485 #[no_trace]
486 failure_steps: WaitForAllFailureSteps,
487
488 rejected: Cell<bool>,
490}
491
492impl Callback for WaitForAllRejectionHandler {
493 fn callback(&self, _cx: SafeJSContext, v: HandleValue, _realm: InRealm, _can_gc: CanGc) {
494 if self.rejected.replace(true) {
497 return;
499 }
500
501 (self.failure_steps)(v);
504 }
505}
506
507#[derive(JSTraceable, MallocSizeOf)]
510pub(crate) struct WaitForAllSuccessStepsMicrotask {
511 global: DomRoot<GlobalScope>,
512
513 #[ignore_malloc_size_of = "Closure is hard"]
514 #[no_trace]
515 success_steps: WaitForAllSuccessSteps,
516}
517
518impl MicrotaskRunnable for WaitForAllSuccessStepsMicrotask {
519 fn handler(&self, _can_gc: CanGc) {
520 (self.success_steps)(vec![]);
521 }
522
523 fn enter_realm(&self) -> JSAutoRealm {
524 enter_realm(&*self.global)
525 }
526}
527
528#[cfg_attr(crown, allow(crown::unrooted_must_root))]
530pub(crate) fn wait_for_all(
531 cx: SafeJSContext,
532 global: &GlobalScope,
533 promises: Vec<Rc<Promise>>,
534 success_steps: WaitForAllSuccessSteps,
535 failure_steps: WaitForAllFailureSteps,
536 realm: InRealm,
537 can_gc: CanGc,
538) {
539 let fulfilled_count: Rc<RefCell<usize>> = Default::default();
541
542 let rejection_handler = WaitForAllRejectionHandler {
551 failure_steps,
552 rejected: Default::default(),
553 };
554
555 if promises.is_empty() {
560 global.enqueue_microtask(Microtask::WaitForAllSuccessSteps(
562 WaitForAllSuccessStepsMicrotask {
563 global: DomRoot::from_ref(global),
564 success_steps,
565 },
566 ));
567
568 return;
570 }
571
572 let result: Rc<RefCell<Vec<Box<Heap<JSVal>>>>> = Default::default();
577
578 for (promise_index, promise) in promises.into_iter().enumerate() {
580 let result = result.clone();
581
582 {
583 let mut result_list = result.borrow_mut();
585 rooted!(in(*cx) let null_value = NullValue());
586 result_list.push(Heap::boxed(null_value.get()));
587 }
588
589 let handler = PromiseNativeHandler::new(
600 global,
601 Some(Box::new(WaitForAllFulfillmentHandler {
602 success_steps: success_steps.clone(),
603 result,
604 promise_index,
605 fulfilled_count: fulfilled_count.clone(),
606 })),
607 Some(Box::new(rejection_handler.clone())),
608 can_gc,
609 );
610 promise.append_native_handler(&handler, realm, can_gc);
611
612 }
615}
616
617pub(crate) fn wait_for_all_promise(
619 cx: SafeJSContext,
620 global: &GlobalScope,
621 promises: Vec<Rc<Promise>>,
622 realm: InRealm,
623 can_gc: CanGc,
624) -> Rc<Promise> {
625 let promise = Promise::new(global, can_gc);
627 let success_promise = promise.clone();
628 let failure_promise = promise.clone();
629
630 let success_steps = Rc::new(move |results: Vec<HandleValue>| {
632 success_promise.resolve_native(&results, can_gc);
634 });
635
636 let failure_steps = Rc::new(move |reason: HandleValue| {
638 failure_promise.reject_native(&reason, can_gc);
640 });
641
642 wait_for_all(
644 cx,
645 global,
646 promises,
647 success_steps,
648 failure_steps,
649 realm,
650 can_gc,
651 );
652
653 promise
655}