runtime/
runtime.rs

1/*
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 */
6
7use 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}