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};
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}
39
40impl EvalOptions {
41 fn to_flag(&self) -> i32 {
42 let mut flag = if self.global {
43 qjs::JS_EVAL_TYPE_GLOBAL
44 } else {
45 qjs::JS_EVAL_TYPE_MODULE
46 };
47
48 if self.strict {
49 flag |= qjs::JS_EVAL_FLAG_STRICT;
50 }
51
52 if self.backtrace_barrier {
53 flag |= qjs::JS_EVAL_FLAG_BACKTRACE_BARRIER;
54 }
55
56 if self.promise {
57 flag |= qjs::JS_EVAL_FLAG_ASYNC;
58 }
59
60 flag as i32
61 }
62}
63
64impl Default for EvalOptions {
65 fn default() -> Self {
66 EvalOptions {
67 global: true,
68 strict: true,
69 backtrace_barrier: false,
70 promise: false,
71 }
72 }
73}
74
75#[derive(Debug)]
77pub struct Ctx<'js> {
78 ctx: NonNull<qjs::JSContext>,
79 _marker: Invariant<'js>,
80}
81
82impl<'js> Clone for Ctx<'js> {
83 fn clone(&self) -> Self {
84 unsafe { qjs::JS_DupContext(self.ctx.as_ptr()) };
85 Ctx {
86 ctx: self.ctx,
87 _marker: self._marker,
88 }
89 }
90}
91
92impl<'js> Drop for Ctx<'js> {
93 fn drop(&mut self) {
94 unsafe { qjs::JS_FreeContext(self.ctx.as_ptr()) };
95 }
96}
97
98unsafe impl Send for Ctx<'_> {}
99
100#[repr(C)] pub(crate) struct RefCountHeader {
102 pub ref_count: i32, }
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 let val = qjs::JS_Eval(
146 self.ctx.as_ptr(),
147 src.as_ptr(),
148 len as _,
149 file_name.as_ptr(),
150 flag,
151 );
152 self.handle_exception(val)
153 }
154
155 pub fn eval<V: FromJs<'js>, S: Into<Vec<u8>>>(&self, source: S) -> Result<V> {
157 self.eval_with_options(source, Default::default())
158 }
159
160 pub fn eval_promise<S: Into<Vec<u8>>>(&self, source: S) -> Result<Promise<'js>> {
165 self.eval_with_options(
166 source,
167 EvalOptions {
168 promise: true,
169 ..Default::default()
170 },
171 )
172 }
173
174 pub fn eval_with_options<V: FromJs<'js>, S: Into<Vec<u8>>>(
176 &self,
177 source: S,
178 options: EvalOptions,
179 ) -> Result<V> {
180 let file_name = c"eval_script";
181
182 V::from_js(self, unsafe {
183 let val = self.eval_raw(source, file_name, options.to_flag())?;
184 Value::from_js_value(self.clone(), val)
185 })
186 }
187
188 #[cfg(feature = "std")]
189 pub fn eval_file<V: FromJs<'js>, P: AsRef<Path>>(&self, path: P) -> Result<V> {
191 self.eval_file_with_options(path, Default::default())
192 }
193
194 #[cfg(feature = "std")]
195 pub fn eval_file_with_options<V: FromJs<'js>, P: AsRef<Path>>(
196 &self,
197 path: P,
198 options: EvalOptions,
199 ) -> Result<V> {
200 let buffer = fs::read(path.as_ref())?;
201 let file_name = CString::new(
202 path.as_ref()
203 .file_name()
204 .unwrap()
205 .to_string_lossy()
206 .into_owned(),
207 )?;
208
209 V::from_js(self, unsafe {
210 let val = self.eval_raw(buffer, file_name.as_c_str(), options.to_flag())?;
211 Value::from_js_value(self.clone(), val)
212 })
213 }
214
215 pub fn globals(&self) -> Object<'js> {
217 unsafe {
218 let v = qjs::JS_GetGlobalObject(self.ctx.as_ptr());
219 Object::from_js_value(self.clone(), v)
220 }
221 }
222
223 pub fn catch(&self) -> Value<'js> {
239 unsafe {
240 let v = qjs::JS_GetException(self.ctx.as_ptr());
241 Value::from_js_value(self.clone(), v)
242 }
243 }
244
245 pub fn throw(&self, value: Value<'js>) -> Error {
248 unsafe {
249 let v = value.into_js_value();
250 qjs::JS_Throw(self.ctx.as_ptr(), v);
251 }
252 Error::Exception
253 }
254
255 pub fn json_parse<S>(&self, json: S) -> Result<Value<'js>>
257 where
258 S: Into<Vec<u8>>,
259 {
260 let src = json.into();
261 let len = src.len();
262 let src = CString::new(src)?;
263 unsafe {
264 let name = b"<input>\0";
265 let v = qjs::JS_ParseJSON(
266 self.as_ptr(),
267 src.as_ptr().cast(),
268 len.try_into().expect(qjs::SIZE_T_ERROR),
269 name.as_ptr().cast(),
270 );
271 self.handle_exception(v)?;
272 Ok(Value::from_js_value(self.clone(), v))
273 }
274 }
275
276 pub fn json_stringify<V>(&self, value: V) -> Result<Option<String<'js>>>
278 where
279 V: IntoJs<'js>,
280 {
281 self.json_stringify_inner(&value.into_js(self)?, qjs::JS_UNDEFINED, qjs::JS_UNDEFINED)
282 }
283
284 pub fn json_stringify_replacer<V, R>(
289 &self,
290 value: V,
291 replacer: R,
292 ) -> Result<Option<String<'js>>>
293 where
294 V: IntoJs<'js>,
295 R: IntoJs<'js>,
296 {
297 let replacer = replacer.into_js(self)?;
298
299 self.json_stringify_inner(
300 &value.into_js(self)?,
301 replacer.as_js_value(),
302 qjs::JS_UNDEFINED,
303 )
304 }
305
306 pub fn json_stringify_replacer_space<V, R, S>(
316 &self,
317 value: V,
318 replacer: R,
319 space: S,
320 ) -> Result<Option<String<'js>>>
321 where
322 V: IntoJs<'js>,
323 R: IntoJs<'js>,
324 S: IntoJs<'js>,
325 {
326 let replacer = replacer.into_js(self)?;
327 let space = space.into_js(self)?;
328
329 self.json_stringify_inner(
330 &value.into_js(self)?,
331 replacer.as_js_value(),
332 space.as_js_value(),
333 )
334 }
335
336 fn json_stringify_inner(
338 &self,
339 value: &Value<'js>,
340 replacer: qjs::JSValueConst,
341 space: qjs::JSValueConst,
342 ) -> Result<Option<String<'js>>> {
343 unsafe {
344 let res = qjs::JS_JSONStringify(self.as_ptr(), value.as_js_value(), replacer, space);
345 self.handle_exception(res)?;
346 let v = Value::from_js_value(self.clone(), res);
347 if v.is_undefined() {
348 Ok(None)
349 } else {
350 let v = v.into_string().expect(
351 "JS_JSONStringify did not return either an exception, undefined, or a string",
352 );
353 Ok(Some(v))
354 }
355 }
356 }
357
358 pub fn promise(&self) -> Result<(Promise<'js>, Function<'js>, Function<'js>)> {
360 let mut funcs = mem::MaybeUninit::<(qjs::JSValue, qjs::JSValue)>::uninit();
361
362 Ok(unsafe {
363 let promise = self.handle_exception(qjs::JS_NewPromiseCapability(
364 self.ctx.as_ptr(),
365 funcs.as_mut_ptr() as _,
366 ))?;
367 let (resolve, reject) = funcs.assume_init();
368 (
369 Promise::from_js_value(self.clone(), promise),
370 Function::from_js_value(self.clone(), resolve),
371 Function::from_js_value(self.clone(), reject),
372 )
373 })
374 }
375
376 pub fn execute_pending_job(&self) -> bool {
381 let mut ptr = MaybeUninit::<*mut qjs::JSContext>::uninit();
382 let rt = unsafe { qjs::JS_GetRuntime(self.ctx.as_ptr()) };
383 let res = unsafe { qjs::JS_ExecutePendingJob(rt, ptr.as_mut_ptr()) };
384 res != 0
385 }
386
387 pub(crate) unsafe fn get_opaque(&self) -> &Opaque<'js> {
388 Opaque::from_runtime_ptr(qjs::JS_GetRuntime(self.ctx.as_ptr()))
389 }
390
391 #[cfg(feature = "futures")]
393 #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "futures")))]
394 pub fn spawn<F>(&self, future: F)
395 where
396 F: Future<Output = ()> + 'js,
397 {
398 unsafe { self.get_opaque().push(future) }
399 }
400
401 pub unsafe fn from_raw_invariant(ctx: NonNull<qjs::JSContext>, inv: Invariant<'js>) -> Self {
408 unsafe { qjs::JS_DupContext(ctx.as_ptr()) };
409 Ctx { ctx, _marker: inv }
410 }
411
412 pub unsafe fn from_raw(ctx: NonNull<qjs::JSContext>) -> Self {
419 unsafe { qjs::JS_DupContext(ctx.as_ptr()) };
420 Ctx {
421 ctx,
422 _marker: Invariant::new(),
423 }
424 }
425
426 pub fn script_or_module_name(&self, stack_level: isize) -> Option<Atom<'js>> {
432 let stack_level = core::ffi::c_int::try_from(stack_level).unwrap();
433 let atom = unsafe { qjs::JS_GetScriptOrModuleName(self.as_ptr(), stack_level) };
434 #[allow(clippy::useless_conversion)] if qjs::__JS_ATOM_NULL == atom.try_into().unwrap() {
436 unsafe { qjs::JS_FreeAtom(self.as_ptr(), atom) };
437 return None;
438 }
439 unsafe { Some(Atom::from_atom_val(self.clone(), atom)) }
440 }
441
442 pub fn run_gc(&self) {
448 unsafe { qjs::JS_RunGC(qjs::JS_GetRuntime(self.ctx.as_ptr())) }
449 }
450
451 pub fn store_userdata<U>(&self, data: U) -> StdResult<Option<Box<U>>, UserDataError<U>>
457 where
458 U: JsLifetime<'js>,
459 U::Changed<'static>: Any,
460 {
461 unsafe { self.get_opaque().insert_userdata(data) }
462 }
463
464 pub fn remove_userdata<U>(&self) -> StdResult<Option<Box<U>>, UserDataError<()>>
469 where
470 U: JsLifetime<'js>,
471 U::Changed<'static>: Any,
472 {
473 unsafe { self.get_opaque().remove_userdata() }
474 }
475
476 pub fn userdata<U>(&self) -> Option<UserDataGuard<U>>
480 where
481 U: JsLifetime<'js>,
482 U::Changed<'static>: Any,
483 {
484 unsafe { self.get_opaque().get_userdata() }
485 }
486
487 pub fn as_raw(&self) -> NonNull<qjs::JSContext> {
489 self.ctx
490 }
491}
492
493#[cfg(test)]
494mod test {
495 use crate::{CatchResultExt, JsLifetime};
496
497 #[test]
498 fn exports() {
499 use crate::{context::intrinsic, Context, Function, Module, Promise, Runtime};
500
501 let runtime = Runtime::new().unwrap();
502 let ctx = Context::custom::<(intrinsic::Promise, intrinsic::Eval)>(&runtime).unwrap();
503 ctx.with(|ctx| {
504 let (module, promise) = Module::declare(ctx, "test", "export default async () => 1;")
505 .unwrap()
506 .eval()
507 .unwrap();
508 promise.finish::<()>().unwrap();
509 let func: Function = module.get("default").unwrap();
510 func.call::<(), Promise>(()).unwrap();
511 });
512 }
513
514 #[test]
515 fn eval() {
516 use crate::{Context, Runtime};
517
518 let runtime = Runtime::new().unwrap();
519 let ctx = Context::full(&runtime).unwrap();
520 ctx.with(|ctx| {
521 let res: String = ctx
522 .eval(
523 r#"
524 function test() {
525 var foo = "bar";
526 return foo;
527 }
528
529 test()
530 "#,
531 )
532 .unwrap();
533
534 assert_eq!("bar".to_string(), res);
535 })
536 }
537
538 #[test]
539 fn eval_minimal_test() {
540 use crate::{Context, Runtime};
541
542 let runtime = Runtime::new().unwrap();
543 let ctx = Context::full(&runtime).unwrap();
544 ctx.with(|ctx| {
545 let res: i32 = ctx.eval(" 1 + 1 ").unwrap();
546 assert_eq!(2, res);
547 })
548 }
549
550 #[test]
551 #[should_panic(expected = "foo is not defined")]
552 fn eval_with_sloppy_code() {
553 use crate::{CatchResultExt, Context, Runtime};
554
555 let runtime = Runtime::new().unwrap();
556 let ctx = Context::full(&runtime).unwrap();
557 ctx.with(|ctx| {
558 let _: String = ctx
559 .eval(
560 r#"
561 function test() {
562 foo = "bar";
563 return foo;
564 }
565
566 test()
567 "#,
568 )
569 .catch(&ctx)
570 .unwrap();
571 })
572 }
573
574 #[test]
575 fn eval_with_options_no_strict_sloppy_code() {
576 use crate::{context::EvalOptions, Context, Runtime};
577
578 let runtime = Runtime::new().unwrap();
579 let ctx = Context::full(&runtime).unwrap();
580 ctx.with(|ctx| {
581 let res: String = ctx
582 .eval_with_options(
583 r#"
584 function test() {
585 foo = "bar";
586 return foo;
587 }
588
589 test()
590 "#,
591 EvalOptions {
592 strict: false,
593 ..Default::default()
594 },
595 )
596 .unwrap();
597
598 assert_eq!("bar".to_string(), res);
599 })
600 }
601
602 #[test]
603 #[should_panic(expected = "foo is not defined")]
604 fn eval_with_options_strict_sloppy_code() {
605 use crate::{context::EvalOptions, 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_with_options(
612 r#"
613 function test() {
614 foo = "bar";
615 return foo;
616 }
617
618 test()
619 "#,
620 EvalOptions {
621 strict: true,
622 ..Default::default()
623 },
624 )
625 .catch(&ctx)
626 .unwrap();
627 })
628 }
629
630 #[test]
631 fn json_parse() {
632 use crate::{Array, Context, Object, Runtime};
633
634 let runtime = Runtime::new().unwrap();
635 let ctx = Context::full(&runtime).unwrap();
636 ctx.with(|ctx| {
637 let v = ctx
638 .json_parse(r#"{ "a": { "b": 1, "c": true }, "d": [0,"foo"] }"#)
639 .unwrap();
640 let obj = v.into_object().unwrap();
641 let inner_obj: Object = obj.get("a").unwrap();
642 assert_eq!(inner_obj.get::<_, i32>("b").unwrap(), 1);
643 assert!(inner_obj.get::<_, bool>("c").unwrap());
644 let inner_array: Array = obj.get("d").unwrap();
645 assert_eq!(inner_array.get::<i32>(0).unwrap(), 0);
646 assert_eq!(inner_array.get::<String>(1).unwrap(), "foo".to_string());
647 })
648 }
649
650 #[test]
651 fn json_stringify() {
652 use crate::{Array, Context, Object, Runtime};
653
654 let runtime = Runtime::new().unwrap();
655 let ctx = Context::full(&runtime).unwrap();
656 ctx.with(|ctx| {
657 let obj_inner = Object::new(ctx.clone()).unwrap();
658 obj_inner.set("b", 1).unwrap();
659 obj_inner.set("c", true).unwrap();
660
661 let array_inner = Array::new(ctx.clone()).unwrap();
662 array_inner.set(0, 0).unwrap();
663 array_inner.set(1, "foo").unwrap();
664
665 let obj = Object::new(ctx.clone()).unwrap();
666 obj.set("a", obj_inner).unwrap();
667 obj.set("d", array_inner).unwrap();
668
669 let str = ctx
670 .json_stringify(obj)
671 .unwrap()
672 .unwrap()
673 .to_string()
674 .unwrap();
675
676 assert_eq!(str, r#"{"a":{"b":1,"c":true},"d":[0,"foo"]}"#);
677 })
678 }
679
680 #[test]
681 fn userdata() {
682 use crate::{Context, Function, Runtime};
683
684 pub struct MyUserData<'js> {
685 base: Function<'js>,
686 }
687
688 unsafe impl<'js> JsLifetime<'js> for MyUserData<'js> {
689 type Changed<'to> = MyUserData<'to>;
690 }
691
692 let rt = Runtime::new().unwrap();
693 let ctx = Context::full(&rt).unwrap();
694
695 ctx.with(|ctx| {
696 let func = ctx.eval("() => 42").catch(&ctx).unwrap();
697 ctx.store_userdata(MyUserData { base: func }).unwrap();
698 });
699
700 ctx.with(|ctx| {
701 let userdata = ctx.userdata::<MyUserData>().unwrap();
702
703 assert!(ctx.remove_userdata::<MyUserData>().is_err());
704
705 let r: usize = userdata.base.call(()).unwrap();
706 assert_eq!(r, 42)
707 });
708
709 ctx.with(|ctx| {
710 ctx.remove_userdata::<MyUserData>().unwrap().unwrap();
711 })
712 }
713}