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}