1use std::ffi::{c_int, c_void};
2use std::{convert::TryFrom, marker::PhantomData, panic::RefUnwindSafe};
3
4use anyhow::Result;
5use libquickjs_ng_sys as q;
6
7use crate::utils::create_string;
8use crate::utils::create_undefined;
9use crate::ExecutionError;
10use crate::OwnedJsValue;
11use crate::ValueError;
12
13pub trait IntoCallbackResult {
14 fn into_callback_res(self, context: *mut q::JSContext) -> Result<OwnedJsValue, String>;
15}
16
17impl<T> IntoCallbackResult for T
18where
19 OwnedJsValue: From<(*mut q::JSContext, T)>,
20{
21 fn into_callback_res(self, context: *mut q::JSContext) -> Result<OwnedJsValue, String> {
22 Ok((context, self).into())
23 }
24}
25
26impl<T, E: std::fmt::Display> IntoCallbackResult for Result<T, E>
27where
28 OwnedJsValue: From<(*mut q::JSContext, T)>,
29{
30 fn into_callback_res(self, context: *mut q::JSContext) -> Result<OwnedJsValue, String> {
31 match self {
32 Ok(v) => Ok((context, v).into()),
33 Err(e) => Err(e.to_string()),
34 }
35 }
36}
37
38pub trait Callback<F>: RefUnwindSafe {
41 fn argument_count(&self) -> usize;
43
44 fn call(
52 &self,
53 context: *mut q::JSContext,
54 args: Vec<OwnedJsValue>,
55 ) -> Result<Result<OwnedJsValue, String>, ValueError>;
56}
57
58macro_rules! impl_callback {
59 (@call $len:literal $self:ident $args:ident ) => {
60 $self()
61 };
62
63 (@call $len:literal $self:ident $args:ident $( $arg:ident ),* ) => {
64 {
65 let mut iter = $args.into_iter();
66 $self(
67 $(
68 $arg::try_from(iter.next().unwrap())?,
69 )*
70 )
71 }
72 };
73
74 [ $( $len:literal : ( $( $arg:ident, )* ), )* ] => {
75 $(
76
77 impl<
78 $( $arg, )*
79 E,
80 R,
81 F,
82 > Callback<PhantomData<(
83 $( &$arg, )*
84 &E,
85 &R,
86 &F,
87 )>> for F
88 where
89 $( $arg: TryFrom<OwnedJsValue, Error = E>, )*
90 ValueError: From<E>,
91 R: IntoCallbackResult,
92 F: Fn( $( $arg, )* ) -> R + Sized + RefUnwindSafe,
93 {
94 fn argument_count(&self) -> usize {
95 $len
96 }
97
98 fn call(&self, context: *mut q::JSContext, args: Vec<OwnedJsValue>)
99 -> Result<Result<OwnedJsValue, String>, ValueError> {
100 if args.len() != $len {
101 return Ok(Err(format!(
102 "Invalid argument count: Expected {}, got {}",
103 self.argument_count(),
104 args.len()
105 )));
106 }
107
108 let res = impl_callback!(@call $len self args $($arg),* );
109 Ok(res.into_callback_res(context))
110 }
111 }
112 )*
113 };
114}
115
116impl<R, F> Callback<PhantomData<(&R, &F)>> for F
117where
118 R: IntoCallbackResult,
119 F: Fn() -> R + Sized + RefUnwindSafe,
120{
121 fn argument_count(&self) -> usize {
122 0
123 }
124
125 fn call(
126 &self,
127 context: *mut q::JSContext,
128 args: Vec<OwnedJsValue>,
129 ) -> Result<Result<OwnedJsValue, String>, ValueError> {
130 if !args.is_empty() {
131 return Ok(Err(format!(
132 "Invalid argument count: Expected 0, got {}",
133 args.len(),
134 )));
135 }
136
137 let res = self();
138 Ok(res.into_callback_res(context))
139 }
140}
141
142impl_callback![
143 1: (A1,),
144 2: (A1, A2,),
145 3: (A1, A2, A3,),
146 4: (A1, A2, A3, A4,),
147 5: (A1, A2, A3, A4, A5,),
148];
149
150pub struct Arguments(Vec<OwnedJsValue>);
155
156impl Arguments {
157 pub fn into_vec(self) -> Vec<OwnedJsValue> {
159 self.0
160 }
161}
162
163impl<F> Callback<PhantomData<(&Arguments, &F)>> for F
164where
165 F: Fn(Arguments) + Sized + RefUnwindSafe,
166{
167 fn argument_count(&self) -> usize {
168 0
169 }
170
171 fn call(
172 &self,
173 context: *mut q::JSContext,
174 args: Vec<OwnedJsValue>,
175 ) -> Result<Result<OwnedJsValue, String>, ValueError> {
176 (self)(Arguments(args));
177 Ok(Ok(OwnedJsValue::new(context, create_undefined())))
178 }
179}
180
181impl<F, R> Callback<PhantomData<(&Arguments, &F, &R)>> for F
182where
183 R: IntoCallbackResult,
184 F: Fn(Arguments) -> R + Sized + RefUnwindSafe,
185{
186 fn argument_count(&self) -> usize {
187 0
188 }
189
190 fn call(
191 &self,
192 context: *mut q::JSContext,
193 args: Vec<OwnedJsValue>,
194 ) -> Result<Result<OwnedJsValue, String>, ValueError> {
195 let res = (self)(Arguments(args));
196 Ok(res.into_callback_res(context))
197 }
198}
199
200pub fn exec_callback<F>(
202 context: *mut q::JSContext,
203 argc: c_int,
204 argv: *mut q::JSValue,
205 callback: &impl Callback<F>,
206) -> Result<q::JSValue, ExecutionError> {
207 let result = std::panic::catch_unwind(|| {
208 let arg_slice = unsafe { std::slice::from_raw_parts(argv, argc as usize) };
209
210 let args = arg_slice
211 .iter()
212 .map(|raw| OwnedJsValue::own(context, raw))
213 .collect::<Vec<_>>();
214
215 match callback.call(context, args) {
216 Ok(Ok(result)) => {
217 let serialized = unsafe { result.extract() };
218 Ok(serialized)
219 }
220 Ok(Err(e)) => Err(ExecutionError::Exception(OwnedJsValue::new(
222 context,
223 create_string(context, &e).unwrap(),
224 ))),
225 Err(e) => Err(e.into()),
226 }
227 });
228
229 match result {
230 Ok(r) => r,
231 Err(_e) => Err(ExecutionError::Internal("Callback panicked!".to_string())),
232 }
233}
234
235pub type CustomCallback = fn(*mut q::JSContext, &[q::JSValue]) -> Result<Option<q::JSValue>>;
236pub type WrappedCallback = dyn Fn(c_int, *mut q::JSValue) -> q::JSValue;
237
238pub unsafe fn build_closure_trampoline<F>(
246 closure: F,
247) -> ((Box<WrappedCallback>, Box<q::JSValue>), q::JSCFunctionData)
248where
249 F: Fn(c_int, *mut q::JSValue) -> q::JSValue + 'static,
250{
251 unsafe extern "C" fn trampoline<F>(
252 _ctx: *mut q::JSContext,
253 _this: q::JSValue,
254 argc: c_int,
255 argv: *mut q::JSValue,
256 _magic: c_int,
257 data: *mut q::JSValue,
258 ) -> q::JSValue
259 where
260 F: Fn(c_int, *mut q::JSValue) -> q::JSValue,
261 {
262 let closure_ptr = q::JS_Ext_GetPtr(*data);
263 let closure: &mut F = &mut *(closure_ptr as *mut F);
264 (*closure)(argc, argv)
265 }
266
267 let boxed_f = Box::new(closure);
268
269 let data = Box::new(q::JS_Ext_NewPointer(
270 q::JS_TAG_NULL,
271 (&*boxed_f) as *const F as *mut c_void,
272 ));
273
274 ((boxed_f, data), Some(trampoline::<F>))
275}