runtime/event_loop/
mod.rs1use std::collections::VecDeque;
8use std::ffi::c_void;
9use std::future::poll_fn;
10use std::task;
11use std::task::Poll;
12
13use ion::format::{Config, format_value};
14use ion::{Context, ErrorReport, Local, Promise};
15use mozjs::jsapi::{Handle, Heap, JSContext, JSObject, PromiseRejectionHandlingState};
16
17use crate::ContextExt as _;
18use crate::event_loop::future::FutureQueue;
19use crate::event_loop::macrotasks::MacrotaskQueue;
20use crate::event_loop::microtasks::MicrotaskQueue;
21
22pub(crate) mod future;
23pub(crate) mod macrotasks;
24pub(crate) mod microtasks;
25
26#[derive(Default)]
27pub struct EventLoop {
28 pub(crate) futures: Option<FutureQueue>,
29 pub(crate) microtasks: Option<MicrotaskQueue>,
30 pub(crate) macrotasks: Option<MacrotaskQueue>,
31 pub(crate) unhandled_rejections: VecDeque<Box<Heap<*mut JSObject>>>,
32}
33
34impl EventLoop {
35 pub async fn run_event_loop(&mut self, cx: &Context) -> Result<(), Option<ErrorReport>> {
36 let mut complete = false;
37 poll_fn(|wcx| self.poll_event_loop(cx, wcx, &mut complete)).await
38 }
39
40 fn poll_event_loop(
41 &mut self, cx: &Context, wcx: &mut task::Context, complete: &mut bool,
42 ) -> Poll<Result<(), Option<ErrorReport>>> {
43 if let Some(futures) = &mut self.futures
44 && !futures.is_empty()
45 {
46 futures.run_futures(cx, wcx)?;
47 }
48
49 if let Some(microtasks) = &mut self.microtasks
50 && !microtasks.is_empty()
51 {
52 microtasks.run_jobs(cx)?;
53 }
54
55 if let Some(macrotasks) = &mut self.macrotasks
56 && !macrotasks.is_empty()
57 {
58 macrotasks.run_job(cx)?;
59 }
60
61 while let Some(promise) = self.unhandled_rejections.pop_front() {
62 let promise = Promise::from(unsafe { Local::from_heap(&promise) }).unwrap();
63 let result = promise.result(cx);
64 eprintln!(
65 "Unhandled Promise Rejection: {}",
66 format_value(cx, Config::default(), &result)
67 );
68 }
69
70 let empty = self.is_empty();
71 if empty && *complete {
72 Poll::Ready(Ok(()))
73 } else {
74 wcx.waker().wake_by_ref();
75 *complete = empty;
76 Poll::Pending
77 }
78 }
79
80 fn is_empty(&self) -> bool {
81 self.microtasks.as_ref().is_none_or(MicrotaskQueue::is_empty)
82 && self.futures.as_ref().is_none_or(FutureQueue::is_empty)
83 && self.macrotasks.as_ref().is_none_or(MacrotaskQueue::is_empty)
84 }
85}
86
87pub(crate) unsafe extern "C" fn promise_rejection_tracker_callback(
88 cx: *mut JSContext, _: bool, promise: Handle<*mut JSObject>, state: PromiseRejectionHandlingState, _: *mut c_void,
89) {
90 let cx = unsafe { &Context::new_unchecked(cx) };
91 let promise = Promise::from(unsafe { Local::from_raw_handle(promise) }).unwrap();
92 let unhandled = unsafe { &mut cx.get_private().event_loop.unhandled_rejections };
93 match state {
94 PromiseRejectionHandlingState::Unhandled => unhandled.push_back(Heap::boxed(promise.get())),
95 PromiseRejectionHandlingState::Handled => {
96 let idx = unhandled.iter().position(|unhandled| unhandled.get() == promise.get());
97 if let Some(idx) = idx {
98 unhandled.swap_remove_back(idx);
99 }
100 }
101 }
102}