1use std::any::TypeId;
8use std::collections::HashMap;
9use std::ptr;
10
11use ion::module::{ModuleLoader, init_module_loader};
12use ion::object::default_new_global;
13use ion::{Context, ContextInner, ErrorReport, Object};
14use mozjs::gc::Traceable;
15use mozjs::glue::CreateJobQueue;
16use mozjs::jsapi::{
17 ContextOptionsRef, Heap, JSAutoRealm, JSObject, JSTracer, SetJobQueue, SetPromiseRejectionTrackerCallback,
18};
19use mozjs::jsval::JSVal;
20use uuid::Uuid;
21
22use crate::event_loop::future::FutureQueue;
23use crate::event_loop::macrotasks::MacrotaskQueue;
24use crate::event_loop::microtasks::{JOB_QUEUE_TRAPS, MicrotaskQueue};
25use crate::event_loop::{EventLoop, promise_rejection_tracker_callback};
26use crate::globals::{init_globals, init_microtasks, init_timers};
27use crate::module::StandardModules;
28
29#[derive(Default)]
30pub struct ContextPrivate {
31 pub(crate) event_loop: EventLoop,
32 pub(crate) blob_store: HashMap<Uuid, Box<Heap<*mut JSObject>>>,
33 pub(crate) globals: HashMap<TypeId, Box<Heap<JSVal>>>,
34}
35
36unsafe impl Traceable for ContextPrivate {
37 unsafe fn trace(&self, trc: *mut JSTracer) {
38 self.blob_store.values().for_each(|blob| unsafe { blob.trace(trc) });
39 self.globals.values().for_each(|global| unsafe { global.trace(trc) });
40 }
41}
42
43pub trait ContextExt {
44 #[expect(clippy::mut_from_ref)]
45 unsafe fn get_private(&self) -> &mut ContextPrivate;
46}
47
48impl ContextExt for Context {
49 unsafe fn get_private(&self) -> &mut ContextPrivate {
50 unsafe { (*self.get_raw_private()).as_any_mut().downcast_mut().unwrap() }
51 }
52}
53
54pub struct Runtime<'cx> {
55 global: Object<'cx>,
56 cx: &'cx Context,
57 #[expect(dead_code)]
58 realm: JSAutoRealm,
59}
60
61impl<'cx> Runtime<'cx> {
62 pub fn cx(&self) -> &Context {
63 self.cx
64 }
65
66 pub fn global(&self) -> &Object<'cx> {
67 &self.global
68 }
69
70 pub fn global_mut(&mut self) -> &Object<'cx> {
71 &mut self.global
72 }
73
74 pub async fn run_event_loop(&self) -> Result<(), Option<ErrorReport>> {
75 let event_loop = unsafe { &mut self.cx.get_private().event_loop };
76 event_loop.run_event_loop(self.cx).await
77 }
78}
79
80impl Drop for Runtime<'_> {
81 fn drop(&mut self) {
82 let inner_private = self.cx.get_inner_data().as_ptr();
83 unsafe {
84 let _ = Box::from_raw(inner_private);
85 ContextInner::remove_tracer(self.cx.as_ptr(), inner_private);
86 }
87 }
88}
89
90#[derive(Copy, Clone, Debug)]
91pub struct RuntimeBuilder<ML: ModuleLoader + 'static = (), Std: StandardModules + 'static = ()> {
92 microtask_queue: bool,
93 macrotask_queue: bool,
94 modules: Option<ML>,
95 standard_modules: Option<Std>,
96}
97
98impl<ML: ModuleLoader + 'static, Std: StandardModules + 'static> RuntimeBuilder<ML, Std> {
99 pub fn new() -> RuntimeBuilder<ML, Std> {
100 RuntimeBuilder::default()
101 }
102
103 pub fn macrotask_queue(mut self) -> RuntimeBuilder<ML, Std> {
104 self.macrotask_queue = true;
105 self
106 }
107
108 pub fn microtask_queue(mut self) -> RuntimeBuilder<ML, Std> {
109 self.microtask_queue = true;
110 self
111 }
112
113 pub fn modules(mut self, loader: ML) -> RuntimeBuilder<ML, Std> {
114 self.modules = Some(loader);
115 self
116 }
117
118 pub fn standard_modules(mut self, standard_modules: Std) -> RuntimeBuilder<ML, Std> {
119 self.standard_modules = Some(standard_modules);
120 self
121 }
122
123 pub fn build(self, cx: &mut Context) -> Runtime<'_> {
124 let global = default_new_global(cx);
125 let realm = JSAutoRealm::new(cx.as_ptr(), global.handle().get());
126
127 let global_obj = global.handle().get();
128 global.set_as(cx, "global", &global_obj);
129 init_globals(cx, &global);
130
131 let mut private = Box::<ContextPrivate>::default();
132
133 if self.microtask_queue {
134 private.event_loop.microtasks = Some(MicrotaskQueue::default());
135 init_microtasks(cx, &global);
136 private.event_loop.futures = Some(FutureQueue::default());
137
138 unsafe {
139 SetJobQueue(
140 cx.as_ptr(),
141 CreateJobQueue(
142 &raw const JOB_QUEUE_TRAPS,
143 ptr::from_ref(private.event_loop.microtasks.as_ref().unwrap()).cast(),
144 ptr::null_mut(),
145 ),
146 );
147 SetPromiseRejectionTrackerCallback(
148 cx.as_ptr(),
149 Some(promise_rejection_tracker_callback),
150 ptr::null_mut(),
151 );
152 }
153 }
154 if self.macrotask_queue {
155 private.event_loop.macrotasks = Some(MacrotaskQueue::default());
156 init_timers(cx, &global);
157 }
158
159 let _options = unsafe { &mut *ContextOptionsRef(cx.as_ptr()) };
160
161 cx.set_private(private);
162
163 let has_loader = self.modules.is_some();
164 if let Some(loader) = self.modules {
165 init_module_loader(cx, loader);
166 }
167
168 if let Some(standard_modules) = self.standard_modules {
169 if has_loader {
170 standard_modules.init(cx, &global);
171 } else {
172 standard_modules.init_globals(cx, &global);
173 }
174 }
175
176 Runtime { global, cx, realm }
177 }
178}
179
180impl<ML: ModuleLoader + 'static, Std: StandardModules + 'static> Default for RuntimeBuilder<ML, Std> {
181 fn default() -> RuntimeBuilder<ML, Std> {
182 RuntimeBuilder {
183 microtask_queue: false,
184 macrotask_queue: false,
185 modules: None,
186 standard_modules: None,
187 }
188 }
189}