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#[non_exhaustive]
29pub struct EvalOptions {
30 pub global: bool,
32 pub strict: bool,
34 pub backtrace_barrier: bool,
36 pub promise: bool,
38 #[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#[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
105#[repr(C)] pub(crate) struct RefCountHeader {
107 pub ref_count: i32, }
109
110impl<'js> Ctx<'js> {
111 pub(crate) fn as_ptr(&self) -> *mut qjs::JSContext {
112 self.ctx.as_ptr()
113 }
114
115 pub(crate) unsafe fn from_ptr(ctx: *mut qjs::JSContext) -> Self {
116 unsafe { qjs::JS_DupContext(ctx) };
117 let ctx = NonNull::new_unchecked(ctx);
118 Ctx {
119 ctx,
120 _marker: Invariant::new(),
121 }
122 }
123
124 pub(crate) unsafe fn new(ctx: &'js Context) -> Self {
125 unsafe { qjs::JS_DupContext(ctx.0.ctx().as_ptr()) };
126 Ctx {
127 ctx: ctx.0.ctx(),
128 _marker: Invariant::new(),
129 }
130 }
131
132 #[cfg(feature = "futures")]
133 pub(crate) unsafe fn new_async(ctx: &'js AsyncContext) -> Self {
134 unsafe { qjs::JS_DupContext(ctx.0.ctx().as_ptr()) };
135 Ctx {
136 ctx: ctx.0.ctx(),
137 _marker: Invariant::new(),
138 }
139 }
140
141 pub(crate) unsafe fn eval_raw<S: Into<Vec<u8>>>(
142 &self,
143 source: S,
144 file_name: &CStr,
145 flag: i32,
146 ) -> Result<qjs::JSValue> {
147 let src = source.into();
148 let len = src.len();
149 let src = CString::new(src)?;
150 let val = qjs::JS_Eval(
151 self.ctx.as_ptr(),
152 src.as_ptr(),
153 len as _,
154 file_name.as_ptr(),
155 flag,
156 );
157 self.handle_exception(val)
158 }
159
160 pub fn eval<V: FromJs<'js>, S: Into<Vec<u8>>>(&self, source: S) -> Result<V> {
162 self.eval_with_options(source, Default::default())
163 }
164
165 pub fn eval_promise<S: Into<Vec<u8>>>(&self, source: S) -> Result<Promise<'js>> {
170 self.eval_with_options(
171 source,
172 EvalOptions {
173 promise: true,
174 ..Default::default()
175 },
176 )
177 }
178
179 pub fn eval_with_options<V: FromJs<'js>, S: Into<Vec<u8>>>(
181 &self,
182 source: S,
183 options: EvalOptions,
184 ) -> Result<V> {
185 #[cfg(feature = "std")]
186 let file_name = {
187 if let Some(filename) = &options.filename {
188 &CString::new(filename.clone())?
189 } else {
190 c"eval_script"
191 }
192 };
193
194 #[cfg(not(feature = "std"))]
195 let file_name = c"eval_script";
196
197 V::from_js(self, unsafe {
198 let val = self.eval_raw(source, file_name, options.to_flag())?;
199 Value::from_js_value(self.clone(), val)
200 })
201 }
202
203 #[cfg(feature = "std")]
204 pub fn eval_file<V: FromJs<'js>, P: AsRef<Path>>(&self, path: P) -> Result<V> {
206 self.eval_file_with_options(path, Default::default())
207 }
208
209 #[cfg(feature = "std")]
210 pub fn eval_file_with_options<V: FromJs<'js>, P: AsRef<Path>>(
211 &self,
212 path: P,
213 options: EvalOptions,
214 ) -> Result<V> {
215 let buffer = fs::read(path.as_ref())?;
216 let file_name = CString::new(
217 path.as_ref()
218 .file_name()
219 .unwrap()
220 .to_string_lossy()
221 .into_owned(),
222 )?;
223
224 V::from_js(self, unsafe {
225 let val = self.eval_raw(buffer, file_name.as_c_str(), options.to_flag())?;
226 Value::from_js_value(self.clone(), val)
227 })
228 }
229
230 pub fn globals(&self) -> Object<'js> {
232 unsafe {
233 let v = qjs::JS_GetGlobalObject(self.ctx.as_ptr());
234 Object::from_js_value(self.clone(), v)
235 }
236 }
237
238 pub fn catch(&self) -> Value<'js> {
254 unsafe {
255 let v = qjs::JS_GetException(self.ctx.as_ptr());
256 Value::from_js_value(self.clone(), v)
257 }
258 }
259
260 pub fn throw(&self, value: Value<'js>) -> Error {
263 unsafe {
264 let v = value.into_js_value();
265 qjs::JS_Throw(self.ctx.as_ptr(), v);
266 }
267 Error::Exception
268 }
269
270 pub fn json_parse<S>(&self, json: S) -> Result<Value<'js>>
272 where
273 S: Into<Vec<u8>>,
274 {
275 let src = json.into();
276 let len = src.len();
277 let src = CString::new(src)?;
278 unsafe {
279 let name = b"<input>\0";
280 let v = qjs::JS_ParseJSON(
281 self.as_ptr(),
282 src.as_ptr().cast(),
283 len.try_into().expect(qjs::SIZE_T_ERROR),
284 name.as_ptr().cast(),
285 );
286 self.handle_exception(v)?;
287 Ok(Value::from_js_value(self.clone(), v))
288 }
289 }
290
291 pub fn json_stringify<V>(&self, value: V) -> Result<Option<String<'js>>>
293 where
294 V: IntoJs<'js>,
295 {
296 self.json_stringify_inner(&value.into_js(self)?, qjs::JS_UNDEFINED, qjs::JS_UNDEFINED)
297 }
298
299 pub fn json_stringify_replacer<V, R>(
304 &self,
305 value: V,
306 replacer: R,
307 ) -> Result<Option<String<'js>>>
308 where
309 V: IntoJs<'js>,
310 R: IntoJs<'js>,
311 {
312 let replacer = replacer.into_js(self)?;
313
314 self.json_stringify_inner(
315 &value.into_js(self)?,
316 replacer.as_js_value(),
317 qjs::JS_UNDEFINED,
318 )
319 }
320
321 pub fn json_stringify_replacer_space<V, R, S>(
331 &self,
332 value: V,
333 replacer: R,
334 space: S,
335 ) -> Result<Option<String<'js>>>
336 where
337 V: IntoJs<'js>,
338 R: IntoJs<'js>,
339 S: IntoJs<'js>,
340 {
341 let replacer = replacer.into_js(self)?;
342 let space = space.into_js(self)?;
343
344 self.json_stringify_inner(
345 &value.into_js(self)?,
346 replacer.as_js_value(),
347 space.as_js_value(),
348 )
349 }
350
351 fn json_stringify_inner(
353 &self,
354 value: &Value<'js>,
355 replacer: qjs::JSValueConst,
356 space: qjs::JSValueConst,
357 ) -> Result<Option<String<'js>>> {
358 unsafe {
359 let res = qjs::JS_JSONStringify(self.as_ptr(), value.as_js_value(), replacer, space);
360 self.handle_exception(res)?;
361 let v = Value::from_js_value(self.clone(), res);
362 if v.is_undefined() {
363 Ok(None)
364 } else {
365 let v = v.into_string().expect(
366 "JS_JSONStringify did not return either an exception, undefined, or a string",
367 );
368 Ok(Some(v))
369 }
370 }
371 }
372
373 pub fn promise(&self) -> Result<(Promise<'js>, Function<'js>, Function<'js>)> {
375 let mut funcs = mem::MaybeUninit::<(qjs::JSValue, qjs::JSValue)>::uninit();
376
377 Ok(unsafe {
378 let promise = self.handle_exception(qjs::JS_NewPromiseCapability(
379 self.ctx.as_ptr(),
380 funcs.as_mut_ptr() as _,
381 ))?;
382 let (resolve, reject) = funcs.assume_init();
383 (
384 Promise::from_js_value(self.clone(), promise),
385 Function::from_js_value(self.clone(), resolve),
386 Function::from_js_value(self.clone(), reject),
387 )
388 })
389 }
390
391 pub fn execute_pending_job(&self) -> bool {
396 let mut ptr = MaybeUninit::<*mut qjs::JSContext>::uninit();
397 let rt = unsafe { qjs::JS_GetRuntime(self.ctx.as_ptr()) };
398 let res = unsafe { qjs::JS_ExecutePendingJob(rt, ptr.as_mut_ptr()) };
399 res != 0
400 }
401
402 pub(crate) unsafe fn get_opaque(&self) -> &Opaque<'js> {
403 Opaque::from_runtime_ptr(qjs::JS_GetRuntime(self.ctx.as_ptr()))
404 }
405
406 #[cfg(feature = "futures")]
408 #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "futures")))]
409 pub fn spawn<F>(&self, future: F)
410 where
411 F: Future<Output = ()> + 'js,
412 {
413 unsafe { self.get_opaque().push(future) }
414 }
415
416 pub unsafe fn from_raw_invariant(ctx: NonNull<qjs::JSContext>, inv: Invariant<'js>) -> Self {
423 unsafe { qjs::JS_DupContext(ctx.as_ptr()) };
424 Ctx { ctx, _marker: inv }
425 }
426
427 pub unsafe fn from_raw(ctx: NonNull<qjs::JSContext>) -> Self {
434 unsafe { qjs::JS_DupContext(ctx.as_ptr()) };
435 Ctx {
436 ctx,
437 _marker: Invariant::new(),
438 }
439 }
440
441 pub fn script_or_module_name(&self, stack_level: isize) -> Option<Atom<'js>> {
447 let stack_level = core::ffi::c_int::try_from(stack_level).unwrap();
448 let atom = unsafe { qjs::JS_GetScriptOrModuleName(self.as_ptr(), stack_level) };
449 #[allow(clippy::useless_conversion)] if qjs::__JS_ATOM_NULL == atom.try_into().unwrap() {
451 unsafe { qjs::JS_FreeAtom(self.as_ptr(), atom) };
452 return None;
453 }
454 unsafe { Some(Atom::from_atom_val(self.clone(), atom)) }
455 }
456
457 pub fn run_gc(&self) {
463 unsafe { qjs::JS_RunGC(qjs::JS_GetRuntime(self.ctx.as_ptr())) }
464 }
465
466 pub fn store_userdata<U>(&self, data: U) -> StdResult<Option<Box<U>>, UserDataError<U>>
472 where
473 U: JsLifetime<'js>,
474 U::Changed<'static>: Any,
475 {
476 unsafe { self.get_opaque().insert_userdata(data) }
477 }
478
479 pub fn remove_userdata<U>(&self) -> StdResult<Option<Box<U>>, UserDataError<()>>
484 where
485 U: JsLifetime<'js>,
486 U::Changed<'static>: Any,
487 {
488 unsafe { self.get_opaque().remove_userdata() }
489 }
490
491 pub fn userdata<U>(&self) -> Option<UserDataGuard<U>>
495 where
496 U: JsLifetime<'js>,
497 U::Changed<'static>: Any,
498 {
499 unsafe { self.get_opaque().get_userdata() }
500 }
501
502 pub fn as_raw(&self) -> NonNull<qjs::JSContext> {
504 self.ctx
505 }
506}
507
508#[cfg(test)]
509mod test {
510 use crate::{CatchResultExt, JsLifetime};
511
512 #[test]
513 fn exports() {
514 use crate::{context::intrinsic, Context, Function, Module, Promise, Runtime};
515
516 let runtime = Runtime::new().unwrap();
517 let ctx = Context::custom::<(intrinsic::Promise, intrinsic::Eval)>(&runtime).unwrap();
518 ctx.with(|ctx| {
519 let (module, promise) = Module::declare(ctx, "test", "export default async () => 1;")
520 .unwrap()
521 .eval()
522 .unwrap();
523 promise.finish::<()>().unwrap();
524 let func: Function = module.get("default").unwrap();
525 func.call::<(), Promise>(()).unwrap();
526 });
527 }
528
529 #[test]
530 fn eval() {
531 use crate::{Context, Runtime};
532
533 let runtime = Runtime::new().unwrap();
534 let ctx = Context::full(&runtime).unwrap();
535 ctx.with(|ctx| {
536 let res: String = ctx
537 .eval(
538 r#"
539 function test() {
540 var foo = "bar";
541 return foo;
542 }
543
544 test()
545 "#,
546 )
547 .unwrap();
548
549 assert_eq!("bar".to_string(), res);
550 })
551 }
552
553 #[test]
554 fn eval_minimal_test() {
555 use crate::{Context, Runtime};
556
557 let runtime = Runtime::new().unwrap();
558 let ctx = Context::full(&runtime).unwrap();
559 ctx.with(|ctx| {
560 let res: i32 = ctx.eval(" 1 + 1 ").unwrap();
561 assert_eq!(2, res);
562 })
563 }
564
565 #[test]
566 #[should_panic(expected = "foo is not defined")]
567 fn eval_with_sloppy_code() {
568 use crate::{CatchResultExt, Context, Runtime};
569
570 let runtime = Runtime::new().unwrap();
571 let ctx = Context::full(&runtime).unwrap();
572 ctx.with(|ctx| {
573 let _: String = ctx
574 .eval(
575 r#"
576 function test() {
577 foo = "bar";
578 return foo;
579 }
580
581 test()
582 "#,
583 )
584 .catch(&ctx)
585 .unwrap();
586 })
587 }
588
589 #[test]
590 fn eval_with_options_no_strict_sloppy_code() {
591 use crate::{context::EvalOptions, Context, Runtime};
592
593 let runtime = Runtime::new().unwrap();
594 let ctx = Context::full(&runtime).unwrap();
595 ctx.with(|ctx| {
596 let res: String = ctx
597 .eval_with_options(
598 r#"
599 function test() {
600 foo = "bar";
601 return foo;
602 }
603
604 test()
605 "#,
606 EvalOptions {
607 strict: false,
608 ..Default::default()
609 },
610 )
611 .unwrap();
612
613 assert_eq!("bar".to_string(), res);
614 })
615 }
616
617 #[test]
618 #[should_panic(expected = "foo is not defined")]
619 fn eval_with_options_strict_sloppy_code() {
620 use crate::{context::EvalOptions, CatchResultExt, Context, Runtime};
621
622 let runtime = Runtime::new().unwrap();
623 let ctx = Context::full(&runtime).unwrap();
624 ctx.with(|ctx| {
625 let _: String = ctx
626 .eval_with_options(
627 r#"
628 function test() {
629 foo = "bar";
630 return foo;
631 }
632
633 test()
634 "#,
635 EvalOptions {
636 strict: true,
637 ..Default::default()
638 },
639 )
640 .catch(&ctx)
641 .unwrap();
642 })
643 }
644
645 #[test]
646 fn json_parse() {
647 use crate::{Array, Context, Object, Runtime};
648
649 let runtime = Runtime::new().unwrap();
650 let ctx = Context::full(&runtime).unwrap();
651 ctx.with(|ctx| {
652 let v = ctx
653 .json_parse(r#"{ "a": { "b": 1, "c": true }, "d": [0,"foo"] }"#)
654 .unwrap();
655 let obj = v.into_object().unwrap();
656 let inner_obj: Object = obj.get("a").unwrap();
657 assert_eq!(inner_obj.get::<_, i32>("b").unwrap(), 1);
658 assert!(inner_obj.get::<_, bool>("c").unwrap());
659 let inner_array: Array = obj.get("d").unwrap();
660 assert_eq!(inner_array.get::<i32>(0).unwrap(), 0);
661 assert_eq!(inner_array.get::<String>(1).unwrap(), "foo".to_string());
662 })
663 }
664
665 #[test]
666 fn json_stringify() {
667 use crate::{Array, Context, Object, Runtime};
668
669 let runtime = Runtime::new().unwrap();
670 let ctx = Context::full(&runtime).unwrap();
671 ctx.with(|ctx| {
672 let obj_inner = Object::new(ctx.clone()).unwrap();
673 obj_inner.set("b", 1).unwrap();
674 obj_inner.set("c", true).unwrap();
675
676 let array_inner = Array::new(ctx.clone()).unwrap();
677 array_inner.set(0, 0).unwrap();
678 array_inner.set(1, "foo").unwrap();
679
680 let obj = Object::new(ctx.clone()).unwrap();
681 obj.set("a", obj_inner).unwrap();
682 obj.set("d", array_inner).unwrap();
683
684 let str = ctx
685 .json_stringify(obj)
686 .unwrap()
687 .unwrap()
688 .to_string()
689 .unwrap();
690
691 assert_eq!(str, r#"{"a":{"b":1,"c":true},"d":[0,"foo"]}"#);
692 })
693 }
694
695 #[test]
696 fn userdata() {
697 use crate::{Context, Function, Runtime};
698
699 pub struct MyUserData<'js> {
700 base: Function<'js>,
701 }
702
703 unsafe impl<'js> JsLifetime<'js> for MyUserData<'js> {
704 type Changed<'to> = MyUserData<'to>;
705 }
706
707 let rt = Runtime::new().unwrap();
708 let ctx = Context::full(&rt).unwrap();
709
710 ctx.with(|ctx| {
711 let func = ctx.eval("() => 42").catch(&ctx).unwrap();
712 ctx.store_userdata(MyUserData { base: func }).unwrap();
713 });
714
715 ctx.with(|ctx| {
716 let userdata = ctx.userdata::<MyUserData>().unwrap();
717
718 assert!(ctx.remove_userdata::<MyUserData>().is_err());
719
720 let r: usize = userdata.base.call(()).unwrap();
721 assert_eq!(r, 42)
722 });
723
724 ctx.with(|ctx| {
725 ctx.remove_userdata::<MyUserData>().unwrap().unwrap();
726 })
727 }
728}