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_ClearPendingException,
24 JS_GetFunctionObject, JS_NewFunction, JSAutoRealm, JSContext as RawJSContext, JSObject,
25 NewFunctionWithReserved, PromiseState, PromiseUserInputEventHandlingState, RemoveRawValueRoot,
26 SetFunctionNativeReserved,
27};
28use js::jsval::{Int32Value, JSVal, NullValue, ObjectValue, UndefinedValue};
29use js::realm::{AutoRealm, CurrentRealm};
30use js::rust::wrappers::{
31 AddPromiseReactions, CallOriginalPromiseReject, CallOriginalPromiseResolve,
32 GetPromiseIsHandled, GetPromiseState, IsPromiseObject, NewPromiseObject, RejectPromise,
33 ResolvePromise, SetAnyPromiseIsHandled, SetPromiseUserInputEventHandlingState,
34};
35use js::rust::{HandleObject, HandleValue, MutableHandleObject, Runtime};
36use script_bindings::conversions::SafeToJSValConvertible;
37
38use crate::dom::bindings::conversions::root_from_object;
39use crate::dom::bindings::error::{Error, ErrorToJsval};
40use crate::dom::bindings::reflector::{DomGlobal, DomObject, MutDomObject, Reflector};
41use crate::dom::bindings::root::{AsHandleValue, DomRoot};
42use crate::dom::bindings::settings_stack::AutoEntryScript;
43use crate::dom::globalscope::GlobalScope;
44use crate::dom::promisenativehandler::{Callback, PromiseNativeHandler};
45use crate::microtask::{Microtask, MicrotaskRunnable};
46use crate::realms::{AlreadyInRealm, InRealm, enter_realm};
47use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
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: SafeJSContext);
65}
66
67impl PromiseHelper for Rc<Promise> {
68 #[expect(unsafe_code)]
69 fn initialize(&self, cx: SafeJSContext) {
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 self.reflector.drop_memory(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(global: &GlobalScope, can_gc: CanGc) -> Rc<Promise> {
101 let realm = enter_realm(global);
102 let comp = InRealm::Entered(&realm);
103 Promise::new_in_current_realm(comp, can_gc)
104 }
105
106 pub(crate) fn new_in_current_realm(_comp: InRealm, can_gc: CanGc) -> Rc<Promise> {
107 let cx = GlobalScope::get_cx();
108 rooted!(in(*cx) let mut obj = ptr::null_mut::<JSObject>());
109 Promise::create_js_promise(cx, obj.handle_mut(), can_gc);
110 Promise::new_with_js_promise(obj.handle(), cx)
111 }
112
113 pub(crate) fn new2(cx: &mut js::context::JSContext, global: &GlobalScope) -> Rc<Promise> {
114 let mut realm = AutoRealm::new(
115 cx,
116 std::ptr::NonNull::new(global.reflector().get_jsobject().get()).unwrap(),
117 );
118 let mut current_realm = realm.current_realm();
119 Promise::new_in_realm(&mut current_realm)
120 }
121
122 pub(crate) fn new_in_realm(current_realm: &mut CurrentRealm) -> Rc<Promise> {
123 let cx = current_realm.deref_mut();
124 rooted!(&in(cx) let mut obj = ptr::null_mut::<JSObject>());
125 Promise::create_js_promise(cx.into(), obj.handle_mut(), CanGc::from_cx(cx));
126 Promise::new_with_js_promise(obj.handle(), cx.into())
127 }
128
129 pub(crate) fn duplicate(&self) -> Rc<Promise> {
130 let cx = GlobalScope::get_cx();
131 Promise::new_with_js_promise(self.reflector().get_jsobject(), cx)
132 }
133
134 #[expect(unsafe_code)]
135 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
136 pub(crate) fn new_with_js_promise(obj: HandleObject, cx: SafeJSContext) -> Rc<Promise> {
137 unsafe {
138 assert!(IsPromiseObject(obj));
139 let promise = Promise {
140 reflector: Reflector::new(),
141 permanent_js_root: Heap::default(),
142 };
143 let promise = Rc::new(promise);
144 promise.init_reflector::<Promise>(obj.get());
145 promise.initialize(cx);
146 promise
147 }
148 }
149
150 #[expect(unsafe_code)]
151 fn create_js_promise(cx: SafeJSContext, mut obj: MutableHandleObject, _can_gc: CanGc) {
154 unsafe {
155 let do_nothing_func = JS_NewFunction(
156 *cx,
157 Some(do_nothing_promise_executor),
158 2,
159 0,
160 ptr::null(),
161 );
162 assert!(!do_nothing_func.is_null());
163 rooted!(in(*cx) let do_nothing_obj = JS_GetFunctionObject(do_nothing_func));
164 assert!(!do_nothing_obj.is_null());
165 obj.set(NewPromiseObject(*cx, do_nothing_obj.handle()));
166 assert!(!obj.is_null());
167 let is_user_interacting = if ScriptThread::is_user_interacting() {
168 PromiseUserInputEventHandlingState::HadUserInteractionAtCreation
169 } else {
170 PromiseUserInputEventHandlingState::DidntHaveUserInteractionAtCreation
171 };
172 SetPromiseUserInputEventHandlingState(obj.handle(), is_user_interacting);
173 }
174 }
175
176 #[expect(unsafe_code)]
177 pub(crate) fn new_resolved(
178 global: &GlobalScope,
179 cx: SafeJSContext,
180 value: impl SafeToJSValConvertible,
181 can_gc: CanGc,
182 ) -> Rc<Promise> {
183 let _ac = JSAutoRealm::new(*cx, global.reflector().get_jsobject().get());
184 rooted!(in(*cx) let mut rval = UndefinedValue());
185 value.safe_to_jsval(cx, rval.handle_mut(), can_gc);
186 unsafe {
187 rooted!(in(*cx) let p = CallOriginalPromiseResolve(*cx, rval.handle()));
188 assert!(!p.handle().is_null());
189 Promise::new_with_js_promise(p.handle(), cx)
190 }
191 }
192
193 #[expect(unsafe_code)]
194 pub(crate) fn new_rejected(
195 global: &GlobalScope,
196 cx: SafeJSContext,
197 value: impl SafeToJSValConvertible,
198 can_gc: CanGc,
199 ) -> Rc<Promise> {
200 let _ac = JSAutoRealm::new(*cx, global.reflector().get_jsobject().get());
201 rooted!(in(*cx) let mut rval = UndefinedValue());
202 value.safe_to_jsval(cx, rval.handle_mut(), can_gc);
203 unsafe {
204 rooted!(in(*cx) let p = CallOriginalPromiseReject(*cx, rval.handle()));
205 assert!(!p.handle().is_null());
206 Promise::new_with_js_promise(p.handle(), cx)
207 }
208 }
209
210 pub(crate) fn resolve_native<T>(&self, val: &T, can_gc: CanGc)
211 where
212 T: SafeToJSValConvertible,
213 {
214 let cx = GlobalScope::get_cx();
215 let _ac = enter_realm(self);
216 rooted!(in(*cx) let mut v = UndefinedValue());
217 val.safe_to_jsval(cx, v.handle_mut(), can_gc);
218 self.resolve(cx, v.handle(), can_gc);
219 }
220
221 #[expect(unsafe_code)]
222 pub(crate) fn resolve(&self, cx: SafeJSContext, value: HandleValue, _can_gc: CanGc) {
223 unsafe {
224 if !ResolvePromise(*cx, self.promise_obj(), value) {
225 JS_ClearPendingException(*cx);
226 }
227 }
228 }
229
230 pub(crate) fn reject_native<T>(&self, val: &T, can_gc: CanGc)
231 where
232 T: SafeToJSValConvertible,
233 {
234 let cx = GlobalScope::get_cx();
235 let _ac = enter_realm(self);
236 rooted!(in(*cx) let mut v = UndefinedValue());
237 val.safe_to_jsval(cx, v.handle_mut(), can_gc);
238 self.reject(cx, v.handle(), can_gc);
239 }
240
241 pub(crate) fn reject_error(&self, error: Error, can_gc: CanGc) {
242 let cx = GlobalScope::get_cx();
243 let _ac = enter_realm(self);
244 rooted!(in(*cx) let mut v = UndefinedValue());
245 error.to_jsval(cx, &self.global(), v.handle_mut(), can_gc);
246 self.reject(cx, v.handle(), can_gc);
247 }
248
249 #[expect(unsafe_code)]
250 pub(crate) fn reject(&self, cx: SafeJSContext, value: HandleValue, _can_gc: CanGc) {
251 unsafe {
252 if !RejectPromise(*cx, self.promise_obj(), value) {
253 JS_ClearPendingException(*cx);
254 }
255 }
256 }
257
258 #[expect(unsafe_code)]
259 pub(crate) fn is_fulfilled(&self) -> bool {
260 let state = unsafe { GetPromiseState(self.promise_obj()) };
261 matches!(state, PromiseState::Rejected | PromiseState::Fulfilled)
262 }
263
264 #[expect(unsafe_code)]
265 pub(crate) fn is_rejected(&self) -> bool {
266 let state = unsafe { GetPromiseState(self.promise_obj()) };
267 matches!(state, PromiseState::Rejected)
268 }
269
270 #[expect(unsafe_code)]
271 pub(crate) fn is_pending(&self) -> bool {
272 let state = unsafe { GetPromiseState(self.promise_obj()) };
273 matches!(state, PromiseState::Pending)
274 }
275
276 #[expect(unsafe_code)]
277 pub(crate) fn promise_obj(&self) -> HandleObject<'_> {
278 let obj = self.reflector().get_jsobject();
279 unsafe {
280 assert!(IsPromiseObject(obj));
281 }
282 obj
283 }
284
285 #[expect(unsafe_code)]
286 pub(crate) fn append_native_handler(
287 &self,
288 handler: &PromiseNativeHandler,
289 realm: InRealm,
290 can_gc: CanGc,
291 ) {
292 let _ais = AutoEntryScript::new(&handler.global_(realm));
293 let cx = GlobalScope::get_cx();
294 rooted!(in(*cx) let resolve_func =
295 create_native_handler_function(*cx,
296 handler.reflector().get_jsobject(),
297 NativeHandlerTask::Resolve,
298 can_gc));
299
300 rooted!(in(*cx) let reject_func =
301 create_native_handler_function(*cx,
302 handler.reflector().get_jsobject(),
303 NativeHandlerTask::Reject,
304 can_gc));
305
306 unsafe {
307 let ok = AddPromiseReactions(
308 *cx,
309 self.promise_obj(),
310 resolve_func.handle(),
311 reject_func.handle(),
312 );
313 assert!(ok);
314 }
315 }
316
317 #[expect(unsafe_code)]
318 pub(crate) fn get_promise_is_handled(&self) -> bool {
319 unsafe { GetPromiseIsHandled(self.reflector().get_jsobject()) }
320 }
321
322 #[expect(unsafe_code)]
323 pub(crate) fn set_promise_is_handled(&self) -> bool {
324 let cx = GlobalScope::get_cx();
325 unsafe { SetAnyPromiseIsHandled(*cx, self.reflector().get_jsobject()) }
326 }
327}
328
329#[expect(unsafe_code)]
330unsafe extern "C" fn do_nothing_promise_executor(
331 _cx: *mut RawJSContext,
332 argc: u32,
333 vp: *mut JSVal,
334) -> bool {
335 let args = unsafe { CallArgs::from_vp(vp, argc) };
336 args.rval().set(UndefinedValue());
337 true
338}
339
340const SLOT_NATIVEHANDLER: usize = 0;
341const SLOT_NATIVEHANDLER_TASK: usize = 1;
342
343#[derive(PartialEq)]
344enum NativeHandlerTask {
345 Resolve = 0,
346 Reject = 1,
347}
348
349#[expect(unsafe_code)]
350unsafe extern "C" fn native_handler_callback(
351 cx: *mut RawJSContext,
352 argc: u32,
353 vp: *mut JSVal,
354) -> bool {
355 let mut cx = unsafe { JSContext::from_ptr(ptr::NonNull::new(cx).unwrap()) };
357 let mut cx = CurrentRealm::assert(&mut cx);
358 let cx = &mut cx;
359
360 let args = unsafe { CallArgs::from_vp(vp, argc) };
361 let native_handler_value =
362 unsafe { *GetFunctionNativeReserved(args.callee(), SLOT_NATIVEHANDLER) };
363 rooted!(&in(cx) let native_handler_value = native_handler_value);
364 assert!(native_handler_value.get().is_object());
365
366 let handler = unsafe {
367 root_from_object::<PromiseNativeHandler>(native_handler_value.to_object(), cx.raw_cx())
368 }
369 .expect("unexpected value for native handler in promise native handler callback");
370
371 let native_handler_task_value =
372 unsafe { *GetFunctionNativeReserved(args.callee(), SLOT_NATIVEHANDLER_TASK) };
373 rooted!(&in(cx) let native_handler_task_value = native_handler_task_value);
374 match native_handler_task_value.to_int32() {
375 native_handler_task_value
376 if native_handler_task_value == NativeHandlerTask::Resolve as i32 =>
377 {
378 handler.resolved_callback(cx, unsafe { HandleValue::from_raw(args.get(0)) })
379 },
380 native_handler_task_value
381 if native_handler_task_value == NativeHandlerTask::Reject as i32 =>
382 {
383 handler.rejected_callback(cx, unsafe { HandleValue::from_raw(args.get(0)) })
384 },
385 _ => panic!("unexpected native handler task value"),
386 };
387
388 true
389}
390
391#[expect(unsafe_code)]
392fn create_native_handler_function(
395 cx: *mut RawJSContext,
396 holder: HandleObject,
397 task: NativeHandlerTask,
398 _can_gc: CanGc,
399) -> *mut JSObject {
400 unsafe {
401 let func = NewFunctionWithReserved(cx, Some(native_handler_callback), 1, 0, ptr::null());
402 assert!(!func.is_null());
403
404 rooted!(in(cx) let obj = JS_GetFunctionObject(func));
405 assert!(!obj.is_null());
406 SetFunctionNativeReserved(obj.get(), SLOT_NATIVEHANDLER, &ObjectValue(*holder));
407 SetFunctionNativeReserved(obj.get(), SLOT_NATIVEHANDLER_TASK, &Int32Value(task as i32));
408 obj.get()
409 }
410}
411
412impl FromJSValConvertibleRc for Promise {
413 #[expect(unsafe_code)]
414 unsafe fn from_jsval(
415 cx: *mut RawJSContext,
416 value: HandleValue,
417 ) -> Result<ConversionResult<Rc<Promise>>, ()> {
418 if value.get().is_null() {
419 return Ok(ConversionResult::Failure(c"null not allowed".into()));
420 }
421
422 let cx = unsafe { SafeJSContext::from_ptr(cx) };
423 let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
424 let global_scope =
425 unsafe { GlobalScope::from_context(*cx, InRealm::Already(&in_realm_proof)) };
426
427 let promise = Promise::new_resolved(&global_scope, cx, value, CanGc::note());
428 Ok(ConversionResult::Success(promise))
429 }
430}
431
432type WaitForAllSuccessSteps = Rc<dyn Fn(Vec<HandleValue>)>;
434
435type WaitForAllFailureSteps = Rc<dyn Fn(HandleValue)>;
437
438#[derive(JSTraceable, MallocSizeOf)]
441#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
442struct WaitForAllFulfillmentHandler {
443 #[ignore_malloc_size_of = "callbacks are hard"]
445 #[no_trace]
446 success_steps: WaitForAllSuccessSteps,
447
448 #[ignore_malloc_size_of = "mozjs"]
450 #[expect(clippy::vec_box)]
451 result: Rc<RefCell<Vec<Box<Heap<JSVal>>>>>,
452
453 promise_index: usize,
455
456 #[conditional_malloc_size_of]
458 fulfilled_count: Rc<RefCell<usize>>,
459}
460
461impl Callback for WaitForAllFulfillmentHandler {
462 fn callback(&self, _cx: &mut CurrentRealm, v: HandleValue) {
463 let equals_total = {
466 let result = self.result.borrow_mut();
468 result[self.promise_index].set(v.get());
469
470 let mut fulfilled_count = self.fulfilled_count.borrow_mut();
472 *fulfilled_count += 1;
473
474 *fulfilled_count == result.len()
475 };
476
477 if equals_total {
479 let result_ref = self.result.borrow();
480 let result_handles: Vec<HandleValue> =
481 result_ref.iter().map(|v| v.as_handle_value()).collect();
482
483 (self.success_steps)(result_handles);
484 }
485 }
486}
487
488#[derive(Clone, JSTraceable, MallocSizeOf)]
491struct WaitForAllRejectionHandler {
492 #[ignore_malloc_size_of = "callbacks are hard"]
494 #[no_trace]
495 failure_steps: WaitForAllFailureSteps,
496
497 rejected: Cell<bool>,
499}
500
501impl Callback for WaitForAllRejectionHandler {
502 fn callback(&self, _cx: &mut CurrentRealm, v: HandleValue) {
503 if self.rejected.replace(true) {
506 return;
508 }
509
510 (self.failure_steps)(v);
513 }
514}
515
516#[derive(JSTraceable, MallocSizeOf)]
519pub(crate) struct WaitForAllSuccessStepsMicrotask {
520 global: DomRoot<GlobalScope>,
521
522 #[ignore_malloc_size_of = "Closure is hard"]
523 #[no_trace]
524 success_steps: WaitForAllSuccessSteps,
525}
526
527impl MicrotaskRunnable for WaitForAllSuccessStepsMicrotask {
528 fn handler(&self, _can_gc: CanGc) {
529 (self.success_steps)(vec![]);
530 }
531
532 fn enter_realm(&self) -> JSAutoRealm {
533 enter_realm(&*self.global)
534 }
535}
536
537#[cfg_attr(crown, expect(crown::unrooted_must_root))]
539pub(crate) fn wait_for_all(
540 cx: SafeJSContext,
541 global: &GlobalScope,
542 promises: Vec<Rc<Promise>>,
543 success_steps: WaitForAllSuccessSteps,
544 failure_steps: WaitForAllFailureSteps,
545 realm: InRealm,
546 can_gc: CanGc,
547) {
548 let fulfilled_count: Rc<RefCell<usize>> = Default::default();
550
551 let rejection_handler = WaitForAllRejectionHandler {
560 failure_steps,
561 rejected: Default::default(),
562 };
563
564 if promises.is_empty() {
569 global.enqueue_microtask(Microtask::WaitForAllSuccessSteps(
571 WaitForAllSuccessStepsMicrotask {
572 global: DomRoot::from_ref(global),
573 success_steps,
574 },
575 ));
576
577 return;
579 }
580
581 let result: Rc<RefCell<Vec<Box<Heap<JSVal>>>>> = Default::default();
586
587 for (promise_index, promise) in promises.into_iter().enumerate() {
589 let result = result.clone();
590
591 {
592 let mut result_list = result.borrow_mut();
594 rooted!(in(*cx) let null_value = NullValue());
595 result_list.push(Heap::boxed(null_value.get()));
596 }
597
598 let handler = PromiseNativeHandler::new(
609 global,
610 Some(Box::new(WaitForAllFulfillmentHandler {
611 success_steps: success_steps.clone(),
612 result,
613 promise_index,
614 fulfilled_count: fulfilled_count.clone(),
615 })),
616 Some(Box::new(rejection_handler.clone())),
617 can_gc,
618 );
619 promise.append_native_handler(&handler, realm, can_gc);
620
621 }
624}
625
626pub(crate) fn wait_for_all_promise(
628 cx: SafeJSContext,
629 global: &GlobalScope,
630 promises: Vec<Rc<Promise>>,
631 realm: InRealm,
632 can_gc: CanGc,
633) -> Rc<Promise> {
634 let promise = Promise::new(global, can_gc);
636 let success_promise = promise.clone();
637 let failure_promise = promise.clone();
638
639 let success_steps = Rc::new(move |results: Vec<HandleValue>| {
641 success_promise.resolve_native(&results, can_gc);
643 });
644
645 let failure_steps = Rc::new(move |reason: HandleValue| {
647 failure_promise.reject_native(&reason, can_gc);
649 });
650
651 wait_for_all(
653 cx,
654 global,
655 promises,
656 success_steps,
657 failure_steps,
658 realm,
659 can_gc,
660 );
661
662 promise
664}