1use std::any::TypeId;
8use std::collections::hash_map::Entry;
9use std::ffi::CStr;
10use std::ptr;
11
12use ion_proc::Traceable;
13use mozjs::error::throw_type_error;
14use mozjs::gc::{HandleObject, Traceable};
15use mozjs::glue::JS_GetReservedSlot;
16use mozjs::jsapi::{
17 GCContext, Heap, JS_GetConstructor, JS_HasInstance, JS_InitClass, JS_InstanceOf, JS_NewObjectWithGivenProto,
18 JS_SetReservedSlot, JSContext, JSFunction, JSFunctionSpec, JSObject, JSPropertySpec, JSTracer,
19};
20use mozjs::jsval::{JSVal, NullValue, PrivateValue, UndefinedValue};
21use mozjs::rust::get_object_class;
22
23pub use crate::class::native::{MAX_PROTO_CHAIN_LENGTH, NativeClass, PrototypeChain, TypeIdWrapper};
24pub use crate::class::reflect::{Castable, DerivedFrom, NativeObject, Reflector};
25use crate::conversions::{IntoValue, ToValue as _};
26use crate::function::{__handle_native_constructor_result, NativeFunction};
27use crate::{
28 Arguments, Context, Error, ErrorKind, Exception, Function, Local, Object, Result, ResultExc, Value,
29 class_num_reserved_slots,
30};
31
32mod native;
33mod reflect;
34
35#[derive(Debug, Traceable)]
37#[trace(crate = crate)]
38pub struct ClassInfo {
39 #[trace(no_trace)]
40 class: &'static NativeClass,
41 pub constructor: Box<Heap<*mut JSFunction>>,
42 pub prototype: Box<Heap<*mut JSObject>>,
43}
44
45pub trait ClassDefinition: NativeObject {
46 fn class() -> &'static NativeClass;
47
48 fn proto_class() -> Option<&'static NativeClass> {
49 None
50 }
51
52 fn parent_prototype(_: &Context) -> Option<Local<'_, *mut JSObject>> {
53 None
54 }
55
56 fn constructor() -> (Option<NativeFunction>, u32);
57
58 fn functions() -> Option<&'static [JSFunctionSpec]> {
59 None
60 }
61
62 fn properties() -> Option<&'static [JSPropertySpec]> {
63 None
64 }
65
66 fn static_functions() -> Option<&'static [JSFunctionSpec]> {
67 None
68 }
69
70 fn static_properties() -> Option<&'static [JSPropertySpec]> {
71 None
72 }
73
74 fn init_class<'cx>(cx: &'cx Context, object: &Object) -> (bool, &'cx ClassInfo) {
75 let infos = unsafe { &mut (*cx.get_inner_data().as_ptr()).class_infos };
76
77 match infos.entry(TypeId::of::<Self>()) {
78 Entry::Occupied(o) => (false, o.into_mut()),
79 Entry::Vacant(entry) => {
80 let proto_class = Self::proto_class().map_or_else(ptr::null, |class| &raw const class.base);
81 let parent_proto = Self::parent_prototype(cx).map_or_else(HandleObject::null, |proto| proto.handle());
82
83 let (constructor, nargs) = Self::constructor();
84
85 let properties = Self::properties();
86 let functions = Self::functions();
87 let static_properties = Self::static_properties();
88 let static_functions = Self::static_functions();
89
90 assert!(has_zero_spec(properties));
91 assert!(has_zero_spec(functions));
92 assert!(has_zero_spec(static_properties));
93 assert!(has_zero_spec(static_functions));
94
95 let class = unsafe {
96 JS_InitClass(
97 cx.as_ptr(),
98 object.handle().into(),
99 proto_class,
100 parent_proto.into(),
101 Self::class().base.name,
102 Some(constructor.unwrap_or(illegal_constructor)),
103 nargs,
104 unwrap_specs(properties),
105 unwrap_specs(functions),
106 unwrap_specs(static_properties),
107 unwrap_specs(static_functions),
108 )
109 };
110 let prototype = cx.root(class);
111
112 let constructor = unsafe { JS_GetConstructor(cx.as_ptr(), prototype.handle().into()) };
113 let constructor = Object::from(cx.root(constructor));
114 let constructor = Function::from_object(cx, &constructor).unwrap();
115
116 let class_info = ClassInfo {
117 class: Self::class(),
118 constructor: Heap::boxed(constructor.get()),
119 prototype: Heap::boxed(prototype.get()),
120 };
121
122 (true, entry.insert(class_info))
123 }
124 }
125 }
126
127 fn new_raw_object(cx: &Context) -> *mut JSObject {
128 let infos = unsafe { &mut (*cx.get_inner_data().as_ptr()).class_infos };
129 let info = infos.get(&TypeId::of::<Self>()).expect("Uninitialised Class");
130 unsafe {
131 JS_NewObjectWithGivenProto(
132 cx.as_ptr(),
133 &raw const Self::class().base,
134 Local::from_heap(&info.prototype).handle().into(),
135 )
136 }
137 }
138
139 fn new_object(cx: &Context, native: Box<Self>) -> *mut JSObject {
140 let object = Self::new_raw_object(cx);
141 unsafe {
142 Self::set_private(object, native);
143 }
144 object
145 }
146
147 unsafe fn get_private_unchecked<'a>(object: &Object<'a>) -> &'a Self {
148 unsafe {
149 let mut value = UndefinedValue();
150 JS_GetReservedSlot(object.handle().get(), 0, &raw mut value);
151 &*(value.to_private().cast::<Self>())
152 }
153 }
154
155 fn get_private<'a>(cx: &Context, object: &Object<'a>) -> Result<&'a Self> {
156 check_private::<Self>(cx, object)?;
157 Ok(unsafe { Self::get_private_unchecked(object) })
158 }
159
160 unsafe fn get_mut_private_unchecked<'a>(object: &Object<'a>) -> &'a mut Self {
161 unsafe {
162 let mut value = UndefinedValue();
163 JS_GetReservedSlot(object.handle().get(), 0, &raw mut value);
164 &mut *(value.to_private().cast_mut().cast::<Self>())
165 }
166 }
167
168 fn get_mut_private<'a>(cx: &Context, object: &Object<'a>) -> Result<&'a mut Self> {
169 check_private::<Self>(cx, object)?;
170 Ok(unsafe { Self::get_mut_private_unchecked(object) })
171 }
172
173 unsafe fn set_private(object: *mut JSObject, native: Box<Self>) {
174 native.reflector().set(object);
175 unsafe {
176 JS_SetReservedSlot(object, 0, &PrivateValue(Box::into_raw(native).cast_const().cast()));
177 }
178 }
179
180 fn instance_of(cx: &Context, object: &Object) -> bool {
181 unsafe {
182 JS_InstanceOf(
183 cx.as_ptr(),
184 object.handle().into(),
185 &raw const Self::class().base,
186 ptr::null_mut(),
187 )
188 }
189 }
190
191 fn has_instance(cx: &Context, object: &Object) -> Result<bool> {
192 let infos = unsafe { &mut (*cx.get_inner_data().as_ptr()).class_infos };
193 let constructor =
194 Function::from(cx.root(infos.get(&TypeId::of::<Self>()).expect("Uninitialised Class").constructor.get()))
195 .to_object(cx);
196 let object = object.as_value(cx);
197 let mut has_instance = false;
198 let result = unsafe {
199 JS_HasInstance(
200 cx.as_ptr(),
201 constructor.handle().into(),
202 object.handle().into(),
203 &raw mut has_instance,
204 )
205 };
206 if result { Ok(has_instance) } else { Err(Error::none()) }
207 }
208}
209
210pub struct ClassObjectWrapper<T: ClassDefinition>(pub Box<T>);
211
212impl<T: ClassDefinition> IntoValue<'_> for ClassObjectWrapper<T> {
213 fn into_value(self: Box<Self>, cx: &Context, value: &mut Value) {
214 T::new_object(cx, self.0).to_value(cx, value);
215 }
216}
217
218trait SpecZero {
219 fn is_zeroed(&self) -> bool;
220}
221
222impl SpecZero for JSFunctionSpec {
223 fn is_zeroed(&self) -> bool {
224 self.is_zeroed()
225 }
226}
227
228impl SpecZero for JSPropertySpec {
229 fn is_zeroed(&self) -> bool {
230 self.is_zeroed()
231 }
232}
233
234fn has_zero_spec<T: SpecZero>(specs: Option<&[T]>) -> bool {
235 specs.and_then(|s| s.last()).is_none_or(T::is_zeroed)
236}
237
238fn unwrap_specs<T>(specs: Option<&[T]>) -> *const T {
239 specs.map_or_else(ptr::null, <[T]>::as_ptr)
240}
241
242fn check_private<T: ClassDefinition>(cx: &Context, object: &Object) -> Result<()> {
243 if unsafe { class_num_reserved_slots(get_object_class(object.handle().get())) >= 1 }
244 && (T::instance_of(cx, object) || T::has_instance(cx, object)?)
245 {
246 Ok(())
247 } else {
248 let name = unsafe { CStr::from_ptr(T::class().base.name).to_str()? };
249 Err(Error::new(
250 format!("Object does not implement interface {name}"),
251 ErrorKind::Type,
252 ))
253 }
254}
255
256unsafe extern "C" fn illegal_constructor(cx: *mut JSContext, _: u32, _: *mut JSVal) -> bool {
257 unsafe {
258 throw_type_error(cx, "Illegal constructor.");
259 }
260 false
261}
262
263#[doc(hidden)]
264pub fn construct_native_object<'cx, T, C>(cx: &'cx Context, args: &mut Arguments<'cx>, constructor: C) -> bool
265where
266 T: ClassDefinition,
267 C: for<'cx2> FnOnce(&'cx2 Context, &mut Arguments<'cx2>, &mut Object<'cx2>) -> ResultExc<()>,
268{
269 if !args.is_constructing() {
270 debug_assert!(!Exception::is_pending(cx));
271 unsafe {
272 let name = CStr::from_ptr(T::class().base.name).to_str().unwrap();
273 throw_type_error(cx.as_ptr(), &format!("{name} constructor: 'new' is required"));
274 }
275 return false;
276 }
277
278 let class_info = T::init_class(cx, &Object::global(cx)).1;
279
280 let mut this = Object::from(cx.root(unsafe {
281 JS_NewObjectWithGivenProto(cx.as_ptr(), &raw const T::class().base, class_info.prototype.handle())
282 }));
283
284 let result = ::std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| constructor(cx, args, &mut this)));
285
286 __handle_native_constructor_result(cx, result, &this, &mut args.rval())
287}
288
289#[doc(hidden)]
290pub unsafe extern "C" fn finalise_native_object_operation<T>(_: *mut GCContext, this: *mut JSObject) {
291 let mut value = NullValue();
292 unsafe {
293 JS_GetReservedSlot(this, 0, &raw mut value);
294 }
295 if value.is_double() && value.asBits_ & 0xFFFF000000000000 == 0 {
296 let private = value.to_private().cast_mut().cast::<T>();
297 let _ = unsafe { Box::from_raw(private) };
298 }
299}
300
301#[doc(hidden)]
302pub unsafe extern "C" fn trace_native_object_operation<T: Traceable>(trc: *mut JSTracer, this: *mut JSObject) {
303 let mut value = NullValue();
304 unsafe {
305 JS_GetReservedSlot(this, 0, &raw mut value);
306 }
307 if value.is_double() && value.asBits_ & 0xFFFF000000000000 == 0 {
308 unsafe {
309 let private = &*(value.to_private().cast::<T>());
310 private.trace(trc);
311 }
312 }
313}