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
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 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 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 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 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 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 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 pub fn has_exception(&self) -> bool {
261 unsafe { qjs::JS_HasException(self.ctx.as_ptr()) }
262 }
263
264 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 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 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 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 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 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 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 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 res != 0
404 }
405
406 pub(crate) unsafe fn get_opaque(&self) -> &Opaque<'js> {
407 Opaque::from_runtime_ptr(qjs::JS_GetRuntime(self.ctx.as_ptr()))
408 }
409
410 #[cfg(feature = "futures")]
412 #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "futures")))]
413 pub fn spawn<F>(&self, future: F)
414 where
415 F: Future<Output = ()> + 'js,
416 {
417 unsafe { self.get_opaque().push(future) }
418 }
419
420 pub unsafe fn from_raw_invariant(ctx: NonNull<qjs::JSContext>, inv: Invariant<'js>) -> Self {
427 unsafe { qjs::JS_DupContext(ctx.as_ptr()) };
428 Ctx { ctx, _marker: inv }
429 }
430
431 pub unsafe fn from_raw(ctx: NonNull<qjs::JSContext>) -> Self {
438 unsafe { qjs::JS_DupContext(ctx.as_ptr()) };
439 Ctx {
440 ctx,
441 _marker: Invariant::new(),
442 }
443 }
444
445 pub fn script_or_module_name(&self, stack_level: isize) -> Option<Atom<'js>> {
451 let stack_level = core::ffi::c_int::try_from(stack_level).unwrap();
452 let atom = unsafe { qjs::JS_GetScriptOrModuleName(self.as_ptr(), stack_level) };
453 #[allow(clippy::useless_conversion)] if qjs::__JS_ATOM_NULL == atom.try_into().unwrap() {
455 unsafe { qjs::JS_FreeAtom(self.as_ptr(), atom) };
456 return None;
457 }
458 unsafe { Some(Atom::from_atom_val(self.clone(), atom)) }
459 }
460
461 pub fn run_gc(&self) {
467 unsafe { qjs::JS_RunGC(qjs::JS_GetRuntime(self.ctx.as_ptr())) }
468 }
469
470 pub fn store_userdata<U>(&self, data: U) -> StdResult<Option<Box<U>>, UserDataError<U>>
476 where
477 U: JsLifetime<'js>,
478 U::Changed<'static>: Any,
479 {
480 unsafe { self.get_opaque().insert_userdata(data) }
481 }
482
483 pub fn remove_userdata<U>(&self) -> StdResult<Option<Box<U>>, UserDataError<()>>
488 where
489 U: JsLifetime<'js>,
490 U::Changed<'static>: Any,
491 {
492 unsafe { self.get_opaque().remove_userdata() }
493 }
494
495 pub fn userdata<U>(&self) -> Option<UserDataGuard<U>>
499 where
500 U: JsLifetime<'js>,
501 U::Changed<'static>: Any,
502 {
503 unsafe { self.get_opaque().get_userdata() }
504 }
505
506 pub fn as_raw(&self) -> NonNull<qjs::JSContext> {
508 self.ctx
509 }
510}
511
512#[cfg(test)]
513mod test {
514 use crate::{CatchResultExt, JsLifetime};
515
516 #[test]
517 fn exports() {
518 use crate::{context::intrinsic, Context, Function, Module, Promise, Runtime};
519
520 let runtime = Runtime::new().unwrap();
521 let ctx = Context::custom::<(intrinsic::Promise, intrinsic::Eval)>(&runtime).unwrap();
522 ctx.with(|ctx| {
523 let (module, promise) = Module::declare(ctx, "test", "export default async () => 1;")
524 .unwrap()
525 .eval()
526 .unwrap();
527 promise.finish::<()>().unwrap();
528 let func: Function = module.get("default").unwrap();
529 func.call::<(), Promise>(()).unwrap();
530 });
531 }
532
533 #[test]
534 fn eval() {
535 use crate::{Context, Runtime};
536
537 let runtime = Runtime::new().unwrap();
538 let ctx = Context::full(&runtime).unwrap();
539 ctx.with(|ctx| {
540 let res: String = ctx
541 .eval(
542 r#"
543 function test() {
544 var foo = "bar";
545 return foo;
546 }
547
548 test()
549 "#,
550 )
551 .unwrap();
552
553 assert_eq!("bar".to_string(), res);
554 })
555 }
556
557 #[test]
558 fn eval_minimal_test() {
559 use crate::{Context, Runtime};
560
561 let runtime = Runtime::new().unwrap();
562 let ctx = Context::full(&runtime).unwrap();
563 ctx.with(|ctx| {
564 let res: i32 = ctx.eval(" 1 + 1 ").unwrap();
565 assert_eq!(2, res);
566 })
567 }
568
569 #[test]
570 #[should_panic(expected = "foo is not defined")]
571 fn eval_with_sloppy_code() {
572 use crate::{CatchResultExt, Context, Runtime};
573
574 let runtime = Runtime::new().unwrap();
575 let ctx = Context::full(&runtime).unwrap();
576 ctx.with(|ctx| {
577 let _: String = ctx
578 .eval(
579 r#"
580 function test() {
581 foo = "bar";
582 return foo;
583 }
584
585 test()
586 "#,
587 )
588 .catch(&ctx)
589 .unwrap();
590 })
591 }
592
593 #[test]
594 fn eval_with_options_no_strict_sloppy_code() {
595 use crate::{context::EvalOptions, Context, Runtime};
596
597 let runtime = Runtime::new().unwrap();
598 let ctx = Context::full(&runtime).unwrap();
599 ctx.with(|ctx| {
600 let res: String = ctx
601 .eval_with_options(
602 r#"
603 function test() {
604 foo = "bar";
605 return foo;
606 }
607
608 test()
609 "#,
610 EvalOptions {
611 strict: false,
612 ..Default::default()
613 },
614 )
615 .unwrap();
616
617 assert_eq!("bar".to_string(), res);
618 })
619 }
620
621 #[test]
622 #[should_panic(expected = "foo is not defined")]
623 fn eval_with_options_strict_sloppy_code() {
624 use crate::{context::EvalOptions, CatchResultExt, Context, Runtime};
625
626 let runtime = Runtime::new().unwrap();
627 let ctx = Context::full(&runtime).unwrap();
628 ctx.with(|ctx| {
629 let _: String = ctx
630 .eval_with_options(
631 r#"
632 function test() {
633 foo = "bar";
634 return foo;
635 }
636
637 test()
638 "#,
639 EvalOptions {
640 strict: true,
641 ..Default::default()
642 },
643 )
644 .catch(&ctx)
645 .unwrap();
646 })
647 }
648
649 #[test]
650 fn json_parse() {
651 use crate::{Array, Context, Object, Runtime};
652
653 let runtime = Runtime::new().unwrap();
654 let ctx = Context::full(&runtime).unwrap();
655 ctx.with(|ctx| {
656 let v = ctx
657 .json_parse(r#"{ "a": { "b": 1, "c": true }, "d": [0,"foo"] }"#)
658 .unwrap();
659 let obj = v.into_object().unwrap();
660 let inner_obj: Object = obj.get("a").unwrap();
661 assert_eq!(inner_obj.get::<_, i32>("b").unwrap(), 1);
662 assert!(inner_obj.get::<_, bool>("c").unwrap());
663 let inner_array: Array = obj.get("d").unwrap();
664 assert_eq!(inner_array.get::<i32>(0).unwrap(), 0);
665 assert_eq!(inner_array.get::<String>(1).unwrap(), "foo".to_string());
666 })
667 }
668
669 #[test]
670 fn json_stringify() {
671 use crate::{Array, Context, Object, Runtime};
672
673 let runtime = Runtime::new().unwrap();
674 let ctx = Context::full(&runtime).unwrap();
675 ctx.with(|ctx| {
676 let obj_inner = Object::new(ctx.clone()).unwrap();
677 obj_inner.set("b", 1).unwrap();
678 obj_inner.set("c", true).unwrap();
679
680 let array_inner = Array::new(ctx.clone()).unwrap();
681 array_inner.set(0, 0).unwrap();
682 array_inner.set(1, "foo").unwrap();
683
684 let obj = Object::new(ctx.clone()).unwrap();
685 obj.set("a", obj_inner).unwrap();
686 obj.set("d", array_inner).unwrap();
687
688 let str = ctx
689 .json_stringify(obj)
690 .unwrap()
691 .unwrap()
692 .to_string()
693 .unwrap();
694
695 assert_eq!(str, r#"{"a":{"b":1,"c":true},"d":[0,"foo"]}"#);
696 })
697 }
698
699 #[test]
700 fn userdata() {
701 use crate::{Context, Function, Runtime};
702
703 pub struct MyUserData<'js> {
704 base: Function<'js>,
705 }
706
707 unsafe impl<'js> JsLifetime<'js> for MyUserData<'js> {
708 type Changed<'to> = MyUserData<'to>;
709 }
710
711 let rt = Runtime::new().unwrap();
712 let ctx = Context::full(&rt).unwrap();
713
714 ctx.with(|ctx| {
715 let func = ctx.eval("() => 42").catch(&ctx).unwrap();
716 ctx.store_userdata(MyUserData { base: func }).unwrap();
717 });
718
719 ctx.with(|ctx| {
720 let userdata = ctx.userdata::<MyUserData>().unwrap();
721
722 assert!(ctx.remove_userdata::<MyUserData>().is_err());
723
724 let r: usize = userdata.base.call(()).unwrap();
725 assert_eq!(r, 42)
726 });
727
728 ctx.with(|ctx| {
729 ctx.remove_userdata::<MyUserData>().unwrap().unwrap();
730 })
731 }
732}