Skip to main content

rquickjs_core/context/
ctx.rs

1#[cfg(feature = "futures")]
2use core::future::Future;
3
4use alloc::{boxed::Box, ffi::CString, vec::Vec};
5use core::{
6    any::Any,
7    ffi::CStr,
8    mem::{self, MaybeUninit},
9    ptr::NonNull,
10    result::Result as StdResult,
11};
12
13#[cfg(feature = "std")]
14use std::{fs, path::Path, string::String as StdString};
15
16#[cfg(feature = "futures")]
17use crate::AsyncContext;
18use crate::{
19    markers::Invariant,
20    qjs,
21    runtime::{opaque::Opaque, UserDataError, UserDataGuard},
22    Atom, Error, FromJs, Function, IntoJs, JsLifetime, Object, Promise, Result, String, Value,
23};
24
25use super::Context;
26
27/// Eval options.
28#[non_exhaustive]
29pub struct EvalOptions {
30    /// Global code.
31    pub global: bool,
32    /// Force 'strict' mode.
33    pub strict: bool,
34    /// Don't include the stack frames before this eval in the Error() backtraces.
35    pub backtrace_barrier: bool,
36    /// Support top-level-await.
37    pub promise: bool,
38    /// Filename. Ignored when calling eval_file_*.
39    #[cfg(feature = "std")]
40    pub filename: Option<StdString>,
41}
42
43impl EvalOptions {
44    fn to_flag(&self) -> i32 {
45        let mut flag = if self.global {
46            qjs::JS_EVAL_TYPE_GLOBAL
47        } else {
48            qjs::JS_EVAL_TYPE_MODULE
49        };
50
51        if self.strict {
52            flag |= qjs::JS_EVAL_FLAG_STRICT;
53        }
54
55        if self.backtrace_barrier {
56            flag |= qjs::JS_EVAL_FLAG_BACKTRACE_BARRIER;
57        }
58
59        if self.promise {
60            flag |= qjs::JS_EVAL_FLAG_ASYNC;
61        }
62
63        flag as i32
64    }
65}
66
67impl Default for EvalOptions {
68    fn default() -> Self {
69        EvalOptions {
70            global: true,
71            strict: true,
72            backtrace_barrier: false,
73            promise: false,
74            #[cfg(feature = "std")]
75            filename: None,
76        }
77    }
78}
79
80/// Context in use, passed to [`Context::with`].
81#[derive(Debug)]
82pub struct Ctx<'js> {
83    ctx: NonNull<qjs::JSContext>,
84    _marker: Invariant<'js>,
85}
86
87impl<'js> Clone for Ctx<'js> {
88    fn clone(&self) -> Self {
89        unsafe { qjs::JS_DupContext(self.ctx.as_ptr()) };
90        Ctx {
91            ctx: self.ctx,
92            _marker: self._marker,
93        }
94    }
95}
96
97impl<'js> Drop for Ctx<'js> {
98    fn drop(&mut self) {
99        unsafe { qjs::JS_FreeContext(self.ctx.as_ptr()) };
100    }
101}
102
103unsafe impl Send for Ctx<'_> {}
104
105impl<'js> Ctx<'js> {
106    pub(crate) fn as_ptr(&self) -> *mut qjs::JSContext {
107        self.ctx.as_ptr()
108    }
109
110    pub(crate) unsafe fn from_ptr(ctx: *mut qjs::JSContext) -> Self {
111        unsafe { qjs::JS_DupContext(ctx) };
112        let ctx = NonNull::new_unchecked(ctx);
113        Ctx {
114            ctx,
115            _marker: Invariant::new(),
116        }
117    }
118
119    pub(crate) unsafe fn new(ctx: &'js Context) -> Self {
120        unsafe { qjs::JS_DupContext(ctx.0.ctx().as_ptr()) };
121        Ctx {
122            ctx: ctx.0.ctx(),
123            _marker: Invariant::new(),
124        }
125    }
126
127    #[cfg(feature = "futures")]
128    pub(crate) unsafe fn new_async(ctx: &'js AsyncContext) -> Self {
129        unsafe { qjs::JS_DupContext(ctx.0.ctx().as_ptr()) };
130        Ctx {
131            ctx: ctx.0.ctx(),
132            _marker: Invariant::new(),
133        }
134    }
135
136    pub(crate) unsafe fn eval_raw<S: Into<Vec<u8>>>(
137        &self,
138        source: S,
139        file_name: &CStr,
140        flag: i32,
141    ) -> Result<qjs::JSValue> {
142        let src = source.into();
143        let len = src.len();
144        let src = CString::new(src)?;
145
146        #[cfg(feature = "parallel")]
147        qjs::JS_UpdateStackTop(qjs::JS_GetRuntime(self.ctx.as_ptr()));
148
149        let val = qjs::JS_Eval(
150            self.ctx.as_ptr(),
151            src.as_ptr(),
152            len as _,
153            file_name.as_ptr(),
154            flag,
155        );
156        self.handle_exception(val)
157    }
158
159    /// Evaluate a script in global context.
160    pub fn eval<V: FromJs<'js>, S: Into<Vec<u8>>>(&self, source: S) -> Result<V> {
161        self.eval_with_options(source, Default::default())
162    }
163
164    /// Evaluate a script in global context with top level await support.
165    ///
166    /// This function always returns a promise which resolves to the result of the evaluated
167    /// expression.
168    pub fn eval_promise<S: Into<Vec<u8>>>(&self, source: S) -> Result<Promise<'js>> {
169        self.eval_with_options(
170            source,
171            EvalOptions {
172                promise: true,
173                ..Default::default()
174            },
175        )
176    }
177
178    /// Evaluate a script with the given options.
179    pub fn eval_with_options<V: FromJs<'js>, S: Into<Vec<u8>>>(
180        &self,
181        source: S,
182        options: EvalOptions,
183    ) -> Result<V> {
184        #[cfg(feature = "std")]
185        let file_name = {
186            if let Some(filename) = &options.filename {
187                &CString::new(filename.clone())?
188            } else {
189                c"eval_script"
190            }
191        };
192
193        #[cfg(not(feature = "std"))]
194        let file_name = c"eval_script";
195
196        V::from_js(self, unsafe {
197            let val = self.eval_raw(source, file_name, options.to_flag())?;
198            Value::from_js_value(self.clone(), val)
199        })
200    }
201
202    #[cfg(feature = "std")]
203    /// Evaluate a script directly from a file.
204    pub fn eval_file<V: FromJs<'js>, P: AsRef<Path>>(&self, path: P) -> Result<V> {
205        self.eval_file_with_options(path, Default::default())
206    }
207
208    #[cfg(feature = "std")]
209    pub fn eval_file_with_options<V: FromJs<'js>, P: AsRef<Path>>(
210        &self,
211        path: P,
212        options: EvalOptions,
213    ) -> Result<V> {
214        let buffer = fs::read(path.as_ref())?;
215        let file_name = CString::new(
216            path.as_ref()
217                .file_name()
218                .unwrap()
219                .to_string_lossy()
220                .into_owned(),
221        )?;
222
223        V::from_js(self, unsafe {
224            let val = self.eval_raw(buffer, file_name.as_c_str(), options.to_flag())?;
225            Value::from_js_value(self.clone(), val)
226        })
227    }
228
229    /// Returns the global object of this context.
230    pub fn globals(&self) -> Object<'js> {
231        unsafe {
232            let v = qjs::JS_GetGlobalObject(self.ctx.as_ptr());
233            Object::from_js_value(self.clone(), v)
234        }
235    }
236
237    /// Returns the last raised JavaScript exception, if there is no exception the JavaScript value `null` is returned.
238    ///
239    /// # Usage
240    /// ```
241    /// # use rquickjs::{Error, Context, Runtime};
242    /// # let rt = Runtime::new().unwrap();
243    /// # let ctx = Context::full(&rt).unwrap();
244    /// # ctx.with(|ctx|{
245    /// if let Err(Error::Exception) = ctx.eval::<(),_>("throw 3"){
246    ///     assert_eq!(ctx.catch().as_int(),Some(3));
247    /// # }else{
248    /// #    panic!()
249    /// }
250    /// # });
251    /// ```
252    pub fn catch(&self) -> Value<'js> {
253        unsafe {
254            let v = qjs::JS_GetException(self.ctx.as_ptr());
255            Value::from_js_value(self.clone(), v)
256        }
257    }
258
259    /// Returns true if there is a pending JavaScript exception.
260    pub fn has_exception(&self) -> bool {
261        unsafe { qjs::JS_HasException(self.ctx.as_ptr()) }
262    }
263
264    /// Throws a JavaScript value as a new exception.
265    /// Always returns `Error::Exception`;
266    pub fn throw(&self, value: Value<'js>) -> Error {
267        unsafe {
268            let v = value.into_js_value();
269            qjs::JS_Throw(self.ctx.as_ptr(), v);
270        }
271        Error::Exception
272    }
273
274    /// Parse json into a JavaScript value.
275    pub fn json_parse<S>(&self, json: S) -> Result<Value<'js>>
276    where
277        S: Into<Vec<u8>>,
278    {
279        let src = json.into();
280        let len = src.len();
281        let src = CString::new(src)?;
282        unsafe {
283            let name = b"<input>\0";
284            let v = qjs::JS_ParseJSON(
285                self.as_ptr(),
286                src.as_ptr().cast(),
287                len.try_into().expect(qjs::SIZE_T_ERROR),
288                name.as_ptr().cast(),
289            );
290            self.handle_exception(v)?;
291            Ok(Value::from_js_value(self.clone(), v))
292        }
293    }
294
295    /// Stringify a JavaScript value into its JSON representation
296    pub fn json_stringify<V>(&self, value: V) -> Result<Option<String<'js>>>
297    where
298        V: IntoJs<'js>,
299    {
300        self.json_stringify_inner(&value.into_js(self)?, qjs::JS_UNDEFINED, qjs::JS_UNDEFINED)
301    }
302
303    /// Stringify a JavaScript value into its JSON representation with a possible replacer.
304    ///
305    /// The replacer is the same as the replacer argument for `JSON.stringify`.
306    /// It is is a function that alters the behavior of the stringification process.
307    pub fn json_stringify_replacer<V, R>(
308        &self,
309        value: V,
310        replacer: R,
311    ) -> Result<Option<String<'js>>>
312    where
313        V: IntoJs<'js>,
314        R: IntoJs<'js>,
315    {
316        let replacer = replacer.into_js(self)?;
317
318        self.json_stringify_inner(
319            &value.into_js(self)?,
320            replacer.as_js_value(),
321            qjs::JS_UNDEFINED,
322        )
323    }
324
325    /// Stringify a JavaScript value into its JSON representation with a possible replacer and
326    /// spaces
327    ///
328    /// The replacer is the same as the replacer argument for `JSON.stringify`.
329    /// It is is a function that alters the behavior of the stringification process.
330    ///
331    /// Space is either a number or a string which is used to insert whitespace into the output
332    /// string for readability purposes. This behaves the same as the space argument for
333    /// `JSON.stringify`.
334    pub fn json_stringify_replacer_space<V, R, S>(
335        &self,
336        value: V,
337        replacer: R,
338        space: S,
339    ) -> Result<Option<String<'js>>>
340    where
341        V: IntoJs<'js>,
342        R: IntoJs<'js>,
343        S: IntoJs<'js>,
344    {
345        let replacer = replacer.into_js(self)?;
346        let space = space.into_js(self)?;
347
348        self.json_stringify_inner(
349            &value.into_js(self)?,
350            replacer.as_js_value(),
351            space.as_js_value(),
352        )
353    }
354
355    // Inner non-generic version of json stringify>
356    fn json_stringify_inner(
357        &self,
358        value: &Value<'js>,
359        replacer: qjs::JSValueConst,
360        space: qjs::JSValueConst,
361    ) -> Result<Option<String<'js>>> {
362        unsafe {
363            let res = qjs::JS_JSONStringify(self.as_ptr(), value.as_js_value(), replacer, space);
364            self.handle_exception(res)?;
365            let v = Value::from_js_value(self.clone(), res);
366            if v.is_undefined() {
367                Ok(None)
368            } else {
369                let v = v.into_string().expect(
370                    "JS_JSONStringify did not return either an exception, undefined, or a string",
371                );
372                Ok(Some(v))
373            }
374        }
375    }
376
377    /// Creates javascipt promise along with its reject and resolve functions.
378    pub fn promise(&self) -> Result<(Promise<'js>, Function<'js>, Function<'js>)> {
379        let mut funcs = mem::MaybeUninit::<[qjs::JSValue; 2]>::uninit();
380
381        Ok(unsafe {
382            let promise = self.handle_exception(qjs::JS_NewPromiseCapability(
383                self.ctx.as_ptr(),
384                funcs.as_mut_ptr() as _,
385            ))?;
386            let [resolve, reject] = funcs.assume_init();
387            (
388                Promise::from_js_value(self.clone(), promise),
389                Function::from_js_value(self.clone(), resolve),
390                Function::from_js_value(self.clone(), reject),
391            )
392        })
393    }
394
395    /// Executes a quickjs job.
396    ///
397    /// Returns wether a job was actually executed.
398    /// If this function returned false, no job was pending.
399    pub fn execute_pending_job(&self) -> bool {
400        let mut ptr = MaybeUninit::<*mut qjs::JSContext>::uninit();
401        let rt = unsafe { qjs::JS_GetRuntime(self.ctx.as_ptr()) };
402        let res = unsafe { qjs::JS_ExecutePendingJob(rt, ptr.as_mut_ptr()) };
403        if res < 0 {
404            // A negative result means the job threw, and `JS_ExecutePendingJob`
405            // leaves the exception pending on the context it ran in (`*ptr`,
406            // which need not be `self` in a multi-context runtime) instead of
407            // surfacing it here. Drain it so it can't be mistaken for a real
408            // exception later by unrelated code that checks for one (e.g. the
409            // module loader's "is a module already loaded" check).
410            let ctx = unsafe { Self::from_ptr(ptr.assume_init()) };
411            ctx.catch();
412        }
413        res != 0
414    }
415
416    pub(crate) unsafe fn get_opaque(&self) -> &Opaque<'js> {
417        Opaque::from_runtime_ptr(qjs::JS_GetRuntime(self.ctx.as_ptr()))
418    }
419
420    /// Spawn future using configured async runtime
421    #[cfg(feature = "futures")]
422    #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "futures")))]
423    pub fn spawn<F>(&self, future: F)
424    where
425        F: Future<Output = ()> + 'js,
426    {
427        unsafe { self.get_opaque().push(future) }
428    }
429
430    /// Create a new `Ctx` from a pointer to the context and a invariant lifetime.
431    ///
432    /// # Safety
433    /// User must ensure that a lock was acquired over the runtime and that invariant is a unique
434    /// lifetime which can't be coerced to a lifetime outside the scope of the lock of to the
435    /// lifetime of another runtime.
436    pub unsafe fn from_raw_invariant(ctx: NonNull<qjs::JSContext>, inv: Invariant<'js>) -> Self {
437        unsafe { qjs::JS_DupContext(ctx.as_ptr()) };
438        Ctx { ctx, _marker: inv }
439    }
440
441    /// Create a new `Ctx` from a pointer to the context and a invariant lifetime.
442    ///
443    /// # Safety
444    /// User must ensure that a lock was acquired over the runtime and that invariant is a unique
445    /// lifetime which can't be coerced to a lifetime outside the scope of the lock of to the
446    /// lifetime of another runtime.
447    pub unsafe fn from_raw(ctx: NonNull<qjs::JSContext>) -> Self {
448        unsafe { qjs::JS_DupContext(ctx.as_ptr()) };
449        Ctx {
450            ctx,
451            _marker: Invariant::new(),
452        }
453    }
454
455    /// Returns the name of the current module or script that is running.
456    ///
457    /// It called from a javascript callback it will return the current running javascript script
458    /// name.
459    /// Otherwise it will return none.
460    pub fn script_or_module_name(&self, stack_level: isize) -> Option<Atom<'js>> {
461        let stack_level = core::ffi::c_int::try_from(stack_level).unwrap();
462        let atom = unsafe { qjs::JS_GetScriptOrModuleName(self.as_ptr(), stack_level) };
463        #[allow(clippy::useless_conversion)] //needed for multi platform binding support
464        if qjs::__JS_ATOM_NULL == atom.try_into().unwrap() {
465            unsafe { qjs::JS_FreeAtom(self.as_ptr(), atom) };
466            return None;
467        }
468        unsafe { Some(Atom::from_atom_val(self.clone(), atom)) }
469    }
470
471    /// Runs the quickjs garbage collector for a cycle.
472    ///
473    /// Quickjs uses reference counting with a collection cycle for cyclic references.
474    /// This runs the cyclic reference collector cycle, types which are not part of a reference cycle
475    /// will be freed the momement their reference count becomes zero.
476    pub fn run_gc(&self) {
477        unsafe { qjs::JS_RunGC(qjs::JS_GetRuntime(self.ctx.as_ptr())) }
478    }
479
480    /// Store a type in the runtime which can be retrieved later with `Ctx::userdata`.
481    ///
482    /// Returns the value from the argument if the userdata is currently being accessed and
483    /// insertion is not possible.
484    /// Otherwise returns the exising value for this type if it existed.
485    pub fn store_userdata<U>(&self, data: U) -> StdResult<Option<Box<U>>, UserDataError<U>>
486    where
487        U: JsLifetime<'js>,
488        U::Changed<'static>: Any,
489    {
490        unsafe { self.get_opaque().insert_userdata(data) }
491    }
492
493    /// Remove the userdata of the given type from the userdata storage.
494    ///
495    /// Returns Err(()) if the userdata is currently being accessed and removing isn't possible.
496    /// Returns Ok(None) if userdata of the given type wasn't inserted.
497    pub fn remove_userdata<U>(&self) -> StdResult<Option<Box<U>>, UserDataError<()>>
498    where
499        U: JsLifetime<'js>,
500        U::Changed<'static>: Any,
501    {
502        unsafe { self.get_opaque().remove_userdata() }
503    }
504
505    /// Retrieves a borrow to the userdata of the given type from the userdata storage.
506    ///
507    /// Returns None if userdata of the given type wasn't inserted.
508    pub fn userdata<U>(&self) -> Option<UserDataGuard<U>>
509    where
510        U: JsLifetime<'js>,
511        U::Changed<'static>: Any,
512    {
513        unsafe { self.get_opaque().get_userdata() }
514    }
515
516    /// Returns the pointer to the C library context.
517    pub fn as_raw(&self) -> NonNull<qjs::JSContext> {
518        self.ctx
519    }
520}
521
522#[cfg(test)]
523mod test {
524    use crate::{CatchResultExt, JsLifetime};
525
526    #[test]
527    fn exports() {
528        use crate::{context::intrinsic, Context, Function, Module, Promise, Runtime};
529
530        let runtime = Runtime::new().unwrap();
531        let ctx = Context::custom::<(intrinsic::Promise, intrinsic::Eval)>(&runtime).unwrap();
532        ctx.with(|ctx| {
533            let (module, promise) = Module::declare(ctx, "test", "export default async () => 1;")
534                .unwrap()
535                .eval()
536                .unwrap();
537            promise.finish::<()>().unwrap();
538            let func: Function = module.get("default").unwrap();
539            func.call::<(), Promise>(()).unwrap();
540        });
541    }
542
543    #[test]
544    fn execute_pending_job_clears_exception_from_failed_job() {
545        use crate::{Context, Runtime};
546
547        let runtime = Runtime::new().unwrap();
548        let ctx = Context::full(&runtime).unwrap();
549        ctx.with(|ctx| {
550            // Schedule a job that throws when it runs.
551            ctx.eval::<(), _>("queueMicrotask(() => { throw new Error('boom'); });")
552                .unwrap();
553
554            // Run it: the job throws, but that must not leave a pending
555            // exception on the context for unrelated code to trip over later.
556            while ctx.execute_pending_job() {}
557
558            assert!(!ctx.has_exception());
559
560            // The context must still be perfectly usable afterwards.
561            let sum: i32 = ctx.eval("1 + 1").unwrap();
562            assert_eq!(sum, 2);
563        })
564    }
565
566    #[test]
567    fn eval() {
568        use crate::{Context, Runtime};
569
570        let runtime = Runtime::new().unwrap();
571        let ctx = Context::full(&runtime).unwrap();
572        ctx.with(|ctx| {
573            let res: String = ctx
574                .eval(
575                    r#"
576                    function test() {
577                        var foo = "bar";
578                        return foo;
579                    }
580
581                    test()
582                "#,
583                )
584                .unwrap();
585
586            assert_eq!("bar".to_string(), res);
587        })
588    }
589
590    #[test]
591    fn eval_minimal_test() {
592        use crate::{Context, Runtime};
593
594        let runtime = Runtime::new().unwrap();
595        let ctx = Context::full(&runtime).unwrap();
596        ctx.with(|ctx| {
597            let res: i32 = ctx.eval(" 1 + 1 ").unwrap();
598            assert_eq!(2, res);
599        })
600    }
601
602    #[test]
603    #[should_panic(expected = "foo is not defined")]
604    fn eval_with_sloppy_code() {
605        use crate::{CatchResultExt, Context, Runtime};
606
607        let runtime = Runtime::new().unwrap();
608        let ctx = Context::full(&runtime).unwrap();
609        ctx.with(|ctx| {
610            let _: String = ctx
611                .eval(
612                    r#"
613                    function test() {
614                        foo = "bar";
615                        return foo;
616                    }
617
618                    test()
619                "#,
620                )
621                .catch(&ctx)
622                .unwrap();
623        })
624    }
625
626    #[test]
627    fn eval_with_options_no_strict_sloppy_code() {
628        use crate::{context::EvalOptions, Context, Runtime};
629
630        let runtime = Runtime::new().unwrap();
631        let ctx = Context::full(&runtime).unwrap();
632        ctx.with(|ctx| {
633            let res: String = ctx
634                .eval_with_options(
635                    r#"
636                    function test() {
637                        foo = "bar";
638                        return foo;
639                    }
640
641                    test()
642                "#,
643                    EvalOptions {
644                        strict: false,
645                        ..Default::default()
646                    },
647                )
648                .unwrap();
649
650            assert_eq!("bar".to_string(), res);
651        })
652    }
653
654    #[test]
655    #[should_panic(expected = "foo is not defined")]
656    fn eval_with_options_strict_sloppy_code() {
657        use crate::{context::EvalOptions, CatchResultExt, Context, Runtime};
658
659        let runtime = Runtime::new().unwrap();
660        let ctx = Context::full(&runtime).unwrap();
661        ctx.with(|ctx| {
662            let _: String = ctx
663                .eval_with_options(
664                    r#"
665                    function test() {
666                        foo = "bar";
667                        return foo;
668                    }
669
670                    test()
671                "#,
672                    EvalOptions {
673                        strict: true,
674                        ..Default::default()
675                    },
676                )
677                .catch(&ctx)
678                .unwrap();
679        })
680    }
681
682    #[test]
683    fn json_parse() {
684        use crate::{Array, Context, Object, Runtime};
685
686        let runtime = Runtime::new().unwrap();
687        let ctx = Context::full(&runtime).unwrap();
688        ctx.with(|ctx| {
689            let v = ctx
690                .json_parse(r#"{ "a": { "b": 1, "c": true }, "d": [0,"foo"] }"#)
691                .unwrap();
692            let obj = v.into_object().unwrap();
693            let inner_obj: Object = obj.get("a").unwrap();
694            assert_eq!(inner_obj.get::<_, i32>("b").unwrap(), 1);
695            assert!(inner_obj.get::<_, bool>("c").unwrap());
696            let inner_array: Array = obj.get("d").unwrap();
697            assert_eq!(inner_array.get::<i32>(0).unwrap(), 0);
698            assert_eq!(inner_array.get::<String>(1).unwrap(), "foo".to_string());
699        })
700    }
701
702    #[test]
703    fn json_stringify() {
704        use crate::{Array, Context, Object, Runtime};
705
706        let runtime = Runtime::new().unwrap();
707        let ctx = Context::full(&runtime).unwrap();
708        ctx.with(|ctx| {
709            let obj_inner = Object::new(ctx.clone()).unwrap();
710            obj_inner.set("b", 1).unwrap();
711            obj_inner.set("c", true).unwrap();
712
713            let array_inner = Array::new(ctx.clone()).unwrap();
714            array_inner.set(0, 0).unwrap();
715            array_inner.set(1, "foo").unwrap();
716
717            let obj = Object::new(ctx.clone()).unwrap();
718            obj.set("a", obj_inner).unwrap();
719            obj.set("d", array_inner).unwrap();
720
721            let str = ctx
722                .json_stringify(obj)
723                .unwrap()
724                .unwrap()
725                .to_string()
726                .unwrap();
727
728            assert_eq!(str, r#"{"a":{"b":1,"c":true},"d":[0,"foo"]}"#);
729        })
730    }
731
732    #[test]
733    fn userdata() {
734        use crate::{Context, Function, Runtime};
735
736        pub struct MyUserData<'js> {
737            base: Function<'js>,
738        }
739
740        unsafe impl<'js> JsLifetime<'js> for MyUserData<'js> {
741            type Changed<'to> = MyUserData<'to>;
742        }
743
744        let rt = Runtime::new().unwrap();
745        let ctx = Context::full(&rt).unwrap();
746
747        ctx.with(|ctx| {
748            let func = ctx.eval("() => 42").catch(&ctx).unwrap();
749            ctx.store_userdata(MyUserData { base: func }).unwrap();
750        });
751
752        ctx.with(|ctx| {
753            let userdata = ctx.userdata::<MyUserData>().unwrap();
754
755            assert!(ctx.remove_userdata::<MyUserData>().is_err());
756
757            let r: usize = userdata.base.call(()).unwrap();
758            assert_eq!(r, 42)
759        });
760
761        ctx.with(|ctx| {
762            ctx.remove_userdata::<MyUserData>().unwrap().unwrap();
763        })
764    }
765}