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 #[allow(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 #[allow(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 #[allow(unsafe_code)]
110 pub(crate) fn duplicate(&self) -> Rc<Promise> {
111 let cx = GlobalScope::get_cx();
112 Promise::new_with_js_promise(self.reflector().get_jsobject(), cx)
113 }
114
115 #[allow(unsafe_code)]
116 #[cfg_attr(crown, allow(crown::unrooted_must_root))]
117 pub(crate) fn new_with_js_promise(obj: HandleObject, cx: SafeJSContext) -> Rc<Promise> {
118 unsafe {
119 assert!(IsPromiseObject(obj));
120 let promise = Promise {
121 reflector: Reflector::new(),
122 permanent_js_root: Heap::default(),
123 };
124 let promise = Rc::new(promise);
125 promise.init_reflector(obj.get());
126 promise.initialize(cx);
127 promise
128 }
129 }
130
131 #[allow(unsafe_code)]
132 fn create_js_promise(cx: SafeJSContext, mut obj: MutableHandleObject, _can_gc: CanGc) {
135 unsafe {
136 let do_nothing_func = JS_NewFunction(
137 *cx,
138 Some(do_nothing_promise_executor),
139 2,
140 0,
141 ptr::null(),
142 );
143 assert!(!do_nothing_func.is_null());
144 rooted!(in(*cx) let do_nothing_obj = JS_GetFunctionObject(do_nothing_func));
145 assert!(!do_nothing_obj.is_null());
146 obj.set(NewPromiseObject(*cx, do_nothing_obj.handle()));
147 assert!(!obj.is_null());
148 let is_user_interacting = if ScriptThread::is_user_interacting() {
149 PromiseUserInputEventHandlingState::HadUserInteractionAtCreation
150 } else {
151 PromiseUserInputEventHandlingState::DidntHaveUserInteractionAtCreation
152 };
153 SetPromiseUserInputEventHandlingState(obj.handle(), is_user_interacting);
154 }
155 }
156
157 #[allow(unsafe_code)]
158 #[cfg_attr(crown, allow(crown::unrooted_must_root))]
159 pub(crate) fn new_resolved(
160 global: &GlobalScope,
161 cx: SafeJSContext,
162 value: impl SafeToJSValConvertible,
163 _can_gc: CanGc,
164 ) -> Rc<Promise> {
165 let _ac = JSAutoRealm::new(*cx, global.reflector().get_jsobject().get());
166 rooted!(in(*cx) let mut rval = UndefinedValue());
167 value.safe_to_jsval(cx, rval.handle_mut());
168 unsafe {
169 rooted!(in(*cx) let p = CallOriginalPromiseResolve(*cx, rval.handle()));
170 assert!(!p.handle().is_null());
171 Promise::new_with_js_promise(p.handle(), cx)
172 }
173 }
174
175 #[allow(unsafe_code)]
176 #[cfg_attr(crown, allow(crown::unrooted_must_root))]
177 pub(crate) fn new_rejected(
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());
186 unsafe {
187 rooted!(in(*cx) let p = CallOriginalPromiseReject(*cx, rval.handle()));
188 assert!(!p.handle().is_null());
189 Promise::new_with_js_promise(p.handle(), cx)
190 }
191 }
192
193 pub(crate) fn resolve_native<T>(&self, val: &T, can_gc: CanGc)
194 where
195 T: SafeToJSValConvertible,
196 {
197 let cx = GlobalScope::get_cx();
198 let _ac = enter_realm(self);
199 rooted!(in(*cx) let mut v = UndefinedValue());
200 val.safe_to_jsval(cx, v.handle_mut());
201 self.resolve(cx, v.handle(), can_gc);
202 }
203
204 #[allow(unsafe_code)]
205 #[cfg_attr(crown, allow(crown::unrooted_must_root))]
206 pub(crate) fn resolve(&self, cx: SafeJSContext, value: HandleValue, _can_gc: CanGc) {
207 unsafe {
208 if !ResolvePromise(*cx, self.promise_obj(), value) {
209 JS_ClearPendingException(*cx);
210 }
211 }
212 }
213
214 pub(crate) fn reject_native<T>(&self, val: &T, can_gc: CanGc)
215 where
216 T: SafeToJSValConvertible,
217 {
218 let cx = GlobalScope::get_cx();
219 let _ac = enter_realm(self);
220 rooted!(in(*cx) let mut v = UndefinedValue());
221 val.safe_to_jsval(cx, v.handle_mut());
222 self.reject(cx, v.handle(), can_gc);
223 }
224
225 pub(crate) fn reject_error(&self, error: Error, can_gc: CanGc) {
226 let cx = GlobalScope::get_cx();
227 let _ac = enter_realm(self);
228 rooted!(in(*cx) let mut v = UndefinedValue());
229 error.to_jsval(cx, &self.global(), v.handle_mut(), can_gc);
230 self.reject(cx, v.handle(), can_gc);
231 }
232
233 #[allow(unsafe_code)]
234 #[cfg_attr(crown, allow(crown::unrooted_must_root))]
235 pub(crate) fn reject(&self, cx: SafeJSContext, value: HandleValue, _can_gc: CanGc) {
236 unsafe {
237 if !RejectPromise(*cx, self.promise_obj(), value) {
238 JS_ClearPendingException(*cx);
239 }
240 }
241 }
242
243 #[allow(unsafe_code)]
244 pub(crate) fn is_fulfilled(&self) -> bool {
245 let state = unsafe { GetPromiseState(self.promise_obj()) };
246 matches!(state, PromiseState::Rejected | PromiseState::Fulfilled)
247 }
248
249 #[allow(unsafe_code)]
250 pub(crate) fn is_rejected(&self) -> bool {
251 let state = unsafe { GetPromiseState(self.promise_obj()) };
252 matches!(state, PromiseState::Rejected)
253 }
254
255 #[allow(unsafe_code)]
256 pub(crate) fn is_pending(&self) -> bool {
257 let state = unsafe { GetPromiseState(self.promise_obj()) };
258 matches!(state, PromiseState::Pending)
259 }
260
261 #[allow(unsafe_code)]
262 pub(crate) fn promise_obj(&self) -> HandleObject<'_> {
263 let obj = self.reflector().get_jsobject();
264 unsafe {
265 assert!(IsPromiseObject(obj));
266 }
267 obj
268 }
269
270 #[allow(unsafe_code)]
271 pub(crate) fn append_native_handler(
272 &self,
273 handler: &PromiseNativeHandler,
274 realm: InRealm,
275 can_gc: CanGc,
276 ) {
277 let _ais = AutoEntryScript::new(&handler.global_(realm));
278 let cx = GlobalScope::get_cx();
279 rooted!(in(*cx) let resolve_func =
280 create_native_handler_function(*cx,
281 handler.reflector().get_jsobject(),
282 NativeHandlerTask::Resolve,
283 can_gc));
284
285 rooted!(in(*cx) let reject_func =
286 create_native_handler_function(*cx,
287 handler.reflector().get_jsobject(),
288 NativeHandlerTask::Reject,
289 can_gc));
290
291 unsafe {
292 let ok = AddPromiseReactions(
293 *cx,
294 self.promise_obj(),
295 resolve_func.handle(),
296 reject_func.handle(),
297 );
298 assert!(ok);
299 }
300 }
301
302 #[allow(unsafe_code)]
303 pub(crate) fn get_promise_is_handled(&self) -> bool {
304 unsafe { GetPromiseIsHandled(self.reflector().get_jsobject()) }
305 }
306
307 #[allow(unsafe_code)]
308 pub(crate) fn set_promise_is_handled(&self) -> bool {
309 let cx = GlobalScope::get_cx();
310 unsafe { SetAnyPromiseIsHandled(*cx, self.reflector().get_jsobject()) }
311 }
312}
313
314#[allow(unsafe_code)]
315unsafe extern "C" fn do_nothing_promise_executor(
316 _cx: *mut JSContext,
317 argc: u32,
318 vp: *mut JSVal,
319) -> bool {
320 let args = CallArgs::from_vp(vp, argc);
321 args.rval().set(UndefinedValue());
322 true
323}
324
325const SLOT_NATIVEHANDLER: usize = 0;
326const SLOT_NATIVEHANDLER_TASK: usize = 1;
327
328#[derive(PartialEq)]
329enum NativeHandlerTask {
330 Resolve = 0,
331 Reject = 1,
332}
333
334#[allow(unsafe_code)]
335unsafe extern "C" fn native_handler_callback(
336 cx: *mut JSContext,
337 argc: u32,
338 vp: *mut JSVal,
339) -> bool {
340 let cx = SafeJSContext::from_ptr(cx);
341 let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
342
343 let args = CallArgs::from_vp(vp, argc);
344 rooted!(in(*cx) let v = *GetFunctionNativeReserved(args.callee(), SLOT_NATIVEHANDLER));
345 assert!(v.get().is_object());
346
347 let handler = root_from_object::<PromiseNativeHandler>(v.to_object(), *cx)
348 .expect("unexpected value for native handler in promise native handler callback");
349
350 rooted!(in(*cx) let v = *GetFunctionNativeReserved(args.callee(), SLOT_NATIVEHANDLER_TASK));
351 match v.to_int32() {
352 v if v == NativeHandlerTask::Resolve as i32 => handler.resolved_callback(
353 *cx,
354 HandleValue::from_raw(args.get(0)),
355 InRealm::Already(&in_realm_proof),
356 CanGc::note(),
357 ),
358 v if v == NativeHandlerTask::Reject as i32 => handler.rejected_callback(
359 *cx,
360 HandleValue::from_raw(args.get(0)),
361 InRealm::Already(&in_realm_proof),
362 CanGc::note(),
363 ),
364 _ => panic!("unexpected native handler task value"),
365 };
366
367 true
368}
369
370#[allow(unsafe_code)]
371fn create_native_handler_function(
374 cx: *mut JSContext,
375 holder: HandleObject,
376 task: NativeHandlerTask,
377 _can_gc: CanGc,
378) -> *mut JSObject {
379 unsafe {
380 let func = NewFunctionWithReserved(cx, Some(native_handler_callback), 1, 0, ptr::null());
381 assert!(!func.is_null());
382
383 rooted!(in(cx) let obj = JS_GetFunctionObject(func));
384 assert!(!obj.is_null());
385 SetFunctionNativeReserved(obj.get(), SLOT_NATIVEHANDLER, &ObjectValue(*holder));
386 SetFunctionNativeReserved(obj.get(), SLOT_NATIVEHANDLER_TASK, &Int32Value(task as i32));
387 obj.get()
388 }
389}
390
391impl FromJSValConvertibleRc for Promise {
392 #[allow(unsafe_code)]
393 unsafe fn from_jsval(
394 cx: *mut JSContext,
395 value: HandleValue,
396 ) -> Result<ConversionResult<Rc<Promise>>, ()> {
397 if value.get().is_null() {
398 return Ok(ConversionResult::Failure("null not allowed".into()));
399 }
400
401 let cx = SafeJSContext::from_ptr(cx);
402 let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
403 let global_scope = GlobalScope::from_context(*cx, InRealm::Already(&in_realm_proof));
404
405 let promise = Promise::new_resolved(&global_scope, cx, value, CanGc::note());
406 Ok(ConversionResult::Success(promise))
407 }
408}
409
410type WaitForAllSuccessSteps = Rc<dyn Fn(Vec<HandleValue>)>;
412
413type WaitForAllFailureSteps = Rc<dyn Fn(HandleValue)>;
415
416#[derive(JSTraceable, MallocSizeOf)]
419#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
420struct WaitForAllFulfillmentHandler {
421 #[ignore_malloc_size_of = "Rc is hard"]
423 #[no_trace]
424 success_steps: WaitForAllSuccessSteps,
425
426 #[ignore_malloc_size_of = "Rc is hard"]
428 #[allow(clippy::vec_box)]
429 result: Rc<RefCell<Vec<Box<Heap<JSVal>>>>>,
430
431 promise_index: usize,
433
434 #[ignore_malloc_size_of = "Rc is hard"]
436 fulfilled_count: Rc<RefCell<usize>>,
437}
438
439impl Callback for WaitForAllFulfillmentHandler {
440 fn callback(&self, _cx: SafeJSContext, v: HandleValue, _realm: InRealm, _can_gc: CanGc) {
441 let equals_total = {
444 let result = self.result.borrow_mut();
446 result[self.promise_index].set(v.get());
447
448 let mut fulfilled_count = self.fulfilled_count.borrow_mut();
450 *fulfilled_count += 1;
451
452 *fulfilled_count == result.len()
453 };
454
455 if equals_total {
457 let result_ref = self.result.borrow();
458 let result_handles: Vec<HandleValue> =
459 result_ref.iter().map(|v| v.as_handle_value()).collect();
460
461 (self.success_steps)(result_handles);
462 }
463 }
464}
465
466#[derive(Clone, JSTraceable, MallocSizeOf)]
469struct WaitForAllRejectionHandler {
470 #[ignore_malloc_size_of = "Rc is hard"]
472 #[no_trace]
473 failure_steps: WaitForAllFailureSteps,
474
475 rejected: Cell<bool>,
477}
478
479impl Callback for WaitForAllRejectionHandler {
480 fn callback(&self, _cx: SafeJSContext, v: HandleValue, _realm: InRealm, _can_gc: CanGc) {
481 if self.rejected.replace(true) {
484 return;
486 }
487
488 (self.failure_steps)(v);
491 }
492}
493
494#[derive(JSTraceable, MallocSizeOf)]
497pub(crate) struct WaitForAllSuccessStepsMicrotask {
498 global: DomRoot<GlobalScope>,
499
500 #[ignore_malloc_size_of = "Closure is hard"]
501 #[no_trace]
502 success_steps: WaitForAllSuccessSteps,
503}
504
505impl MicrotaskRunnable for WaitForAllSuccessStepsMicrotask {
506 fn handler(&self, _can_gc: CanGc) {
507 (self.success_steps)(vec![]);
508 }
509
510 fn enter_realm(&self) -> JSAutoRealm {
511 enter_realm(&*self.global)
512 }
513}
514
515#[cfg_attr(crown, allow(crown::unrooted_must_root))]
517pub(crate) fn wait_for_all(
518 cx: SafeJSContext,
519 global: &GlobalScope,
520 promises: Vec<Rc<Promise>>,
521 success_steps: WaitForAllSuccessSteps,
522 failure_steps: WaitForAllFailureSteps,
523 realm: InRealm,
524 can_gc: CanGc,
525) {
526 let fulfilled_count: Rc<RefCell<usize>> = Default::default();
528
529 let rejection_handler = WaitForAllRejectionHandler {
538 failure_steps,
539 rejected: Default::default(),
540 };
541
542 if promises.is_empty() {
547 global.microtask_queue().enqueue(
549 Microtask::WaitForAllSuccessSteps(WaitForAllSuccessStepsMicrotask {
550 global: DomRoot::from_ref(global),
551 success_steps,
552 }),
553 cx,
554 );
555
556 return;
558 }
559
560 let result: Rc<RefCell<Vec<Box<Heap<JSVal>>>>> = Default::default();
565
566 for (promise_index, promise) in promises.into_iter().enumerate() {
568 let result = result.clone();
569
570 {
571 let mut result_list = result.borrow_mut();
573 rooted!(in(*cx) let null_value = NullValue());
574 result_list.push(Heap::boxed(null_value.get()));
575 }
576
577 let handler = PromiseNativeHandler::new(
588 global,
589 Some(Box::new(WaitForAllFulfillmentHandler {
590 success_steps: success_steps.clone(),
591 result,
592 promise_index,
593 fulfilled_count: fulfilled_count.clone(),
594 })),
595 Some(Box::new(rejection_handler.clone())),
596 can_gc,
597 );
598 promise.append_native_handler(&handler, realm, can_gc);
599
600 }
603}
604
605pub(crate) fn wait_for_all_promise(
607 cx: SafeJSContext,
608 global: &GlobalScope,
609 promises: Vec<Rc<Promise>>,
610 realm: InRealm,
611 can_gc: CanGc,
612) -> Rc<Promise> {
613 let promise = Promise::new(global, can_gc);
615 let success_promise = promise.clone();
616 let failure_promise = promise.clone();
617
618 let success_steps = Rc::new(move |results: Vec<HandleValue>| {
620 success_promise.resolve_native(&results, can_gc);
622 });
623
624 let failure_steps = Rc::new(move |reason: HandleValue| {
626 failure_promise.reject_native(&reason, can_gc);
628 });
629
630 wait_for_all(
632 cx,
633 global,
634 promises,
635 success_steps,
636 failure_steps,
637 realm,
638 can_gc,
639 );
640
641 promise
643}