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;
39use crate::dom::bindings::settings_stack::AutoEntryScript;
40use crate::dom::globalscope::GlobalScope;
41use crate::dom::promisenativehandler::{Callback, PromiseNativeHandler};
42use crate::realms::{AlreadyInRealm, InRealm, enter_realm};
43use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
44use crate::script_thread::ScriptThread;
45
46#[dom_struct]
47#[cfg_attr(crown, crown::unrooted_must_root_lint::allow_unrooted_in_rc)]
48pub(crate) struct Promise {
49 reflector: Reflector,
50 #[ignore_malloc_size_of = "SM handles JS values"]
55 permanent_js_root: Heap<JSVal>,
56}
57
58trait PromiseHelper {
60 fn initialize(&self, cx: SafeJSContext);
61}
62
63impl PromiseHelper for Rc<Promise> {
64 #[allow(unsafe_code)]
65 fn initialize(&self, cx: SafeJSContext) {
66 let obj = self.reflector().get_jsobject();
67 self.permanent_js_root.set(ObjectValue(*obj));
68 unsafe {
69 assert!(AddRawValueRoot(
70 *cx,
71 self.permanent_js_root.get_unsafe(),
72 c"Promise::root".as_ptr(),
73 ));
74 }
75 }
76}
77
78impl Drop for Promise {
82 #[allow(unsafe_code)]
83 fn drop(&mut self) {
84 unsafe {
85 let object = self.permanent_js_root.get().to_object();
86 assert!(!object.is_null());
87 if let Some(cx) = Runtime::get() {
88 RemoveRawValueRoot(cx.as_ptr(), self.permanent_js_root.get_unsafe());
89 }
90 }
91 }
92}
93
94impl Promise {
95 pub(crate) fn new(global: &GlobalScope, can_gc: CanGc) -> Rc<Promise> {
96 let realm = enter_realm(global);
97 let comp = InRealm::Entered(&realm);
98 Promise::new_in_current_realm(comp, can_gc)
99 }
100
101 pub(crate) fn new_in_current_realm(_comp: InRealm, can_gc: CanGc) -> Rc<Promise> {
102 let cx = GlobalScope::get_cx();
103 rooted!(in(*cx) let mut obj = ptr::null_mut::<JSObject>());
104 Promise::create_js_promise(cx, obj.handle_mut(), can_gc);
105 Promise::new_with_js_promise(obj.handle(), cx)
106 }
107
108 #[allow(unsafe_code)]
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 #[allow(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 #[allow(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 #[allow(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());
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 #[allow(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());
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());
200 self.resolve(cx, v.handle(), can_gc);
201 }
202
203 #[allow(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());
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 #[allow(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 #[allow(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 #[allow(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 #[allow(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 #[allow(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 #[allow(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 #[allow(unsafe_code)]
302 pub(crate) fn get_promise_is_handled(&self) -> bool {
303 unsafe { GetPromiseIsHandled(self.reflector().get_jsobject()) }
304 }
305
306 #[allow(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#[allow(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 = 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#[allow(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 = SafeJSContext::from_ptr(cx);
340 let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
341
342 let args = CallArgs::from_vp(vp, argc);
343 rooted!(in(*cx) let v = *GetFunctionNativeReserved(args.callee(), SLOT_NATIVEHANDLER));
344 assert!(v.get().is_object());
345
346 let handler = root_from_object::<PromiseNativeHandler>(v.to_object(), *cx)
347 .expect("unexpected value for native handler in promise native handler callback");
348
349 rooted!(in(*cx) let v = *GetFunctionNativeReserved(args.callee(), SLOT_NATIVEHANDLER_TASK));
350 match v.to_int32() {
351 v if v == NativeHandlerTask::Resolve as i32 => handler.resolved_callback(
352 *cx,
353 HandleValue::from_raw(args.get(0)),
354 InRealm::Already(&in_realm_proof),
355 CanGc::note(),
356 ),
357 v if v == NativeHandlerTask::Reject as i32 => handler.rejected_callback(
358 *cx,
359 HandleValue::from_raw(args.get(0)),
360 InRealm::Already(&in_realm_proof),
361 CanGc::note(),
362 ),
363 _ => panic!("unexpected native handler task value"),
364 };
365
366 true
367}
368
369#[allow(unsafe_code)]
370fn create_native_handler_function(
373 cx: *mut JSContext,
374 holder: HandleObject,
375 task: NativeHandlerTask,
376 _can_gc: CanGc,
377) -> *mut JSObject {
378 unsafe {
379 let func = NewFunctionWithReserved(cx, Some(native_handler_callback), 1, 0, ptr::null());
380 assert!(!func.is_null());
381
382 rooted!(in(cx) let obj = JS_GetFunctionObject(func));
383 assert!(!obj.is_null());
384 SetFunctionNativeReserved(obj.get(), SLOT_NATIVEHANDLER, &ObjectValue(*holder));
385 SetFunctionNativeReserved(obj.get(), SLOT_NATIVEHANDLER_TASK, &Int32Value(task as i32));
386 obj.get()
387 }
388}
389
390impl FromJSValConvertibleRc for Promise {
391 #[allow(unsafe_code)]
392 unsafe fn from_jsval(
393 cx: *mut JSContext,
394 value: HandleValue,
395 ) -> Result<ConversionResult<Rc<Promise>>, ()> {
396 if value.get().is_null() {
397 return Ok(ConversionResult::Failure("null not allowed".into()));
398 }
399
400 let cx = SafeJSContext::from_ptr(cx);
401 let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
402 let global_scope = GlobalScope::from_context(*cx, InRealm::Already(&in_realm_proof));
403
404 let promise = Promise::new_resolved(&global_scope, cx, value, CanGc::note());
405 Ok(ConversionResult::Success(promise))
406 }
407}
408
409type WaitForAllSuccessSteps = Rc<dyn Fn(Vec<HandleValue>)>;
411
412type WaitForAllFailureSteps = Rc<dyn Fn(HandleValue)>;
414
415#[derive(JSTraceable, MallocSizeOf)]
418#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
419struct WaitForAllFulfillmentHandler {
420 #[ignore_malloc_size_of = "Rc is hard"]
422 #[no_trace]
423 success_steps: WaitForAllSuccessSteps,
424
425 #[ignore_malloc_size_of = "Rc is hard"]
427 #[allow(clippy::vec_box)]
428 result: Rc<RefCell<Vec<Box<Heap<JSVal>>>>>,
429
430 promise_index: usize,
432
433 #[ignore_malloc_size_of = "Rc is hard"]
435 fulfilled_count: Rc<RefCell<usize>>,
436}
437
438impl Callback for WaitForAllFulfillmentHandler {
439 fn callback(&self, _cx: SafeJSContext, v: HandleValue, _realm: InRealm, _can_gc: CanGc) {
440 let equals_total = {
443 let result = self.result.borrow_mut();
445 result[self.promise_index].set(v.get());
446
447 let mut fulfilled_count = self.fulfilled_count.borrow_mut();
449 *fulfilled_count += 1;
450
451 *fulfilled_count == result.len()
452 };
453
454 if equals_total {
456 let result_ref = self.result.borrow();
457 let result_handles: Vec<HandleValue> =
458 result_ref.iter().map(|v| v.as_handle_value()).collect();
459
460 (self.success_steps)(result_handles);
461 }
462 }
463}
464
465#[derive(Clone, JSTraceable, MallocSizeOf)]
468struct WaitForAllRejectionHandler {
469 #[ignore_malloc_size_of = "Rc is hard"]
471 #[no_trace]
472 failure_steps: WaitForAllFailureSteps,
473
474 rejected: Cell<bool>,
476}
477
478impl Callback for WaitForAllRejectionHandler {
479 fn callback(&self, _cx: SafeJSContext, v: HandleValue, _realm: InRealm, _can_gc: CanGc) {
480 if self.rejected.replace(true) {
483 return;
485 }
486
487 (self.failure_steps)(v);
490 }
491}
492
493#[cfg_attr(crown, allow(crown::unrooted_must_root))]
495pub(crate) fn wait_for_all(
496 cx: SafeJSContext,
497 global: &GlobalScope,
498 promises: Vec<Rc<Promise>>,
499 success_steps: WaitForAllSuccessSteps,
500 failure_steps: WaitForAllFailureSteps,
501 realm: InRealm,
502 can_gc: CanGc,
503) {
504 let fulfilled_count: Rc<RefCell<usize>> = Default::default();
506
507 let rejection_handler = WaitForAllRejectionHandler {
516 failure_steps,
517 rejected: Default::default(),
518 };
519
520 let result: Rc<RefCell<Vec<Box<Heap<JSVal>>>>> = Default::default();
532
533 for (promise_index, promise) in promises.into_iter().enumerate() {
535 let result = result.clone();
536
537 {
538 let mut result_list = result.borrow_mut();
540 rooted!(in(*cx) let null_value = NullValue());
541 result_list.push(Heap::boxed(null_value.get()));
542 }
543
544 let handler = PromiseNativeHandler::new(
555 global,
556 Some(Box::new(WaitForAllFulfillmentHandler {
557 success_steps: success_steps.clone(),
558 result,
559 promise_index,
560 fulfilled_count: fulfilled_count.clone(),
561 })),
562 Some(Box::new(rejection_handler.clone())),
563 can_gc,
564 );
565 promise.append_native_handler(&handler, realm, can_gc);
566
567 }
570}
571
572pub(crate) fn wait_for_all_promise(
574 cx: SafeJSContext,
575 global: &GlobalScope,
576 promises: Vec<Rc<Promise>>,
577 realm: InRealm,
578 can_gc: CanGc,
579) -> Rc<Promise> {
580 let promise = Promise::new(global, can_gc);
582 let success_promise = promise.clone();
583 let failure_promise = promise.clone();
584
585 let success_steps = Rc::new(move |results: Vec<HandleValue>| {
587 success_promise.resolve_native(&results, can_gc);
589 });
590
591 let failure_steps = Rc::new(move |reason: HandleValue| {
593 failure_promise.reject_native(&reason, can_gc);
595 });
596
597 if promises.is_empty() {
598 let empty_list: Vec<HandleValue> = vec![];
604 promise.resolve_native(&empty_list, can_gc);
605 } else {
606 wait_for_all(
608 cx,
609 global,
610 promises,
611 success_steps,
612 failure_steps,
613 realm,
614 can_gc,
615 );
616 }
617
618 promise
620}