Skip to main content

quickjs_rusty/context/
context.rs

1#![allow(missing_docs)]
2
3use std::{
4    convert::TryFrom,
5    ffi::{c_char, c_int, c_void},
6    sync::Mutex,
7};
8
9use libquickjs_ng_sys::{self as q, JSContext};
10
11use crate::callback::*;
12use crate::console::ConsoleBackend;
13use crate::errors::*;
14use crate::module_loader::*;
15use crate::utils::{create_string, ensure_no_excpetion, get_exception, make_cstring};
16use crate::value::*;
17
18use super::ContextBuilder;
19
20/// Context is a wrapper around a QuickJS Javascript context.
21/// It is the primary way to interact with the runtime.
22///
23/// For each `Context` instance a new instance of QuickJS
24/// runtime is created. It means that it is safe to use
25/// different contexts in different threads, but each
26/// `Context` instance must be used only from a single thread.
27pub struct Context {
28    runtime: *mut q::JSRuntime,
29    pub(crate) context: *mut q::JSContext,
30    /// Stores callback closures and quickjs data pointers.
31    /// This array is write-only and only exists to ensure the lifetime of
32    /// the closure.
33    // A Mutex is used over a RefCell because it needs to be unwind-safe.
34    callbacks: Mutex<Vec<(Box<WrappedCallback>, Box<q::JSValue>)>>,
35    module_loader: Mutex<Option<Box<ModuleLoader>>>,
36}
37
38impl Drop for Context {
39    fn drop(&mut self) {
40        unsafe {
41            q::JS_FreeContext(self.context);
42            q::JS_FreeRuntime(self.runtime);
43
44            // Drop the module loader.
45            let _ = self.module_loader.lock().unwrap().take();
46        }
47    }
48}
49
50impl Context {
51    /// Create a `ContextBuilder` that allows customization of JS Runtime settings.
52    ///
53    /// For details, see the methods on `ContextBuilder`.
54    ///
55    /// ```rust
56    /// let _context = quickjs_rusty::Context::builder()
57    ///     .memory_limit(100_000)
58    ///     .build()
59    ///     .unwrap();
60    /// ```
61    pub fn builder() -> ContextBuilder {
62        ContextBuilder::new()
63    }
64
65    /// Initialize a wrapper by creating a JSRuntime and JSContext.
66    pub fn new(memory_limit: Option<usize>) -> Result<Self, ContextError> {
67        let runtime = unsafe { q::JS_NewRuntime() };
68        if runtime.is_null() {
69            return Err(ContextError::RuntimeCreationFailed);
70        }
71
72        // Configure memory limit if specified.
73        if let Some(limit) = memory_limit {
74            unsafe {
75                q::JS_SetMemoryLimit(runtime, limit as _);
76            }
77        }
78
79        let context = unsafe { q::JS_NewContext(runtime) };
80        if context.is_null() {
81            unsafe {
82                q::JS_FreeRuntime(runtime);
83            }
84            return Err(ContextError::ContextCreationFailed);
85        }
86
87        // Initialize the promise resolver helper code.
88        // This code is needed by Self::resolve_value
89        let wrapper = Self {
90            runtime,
91            context,
92            callbacks: Mutex::new(Vec::new()),
93            module_loader: Mutex::new(None),
94        };
95
96        Ok(wrapper)
97    }
98
99    // See console standard: https://console.spec.whatwg.org
100    pub fn set_console(&self, backend: Box<dyn ConsoleBackend>) -> Result<(), ExecutionError> {
101        use crate::console::Level;
102
103        self.add_callback("__console_write", move |args: Arguments| {
104            let mut args = args.into_vec();
105
106            if args.len() > 1 {
107                let level_raw = args.remove(0);
108
109                let level_opt = level_raw.to_string().ok().and_then(|v| match v.as_str() {
110                    "trace" => Some(Level::Trace),
111                    "debug" => Some(Level::Debug),
112                    "log" => Some(Level::Log),
113                    "info" => Some(Level::Info),
114                    "warn" => Some(Level::Warn),
115                    "error" => Some(Level::Error),
116                    _ => None,
117                });
118
119                if let Some(level) = level_opt {
120                    backend.log(level, args);
121                }
122            }
123        })?;
124
125        self.eval(
126            r#"
127            globalThis.console = {
128                trace: (...args) => {
129                    globalThis.__console_write("trace", ...args);
130                },
131                debug: (...args) => {
132                    globalThis.__console_write("debug", ...args);
133                },
134                log: (...args) => {
135                    globalThis.__console_write("log", ...args);
136                },
137                info: (...args) => {
138                    globalThis.__console_write("info", ...args);
139                },
140                warn: (...args) => {
141                    globalThis.__console_write("warn", ...args);
142                },
143                error: (...args) => {
144                    globalThis.__console_write("error", ...args);
145                },
146            };
147        "#,
148            false,
149        )?;
150
151        Ok(())
152    }
153
154    /// Reset the Javascript engine.
155    ///
156    /// All state and callbacks will be removed.
157    pub fn reset(self) -> Result<Self, ContextError> {
158        unsafe {
159            q::JS_FreeContext(self.context);
160        };
161        self.callbacks.lock().unwrap().clear();
162        let context = unsafe { q::JS_NewContext(self.runtime) };
163        if context.is_null() {
164            return Err(ContextError::ContextCreationFailed);
165        }
166
167        let mut s = self;
168        s.context = context;
169        Ok(s)
170    }
171
172    // Get raw pointer to the underlying QuickJS context.
173    pub unsafe fn context_raw(&self) -> *mut q::JSContext {
174        self.context
175    }
176
177    /// Get the global object.
178    pub fn global(&self) -> Result<OwnedJsObject, ExecutionError> {
179        let global_raw = unsafe { q::JS_GetGlobalObject(self.context) };
180        let global_ref = OwnedJsValue::new(self.context, global_raw);
181        let global = global_ref.try_into_object()?;
182        Ok(global)
183    }
184
185    /// Set a global variable.
186    ///
187    /// ```rust
188    /// use quickjs_rusty::Context;
189    /// let context = Context::builder().build().unwrap();
190    ///
191    /// context.set_global("someGlobalVariable", 42).unwrap();
192    /// let value = context.eval_as::<i32>("someGlobalVariable").unwrap();
193    /// assert_eq!(
194    ///     value,
195    ///     42,
196    /// );
197    /// ```
198    pub fn set_global<T>(&self, name: &str, value: T) -> Result<(), ExecutionError>
199    where
200        T: ToOwnedJsValue,
201    {
202        let global = self.global()?;
203        global.set_property(name, (self.context, value).into())?;
204        Ok(())
205    }
206
207    /// Execute the pending job in the event loop.
208    /// Update the QuickJS runtime's stack top reference to the current native
209    /// stack pointer.
210    ///
211    /// This should be called before any JS execution when entering from Rust,
212    /// so that QuickJS measures the JS stack depth from the current position
213    /// rather than from wherever the runtime was first created.
214    ///
215    /// This is especially important in debug builds where Rust/C frames are
216    /// significantly larger than in release builds.
217    pub fn update_stack_top(&self) {
218        unsafe {
219            q::JS_UpdateStackTop(self.runtime);
220        }
221    }
222
223    /// Set the maximum JS stack size (in bytes).
224    ///
225    /// The default is 1MB. In debug builds the QuickJS C interpreter frames
226    /// are unoptimized and consume more native stack per JS call, so a larger
227    /// limit (e.g. 4MB) may be required to run the same code that works fine
228    /// in release builds.
229    ///
230    /// Use `0` to disable the stack size limit entirely.
231    pub fn set_max_stack_size(&self, size: usize) {
232        unsafe {
233            q::JS_SetMaxStackSize(self.runtime, size);
234        }
235    }
236
237    pub fn execute_pending_job(&self) -> Result<(), ExecutionError> {
238        let mut pctx = Box::new(std::ptr::null_mut::<JSContext>());
239        unsafe {
240            loop {
241                // TODO: is it actually essential to lock the context here?
242                // let _handle = self.context_lock.lock();
243                let err = q::JS_ExecutePendingJob(self.runtime, pctx.as_mut());
244
245                if err <= 0 {
246                    if err < 0 {
247                        ensure_no_excpetion(*pctx)?
248                    }
249                    break;
250                }
251            }
252        }
253        Ok(())
254    }
255
256    /// Check if the given value is an exception, and return the exception if it is.
257    pub fn check_exception(&self, value: &OwnedJsValue) -> Result<(), ExecutionError> {
258        if value.is_exception() {
259            let err = get_exception(self.context)
260                .unwrap_or_else(|| ExecutionError::Internal("Unknown exception".to_string()));
261            Err(err)
262        } else {
263            Ok(())
264        }
265    }
266
267    /// If the given value is a promise, run the event loop until it is
268    /// resolved, and return the final value.
269    pub fn resolve_value(&self, value: OwnedJsValue) -> Result<OwnedJsValue, ExecutionError> {
270        if value.is_object() {
271            let obj = value.try_into_object()?;
272            if obj.is_promise()? {
273                self.eval(
274                    r#"
275                    // Values:
276                    //   - undefined: promise not finished
277                    //   - false: error ocurred, __promiseError is set.
278                    //   - true: finished, __promiseSuccess is set.
279                    var __promiseResult = 0;
280                    var __promiseValue = 0;
281
282                    var __resolvePromise = function(p) {
283                        p
284                            .then(value => {
285                                __promiseResult = true;
286                                __promiseValue = value;
287                            })
288                            .catch(e => {
289                                __promiseResult = false;
290                                __promiseValue = e;
291                            });
292                    }
293                "#,
294                    false,
295                )?;
296
297                let global = self.global()?;
298                let resolver = global
299                    .property_require("__resolvePromise")?
300                    .try_into_function()?;
301
302                // Call the resolver code that sets the result values once
303                // the promise resolves.
304                resolver.call(vec![obj.into_value()])?;
305
306                loop {
307                    let flag = unsafe {
308                        let wrapper_mut = self as *const Self as *mut Self;
309                        let ctx_mut = &mut (*wrapper_mut).context;
310                        q::JS_ExecutePendingJob(self.runtime, ctx_mut)
311                    };
312                    if flag < 0 {
313                        let e = get_exception(self.context).unwrap_or_else(|| {
314                            ExecutionError::Internal("Unknown exception".to_string())
315                        });
316                        return Err(e);
317                    }
318
319                    // Check if promise is finished.
320                    let res_val = global.property_require("__promiseResult")?;
321                    if res_val.is_bool() {
322                        let ok = res_val.to_bool()?;
323                        let value = global.property_require("__promiseValue")?;
324
325                        if ok {
326                            return self.resolve_value(value);
327                        } else {
328                            let err_msg = value.js_to_string()?;
329                            return Err(ExecutionError::Exception(OwnedJsValue::new(
330                                self.context,
331                                create_string(self.context, &err_msg).unwrap(),
332                            )));
333                        }
334                    }
335                }
336            } else {
337                Ok(obj.into_value())
338            }
339        } else {
340            Ok(value)
341        }
342    }
343
344    /// Evaluates Javascript code and returns the value of the final expression.
345    ///
346    /// resolve: Whether to resolve the returned value if it is a promise. See more details as follows.
347    ///
348    /// **Promises**:
349    /// If the evaluated code returns a Promise, the event loop
350    /// will be executed until the promise is finished. The final value of
351    /// the promise will be returned, or a `ExecutionError::Exception` if the
352    /// promise failed.
353    ///
354    /// ```rust
355    /// use quickjs_rusty::Context;
356    /// let context = Context::builder().build().unwrap();
357    ///
358    /// let value = context.eval(" 1 + 2 + 3 ", false).unwrap();
359    /// assert_eq!(
360    ///     value.to_int(),
361    ///     Ok(6),
362    /// );
363    ///
364    /// let value = context.eval(r#"
365    ///     function f() { return 55 * 3; }
366    ///     let y = f();
367    ///     var x = y.toString() + "!"
368    ///     x
369    /// "#, false).unwrap();
370    /// assert_eq!(
371    ///     value.to_string().unwrap(),
372    ///     "165!",
373    /// );
374    /// ```
375    pub fn eval(&self, code: &str, resolve: bool) -> Result<OwnedJsValue, ExecutionError> {
376        let filename = "script.js";
377        let filename_c = make_cstring(filename)?;
378        let code_c = make_cstring(code)?;
379
380        let value_raw = unsafe {
381            q::JS_Eval(
382                self.context,
383                code_c.as_ptr(),
384                code.len(),
385                filename_c.as_ptr(),
386                q::JS_EVAL_TYPE_GLOBAL as i32,
387            )
388        };
389        let value = OwnedJsValue::new(self.context, value_raw);
390
391        self.check_exception(&value)?;
392
393        if resolve {
394            self.resolve_value(value)
395        } else {
396            Ok(value)
397        }
398    }
399
400    /// Evaluates Javascript code and returns the value of the final expression
401    /// on module mode.
402    ///
403    /// resolve: Whether to resolve the returned value if it is a promise. See more details as follows.
404    ///
405    /// **Promises**:
406    /// If the evaluated code returns a Promise, the event loop
407    /// will be executed until the promise is finished. The final value of
408    /// the promise will be returned, or a `ExecutionError::Exception` if the
409    /// promise failed.
410    ///
411    /// **Returns**:
412    /// Return value will always be undefined on module mode.
413    ///
414    /// ```ignore
415    /// use quickjs_rusty::Context;
416    /// let context = Context::builder().build().unwrap();
417    ///
418    /// let value = context.eval_module("import {foo} from 'bar'; foo();", false).unwrap();
419    /// ```
420    pub fn eval_module(&self, code: &str, resolve: bool) -> Result<OwnedJsValue, ExecutionError> {
421        let filename = "module.js";
422        let filename_c = make_cstring(filename)?;
423        let code_c = make_cstring(code)?;
424
425        let value_raw = unsafe {
426            q::JS_Eval(
427                self.context,
428                code_c.as_ptr(),
429                code.len(),
430                filename_c.as_ptr(),
431                q::JS_EVAL_TYPE_MODULE as i32,
432            )
433        };
434        let value = OwnedJsValue::new(self.context, value_raw);
435
436        self.check_exception(&value)?;
437
438        if resolve {
439            self.resolve_value(value)
440        } else {
441            Ok(value)
442        }
443    }
444
445    /// Evaluates Javascript code and returns the value of the final expression
446    /// as a Rust type.
447    ///
448    /// **Promises**:
449    /// If the evaluated code returns a Promise, the event loop
450    /// will be executed until the promise is finished. The final value of
451    /// the promise will be returned, or a `ExecutionError::Exception` if the
452    /// promise failed.
453    ///
454    /// ```rust
455    /// use quickjs_rusty::{Context};
456    /// let context = Context::builder().build().unwrap();
457    ///
458    /// let res = context.eval_as::<bool>(" 100 > 10 ");
459    /// assert_eq!(
460    ///     res,
461    ///     Ok(true),
462    /// );
463    ///
464    /// let value: i32 = context.eval_as(" 10 + 10 ").unwrap();
465    /// assert_eq!(
466    ///     value,
467    ///     20,
468    /// );
469    /// ```
470    pub fn eval_as<R>(&self, code: &str) -> Result<R, ExecutionError>
471    where
472        R: TryFrom<OwnedJsValue>,
473        R::Error: Into<ValueError>,
474    {
475        let value = self.eval(code, true)?;
476        let ret = R::try_from(value).map_err(|e| e.into())?;
477        Ok(ret)
478    }
479
480    /// Evaluates Javascript code and returns the value of the final expression
481    /// on module mode.
482    ///
483    /// **Promises**:
484    /// If the evaluated code returns a Promise, the event loop
485    /// will be executed until the promise is finished. The final value of
486    /// the promise will be returned, or a `ExecutionError::Exception` if the
487    /// promise failed.
488    ///
489    /// ```ignore
490    /// use quickjs_rusty::Context;
491    /// let context = Context::builder().build().unwrap();
492    ///
493    /// let value = context.run_module("./module");
494    /// ```
495    pub fn run_module(&self, filename: &str) -> Result<OwnedJsPromise, ExecutionError> {
496        let filename_c = make_cstring(filename)?;
497
498        let ret = unsafe {
499            q::JS_LoadModule(
500                self.context,
501                ".\0".as_ptr() as *const c_char,
502                filename_c.as_ptr(),
503            )
504        };
505
506        let ret = OwnedJsValue::new(self.context, ret);
507
508        ensure_no_excpetion(self.context)?;
509
510        if ret.is_promise() {
511            Ok(ret.try_into_promise()?)
512        } else {
513            Err(ExecutionError::Internal(
514                "Module did not return a promise".to_string(),
515            ))
516        }
517    }
518
519    /// register module loader function, giving module name as input and return module code as output.
520    pub fn set_module_loader(
521        &self,
522        module_loader_func: JSModuleLoaderFunc,
523        module_normalize: Option<JSModuleNormalizeFunc>,
524        opaque: *mut c_void,
525    ) {
526        let has_module_normalize = module_normalize.is_some();
527
528        let module_loader = ModuleLoader {
529            loader: module_loader_func,
530            normalize: module_normalize,
531            opaque,
532        };
533
534        let module_loader = Box::new(module_loader);
535        let module_loader_ptr = module_loader.as_ref() as *const _ as *mut c_void;
536
537        unsafe {
538            if has_module_normalize {
539                q::JS_SetModuleLoaderFunc(
540                    self.runtime,
541                    Some(js_module_normalize),
542                    Some(js_module_loader),
543                    module_loader_ptr,
544                );
545            } else {
546                q::JS_SetModuleLoaderFunc(
547                    self.runtime,
548                    None,
549                    Some(js_module_loader),
550                    module_loader_ptr,
551                );
552            }
553        }
554
555        *self.module_loader.lock().unwrap() = Some(module_loader);
556    }
557
558    /// Set the host promise rejection tracker.\
559    /// This function works not as expected, see more details in the example.
560    pub fn set_host_promise_rejection_tracker(
561        &self,
562        func: q::JSHostPromiseRejectionTracker,
563        opaque: *mut c_void,
564    ) {
565        unsafe {
566            q::JS_SetHostPromiseRejectionTracker(self.runtime, func, opaque);
567        }
568    }
569
570    /// Set the interrupt handler.\
571    /// Return != 0 if the JS code needs to be interrupted.
572    pub fn set_interrupt_handler(&self, func: q::JSInterruptHandler, opaque: *mut c_void) {
573        unsafe {
574            q::JS_SetInterruptHandler(self.runtime, func, opaque);
575        }
576    }
577
578    /// Call a global function in the Javascript namespace.
579    ///
580    /// **Promises**:
581    /// If the evaluated code returns a Promise, the event loop
582    /// will be executed until the promise is finished. The final value of
583    /// the promise will be returned, or a `ExecutionError::Exception` if the
584    /// promise failed.
585    ///
586    /// ```rust
587    /// use quickjs_rusty::Context;
588    /// let context = Context::builder().build().unwrap();
589    ///
590    /// let res = context.call_function("encodeURIComponent", vec!["a=b"]).unwrap();
591    /// assert_eq!(
592    ///     res.to_string(),
593    ///     Ok("a%3Db".to_string()),
594    /// );
595    /// ```
596    pub fn call_function(
597        &self,
598        function_name: &str,
599        args: impl IntoIterator<Item = impl ToOwnedJsValue>,
600    ) -> Result<OwnedJsValue, ExecutionError> {
601        let qargs = args
602            .into_iter()
603            .map(|v| (self.context, v).into())
604            .collect::<Vec<OwnedJsValue>>();
605
606        let global = self.global()?;
607        let func = global
608            .property_require(function_name)?
609            .try_into_function()?;
610
611        let ret = func.call(qargs)?;
612        let v = self.resolve_value(ret)?;
613
614        Ok(v)
615    }
616
617    /// Create a JS function that is backed by a Rust function or closure.
618    /// Can be used to create a function and add it to an object.
619    ///
620    /// The callback must satisfy several requirements:
621    /// * accepts 0 - 5 arguments
622    /// * each argument must be convertible from a JsValue
623    /// * must return a value
624    /// * the return value must either:
625    ///   - be convertible to JsValue
626    ///   - be a Result<T, E> where T is convertible to JsValue
627    ///     if Err(e) is returned, a Javascript exception will be raised
628    ///
629    /// ```rust
630    /// use quickjs_rusty::{Context, OwnedJsValue};
631    /// use std::collections::HashMap;
632    ///
633    /// let context = Context::builder().build().unwrap();
634    ///
635    /// // Register an object.
636    /// let mut obj = HashMap::<String, OwnedJsValue>::new();
637    /// let func = context
638    ///         .create_callback(|a: i32, b: i32| a + b)
639    ///         .unwrap();
640    /// let func = OwnedJsValue::from((unsafe{context.context_raw()}, func));
641    /// // insert add function into the object.
642    /// obj.insert("add".to_string(), func);
643    /// // insert the myObj to global.
644    /// context.set_global("myObj", obj).unwrap();
645    /// // Now we try out the 'myObj.add' function via eval.    
646    /// let output = context.eval_as::<i32>("myObj.add( 3 , 4 ) ").unwrap();
647    /// assert_eq!(output, 7);
648    /// ```
649    pub fn create_callback<'a, F>(
650        &self,
651        callback: impl Callback<F> + 'static,
652    ) -> Result<JsFunction, ExecutionError> {
653        let argcount = callback.argument_count() as i32;
654
655        let context = self.context;
656        let wrapper = move |argc: c_int, argv: *mut q::JSValue| -> q::JSValue {
657            match exec_callback(context, argc, argv, &callback) {
658                Ok(value) => value,
659                // TODO: better error reporting.
660                Err(e) => {
661                    let js_exception_value = match e {
662                        ExecutionError::Exception(e) => unsafe { e.extract() },
663                        other => create_string(context, other.to_string().as_str()).unwrap(),
664                    };
665                    unsafe {
666                        q::JS_Throw(context, js_exception_value);
667                    }
668
669                    unsafe { q::JS_Ext_NewSpecialValue(q::JS_TAG_EXCEPTION, 0) }
670                }
671            }
672        };
673
674        let (pair, trampoline) = unsafe { build_closure_trampoline(wrapper) };
675        let data = (&*pair.1) as *const q::JSValue as *mut q::JSValue;
676        self.callbacks.lock().unwrap().push(pair);
677
678        let obj = unsafe {
679            let f = q::JS_NewCFunctionData(self.context, trampoline, argcount, 0, 1, data);
680            OwnedJsValue::new(self.context, f)
681        };
682
683        let f = obj.try_into_function()?;
684        Ok(f)
685    }
686
687    /// Add a global JS function that is backed by a Rust function or closure.
688    ///
689    /// The callback must satisfy several requirements:
690    /// * accepts 0 - 5 arguments
691    /// * each argument must be convertible from a JsValue
692    /// * must return a value
693    /// * the return value must either:
694    ///   - be convertible to JsValue
695    ///   - be a Result<T, E> where T is convertible to JsValue
696    ///     if Err(e) is returned, a Javascript exception will be raised
697    ///
698    /// ```rust
699    /// use quickjs_rusty::Context;
700    /// let context = Context::builder().build().unwrap();
701    ///
702    /// // Register a closue as a callback under the "add" name.
703    /// // The 'add' function can now be called from Javascript code.
704    /// context.add_callback("add", |a: i32, b: i32| { a + b }).unwrap();
705    ///
706    /// // Now we try out the 'add' function via eval.
707    /// let output = context.eval_as::<i32>(" add( 3 , 4 ) ").unwrap();
708    /// assert_eq!(
709    ///     output,
710    ///     7,
711    /// );
712    /// ```
713    pub fn add_callback<'a, F>(
714        &self,
715        name: &str,
716        callback: impl Callback<F> + 'static,
717    ) -> Result<(), ExecutionError> {
718        let cfunc = self.create_callback(callback)?;
719        let global = self.global()?;
720        global.set_property(name, cfunc.into_value())?;
721        Ok(())
722    }
723
724    /// create a custom callback function
725    pub fn create_custom_callback(
726        &self,
727        callback: CustomCallback,
728    ) -> Result<JsFunction, ExecutionError> {
729        let context = self.context;
730        let wrapper = move |argc: c_int, argv: *mut q::JSValue| -> q::JSValue {
731            let result = std::panic::catch_unwind(|| {
732                let arg_slice = unsafe { std::slice::from_raw_parts(argv, argc as usize) };
733                match callback(context, arg_slice) {
734                    Ok(Some(value)) => value,
735                    Ok(None) => unsafe { q::JS_Ext_NewSpecialValue(q::JS_TAG_UNDEFINED, 0) },
736                    // TODO: better error reporting.
737                    Err(e) => {
738                        // TODO: should create an Error type.
739                        let js_exception_value =
740                            create_string(context, e.to_string().as_str()).unwrap();
741
742                        unsafe {
743                            q::JS_Throw(context, js_exception_value);
744                        }
745
746                        unsafe { q::JS_Ext_NewSpecialValue(q::JS_TAG_EXCEPTION, 0) }
747                    }
748                }
749            });
750
751            match result {
752                Ok(v) => v,
753                Err(_) => {
754                    // TODO: should create an Error type.
755                    let js_exception_value = create_string(context, "Callback panicked!").unwrap();
756
757                    unsafe {
758                        q::JS_Throw(context, js_exception_value);
759                    }
760
761                    unsafe { q::JS_Ext_NewSpecialValue(q::JS_TAG_EXCEPTION, 0) }
762                }
763            }
764        };
765
766        let (pair, trampoline) = unsafe { build_closure_trampoline(wrapper) };
767        let data = (&*pair.1) as *const q::JSValue as *mut q::JSValue;
768        self.callbacks.lock().unwrap().push(pair);
769
770        let obj = unsafe {
771            let f = q::JS_NewCFunctionData(self.context, trampoline, 0, 0, 1, data);
772            OwnedJsValue::new(self.context, f)
773        };
774
775        let f = obj.try_into_function()?;
776        Ok(f)
777    }
778}