1#[cfg(feature = "futures")]
2use std::future::Future;
3use std::{
4 any::Any,
5 ffi::{CStr, CString},
6 fs,
7 mem::{self, MaybeUninit},
8 path::Path,
9 ptr::NonNull,
10 result::Result as StdResult,
11};
12
13#[cfg(feature = "futures")]
14use crate::AsyncContext;
15use crate::{
16 markers::Invariant,
17 qjs,
18 runtime::{opaque::Opaque, UserDataError, UserDataGuard},
19 Atom, Error, FromJs, Function, IntoJs, JsLifetime, Object, Promise, Result, String, Value,
20};
21
22use super::Context;
23
24#[non_exhaustive]
26pub struct EvalOptions {
27 pub global: bool,
29 pub strict: bool,
31 pub backtrace_barrier: bool,
33 pub promise: bool,
35}
36
37impl EvalOptions {
38 fn to_flag(&self) -> i32 {
39 let mut flag = if self.global {
40 qjs::JS_EVAL_TYPE_GLOBAL
41 } else {
42 qjs::JS_EVAL_TYPE_MODULE
43 };
44
45 if self.strict {
46 flag |= qjs::JS_EVAL_FLAG_STRICT;
47 }
48
49 if self.backtrace_barrier {
50 flag |= qjs::JS_EVAL_FLAG_BACKTRACE_BARRIER;
51 }
52
53 if self.promise {
54 flag |= qjs::JS_EVAL_FLAG_ASYNC;
55 }
56
57 flag as i32
58 }
59}
60
61impl Default for EvalOptions {
62 fn default() -> Self {
63 EvalOptions {
64 global: true,
65 strict: true,
66 backtrace_barrier: false,
67 promise: false,
68 }
69 }
70}
71
72#[derive(Debug)]
74pub struct Ctx<'js> {
75 ctx: NonNull<qjs::JSContext>,
76 _marker: Invariant<'js>,
77}
78
79impl<'js> Clone for Ctx<'js> {
80 fn clone(&self) -> Self {
81 unsafe { qjs::JS_DupContext(self.ctx.as_ptr()) };
82 Ctx {
83 ctx: self.ctx,
84 _marker: self._marker,
85 }
86 }
87}
88
89impl<'js> Drop for Ctx<'js> {
90 fn drop(&mut self) {
91 unsafe { qjs::JS_FreeContext(self.ctx.as_ptr()) };
92 }
93}
94
95unsafe impl Send for Ctx<'_> {}
96
97#[repr(C)] pub(crate) struct RefCountHeader {
99 pub ref_count: i32, }
101
102impl<'js> Ctx<'js> {
103 pub(crate) fn as_ptr(&self) -> *mut qjs::JSContext {
104 self.ctx.as_ptr()
105 }
106
107 pub(crate) unsafe fn from_ptr(ctx: *mut qjs::JSContext) -> Self {
108 unsafe { qjs::JS_DupContext(ctx) };
109 let ctx = NonNull::new_unchecked(ctx);
110 Ctx {
111 ctx,
112 _marker: Invariant::new(),
113 }
114 }
115
116 pub(crate) unsafe fn new(ctx: &'js Context) -> Self {
117 unsafe { qjs::JS_DupContext(ctx.0.ctx.as_ptr()) };
118 Ctx {
119 ctx: ctx.0.ctx,
120 _marker: Invariant::new(),
121 }
122 }
123
124 #[cfg(feature = "futures")]
125 pub(crate) unsafe fn new_async(ctx: &'js AsyncContext) -> Self {
126 unsafe { qjs::JS_DupContext(ctx.0.ctx.as_ptr()) };
127 Ctx {
128 ctx: ctx.0.ctx,
129 _marker: Invariant::new(),
130 }
131 }
132
133 pub(crate) unsafe fn eval_raw<S: Into<Vec<u8>>>(
134 &self,
135 source: S,
136 file_name: &CStr,
137 flag: i32,
138 ) -> Result<qjs::JSValue> {
139 let src = source.into();
140 let len = src.len();
141 let src = CString::new(src)?;
142 let val = qjs::JS_Eval(
143 self.ctx.as_ptr(),
144 src.as_ptr(),
145 len as _,
146 file_name.as_ptr(),
147 flag,
148 );
149 self.handle_exception(val)
150 }
151
152 pub fn eval<V: FromJs<'js>, S: Into<Vec<u8>>>(&self, source: S) -> Result<V> {
154 self.eval_with_options(source, Default::default())
155 }
156
157 pub fn eval_promise<S: Into<Vec<u8>>>(&self, source: S) -> Result<Promise<'js>> {
162 self.eval_with_options(
163 source,
164 EvalOptions {
165 promise: true,
166 ..Default::default()
167 },
168 )
169 }
170
171 pub fn eval_with_options<V: FromJs<'js>, S: Into<Vec<u8>>>(
173 &self,
174 source: S,
175 options: EvalOptions,
176 ) -> Result<V> {
177 let file_name = CStr::from_bytes_with_nul(b"eval_script\0").unwrap();
178
179 V::from_js(self, unsafe {
180 let val = self.eval_raw(source, file_name, options.to_flag())?;
181 Value::from_js_value(self.clone(), val)
182 })
183 }
184
185 pub fn eval_file<V: FromJs<'js>, P: AsRef<Path>>(&self, path: P) -> Result<V> {
187 self.eval_file_with_options(path, Default::default())
188 }
189
190 pub fn eval_file_with_options<V: FromJs<'js>, P: AsRef<Path>>(
191 &self,
192 path: P,
193 options: EvalOptions,
194 ) -> Result<V> {
195 let buffer = fs::read(path.as_ref())?;
196 let file_name = CString::new(
197 path.as_ref()
198 .file_name()
199 .unwrap()
200 .to_string_lossy()
201 .into_owned(),
202 )?;
203
204 V::from_js(self, unsafe {
205 let val = self.eval_raw(buffer, file_name.as_c_str(), options.to_flag())?;
206 Value::from_js_value(self.clone(), val)
207 })
208 }
209
210 pub fn globals(&self) -> Object<'js> {
212 unsafe {
213 let v = qjs::JS_GetGlobalObject(self.ctx.as_ptr());
214 Object::from_js_value(self.clone(), v)
215 }
216 }
217
218 pub fn catch(&self) -> Value<'js> {
234 unsafe {
235 let v = qjs::JS_GetException(self.ctx.as_ptr());
236 Value::from_js_value(self.clone(), v)
237 }
238 }
239
240 pub fn throw(&self, value: Value<'js>) -> Error {
243 unsafe {
244 let v = value.into_js_value();
245 qjs::JS_Throw(self.ctx.as_ptr(), v);
246 }
247 Error::Exception
248 }
249
250 pub fn json_parse<S>(&self, json: S) -> Result<Value<'js>>
252 where
253 S: Into<Vec<u8>>,
254 {
255 let src = json.into();
256 let len = src.len();
257 let src = CString::new(src)?;
258 unsafe {
259 let name = b"<input>\0";
260 let v = qjs::JS_ParseJSON(
261 self.as_ptr(),
262 src.as_ptr().cast(),
263 len.try_into().expect(qjs::SIZE_T_ERROR),
264 name.as_ptr().cast(),
265 );
266 self.handle_exception(v)?;
267 Ok(Value::from_js_value(self.clone(), v))
268 }
269 }
270
271 pub fn json_stringify<V>(&self, value: V) -> Result<Option<String<'js>>>
273 where
274 V: IntoJs<'js>,
275 {
276 self.json_stringify_inner(&value.into_js(self)?, qjs::JS_UNDEFINED, qjs::JS_UNDEFINED)
277 }
278
279 pub fn json_stringify_replacer<V, R>(
284 &self,
285 value: V,
286 replacer: R,
287 ) -> Result<Option<String<'js>>>
288 where
289 V: IntoJs<'js>,
290 R: IntoJs<'js>,
291 {
292 let replacer = replacer.into_js(self)?;
293
294 self.json_stringify_inner(
295 &value.into_js(self)?,
296 replacer.as_js_value(),
297 qjs::JS_UNDEFINED,
298 )
299 }
300
301 pub fn json_stringify_replacer_space<V, R, S>(
311 &self,
312 value: V,
313 replacer: R,
314 space: S,
315 ) -> Result<Option<String<'js>>>
316 where
317 V: IntoJs<'js>,
318 R: IntoJs<'js>,
319 S: IntoJs<'js>,
320 {
321 let replacer = replacer.into_js(self)?;
322 let space = space.into_js(self)?;
323
324 self.json_stringify_inner(
325 &value.into_js(self)?,
326 replacer.as_js_value(),
327 space.as_js_value(),
328 )
329 }
330
331 fn json_stringify_inner(
333 &self,
334 value: &Value<'js>,
335 replacer: qjs::JSValueConst,
336 space: qjs::JSValueConst,
337 ) -> Result<Option<String<'js>>> {
338 unsafe {
339 let res = qjs::JS_JSONStringify(self.as_ptr(), value.as_js_value(), replacer, space);
340 self.handle_exception(res)?;
341 let v = Value::from_js_value(self.clone(), res);
342 if v.is_undefined() {
343 Ok(None)
344 } else {
345 let v = v.into_string().expect(
346 "JS_JSONStringify did not return either an exception, undefined, or a string",
347 );
348 Ok(Some(v))
349 }
350 }
351 }
352
353 pub fn promise(&self) -> Result<(Promise<'js>, Function<'js>, Function<'js>)> {
355 let mut funcs = mem::MaybeUninit::<(qjs::JSValue, qjs::JSValue)>::uninit();
356
357 Ok(unsafe {
358 let promise = self.handle_exception(qjs::JS_NewPromiseCapability(
359 self.ctx.as_ptr(),
360 funcs.as_mut_ptr() as _,
361 ))?;
362 let (resolve, reject) = funcs.assume_init();
363 (
364 Promise::from_js_value(self.clone(), promise),
365 Function::from_js_value(self.clone(), resolve),
366 Function::from_js_value(self.clone(), reject),
367 )
368 })
369 }
370
371 pub fn execute_pending_job(&self) -> bool {
376 let mut ptr = MaybeUninit::<*mut qjs::JSContext>::uninit();
377 let rt = unsafe { qjs::JS_GetRuntime(self.ctx.as_ptr()) };
378 let res = unsafe { qjs::JS_ExecutePendingJob(rt, ptr.as_mut_ptr()) };
379 res != 0
380 }
381
382 pub(crate) unsafe fn get_opaque(&self) -> &Opaque<'js> {
383 Opaque::from_runtime_ptr(qjs::JS_GetRuntime(self.ctx.as_ptr()))
384 }
385
386 #[cfg(feature = "futures")]
388 #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "futures")))]
389 pub fn spawn<F>(&self, future: F)
390 where
391 F: Future<Output = ()> + 'js,
392 {
393 unsafe { self.get_opaque().push(future) }
394 }
395
396 pub unsafe fn from_raw_invariant(ctx: NonNull<qjs::JSContext>, inv: Invariant<'js>) -> Self {
403 unsafe { qjs::JS_DupContext(ctx.as_ptr()) };
404 Ctx { ctx, _marker: inv }
405 }
406
407 pub unsafe fn from_raw(ctx: NonNull<qjs::JSContext>) -> Self {
414 unsafe { qjs::JS_DupContext(ctx.as_ptr()) };
415 Ctx {
416 ctx,
417 _marker: Invariant::new(),
418 }
419 }
420
421 pub fn script_or_module_name(&self, stack_level: isize) -> Option<Atom<'js>> {
427 let stack_level = std::os::raw::c_int::try_from(stack_level).unwrap();
428 let atom = unsafe { qjs::JS_GetScriptOrModuleName(self.as_ptr(), stack_level) };
429 if qjs::__JS_ATOM_NULL as u32 == atom {
430 unsafe { qjs::JS_FreeAtom(self.as_ptr(), atom) };
431 return None;
432 }
433 unsafe { Some(Atom::from_atom_val(self.clone(), atom)) }
434 }
435
436 pub fn run_gc(&self) {
442 unsafe { qjs::JS_RunGC(qjs::JS_GetRuntime(self.ctx.as_ptr())) }
443 }
444
445 pub fn store_userdata<U>(&self, data: U) -> StdResult<Option<Box<U>>, UserDataError<U>>
451 where
452 U: JsLifetime<'js>,
453 U::Changed<'static>: Any,
454 {
455 unsafe { self.get_opaque().insert_userdata(data) }
456 }
457
458 pub fn remove_userdata<U>(&self) -> StdResult<Option<Box<U>>, UserDataError<()>>
463 where
464 U: JsLifetime<'js>,
465 U::Changed<'static>: Any,
466 {
467 unsafe { self.get_opaque().remove_userdata() }
468 }
469
470 pub fn userdata<U>(&self) -> Option<UserDataGuard<U>>
474 where
475 U: JsLifetime<'js>,
476 U::Changed<'static>: Any,
477 {
478 unsafe { self.get_opaque().get_userdata() }
479 }
480
481 pub fn as_raw(&self) -> NonNull<qjs::JSContext> {
483 self.ctx
484 }
485}
486
487#[cfg(test)]
488mod test {
489 use crate::{CatchResultExt, JsLifetime};
490
491 #[test]
492 fn exports() {
493 use crate::{context::intrinsic, Context, Function, Module, Promise, Runtime};
494
495 let runtime = Runtime::new().unwrap();
496 let ctx = Context::custom::<(intrinsic::Promise, intrinsic::Eval)>(&runtime).unwrap();
497 ctx.with(|ctx| {
498 let (module, promise) = Module::declare(ctx, "test", "export default async () => 1;")
499 .unwrap()
500 .eval()
501 .unwrap();
502 promise.finish::<()>().unwrap();
503 let func: Function = module.get("default").unwrap();
504 func.call::<(), Promise>(()).unwrap();
505 });
506 }
507
508 #[test]
509 fn eval() {
510 use crate::{Context, Runtime};
511
512 let runtime = Runtime::new().unwrap();
513 let ctx = Context::full(&runtime).unwrap();
514 ctx.with(|ctx| {
515 let res: String = ctx
516 .eval(
517 r#"
518 function test() {
519 var foo = "bar";
520 return foo;
521 }
522
523 test()
524 "#,
525 )
526 .unwrap();
527
528 assert_eq!("bar".to_string(), res);
529 })
530 }
531
532 #[test]
533 fn eval_minimal_test() {
534 use crate::{Context, Runtime};
535
536 let runtime = Runtime::new().unwrap();
537 let ctx = Context::full(&runtime).unwrap();
538 ctx.with(|ctx| {
539 let res: i32 = ctx.eval(" 1 + 1 ").unwrap();
540 assert_eq!(2, res);
541 })
542 }
543
544 #[test]
545 #[should_panic(expected = "foo is not defined")]
546 fn eval_with_sloppy_code() {
547 use crate::{CatchResultExt, Context, Runtime};
548
549 let runtime = Runtime::new().unwrap();
550 let ctx = Context::full(&runtime).unwrap();
551 ctx.with(|ctx| {
552 let _: String = ctx
553 .eval(
554 r#"
555 function test() {
556 foo = "bar";
557 return foo;
558 }
559
560 test()
561 "#,
562 )
563 .catch(&ctx)
564 .unwrap();
565 })
566 }
567
568 #[test]
569 fn eval_with_options_no_strict_sloppy_code() {
570 use crate::{context::EvalOptions, Context, Runtime};
571
572 let runtime = Runtime::new().unwrap();
573 let ctx = Context::full(&runtime).unwrap();
574 ctx.with(|ctx| {
575 let res: String = ctx
576 .eval_with_options(
577 r#"
578 function test() {
579 foo = "bar";
580 return foo;
581 }
582
583 test()
584 "#,
585 EvalOptions {
586 strict: false,
587 ..Default::default()
588 },
589 )
590 .unwrap();
591
592 assert_eq!("bar".to_string(), res);
593 })
594 }
595
596 #[test]
597 #[should_panic(expected = "foo is not defined")]
598 fn eval_with_options_strict_sloppy_code() {
599 use crate::{context::EvalOptions, CatchResultExt, Context, Runtime};
600
601 let runtime = Runtime::new().unwrap();
602 let ctx = Context::full(&runtime).unwrap();
603 ctx.with(|ctx| {
604 let _: String = ctx
605 .eval_with_options(
606 r#"
607 function test() {
608 foo = "bar";
609 return foo;
610 }
611
612 test()
613 "#,
614 EvalOptions {
615 strict: true,
616 ..Default::default()
617 },
618 )
619 .catch(&ctx)
620 .unwrap();
621 })
622 }
623
624 #[test]
625 fn json_parse() {
626 use crate::{Array, Context, Object, Runtime};
627
628 let runtime = Runtime::new().unwrap();
629 let ctx = Context::full(&runtime).unwrap();
630 ctx.with(|ctx| {
631 let v = ctx
632 .json_parse(r#"{ "a": { "b": 1, "c": true }, "d": [0,"foo"] }"#)
633 .unwrap();
634 let obj = v.into_object().unwrap();
635 let inner_obj: Object = obj.get("a").unwrap();
636 assert_eq!(inner_obj.get::<_, i32>("b").unwrap(), 1);
637 assert!(inner_obj.get::<_, bool>("c").unwrap());
638 let inner_array: Array = obj.get("d").unwrap();
639 assert_eq!(inner_array.get::<i32>(0).unwrap(), 0);
640 assert_eq!(inner_array.get::<String>(1).unwrap(), "foo".to_string());
641 })
642 }
643
644 #[test]
645 fn json_stringify() {
646 use crate::{Array, Context, Object, Runtime};
647
648 let runtime = Runtime::new().unwrap();
649 let ctx = Context::full(&runtime).unwrap();
650 ctx.with(|ctx| {
651 let obj_inner = Object::new(ctx.clone()).unwrap();
652 obj_inner.set("b", 1).unwrap();
653 obj_inner.set("c", true).unwrap();
654
655 let array_inner = Array::new(ctx.clone()).unwrap();
656 array_inner.set(0, 0).unwrap();
657 array_inner.set(1, "foo").unwrap();
658
659 let obj = Object::new(ctx.clone()).unwrap();
660 obj.set("a", obj_inner).unwrap();
661 obj.set("d", array_inner).unwrap();
662
663 let str = ctx
664 .json_stringify(obj)
665 .unwrap()
666 .unwrap()
667 .to_string()
668 .unwrap();
669
670 assert_eq!(str, r#"{"a":{"b":1,"c":true},"d":[0,"foo"]}"#);
671 })
672 }
673
674 #[test]
675 fn userdata() {
676 use crate::{Context, Function, Runtime};
677
678 pub struct MyUserData<'js> {
679 base: Function<'js>,
680 }
681
682 unsafe impl<'js> JsLifetime<'js> for MyUserData<'js> {
683 type Changed<'to> = MyUserData<'to>;
684 }
685
686 let rt = Runtime::new().unwrap();
687 let ctx = Context::full(&rt).unwrap();
688
689 ctx.with(|ctx| {
690 let func = ctx.eval("() => 42").catch(&ctx).unwrap();
691 ctx.store_userdata(MyUserData { base: func }).unwrap();
692 });
693
694 ctx.with(|ctx| {
695 let userdata = ctx.userdata::<MyUserData>().unwrap();
696
697 assert!(ctx.remove_userdata::<MyUserData>().is_err());
698
699 let r: usize = userdata.base.call(()).unwrap();
700 assert_eq!(r, 42)
701 });
702
703 ctx.with(|ctx| {
704 ctx.remove_userdata::<MyUserData>().unwrap().unwrap();
705 })
706 }
707}