ion/
context.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::{Any, TypeId};
8use std::cell::RefCell;
9use std::collections::HashMap;
10use std::ffi::c_void;
11use std::ptr;
12use std::ptr::NonNull;
13
14use mozjs::gc::Traceable;
15use mozjs::jsapi::{
16	JS_AddExtraGCRootsTracer, JS_GetContextPrivate, JS_RemoveExtraGCRootsTracer, JS_SetContextPrivate, JSContext,
17	JSTracer, Rooted,
18};
19use mozjs::rust::Runtime;
20use private::RootedArena;
21
22use crate::Local;
23use crate::class::ClassInfo;
24use crate::module::ModuleLoader;
25
26/// Represents Types that can be Rooted in SpiderMonkey
27#[derive(Clone, Copy, Debug)]
28pub enum GCType {
29	Value,
30	Object,
31	String,
32	Script,
33	PropertyKey,
34	PropertyDescriptor,
35	Function,
36	BigInt,
37	Symbol,
38}
39
40pub trait TraceablePrivate: Traceable + Any {
41	fn as_any(&self) -> &dyn Any;
42
43	fn as_any_mut(&mut self) -> &mut dyn Any;
44}
45
46impl<T: Traceable + Any> TraceablePrivate for T {
47	fn as_any(&self) -> &dyn Any {
48		self
49	}
50
51	fn as_any_mut(&mut self) -> &mut dyn Any {
52		self
53	}
54}
55
56#[derive(Default)]
57pub struct ContextInner {
58	pub class_infos: HashMap<TypeId, ClassInfo>,
59	pub module_loader: Option<Box<dyn ModuleLoader>>,
60	private: Option<Box<dyn TraceablePrivate>>,
61}
62
63impl ContextInner {
64	unsafe fn add_tracer(cx: *mut JSContext, inner: *mut ContextInner) {
65		unsafe {
66			JS_AddExtraGCRootsTracer(cx, Some(ContextInner::trace), inner.cast::<c_void>());
67		}
68	}
69
70	pub unsafe fn remove_tracer(cx: *mut JSContext, inner: *mut ContextInner) {
71		unsafe {
72			JS_RemoveExtraGCRootsTracer(cx, Some(ContextInner::trace), inner.cast::<c_void>());
73		}
74	}
75
76	extern "C" fn trace(trc: *mut JSTracer, data: *mut c_void) {
77		unsafe {
78			let inner = &mut *data.cast::<ContextInner>();
79			inner.class_infos.values().for_each(|info| info.trace(trc));
80			inner.private.trace(trc);
81		}
82	}
83}
84
85/// Represents the thread-local state of the runtime.
86///
87/// Wrapper around [JSContext] that provides lifetime information and convenient APIs.
88pub struct Context {
89	context: NonNull<JSContext>,
90	rooted: RootedArena,
91	order: RefCell<Vec<GCType>>,
92	private: NonNull<ContextInner>,
93}
94
95impl Context {
96	pub fn from_runtime(rt: &Runtime) -> Context {
97		let cx = rt.cx();
98
99		let private = NonNull::new(unsafe { JS_GetContextPrivate(cx).cast::<ContextInner>() }).unwrap_or_else(|| {
100			let private = Box::<ContextInner>::default();
101			let private = Box::into_raw(private);
102			unsafe {
103				JS_SetContextPrivate(cx, private.cast());
104				ContextInner::add_tracer(cx, private);
105			}
106			unsafe { NonNull::new_unchecked(private) }
107		});
108
109		Context {
110			context: unsafe { NonNull::new_unchecked(cx) },
111			rooted: RootedArena::default(),
112			order: RefCell::new(Vec::new()),
113			private,
114		}
115	}
116
117	pub unsafe fn new_unchecked(cx: *mut JSContext) -> Context {
118		Context {
119			context: unsafe { NonNull::new_unchecked(cx) },
120			rooted: RootedArena::default(),
121			order: RefCell::new(Vec::new()),
122			private: unsafe { NonNull::new_unchecked(JS_GetContextPrivate(cx).cast::<ContextInner>()) },
123		}
124	}
125
126	pub fn as_ptr(&self) -> *mut JSContext {
127		self.context.as_ptr()
128	}
129
130	pub fn get_inner_data(&self) -> NonNull<ContextInner> {
131		self.private
132	}
133
134	pub fn get_raw_private(&self) -> *mut dyn TraceablePrivate {
135		let inner = self.get_inner_data();
136		ptr::from_mut(unsafe { (*inner.as_ptr()).private.as_deref_mut().unwrap() })
137	}
138
139	pub fn set_private(&self, private: Box<dyn TraceablePrivate>) {
140		let inner_private = self.get_inner_data();
141		unsafe {
142			(*inner_private.as_ptr()).private = Some(private);
143		}
144	}
145
146	/// Roots a value and returns a `[Local]` to it.
147	/// The Local is only unrooted when the `[Context]` is dropped
148	pub fn root<T: Rootable>(&self, value: T) -> Local<'_, T> {
149		let root = T::alloc(&self.rooted, Rooted::new_unrooted(value));
150		self.order.borrow_mut().push(T::GC_TYPE);
151		Local::new(self, root)
152	}
153}
154
155pub trait Rootable: private::Sealed {}
156
157impl<T: private::Sealed> Rootable for T {}
158
159mod private {
160	use mozjs::gc::RootKind;
161	use mozjs::jsapi::{
162		BigInt, JSFunction, JSObject, JSScript, JSString, PropertyDescriptor, PropertyKey, Rooted, Symbol,
163	};
164	use mozjs::jsval::JSVal;
165	use typed_arena::Arena;
166
167	use super::GCType;
168
169	/// Holds Rooted Values
170	#[derive(Default)]
171	pub struct RootedArena {
172		pub values: Arena<Rooted<JSVal>>,
173		pub objects: Arena<Rooted<*mut JSObject>>,
174		pub strings: Arena<Rooted<*mut JSString>>,
175		pub scripts: Arena<Rooted<*mut JSScript>>,
176		pub property_keys: Arena<Rooted<PropertyKey>>,
177		pub property_descriptors: Arena<Rooted<PropertyDescriptor>>,
178		pub functions: Arena<Rooted<*mut JSFunction>>,
179		pub big_ints: Arena<Rooted<*mut BigInt>>,
180		pub symbols: Arena<Rooted<*mut Symbol>>,
181	}
182
183	#[expect(clippy::mut_from_ref)]
184	pub trait Sealed: RootKind + Copy + Sized {
185		const GC_TYPE: GCType;
186
187		fn alloc(arena: &RootedArena, root: Rooted<Self>) -> &mut Rooted<Self>;
188	}
189
190	macro_rules! impl_rootable {
191		($(($value:ty, $key:ident, $gc_type:ident)$(,)?)*) => {
192			$(
193				impl Sealed for $value {
194					const GC_TYPE: GCType = GCType::$gc_type;
195
196					fn alloc(arena: &RootedArena, root: Rooted<$value>) -> &mut Rooted<$value> {
197						arena.$key.alloc(root)
198					}
199				}
200			)*
201		};
202	}
203
204	impl_rootable! {
205		(JSVal, values, Value),
206		(*mut JSObject, objects, Object),
207		(*mut JSString, strings, String),
208		(*mut JSScript, scripts, Script),
209		(PropertyKey, property_keys, PropertyKey),
210		(PropertyDescriptor, property_descriptors, PropertyDescriptor),
211		(*mut JSFunction, functions, Function),
212		(*mut BigInt, big_ints, BigInt),
213		(*mut Symbol, symbols, Symbol),
214	}
215}
216
217macro_rules! impl_drop {
218	([$self:expr], $(($key:ident, $gc_type:ident)$(,)?)*) => {
219		$(let $key: Vec<_> = $self.rooted.$key.iter_mut().collect();)*
220		$(let mut $key = $key.into_iter().rev();)*
221
222		for ty in $self.order.take().into_iter().rev() {
223			match ty {
224				$(
225					GCType::$gc_type => {
226						let root = $key.next().unwrap();
227						unsafe {
228							root.remove_from_root_stack();
229						}
230					}
231				)*
232			}
233		}
234	}
235}
236
237impl Drop for Context {
238	/// Drops the rooted values in reverse-order to maintain LIFO destruction in the Linked List.
239	fn drop(&mut self) {
240		impl_drop! {
241			[self],
242			(values, Value),
243			(objects, Object),
244			(strings, String),
245			(scripts, Script),
246			(property_keys, PropertyKey),
247			(property_descriptors, PropertyDescriptor),
248			(functions, Function),
249			(big_ints, BigInt),
250			(symbols, Symbol),
251		}
252	}
253}