1use crate::host::HostFunction;
2use crate::object::function::JSFunction;
3use crate::object::object::JSObject;
4use crate::runtime::context::JSContext;
5use crate::value::JSValue;
6use std::collections::VecDeque;
7
8const PROMISE_STATE_SLOT: &str = "__promise_state__";
9const PROMISE_RESULT_SLOT: &str = "__promise_result__";
10const PROMISE_REACTIONS_SLOT: &str = "__promise_reactions__";
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum PromiseState {
14 Pending,
15 Fulfilled,
16 Rejected,
17}
18
19#[derive(Clone)]
20pub struct Reaction {
21 pub handler: JSValue,
22 pub is_reject: bool,
23 pub is_all_settled: bool,
24
25 pub target_promise: JSValue,
26}
27
28pub struct MicrotaskQueue {
29 queue: VecDeque<Microtask>,
30}
31
32pub enum Microtask {
33 Reaction(Reaction, JSValue),
34
35 UserCallback(JSValue, Vec<JSValue>),
36}
37
38impl MicrotaskQueue {
39 pub fn new() -> Self {
40 MicrotaskQueue {
41 queue: VecDeque::new(),
42 }
43 }
44
45 pub fn enqueue(&mut self, task: Microtask) {
46 self.queue.push_back(task);
47 }
48
49 pub fn is_empty(&self) -> bool {
50 self.queue.is_empty()
51 }
52
53 pub fn dequeue(&mut self) -> Option<Microtask> {
54 self.queue.pop_front()
55 }
56}
57
58impl Default for MicrotaskQueue {
59 fn default() -> Self {
60 Self::new()
61 }
62}
63
64fn create_builtin_function(ctx: &mut JSContext, name: &str) -> JSValue {
65 let mut func = JSFunction::new_builtin(ctx.intern(name), 1);
66 func.set_builtin_marker(ctx, name);
67 let ptr = Box::into_raw(Box::new(func)) as usize;
68 ctx.runtime_mut().gc_heap_mut().track_function(ptr);
69 JSValue::new_function(ptr)
70}
71
72fn intern_str(ctx: &mut JSContext, s: &str) -> crate::runtime::atom::Atom {
73 ctx.intern(s)
74}
75
76fn get_promise_state(ctx: &mut JSContext, obj: &JSObject) -> PromiseState {
77 let state_atom = intern_str(ctx, PROMISE_STATE_SLOT);
78 if let Some(state_val) = obj.get(state_atom) {
79 let val = state_val.get_int();
80 match val {
81 1 => PromiseState::Fulfilled,
82 2 => PromiseState::Rejected,
83 _ => PromiseState::Pending,
84 }
85 } else {
86 PromiseState::Pending
87 }
88}
89
90fn set_promise_state(ctx: &mut JSContext, obj: &mut JSObject, state: PromiseState) {
91 let state_val = match state {
92 PromiseState::Pending => JSValue::new_int(0),
93 PromiseState::Fulfilled => JSValue::new_int(1),
94 PromiseState::Rejected => JSValue::new_int(2),
95 };
96 let state_atom = intern_str(ctx, PROMISE_STATE_SLOT);
97 obj.set(state_atom, state_val);
98}
99
100fn get_promise_result(ctx: &mut JSContext, obj: &JSObject) -> JSValue {
101 let result_atom = intern_str(ctx, PROMISE_RESULT_SLOT);
102 obj.get(result_atom).unwrap_or(JSValue::undefined())
103}
104
105fn set_promise_result(ctx: &mut JSContext, obj: &mut JSObject, value: JSValue) {
106 let result_atom = intern_str(ctx, PROMISE_RESULT_SLOT);
107 obj.set(result_atom, value);
108}
109
110fn get_promise_reactions(ctx: &mut JSContext, obj: &JSObject) -> Vec<Reaction> {
111 let reactions_atom = intern_str(ctx, PROMISE_REACTIONS_SLOT);
112 if let Some(reactions_val) = obj.get(reactions_atom) {
113 if reactions_val.is_object() {
114 let ptr = reactions_val.get_ptr();
115
116 if ptr == 0 {
117 return Vec::new();
118 }
119 let arr = reactions_val.as_object();
120 let len_atom = intern_str(ctx, "length");
121 let len = arr.get(len_atom).map(|v| v.get_int() as usize).unwrap_or(0);
122 let mut reactions = Vec::new();
123 for i in 0..len {
124 let idx_atom = intern_str(ctx, &i.to_string());
125 if let Some(r_val) = arr.get(idx_atom) {
126 if r_val.is_object() {
127 let r_obj = r_val.as_object();
128 let handler_atom = intern_str(ctx, "handler");
129 let is_reject_atom = intern_str(ctx, "isReject");
130 if let Some(handler) = r_obj.get(handler_atom) {
131 let is_reject = r_obj
132 .get(is_reject_atom)
133 .map(|v| v.get_bool())
134 .unwrap_or(false);
135 reactions.push(Reaction {
136 handler,
137 is_reject,
138 is_all_settled: false,
139 target_promise: JSValue::undefined(),
140 });
141 }
142 }
143 }
144 }
145 return reactions;
146 }
147 }
148 Vec::new()
149}
150
151fn create_reaction_array(ctx: &mut JSContext, reactions: Vec<Reaction>) -> JSValue {
152 let mut arr = JSObject::new_array();
153 let len_atom = intern_str(ctx, "length");
154 arr.set(len_atom, JSValue::new_int(reactions.len() as i64));
155 for (i, reaction) in reactions.into_iter().enumerate() {
156 let mut r_obj = JSObject::new();
157 let handler_atom = intern_str(ctx, "handler");
158 let is_reject_atom = intern_str(ctx, "isReject");
159 r_obj.set(handler_atom, reaction.handler);
160 r_obj.set(is_reject_atom, JSValue::bool(reaction.is_reject));
161 let r_ptr = Box::into_raw(Box::new(r_obj)) as usize;
162 let idx_atom = intern_str(ctx, &i.to_string());
163 arr.set(idx_atom, JSValue::new_object(r_ptr));
164 }
165 let ptr = Box::into_raw(Box::new(arr)) as usize;
166 JSValue::new_object(ptr)
167}
168
169pub fn is_promise(value: &JSValue) -> bool {
170 if !value.is_object() {
171 return false;
172 }
173 let obj = value.as_object();
174 obj.is_promise()
175}
176
177fn create_promise(ctx: &mut JSContext) -> JSObject {
178 let mut promise = JSObject::new_promise();
179
180 if let Some(proto_ptr) = ctx.get_promise_prototype() {
181 promise.prototype = Some(proto_ptr);
182 }
183 let state_atom = intern_str(ctx, PROMISE_STATE_SLOT);
184 let result_atom = intern_str(ctx, PROMISE_RESULT_SLOT);
185 let reactions_atom = intern_str(ctx, PROMISE_REACTIONS_SLOT);
186 promise.set(state_atom, JSValue::new_int(0));
187 promise.set(result_atom, JSValue::undefined());
188 promise.set(reactions_atom, JSValue::null());
189 promise
190}
191
192fn fulfill_promise(ctx: &mut JSContext, promise: &mut JSObject, value: JSValue) {
193 set_promise_state(ctx, promise, PromiseState::Fulfilled);
194 set_promise_result(ctx, promise, value);
195 process_reactions(ctx, promise, PromiseState::Fulfilled);
196}
197
198fn reject_promise(ctx: &mut JSContext, promise: &mut JSObject, reason: JSValue) {
199 set_promise_state(ctx, promise, PromiseState::Rejected);
200 set_promise_result(ctx, promise, reason);
201 process_reactions(ctx, promise, PromiseState::Rejected);
202}
203
204fn process_reactions(ctx: &mut JSContext, promise: &mut JSObject, state: PromiseState) {
205 let reactions = get_promise_reactions(ctx, promise);
206 for reaction in reactions {
207 let argument = get_promise_result(ctx, promise);
208 let microtask = if reaction.is_reject && state == PromiseState::Rejected {
209 Microtask::Reaction(reaction, argument)
210 } else if !reaction.is_reject && state == PromiseState::Fulfilled {
211 Microtask::Reaction(reaction, argument)
212 } else {
213 continue;
214 };
215 ctx.microtask_enqueue(microtask);
216 }
217}
218
219fn promise_resolve(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
220 let value = if args.len() >= 2 {
221 args[1]
222 } else if !args.is_empty() {
223 args[0]
224 } else {
225 JSValue::undefined()
226 };
227 if value.is_object() {
228 let obj = value.as_object();
229 if obj.is_promise() {
230 return value;
231 }
232 }
233 let mut promise = create_promise(ctx);
234 fulfill_promise(ctx, &mut promise, value);
235 let ptr = Box::into_raw(Box::new(promise)) as usize;
236 JSValue::new_object(ptr)
237}
238
239fn promise_reject(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
240 let reason = if args.len() >= 2 {
241 args[1]
242 } else if !args.is_empty() {
243 args[0]
244 } else {
245 JSValue::undefined()
246 };
247 let mut promise = create_promise(ctx);
248 reject_promise(ctx, &mut promise, reason);
249 let ptr = Box::into_raw(Box::new(promise)) as usize;
250 JSValue::new_object(ptr)
251}
252
253fn promise_then(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
254 if args.is_empty() {
255 return JSValue::undefined();
256 }
257 let this_val = &args[0];
258 if !this_val.is_object() {
259 return JSValue::undefined();
260 }
261 let promise = this_val.as_object_mut();
262 if !promise.is_promise() {
263 return JSValue::undefined();
264 }
265 let on_fulfilled = args.get(1).copied().unwrap_or(JSValue::undefined());
266 let on_rejected = args.get(2).copied().unwrap_or(JSValue::undefined());
267 let new_promise = create_promise(ctx);
268 let ptr_new = Box::into_raw(Box::new(new_promise)) as usize;
269 let target = JSValue::new_object(ptr_new);
270 let state = get_promise_state(ctx, promise);
271 let reactions_atom = intern_str(ctx, PROMISE_REACTIONS_SLOT);
272 match state {
273 PromiseState::Fulfilled => {
274 let result = get_promise_result(ctx, promise);
275 let reaction = Reaction {
276 handler: on_fulfilled,
277 is_reject: false,
278 is_all_settled: false,
279 target_promise: target,
280 };
281 ctx.microtask_enqueue(Microtask::Reaction(reaction, result));
282 }
283 PromiseState::Rejected => {
284 let reason = get_promise_result(ctx, promise);
285 let reaction = Reaction {
286 handler: on_rejected,
287 is_reject: true,
288 is_all_settled: false,
289 target_promise: target,
290 };
291 ctx.microtask_enqueue(Microtask::Reaction(reaction, reason));
292 }
293 PromiseState::Pending => {
294 let mut reactions = get_promise_reactions(ctx, promise);
295 reactions.push(Reaction {
296 handler: on_fulfilled,
297 is_reject: false,
298 is_all_settled: false,
299 target_promise: target,
300 });
301 reactions.push(Reaction {
302 handler: on_rejected,
303 is_reject: true,
304 is_all_settled: false,
305 target_promise: target,
306 });
307 let reactions_arr = create_reaction_array(ctx, reactions);
308 promise.set(reactions_atom, reactions_arr);
309 }
310 }
311 target
312}
313
314fn promise_catch(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
315 if args.is_empty() {
316 return JSValue::undefined();
317 }
318 let this_val = &args[0];
319 let on_rejected = args.get(1).copied().unwrap_or(JSValue::undefined());
320 if !this_val.is_object() {
321 return JSValue::undefined();
322 }
323 let promise = this_val.as_object_mut();
324 if !promise.is_promise() {
325 return JSValue::undefined();
326 }
327 let new_promise = create_promise(ctx);
328 let ptr_new = Box::into_raw(Box::new(new_promise)) as usize;
329 let target = JSValue::new_object(ptr_new);
330 let state = get_promise_state(ctx, promise);
331 let reactions_atom = intern_str(ctx, PROMISE_REACTIONS_SLOT);
332 match state {
333 PromiseState::Fulfilled => {
334 let result = get_promise_result(ctx, promise);
335 let reaction = Reaction {
336 handler: on_rejected,
337 is_reject: false,
338 is_all_settled: false,
339 target_promise: target,
340 };
341 ctx.microtask_enqueue(Microtask::Reaction(reaction, result));
342 }
343 PromiseState::Rejected => {
344 let reason = get_promise_result(ctx, promise);
345 let reaction = Reaction {
346 handler: on_rejected,
347 is_reject: true,
348 is_all_settled: false,
349 target_promise: target,
350 };
351 ctx.microtask_enqueue(Microtask::Reaction(reaction, reason));
352 }
353 PromiseState::Pending => {
354 let mut reactions = get_promise_reactions(ctx, promise);
355 reactions.push(Reaction {
356 handler: on_rejected,
357 is_reject: true,
358 is_all_settled: false,
359 target_promise: target,
360 });
361 let reactions_arr = create_reaction_array(ctx, reactions);
362 promise.set(reactions_atom, reactions_arr);
363 }
364 }
365 target
366}
367
368fn promise_finally(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
369 if args.is_empty() {
370 return JSValue::undefined();
371 }
372 let this_val = &args[0];
373 let on_finally = args.get(1).copied().unwrap_or(JSValue::undefined());
374 if !this_val.is_object() {
375 return JSValue::undefined();
376 }
377 let promise = this_val.as_object_mut();
378 if !promise.is_promise() {
379 return JSValue::undefined();
380 }
381 let new_promise = create_promise(ctx);
382 let ptr_new = Box::into_raw(Box::new(new_promise)) as usize;
383 let target = JSValue::new_object(ptr_new);
384 let state = get_promise_state(ctx, promise);
385 let reactions_atom = intern_str(ctx, PROMISE_REACTIONS_SLOT);
386 match state {
387 PromiseState::Fulfilled => {
388 let result = get_promise_result(ctx, promise);
389 let reaction = Reaction {
390 handler: on_finally,
391 is_reject: false,
392 is_all_settled: false,
393 target_promise: target,
394 };
395 ctx.microtask_enqueue(Microtask::Reaction(reaction, result));
396 }
397 PromiseState::Rejected => {
398 let reason = get_promise_result(ctx, promise);
399 let reaction = Reaction {
400 handler: on_finally,
401 is_reject: true,
402 is_all_settled: false,
403 target_promise: target,
404 };
405 ctx.microtask_enqueue(Microtask::Reaction(reaction, reason));
406 }
407 PromiseState::Pending => {
408 let mut reactions = get_promise_reactions(ctx, promise);
409 reactions.push(Reaction {
410 handler: on_finally,
411 is_reject: false,
412 is_all_settled: false,
413 target_promise: target,
414 });
415 reactions.push(Reaction {
416 handler: on_finally,
417 is_reject: true,
418 is_all_settled: false,
419 target_promise: target,
420 });
421 let reactions_arr = create_reaction_array(ctx, reactions);
422 promise.set(reactions_atom, reactions_arr);
423 }
424 }
425 target
426}
427
428fn promise_all(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
429 let iterable = args.get(0).copied().unwrap_or(JSValue::undefined());
430
431 let result_promise = create_promise(ctx);
432 let result_promise_ptr = Box::into_raw(Box::new(result_promise)) as usize;
433 let result_promise_val = JSValue::new_object(result_promise_ptr);
434
435 if !iterable.is_object() {
436 let rp = result_promise_val.as_object_mut();
437 let err_val =
438 JSValue::new_string(ctx.intern("TypeError: Promise.all requires an iterable"));
439 set_promise_result(ctx, rp, err_val);
440 set_promise_state(ctx, rp, PromiseState::Rejected);
441 return result_promise_val;
442 }
443
444 let obj = iterable.as_object();
445
446 let length_atom = intern_str(ctx, "length");
447 let len_val = obj.get(length_atom).unwrap_or(JSValue::undefined());
448 if !len_val.is_int() {
449 let rp = result_promise_val.as_object_mut();
450 let err_val =
451 JSValue::new_string(ctx.intern("TypeError: Promise.all requires an iterable"));
452 set_promise_result(ctx, rp, err_val);
453 set_promise_state(ctx, rp, PromiseState::Rejected);
454 return result_promise_val;
455 }
456
457 let len = len_val.get_int() as usize;
458
459 let pending_atom = ctx.intern("__promise_all_pending__");
460 let result_promise_mut = result_promise_val.as_object_mut();
461 result_promise_mut.set(pending_atom, JSValue::new_int(len as i64));
462
463 for i in 0..len {
464 let idx_atom = ctx.intern(&i.to_string());
465 let item = obj.get(idx_atom).unwrap_or(JSValue::undefined());
466
467 let reaction = Reaction {
468 handler: JSValue::new_int(i as i64),
469 is_reject: false,
470 is_all_settled: false,
471 target_promise: JSValue::undefined(),
472 };
473 ctx.microtask_enqueue(Microtask::Reaction(reaction, item));
474 }
475
476 result_promise_val
477}
478
479fn promise_race(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
480 let iterable = args.get(0).copied().unwrap_or(JSValue::undefined());
481
482 let result_promise = create_promise(ctx);
483 let result_promise_ptr = Box::into_raw(Box::new(result_promise)) as usize;
484 let result_promise_val = JSValue::new_object(result_promise_ptr);
485
486 if !iterable.is_object() {
487 let rp = result_promise_val.as_object_mut();
488 let err_val =
489 JSValue::new_string(ctx.intern("TypeError: Promise.race requires an iterable"));
490 set_promise_result(ctx, rp, err_val);
491 set_promise_state(ctx, rp, PromiseState::Rejected);
492 return result_promise_val;
493 }
494
495 let obj = iterable.as_object();
496
497 let length_atom = intern_str(ctx, "length");
498 let len_val = obj.get(length_atom).unwrap_or(JSValue::undefined());
499 if !len_val.is_int() {
500 let rp = result_promise_val.as_object_mut();
501 let err_val =
502 JSValue::new_string(ctx.intern("TypeError: Promise.race requires an iterable"));
503 set_promise_result(ctx, rp, err_val);
504 set_promise_state(ctx, rp, PromiseState::Rejected);
505 return result_promise_val;
506 }
507
508 let len = len_val.get_int() as usize;
509
510 if len == 0 {
511 return result_promise_val;
512 }
513
514 for i in 0..len {
515 let idx_atom = ctx.intern(&i.to_string());
516 let item = obj.get(idx_atom).unwrap_or(JSValue::undefined());
517
518 let reaction = Reaction {
519 handler: JSValue::new_int(i as i64),
520 is_reject: false,
521 is_all_settled: false,
522 target_promise: JSValue::undefined(),
523 };
524 ctx.microtask_enqueue(Microtask::Reaction(reaction, item));
525 }
526
527 result_promise_val
528}
529
530fn promise_all_settled(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
531 let iterable = args.get(0).copied().unwrap_or(JSValue::undefined());
532
533 let result_promise = create_promise(ctx);
534 let result_promise_ptr = Box::into_raw(Box::new(result_promise)) as usize;
535 let result_promise_val = JSValue::new_object(result_promise_ptr);
536
537 if !iterable.is_object() {
538 let rp = result_promise_val.as_object_mut();
539 let err_val =
540 JSValue::new_string(ctx.intern("TypeError: Promise.allSettled requires an iterable"));
541 set_promise_result(ctx, rp, err_val);
542 set_promise_state(ctx, rp, PromiseState::Rejected);
543 return result_promise_val;
544 }
545
546 let obj = iterable.as_object();
547
548 let length_atom = intern_str(ctx, "length");
549 let len_val = obj.get(length_atom).unwrap_or(JSValue::undefined());
550 if !len_val.is_int() {
551 let rp = result_promise_val.as_object_mut();
552 let err_val =
553 JSValue::new_string(ctx.intern("TypeError: Promise.allSettled requires an iterable"));
554 set_promise_result(ctx, rp, err_val);
555 set_promise_state(ctx, rp, PromiseState::Rejected);
556 return result_promise_val;
557 }
558
559 let len = len_val.get_int() as usize;
560
561 let pending_atom = intern_str(ctx, "__promise_all_pending__");
562 let result_promise_mut = result_promise_val.as_object_mut();
563 result_promise_mut.set(pending_atom, JSValue::new_int(len as i64));
564
565 for i in 0..len {
566 let idx_atom = ctx.intern(&i.to_string());
567 let item = obj.get(idx_atom).unwrap_or(JSValue::undefined());
568
569 let reaction = Reaction {
570 handler: JSValue::new_int(i as i64),
571 is_reject: false,
572 is_all_settled: true,
573 target_promise: JSValue::undefined(),
574 };
575 ctx.microtask_enqueue(Microtask::Reaction(reaction, item));
576 }
577
578 result_promise_val
579}
580
581fn promise_any(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
582 let iterable = args.get(0).copied().unwrap_or(JSValue::undefined());
583
584 let result_promise = create_promise(ctx);
585 let result_promise_ptr = Box::into_raw(Box::new(result_promise)) as usize;
586 let result_promise_val = JSValue::new_object(result_promise_ptr);
587
588 if !iterable.is_object() {
589 let rp = result_promise_val.as_object_mut();
590 let err_val =
591 JSValue::new_string(ctx.intern("TypeError: Promise.any requires an iterable"));
592 set_promise_result(ctx, rp, err_val);
593 set_promise_state(ctx, rp, PromiseState::Rejected);
594 return result_promise_val;
595 }
596
597 let obj = iterable.as_object();
598
599 let length_atom = intern_str(ctx, "length");
600 let len_val = obj.get(length_atom).unwrap_or(JSValue::undefined());
601 if !len_val.is_int() {
602 let rp = result_promise_val.as_object_mut();
603 let err_val =
604 JSValue::new_string(ctx.intern("TypeError: Promise.any requires an iterable"));
605 set_promise_result(ctx, rp, err_val);
606 set_promise_state(ctx, rp, PromiseState::Rejected);
607 return result_promise_val;
608 }
609
610 let len = len_val.get_int() as usize;
611
612 let pending_atom = intern_str(ctx, "__promise_any_pending__");
613 let result_promise_mut = result_promise_val.as_object_mut();
614 result_promise_mut.set(pending_atom, JSValue::new_int(len as i64));
615
616 for i in 0..len {
617 let idx_atom = ctx.intern(&i.to_string());
618 let item = obj.get(idx_atom).unwrap_or(JSValue::undefined());
619
620 let reaction = Reaction {
621 handler: JSValue::new_int(i as i64),
622 is_reject: false,
623 is_all_settled: false,
624 target_promise: JSValue::undefined(),
625 };
626 ctx.microtask_enqueue(Microtask::Reaction(reaction, item));
627 }
628
629 result_promise_val
630}
631
632fn promise_executor(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
633 if args.len() < 3 {
634 return JSValue::undefined();
635 }
636 let promise_val = args[0];
637 let resolve_fn = args[1];
638 let reject_fn = args[2];
639 if !promise_val.is_object() || !resolve_fn.is_function() || !reject_fn.is_function() {
640 return JSValue::undefined();
641 }
642 let promise_obj = promise_val.as_object_mut();
643 let result = get_promise_result(ctx, promise_obj);
644 if let Ok(result) = call_callback(ctx, resolve_fn, &[result]) {
645 if result.is_object() {
646 let result_obj = result.as_object_mut();
647 if result_obj.is_promise() {
648 let state = get_promise_state(ctx, result_obj);
649 let result_val = get_promise_result(ctx, result_obj);
650 let reactions_atom = intern_str(ctx, PROMISE_REACTIONS_SLOT);
651 match state {
652 PromiseState::Fulfilled => fulfill_promise(ctx, promise_obj, result_val),
653 PromiseState::Rejected => reject_promise(ctx, promise_obj, result_val),
654 PromiseState::Pending => {
655 let mut reactions = get_promise_reactions(ctx, result_obj);
656 reactions.push(Reaction {
657 handler: promise_val,
658 is_reject: false,
659 is_all_settled: false,
660 target_promise: JSValue::undefined(),
661 });
662 let reactions_arr = create_reaction_array(ctx, reactions);
663 result_obj.set(reactions_atom, reactions_arr);
664 }
665 }
666 } else {
667 fulfill_promise(ctx, promise_obj, result);
668 }
669 } else {
670 fulfill_promise(ctx, promise_obj, result);
671 }
672 }
673 JSValue::undefined()
674}
675
676fn call_callback(
677 ctx: &mut JSContext,
678 callback: JSValue,
679 args: &[JSValue],
680) -> Result<JSValue, String> {
681 if let Some(ptr) = ctx.get_register_vm_ptr() {
682 let vm = unsafe { &mut *(ptr as *mut crate::runtime::vm::VM) };
683 vm.call_function(ctx, callback, args)
684 } else {
685 Err("VM not available".to_string())
686 }
687}
688
689fn create_promise_prototype(ctx: &mut JSContext) -> JSValue {
690 let mut proto = JSObject::new_promise();
691 let constructor_atom = intern_str(ctx, "constructor");
692 let then_atom = intern_str(ctx, "then");
693 let catch_atom = intern_str(ctx, "catch");
694 let finally_atom = intern_str(ctx, "finally");
695 proto.set(constructor_atom, JSValue::null());
696 proto.set(then_atom, create_builtin_function(ctx, "promise_then"));
697 proto.set(catch_atom, create_builtin_function(ctx, "promise_catch"));
698 proto.set(
699 finally_atom,
700 create_builtin_function(ctx, "promise_finally"),
701 );
702
703 if let Some(obj_proto_ptr) = ctx.get_object_prototype() {
704 proto.prototype = Some(obj_proto_ptr);
705 }
706 let ptr = Box::into_raw(Box::new(proto)) as usize;
707 ctx.runtime_mut().gc_heap_mut().track(ptr);
708 JSValue::new_object(ptr)
709}
710
711pub fn init_promise(ctx: &mut JSContext) {
712 let promise_atom = ctx.intern("Promise");
713 let mut promise_obj = JSObject::new_function();
714 let proto_value = create_promise_prototype(ctx);
715
716 let proto_ptr = proto_value.get_ptr();
717 ctx.set_promise_prototype(proto_ptr);
718 promise_obj.set(ctx.intern("prototype"), proto_value);
719 promise_obj.set(
720 ctx.intern("resolve"),
721 create_builtin_function(ctx, "promise_resolve"),
722 );
723 promise_obj.set(
724 ctx.intern("reject"),
725 create_builtin_function(ctx, "promise_reject"),
726 );
727 promise_obj.set(
728 ctx.intern("all"),
729 create_builtin_function(ctx, "promise_all"),
730 );
731 promise_obj.set(
732 ctx.intern("race"),
733 create_builtin_function(ctx, "promise_race"),
734 );
735 promise_obj.set(
736 ctx.intern("allSettled"),
737 create_builtin_function(ctx, "promise_allSettled"),
738 );
739 promise_obj.set(
740 ctx.intern("any"),
741 create_builtin_function(ctx, "promise_any"),
742 );
743 let promise_ptr = Box::into_raw(Box::new(promise_obj)) as usize;
744 let promise_value = JSValue::new_object(promise_ptr);
745 let global = ctx.global();
746 if global.is_object() {
747 let global_obj = global.as_object_mut();
748 global_obj.set(promise_atom, promise_value);
749 }
750}
751
752pub fn create_resolved_promise(ctx: &mut JSContext, value: JSValue) -> JSValue {
753 let mut promise = create_promise(ctx);
754 fulfill_promise(ctx, &mut promise, value);
755 let ptr = Box::into_raw(Box::new(promise)) as usize;
756 let result = JSValue::new_object(ptr);
757 result
758}
759
760pub fn create_rejected_promise(ctx: &mut JSContext, reason: JSValue) -> JSValue {
761 let mut promise = create_promise(ctx);
762 reject_promise(ctx, &mut promise, reason);
763 let ptr = Box::into_raw(Box::new(promise)) as usize;
764 JSValue::new_object(ptr)
765}
766
767#[cfg(feature = "process")]
768pub(crate) fn fulfill_promise_with_value(
769 ctx: &mut JSContext,
770 promise_obj_ptr: usize,
771 value: JSValue,
772) {
773 let promise = unsafe { &mut *(promise_obj_ptr as *mut JSObject) };
774 fulfill_promise(ctx, promise, value);
775}
776
777#[cfg(feature = "process")]
778pub(crate) fn reject_promise_with_value(
779 ctx: &mut JSContext,
780 promise_obj_ptr: usize,
781 reason: JSValue,
782) {
783 let promise = unsafe { &mut *(promise_obj_ptr as *mut JSObject) };
784 reject_promise(ctx, promise, reason);
785}
786
787pub fn run_microtasks_with_vm(ctx: &mut JSContext, vm: &mut crate::runtime::vm::VM) {
788 while let Some(task) = ctx.microtask_dequeue() {
789 match task {
790 Microtask::Reaction(reaction, argument) => {
791 let handler = reaction.handler;
792 if handler.is_function() {
793 let result = vm.call_function(ctx, handler, &[argument]);
794 if reaction.target_promise.is_object() {
795 let target = reaction.target_promise.as_object_mut();
796 if target.is_promise() {
797 match result {
798 Ok(val) => fulfill_promise(ctx, target, val),
799 Err(_) => {
800 reject_promise(ctx, target, JSValue::undefined());
801 }
802 }
803 }
804 }
805 }
806 }
807 Microtask::UserCallback(callback, args) => {
808 if callback.is_function() {
809 let _result = vm.call_function(ctx, callback, &args);
810 }
811 }
812 }
813 }
814}
815
816pub fn register_builtins(ctx: &mut JSContext) {
817 ctx.register_builtin(
818 "promise_executor",
819 HostFunction::new("executor", 3, promise_executor),
820 );
821 ctx.register_builtin(
822 "promise_resolve",
823 HostFunction::new("resolve", 1, promise_resolve),
824 );
825 ctx.register_builtin(
826 "promise_reject",
827 HostFunction::new("reject", 1, promise_reject),
828 );
829 ctx.register_builtin("promise_then", HostFunction::new("then", 2, promise_then));
830 ctx.register_builtin(
831 "promise_catch",
832 HostFunction::new("catch", 2, promise_catch),
833 );
834 ctx.register_builtin(
835 "promise_finally",
836 HostFunction::new("finally", 2, promise_finally),
837 );
838 ctx.register_builtin("promise_all", HostFunction::new("all", 1, promise_all));
839 ctx.register_builtin("promise_race", HostFunction::new("race", 1, promise_race));
840 ctx.register_builtin(
841 "promise_allSettled",
842 HostFunction::new("allSettled", 1, promise_all_settled),
843 );
844 ctx.register_builtin("promise_any", HostFunction::new("any", 1, promise_any));
845}