1use crate::builder::QuickJsRuntimeBuilder;
4use crate::jsutils::{helper_tasks, JsError, Script};
5use crate::quickjs_utils::{functions, objects};
6use crate::quickjsrealmadapter::QuickJsRealmAdapter;
7use crate::quickjsruntimeadapter::{
8 CompiledModuleLoaderAdapter, MemoryUsage, NativeModuleLoaderAdapter, QuickJsRuntimeAdapter,
9 ScriptModuleLoaderAdapter, QJS_RT,
10};
11use crate::quickjsvalueadapter::QuickJsValueAdapter;
12use crate::reflection;
13use crate::values::JsValueFacade;
14use either::{Either, Left, Right};
15use hirofa_utils::eventloop::EventLoop;
16use libquickjs_sys as q;
17use lru::LruCache;
18use std::cell::RefCell;
19use std::future::Future;
20use std::num::NonZeroUsize;
21use std::pin::Pin;
22use std::rc::Rc;
23use std::sync::{Arc, Weak};
24use tokio::task::JoinError;
25
26impl Drop for QuickJsRuntimeFacade {
27 fn drop(&mut self) {
28 log::trace!("> EsRuntime::drop");
29 self.clear_contexts();
30 log::trace!("< EsRuntime::drop");
31 }
32}
33
34pub struct QuickjsRuntimeFacadeInner {
35 event_loop: EventLoop,
36}
37
38impl QuickjsRuntimeFacadeInner {
39 pub fn exe_rt_task_in_event_loop<C, R>(&self, consumer: C) -> R
56 where
57 C: FnOnce(&QuickJsRuntimeAdapter) -> R + Send + 'static,
58 R: Send + 'static,
59 {
60 self.exe_task_in_event_loop(|| QuickJsRuntimeAdapter::do_with(consumer))
61 }
62
63 pub fn add_rt_task_to_event_loop<C, R: Send + 'static>(
75 &self,
76 consumer: C,
77 ) -> impl Future<Output = R>
78 where
79 C: FnOnce(&QuickJsRuntimeAdapter) -> R + Send + 'static,
80 {
81 self.add_task_to_event_loop(|| QuickJsRuntimeAdapter::do_with(consumer))
82 }
83
84 pub fn add_rt_task_to_event_loop_void<C>(&self, consumer: C)
85 where
86 C: FnOnce(&QuickJsRuntimeAdapter) + Send + 'static,
87 {
88 self.add_task_to_event_loop_void(|| QuickJsRuntimeAdapter::do_with(consumer))
89 }
90
91 pub fn add_task_to_event_loop_void<C>(&self, task: C)
94 where
95 C: FnOnce() + Send + 'static,
96 {
97 self.event_loop.add_void(move || {
98 task();
99 EventLoop::add_local_void(|| {
100 QuickJsRuntimeAdapter::do_with(|q_js_rt| {
101 q_js_rt.run_pending_jobs_if_any();
102 })
103 })
104 });
105 }
106
107 pub fn exe_task_in_event_loop<C, R: Send + 'static>(&self, task: C) -> R
108 where
109 C: FnOnce() -> R + Send + 'static,
110 {
111 self.event_loop.exe(move || {
112 let res = task();
113 EventLoop::add_local_void(|| {
114 QuickJsRuntimeAdapter::do_with(|q_js_rt| {
115 q_js_rt.run_pending_jobs_if_any();
116 })
117 });
118 res
119 })
120 }
121
122 pub fn add_task_to_event_loop<C, R: Send + 'static>(&self, task: C) -> impl Future<Output = R>
123 where
124 C: FnOnce() -> R + Send + 'static,
125 {
126 self.event_loop.add(move || {
127 let res = task();
128 EventLoop::add_local_void(|| {
129 QuickJsRuntimeAdapter::do_with(|q_js_rt| {
130 q_js_rt.run_pending_jobs_if_any();
131 });
132 });
133 res
134 })
135 }
136
137 #[allow(dead_code)]
139 pub(crate) fn add_local_task_to_event_loop<C>(consumer: C)
140 where
141 C: FnOnce(&QuickJsRuntimeAdapter) + 'static,
142 {
143 EventLoop::add_local_void(move || {
144 QuickJsRuntimeAdapter::do_with(|q_js_rt| {
145 consumer(q_js_rt);
146 });
147 EventLoop::add_local_void(|| {
148 QuickJsRuntimeAdapter::do_with(|q_js_rt| {
149 q_js_rt.run_pending_jobs_if_any();
150 })
151 })
152 });
153 }
154}
155
156pub struct QuickJsRuntimeFacade {
164 inner: Arc<QuickjsRuntimeFacadeInner>,
165}
166
167impl QuickJsRuntimeFacade {
168 pub(crate) fn new(mut builder: QuickJsRuntimeBuilder) -> Self {
169 let ret = Self {
170 inner: Arc::new(QuickjsRuntimeFacadeInner {
171 event_loop: EventLoop::new(),
172 }),
173 };
174
175 ret.exe_task_in_event_loop(|| {
176 let rt_ptr = unsafe { q::JS_NewRuntime() };
177 let rt = QuickJsRuntimeAdapter::new(rt_ptr);
178 QuickJsRuntimeAdapter::init_rt_for_current_thread(rt);
179 functions::init_statics();
180 reflection::init_statics();
181 });
182
183 let rti_weak = Arc::downgrade(&ret.inner);
186
187 ret.exe_task_in_event_loop(move || {
188 QuickJsRuntimeAdapter::do_with_mut(move |m_q_js_rt| {
189 m_q_js_rt.init_rti_ref(rti_weak);
190 })
191 });
192
193 #[cfg(any(
196 feature = "settimeout",
197 feature = "setinterval",
198 feature = "console",
199 feature = "setimmediate"
200 ))]
201 {
202 let res = crate::features::init(&ret);
203 if res.is_err() {
204 panic!("could not init features: {}", res.err().unwrap());
205 }
206 }
207
208 if let Some(interval) = builder.opt_gc_interval {
209 let rti_ref: Weak<QuickjsRuntimeFacadeInner> = Arc::downgrade(&ret.inner);
210 std::thread::spawn(move || loop {
211 std::thread::sleep(interval);
212 if let Some(el) = rti_ref.upgrade() {
213 log::debug!("running gc from gc interval thread");
214 el.event_loop.add_void(|| {
215 QJS_RT
216 .try_with(|rc| {
217 let rt = &*rc.borrow();
218 rt.as_ref().unwrap().gc();
219 })
220 .expect("QJS_RT.try_with failed");
221 });
222 } else {
223 break;
224 }
225 });
226 }
227
228 #[allow(clippy::drain_collect)]
229 let init_hooks: Vec<_> = builder.runtime_init_hooks.drain(..).collect();
230
231 ret.exe_task_in_event_loop(move || {
232 QuickJsRuntimeAdapter::do_with_mut(|q_js_rt| {
233 for native_module_loader in builder.native_module_loaders {
234 q_js_rt.add_native_module_loader(NativeModuleLoaderAdapter::new(
235 native_module_loader,
236 ));
237 }
238 for script_module_loader in builder.script_module_loaders {
239 q_js_rt.add_script_module_loader(ScriptModuleLoaderAdapter::new(
240 script_module_loader,
241 ));
242 }
243 for compiled_module_loader in builder.compiled_module_loaders {
244 q_js_rt.add_compiled_module_loader(CompiledModuleLoaderAdapter::new(
245 compiled_module_loader,
246 ));
247 }
248 q_js_rt.script_pre_processors = builder.script_pre_processors;
249
250 if let Some(limit) = builder.opt_memory_limit_bytes {
251 unsafe {
252 q::JS_SetMemoryLimit(q_js_rt.runtime, limit as _);
253 }
254 }
255 if let Some(threshold) = builder.opt_gc_threshold {
256 unsafe {
257 q::JS_SetGCThreshold(q_js_rt.runtime, threshold as _);
258 }
259 }
260 if let Some(stack_size) = builder.opt_max_stack_size {
261 unsafe {
262 q::JS_SetMaxStackSize(q_js_rt.runtime, stack_size as _);
263 }
264 }
265 if let Some(interrupt_handler) = builder.interrupt_handler {
266 q_js_rt.set_interrupt_handler(interrupt_handler);
267 }
268 })
269 });
270
271 for hook in init_hooks {
272 match hook(&ret) {
273 Ok(_) => {}
274 Err(e) => {
275 panic!("runtime_init_hook failed: {}", e);
276 }
277 }
278 }
279
280 ret
281 }
282
283 pub async fn memory_usage(&self) -> MemoryUsage {
285 self.loop_async(|rt| rt.memory_usage()).await
286 }
287
288 pub(crate) fn clear_contexts(&self) {
289 log::trace!("EsRuntime::clear_contexts");
290 self.exe_task_in_event_loop(|| {
291 let context_ids = QuickJsRuntimeAdapter::get_context_ids();
292 for id in context_ids {
293 let _ = QuickJsRuntimeAdapter::remove_context(id.as_str());
294 }
295 });
296 }
297
298 pub fn add_task_to_event_loop_void<C>(&self, task: C)
301 where
302 C: FnOnce() + Send + 'static,
303 {
304 self.inner.add_task_to_event_loop_void(task)
305 }
306
307 pub fn exe_task_in_event_loop<C, R: Send + 'static>(&self, task: C) -> R
308 where
309 C: FnOnce() -> R + Send + 'static,
310 {
311 self.inner.exe_task_in_event_loop(task)
312 }
313
314 pub fn add_task_to_event_loop<C, R: Send + 'static>(&self, task: C) -> impl Future<Output = R>
315 where
316 C: FnOnce() -> R + Send + 'static,
317 {
318 self.inner.add_task_to_event_loop(task)
319 }
320
321 pub fn add_rt_task_to_event_loop<C, R: Send + 'static>(
333 &self,
334 task: C,
335 ) -> impl Future<Output = R>
336 where
337 C: FnOnce(&QuickJsRuntimeAdapter) -> R + Send + 'static,
338 {
339 self.inner.add_rt_task_to_event_loop(task)
340 }
341
342 pub fn add_rt_task_to_event_loop_void<C>(&self, task: C)
343 where
344 C: FnOnce(&QuickJsRuntimeAdapter) + Send + 'static,
345 {
346 self.inner.add_rt_task_to_event_loop_void(task)
347 }
348
349 #[allow(dead_code)]
351 pub(crate) fn add_local_task_to_event_loop<C>(consumer: C)
352 where
353 C: FnOnce(&QuickJsRuntimeAdapter) + 'static,
354 {
355 QuickjsRuntimeFacadeInner::add_local_task_to_event_loop(consumer)
356 }
357
358 pub fn builder() -> QuickJsRuntimeBuilder {
359 QuickJsRuntimeBuilder::new()
360 }
361
362 pub async fn gc(&self) {
364 self.add_rt_task_to_event_loop(|q_js_rt| q_js_rt.gc()).await
365 }
366
367 pub fn gc_sync(&self) {
369 self.exe_rt_task_in_event_loop(|q_js_rt| q_js_rt.gc())
370 }
371
372 pub fn exe_rt_task_in_event_loop<C, R>(&self, consumer: C) -> R
389 where
390 C: FnOnce(&QuickJsRuntimeAdapter) -> R + Send + 'static,
391 R: Send + 'static,
392 {
393 self.exe_task_in_event_loop(|| QuickJsRuntimeAdapter::do_with(consumer))
394 }
395
396 pub fn set_function<F>(
417 &self,
418 namespace: &[&str],
419 name: &str,
420 function: F,
421 ) -> Result<(), JsError>
422 where
423 F: Fn(&QuickJsRealmAdapter, Vec<JsValueFacade>) -> Result<JsValueFacade, JsError>
424 + Send
425 + 'static,
426 {
427 let name = name.to_string();
428
429 let namespace = namespace
430 .iter()
431 .map(|s| s.to_string())
432 .collect::<Vec<String>>();
433
434 self.exe_rt_task_in_event_loop(move |q_js_rt| {
435 let func_rc = Rc::new(function);
436 let name = name.to_string();
437
438 q_js_rt.add_context_init_hook(move |_q_js_rt, realm| {
439 let namespace_slice = namespace.iter().map(|s| s.as_str()).collect::<Vec<&str>>();
440 let ns = objects::get_namespace_q(realm, &namespace_slice, true)?;
441
442 let func_rc = func_rc.clone();
443
444 let func = functions::new_function_q(
445 realm,
446 name.as_str(),
447 move |realm, _this_ref, args| {
448 let mut args_facades = vec![];
449
450 for arg_ref in args {
451 args_facades.push(realm.to_js_value_facade(arg_ref)?);
452 }
453
454 let res = func_rc(realm, args_facades);
455
456 match res {
457 Ok(val_jsvf) => realm.from_js_value_facade(val_jsvf),
458 Err(e) => Err(e),
459 }
460 },
461 1,
462 )?;
463
464 objects::set_property2_q(realm, &ns, name.as_str(), &func, 0)?;
465
466 Ok(())
467 })
468 })
469 }
470
471 pub fn add_helper_task<T>(task: T)
473 where
474 T: FnOnce() + Send + 'static,
475 {
476 helper_tasks::add_helper_task(task);
477 }
478
479 pub fn add_helper_task_async<R: Send + 'static, T: Future<Output = R> + Send + 'static>(
481 task: T,
482 ) -> impl Future<Output = Result<R, JoinError>> {
483 helper_tasks::add_helper_task_async(task)
484 }
485
486 pub fn create_context(&self, id: &str) -> Result<(), JsError> {
499 let id = id.to_string();
500 self.inner
501 .event_loop
502 .exe(move || QuickJsRuntimeAdapter::create_context(id.as_str()))
503 }
504
505 pub fn drop_context(&self, id: &str) -> anyhow::Result<()> {
507 let id = id.to_string();
508 self.inner
509 .event_loop
510 .exe(move || QuickJsRuntimeAdapter::remove_context(id.as_str()))
511 }
512}
513
514thread_local! {
515 static REALM_ID_LRU_CACHE: RefCell<LruCache<String, ()>> = RefCell::new(LruCache::new(NonZeroUsize::new(128).unwrap()));
518}
519
520fn loop_realm_func<
521 R: Send + 'static,
522 C: FnOnce(&QuickJsRuntimeAdapter, &QuickJsRealmAdapter) -> R + Send + 'static,
523>(
524 realm_name: Option<String>,
525 consumer: C,
526) -> R {
527 if let Some(realm_str) = realm_name.as_ref() {
532 REALM_ID_LRU_CACHE.with(|cache_cell| {
533 let mut cache = cache_cell.borrow_mut();
534 cache.promote(realm_str);
536 });
537 }
538
539 let res: Either<R, C> = QuickJsRuntimeAdapter::do_with(|q_js_rt| {
542 if let Some(realm_str) = realm_name.as_ref() {
543 if let Some(realm) = q_js_rt.get_realm(realm_str) {
544 Left(consumer(q_js_rt, realm))
545 } else {
546 Right(consumer)
547 }
548 } else {
549 Left(consumer(q_js_rt, q_js_rt.get_main_realm()))
550 }
551 });
552
553 match res {
554 Left(r) => r,
555 Right(consumer) => {
556 let realm_str = realm_name.expect("invalid state");
560
561 REALM_ID_LRU_CACHE.with(|cache_cell| {
562 let mut cache = cache_cell.borrow_mut();
563 if cache.len() == cache.cap().get() {
565 if let Some((_evicted_key, _evicted_value)) = cache.pop_lru() {
566 }
570 }
571 cache.put(realm_str.to_string(), ());
572 });
573
574 QuickJsRuntimeAdapter::do_with_mut(|m_rt| {
577 let ctx = QuickJsRealmAdapter::new(realm_str.to_string(), m_rt);
578 m_rt.contexts.insert(realm_str.to_string(), ctx);
579 });
580
581 QuickJsRuntimeAdapter::do_with(|q_js_rt| {
582 let realm = q_js_rt
583 .get_realm(realm_str.as_str())
584 .expect("invalid state");
585 let hooks = &*q_js_rt.context_init_hooks.borrow();
586 for hook in hooks {
587 let res = hook(q_js_rt, realm);
588 if res.is_err() {
589 panic!("realm init hook failed: {}", res.err().unwrap());
590 }
591 }
592
593 consumer(q_js_rt, realm)
594 })
595 }
596 }
597}
598
599impl QuickJsRuntimeFacade {
600 pub fn create_realm(&self, name: &str) -> Result<(), JsError> {
601 let name = name.to_string();
602 self.inner
603 .event_loop
604 .exe(move || QuickJsRuntimeAdapter::create_context(name.as_str()))
605 }
606
607 pub fn destroy_realm(&self, name: &str) -> anyhow::Result<()> {
608 let name = name.to_string();
609 self.exe_task_in_event_loop(move || QuickJsRuntimeAdapter::remove_context(name.as_str()))
610 }
611
612 pub fn has_realm(&self, name: &str) -> Result<bool, JsError> {
613 let name = name.to_string();
614 self.exe_rt_task_in_event_loop(move |rt| Ok(rt.get_realm(name.as_str()).is_some()))
615 }
616
617 pub fn loop_sync<R: Send + 'static, C: FnOnce(&QuickJsRuntimeAdapter) -> R + Send + 'static>(
619 &self,
620 consumer: C,
621 ) -> R {
622 self.exe_rt_task_in_event_loop(consumer)
623 }
624
625 pub fn loop_sync_mut<
626 R: Send + 'static,
627 C: FnOnce(&mut QuickJsRuntimeAdapter) -> R + Send + 'static,
628 >(
629 &self,
630 consumer: C,
631 ) -> R {
632 self.exe_task_in_event_loop(|| QuickJsRuntimeAdapter::do_with_mut(consumer))
633 }
634
635 pub fn loop_async<
638 R: Send + 'static,
639 C: FnOnce(&QuickJsRuntimeAdapter) -> R + Send + 'static,
640 >(
641 &self,
642 consumer: C,
643 ) -> Pin<Box<dyn Future<Output = R> + Send>> {
644 Box::pin(self.add_rt_task_to_event_loop(consumer))
645 }
646
647 pub fn loop_void<C: FnOnce(&QuickJsRuntimeAdapter) + Send + 'static>(&self, consumer: C) {
649 self.add_rt_task_to_event_loop_void(consumer)
650 }
651
652 pub fn loop_realm_sync<
654 R: Send + 'static,
655 C: FnOnce(&QuickJsRuntimeAdapter, &QuickJsRealmAdapter) -> R + Send + 'static,
656 >(
657 &self,
658 realm_name: Option<&str>,
659 consumer: C,
660 ) -> R {
661 let realm_name = realm_name.map(|s| s.to_string());
662 self.exe_task_in_event_loop(|| loop_realm_func(realm_name, consumer))
663 }
664
665 pub fn loop_realm<
668 R: Send + 'static,
669 C: FnOnce(&QuickJsRuntimeAdapter, &QuickJsRealmAdapter) -> R + Send + 'static,
670 >(
671 &self,
672 realm_name: Option<&str>,
673 consumer: C,
674 ) -> Pin<Box<dyn Future<Output = R> + Send>> {
675 let realm_name = realm_name.map(|s| s.to_string());
676 Box::pin(self.add_task_to_event_loop(|| loop_realm_func(realm_name, consumer)))
677 }
678
679 pub fn loop_realm_void<
682 C: FnOnce(&QuickJsRuntimeAdapter, &QuickJsRealmAdapter) + Send + 'static,
683 >(
684 &self,
685 realm_name: Option<&str>,
686 consumer: C,
687 ) {
688 let realm_name = realm_name.map(|s| s.to_string());
689 self.add_task_to_event_loop_void(|| loop_realm_func(realm_name, consumer));
690 }
691
692 #[allow(clippy::type_complexity)]
705 pub fn eval(
706 &self,
707 realm_name: Option<&str>,
708 script: Script,
709 ) -> Pin<Box<dyn Future<Output = Result<JsValueFacade, JsError>> + Send>> {
710 self.loop_realm(realm_name, |_rt, realm| {
711 let res = realm.eval(script);
712 match res {
713 Ok(jsvr) => realm.to_js_value_facade(&jsvr),
714 Err(e) => Err(e),
715 }
716 })
717 }
718
719 #[allow(clippy::type_complexity)]
730 pub fn eval_sync(
731 &self,
732 realm_name: Option<&str>,
733 script: Script,
734 ) -> Result<JsValueFacade, JsError> {
735 self.loop_realm_sync(realm_name, |_rt, realm| {
736 let res = realm.eval(script);
737 match res {
738 Ok(jsvr) => realm.to_js_value_facade(&jsvr),
739 Err(e) => Err(e),
740 }
741 })
742 }
743
744 pub fn eval_module(
779 &self,
780 realm_name: Option<&str>,
781 script: Script,
782 ) -> Pin<Box<dyn Future<Output = Result<JsValueFacade, JsError>> + Send>> {
783 self.loop_realm(realm_name, |_rt, realm| {
784 let res = realm.eval_module(script)?;
785 realm.to_js_value_facade(&res)
786 })
787 }
788
789 pub fn eval_module_sync(
822 &self,
823 realm_name: Option<&str>,
824 script: Script,
825 ) -> Result<JsValueFacade, JsError> {
826 self.loop_realm_sync(realm_name, |_rt, realm| {
827 let res = realm.eval_module(script)?;
828 realm.to_js_value_facade(&res)
829 })
830 }
831
832 #[warn(clippy::type_complexity)]
845 pub fn invoke_function_sync(
846 &self,
847 realm_name: Option<&str>,
848 namespace: &[&str],
849 method_name: &str,
850 args: Vec<JsValueFacade>,
851 ) -> Result<JsValueFacade, JsError> {
852 let movable_namespace: Vec<String> = namespace.iter().map(|s| s.to_string()).collect();
853 let movable_method_name = method_name.to_string();
854
855 self.loop_realm_sync(realm_name, move |_rt, realm| {
856 let args_adapters: Vec<QuickJsValueAdapter> = args
857 .into_iter()
858 .map(|jsvf| realm.from_js_value_facade(jsvf).expect("conversion failed"))
859 .collect();
860
861 let namespace = movable_namespace
862 .iter()
863 .map(|s| s.as_str())
864 .collect::<Vec<&str>>();
865
866 let res = realm.invoke_function_by_name(
867 namespace.as_slice(),
868 movable_method_name.as_str(),
869 args_adapters.as_slice(),
870 );
871
872 match res {
873 Ok(jsvr) => realm.to_js_value_facade(&jsvr),
874 Err(e) => Err(e),
875 }
876 })
877 }
878
879 #[allow(clippy::type_complexity)]
892 pub fn invoke_function(
893 &self,
894 realm_name: Option<&str>,
895 namespace: &[&str],
896 method_name: &str,
897 args: Vec<JsValueFacade>,
898 ) -> Pin<Box<dyn Future<Output = Result<JsValueFacade, JsError>> + Send>> {
899 let movable_namespace: Vec<String> = namespace.iter().map(|s| s.to_string()).collect();
900 let movable_method_name = method_name.to_string();
901
902 self.loop_realm(realm_name, move |_rt, realm| {
903 let args_adapters: Vec<QuickJsValueAdapter> = args
904 .into_iter()
905 .map(|jsvf| realm.from_js_value_facade(jsvf).expect("conversion failed"))
906 .collect();
907
908 let namespace = movable_namespace
909 .iter()
910 .map(|s| s.as_str())
911 .collect::<Vec<&str>>();
912
913 let res = realm.invoke_function_by_name(
914 namespace.as_slice(),
915 movable_method_name.as_str(),
916 args_adapters.as_slice(),
917 );
918
919 match res {
920 Ok(jsvr) => realm.to_js_value_facade(&jsvr),
921 Err(e) => Err(e),
922 }
923 })
924 }
925
926 pub fn invoke_function_void(
927 &self,
928 realm_name: Option<&str>,
929 namespace: &[&str],
930 method_name: &str,
931 args: Vec<JsValueFacade>,
932 ) {
933 let movable_namespace: Vec<String> = namespace.iter().map(|s| s.to_string()).collect();
934 let movable_method_name = method_name.to_string();
935
936 self.loop_realm_void(realm_name, move |_rt, realm| {
937 let args_adapters: Vec<QuickJsValueAdapter> = args
938 .into_iter()
939 .map(|jsvf| realm.from_js_value_facade(jsvf).expect("conversion failed"))
940 .collect();
941
942 let namespace = movable_namespace
943 .iter()
944 .map(|s| s.as_str())
945 .collect::<Vec<&str>>();
946
947 let res = realm
948 .invoke_function_by_name(
949 namespace.as_slice(),
950 movable_method_name.as_str(),
951 args_adapters.as_slice(),
952 )
953 .map(|jsvr| realm.to_js_value_facade(&jsvr));
954
955 match res {
956 Ok(_) => {
957 log::trace!(
958 "js_function_invoke_void succeeded: {}",
959 movable_method_name.as_str()
960 );
961 }
962 Err(err) => {
963 log::trace!(
964 "js_function_invoke_void failed: {}: {}",
965 movable_method_name.as_str(),
966 err
967 );
968 }
969 }
970 })
971 }
972}
973
974#[cfg(test)]
975lazy_static::lazy_static! {
976 static ref INITTED: std::sync::Mutex<bool> = std::sync::Mutex::new(false);
977}
978
979#[cfg(test)]
980pub mod tests {
981 use crate::facades::QuickJsRuntimeFacade;
982 use crate::jsutils::modules::{NativeModuleLoader, ScriptModuleLoader};
983 use crate::jsutils::JsError;
984 use crate::jsutils::Script;
985 use crate::quickjs_utils::{primitives, promises};
986 use crate::quickjsrealmadapter::QuickJsRealmAdapter;
987 use crate::quickjsvalueadapter::QuickJsValueAdapter;
988 use crate::values::{JsValueConvertable, JsValueFacade};
989 use backtrace::Backtrace;
990 use futures::executor::block_on;
991 use log::debug;
992 use std::panic;
993 use std::time::{Duration, Instant};
994
995 struct TestNativeModuleLoader {}
996 struct TestScriptModuleLoader {}
997
998 impl NativeModuleLoader for TestNativeModuleLoader {
999 fn has_module(&self, _q_ctx: &QuickJsRealmAdapter, module_name: &str) -> bool {
1000 module_name.starts_with("greco://")
1001 }
1002
1003 fn get_module_export_names(
1004 &self,
1005 _q_ctx: &QuickJsRealmAdapter,
1006 _module_name: &str,
1007 ) -> Vec<&str> {
1008 vec!["a", "b", "c"]
1009 }
1010
1011 fn get_module_exports(
1012 &self,
1013 _q_ctx: &QuickJsRealmAdapter,
1014 _module_name: &str,
1015 ) -> Vec<(&str, QuickJsValueAdapter)> {
1016 vec![
1017 ("a", primitives::from_i32(1234)),
1018 ("b", primitives::from_i32(64834)),
1019 ("c", primitives::from_i32(333)),
1020 ]
1021 }
1022 }
1023
1024 impl ScriptModuleLoader for TestScriptModuleLoader {
1025 fn normalize_path(
1026 &self,
1027 _realm: &QuickJsRealmAdapter,
1028 _ref_path: &str,
1029 path: &str,
1030 ) -> Option<String> {
1031 if path.eq("notfound.mes") || path.starts_with("greco://") {
1032 None
1033 } else {
1034 Some(path.to_string())
1035 }
1036 }
1037
1038 fn load_module(&self, _realm: &QuickJsRealmAdapter, absolute_path: &str) -> String {
1039 if absolute_path.eq("notfound.mes") || absolute_path.starts_with("greco://") {
1040 panic!("tht realy should not happen");
1041 } else if absolute_path.eq("invalid.mes") {
1042 "I am the great cornholio! thou'gh shalt&s not p4arse mie!".to_string()
1043 } else {
1044 "export const foo = 'bar';\nexport const mltpl = function(a, b){return a*b;}; globalThis;".to_string()
1045 }
1046 }
1047 }
1048
1049 #[test]
1050 fn test_rt_drop() {
1051 let rt = init_test_rt();
1052 log::trace!("before drop");
1053
1054 drop(rt);
1055 log::trace!("after before drop");
1056 std::thread::sleep(Duration::from_secs(5));
1057 log::trace!("after sleep");
1058 }
1059
1060 #[test]
1061 pub fn test_stack_size() {
1062 let rt = init_test_rt();
1063 let res = rt.eval_sync(
1065 None,
1066 Script::new(
1067 "stack_test.js",
1068 "let f = function(a){let f2 = arguments.callee; if (a < 20) {f2(a + 1);}}; f(1);",
1069 ),
1070 );
1071 match res {
1072 Ok(_) => {}
1073 Err(e) => {
1074 log::error!("fail: {}", e);
1075 panic!("fail: {}", e);
1076 }
1077 }
1078
1079 let res = rt.eval_sync(
1080 None,
1081 Script::new(
1082 "stack_test.js",
1083 "let f = function(a){let f2 = arguments.callee; if (a < 1000) {f2(a + 1);}}; f(1);",
1084 ),
1085 );
1086 if res.is_ok() {
1087 panic!("stack should have overflowed");
1088 }
1089 }
1090
1091 pub fn init_logging() {
1092 {
1093 let i_lock = &mut *crate::facades::INITTED.lock().unwrap();
1094 if !*i_lock {
1095 panic::set_hook(Box::new(|panic_info| {
1096 let backtrace = Backtrace::new();
1097 println!("thread panic occurred: {panic_info}\nbacktrace: {backtrace:?}");
1098 log::error!(
1099 "thread panic occurred: {}\nbacktrace: {:?}",
1100 panic_info,
1101 backtrace
1102 );
1103 }));
1104
1105 simple_logging::log_to_file("./quickjs_runtime.log", log::LevelFilter::max())
1106 .expect("could not init logger");
1107
1108 *i_lock = true;
1109 }
1110 }
1111 }
1112
1113 pub fn init_test_rt() -> QuickJsRuntimeFacade {
1114 init_logging();
1115
1116 QuickJsRuntimeFacade::builder()
1117 .gc_interval(Duration::from_secs(1))
1118 .max_stack_size(128 * 1024)
1119 .script_module_loader(TestScriptModuleLoader {})
1120 .native_module_loader(TestNativeModuleLoader {})
1121 .build()
1122 }
1123
1124 #[tokio::test]
1125 async fn test_long() {
1126 let rt = init_test_rt();
1127 let mut start_of_batch = Instant::now();
1128 for i in 1..10 {
1129 let res = rt
1130 .eval(
1131 None,
1132 Script::new(
1133 "test.js",
1134 r#"
1135 (async () => {
1136 return await 1;
1137 })()
1138
1139 "#,
1140 ),
1141 )
1142 .await
1143 .unwrap();
1144
1145 if let JsValueFacade::JsPromise { cached_promise } = res {
1146 let _res = cached_promise.get_promise_result().await;
1147 }
1148
1149 if i % 1000 == 0 {
1150 let now = Instant::now();
1151 let ttpb = now.duration_since(start_of_batch).as_millis();
1152 println!("i: {} time taken per batch = {}ms", i, ttpb);
1153 start_of_batch = now;
1154 }
1155 }
1156 }
1157
1158 #[test]
1159 fn test_func() {
1160 let rt = init_test_rt();
1161 let res = rt.set_function(&["nl", "my", "utils"], "methodA", |_q_ctx, args| {
1162 if args.len() != 2 || !args.first().unwrap().is_i32() || !args.get(1).unwrap().is_i32()
1163 {
1164 Err(JsError::new_str(
1165 "i'd really like 2 args of the int32 kind please",
1166 ))
1167 } else {
1168 let a = args.first().unwrap().get_i32();
1169 let b = args.get(1).unwrap().get_i32();
1170 Ok((a * b).to_js_value_facade())
1171 }
1172 });
1173
1174 match res {
1175 Ok(_) => {}
1176 Err(e) => {
1177 panic!("set_function failed: {}", e);
1178 }
1179 }
1180
1181 let res = rt.eval_sync(
1182 None,
1183 Script::new("test_func.es", "(nl.my.utils.methodA(13, 56));"),
1184 );
1185
1186 match res {
1187 Ok(val) => {
1188 assert!(val.is_i32());
1189 assert_eq!(val.get_i32(), 13 * 56);
1190 }
1191 Err(e) => {
1192 panic!("test_func.es failed: {}", e);
1193 }
1194 }
1195 }
1196
1197 #[test]
1198 fn test_eval_sync() {
1199 let rt = init_test_rt();
1200 let res = rt.eval_sync(None, Script::new("test.es", "console.log('foo bar');"));
1201
1202 match res {
1203 Ok(_) => {}
1204 Err(e) => {
1205 panic!("eval failed: {}", e);
1206 }
1207 }
1208
1209 let res = rt
1210 .eval_sync(None, Script::new("test.es", "(2 * 7);"))
1211 .expect("script failed");
1212
1213 assert_eq!(res.get_i32(), 14);
1214 }
1215
1216 #[test]
1217 fn t1234() {
1218 let rt = init_test_rt();
1220
1221 rt.exe_rt_task_in_event_loop(|q_js_rt| {
1222 let q_ctx = q_js_rt.get_main_realm();
1224 let r = q_ctx.eval(Script::new(
1225 "test_async.es",
1226 "let f = async function(){let p = new Promise((resolve, reject) => {resolve(12345);}); const p2 = await p; return p2}; f()",
1227 )).ok().unwrap();
1228 log::trace!("tag = {}", r.get_tag());
1229 assert!(promises::is_promise_q(q_ctx, &r));
1232
1233 if promises::is_promise_q(q_ctx, &r) {
1234 log::info!("r IS a Promise");
1235 } else {
1236 log::error!("r is NOT a Promise");
1237 }
1238
1239 std::thread::sleep(Duration::from_secs(1));
1240
1241 });
1243 rt.exe_rt_task_in_event_loop(|q_js_rt| {
1244 q_js_rt.run_pending_jobs_if_any();
1245 });
1246
1247 std::thread::sleep(Duration::from_secs(1));
1248 }
1249
1250 #[test]
1251 fn test_eval_await() {
1252 let rt = init_test_rt();
1253
1254 let res = rt.eval_sync(None, Script::new(
1255 "test_async.es",
1256 "{let f = async function(){let p = new Promise((resolve, reject) => {resolve(12345);}); const p2 = await p; return p2}; f()};",
1257 ));
1258
1259 match res {
1260 Ok(esvf) => {
1261 assert!(esvf.is_js_promise());
1262 match esvf {
1263 JsValueFacade::JsPromise { cached_promise } => {
1264 let p_res = cached_promise
1265 .get_promise_result_sync()
1266 .expect("promise timed out");
1267 if p_res.is_err() {
1268 panic!("{:?}", p_res.err().unwrap());
1269 }
1270 let res = p_res.ok().unwrap();
1271 assert!(res.is_i32());
1272 assert_eq!(res.get_i32(), 12345);
1273 }
1274 _ => {}
1275 }
1276 }
1277 Err(e) => {
1278 panic!("eval failed: {}", e);
1279 }
1280 }
1281 }
1282
1283 #[test]
1284 fn test_promise() {
1285 let rt = init_test_rt();
1286
1287 let res = rt.eval_sync(None, Script::new(
1288 "testp2.es",
1289 "let test_promise_P = (new Promise(function(res, rej) {console.log('before res');res(123);console.log('after res');}).then(function (a) {console.log('prom ressed to ' + a);}).catch(function(x) {console.log('p.ca ex=' + x);}))",
1290 ));
1291
1292 match res {
1293 Ok(_) => {}
1294 Err(e) => panic!("p script failed: {}", e),
1295 }
1296 std::thread::sleep(Duration::from_secs(1));
1297 }
1298
1299 #[test]
1300 fn test_module_sync() {
1301 log::info!("> test_module_sync");
1302
1303 let rt = init_test_rt();
1304 debug!("test static import");
1305 let res: Result<JsValueFacade, JsError> = rt.eval_module_sync(
1306 None,
1307 Script::new(
1308 "test.es",
1309 "import {foo} from 'test_module.mes';\n console.log('static imp foo = ' + foo);",
1310 ),
1311 );
1312
1313 match res {
1314 Ok(_) => {
1315 log::debug!("static import ok");
1316 }
1317 Err(e) => {
1318 log::error!("static import failed: {}", e);
1319 }
1320 }
1321
1322 debug!("test dynamic import");
1323 let res: Result<JsValueFacade, JsError> = rt.eval_sync(None, Script::new(
1324 "test_dyn.es",
1325 "console.log('about to load dynamic module');let dyn_p = import('test_module.mes');dyn_p.then(function (some) {console.log('after dyn');console.log('after dyn ' + typeof some);console.log('mltpl 5, 7 = ' + some.mltpl(5, 7));});dyn_p.catch(function (x) {console.log('imp.cat x=' + x);});console.log('dyn done');",
1326 ));
1327
1328 match res {
1329 Ok(_) => {
1330 log::debug!("dynamic import ok");
1331 }
1332 Err(e) => {
1333 log::error!("dynamic import failed: {}", e);
1334 }
1335 }
1336 std::thread::sleep(Duration::from_secs(1));
1337
1338 log::info!("< test_module_sync");
1339 }
1340
1341 async fn test_async1() -> i32 {
1342 let rt = init_test_rt();
1343
1344 let a = rt
1345 .eval(None, Script::new("test_async.es", "122 + 1;"))
1346 .await;
1347 match a {
1348 Ok(a) => a.get_i32(),
1349 Err(e) => panic!("script failed: {}", e),
1350 }
1351 }
1352
1353 #[test]
1354 fn test_async() {
1355 let fut = test_async1();
1356 let res = block_on(fut);
1357 assert_eq!(res, 123);
1358 }
1359}
1360
1361#[cfg(test)]
1362pub mod abstraction_tests {
1363 use crate::builder::QuickJsRuntimeBuilder;
1364 use crate::facades::tests::init_test_rt;
1365 use crate::facades::QuickJsRuntimeFacade;
1366 use crate::jsutils::Script;
1367 use crate::values::JsValueFacade;
1368 use futures::executor::block_on;
1369 use serde::Deserialize;
1370 use serde::Serialize;
1371
1372 async fn example(rt: &QuickJsRuntimeFacade) -> JsValueFacade {
1373 rt.loop_realm(None, |_rt_adapter, realm_adapter| {
1375 let script = Script::new("example.js", "7 + 13");
1376 let value_adapter = realm_adapter.eval(script).expect("script failed");
1377 realm_adapter
1379 .to_js_value_facade(&value_adapter)
1380 .expect("conversion failed")
1381 })
1382 .await
1383 }
1384
1385 #[test]
1386 fn test1() {
1387 let rt = QuickJsRuntimeBuilder::new().build();
1389 let val = block_on(example(&rt));
1390 if let JsValueFacade::I32 { val } = val {
1391 assert_eq!(val, 20);
1392 } else {
1393 panic!("not an i32");
1394 }
1395 }
1396
1397 #[tokio::test]
1398 async fn test_serde() {
1399 let json = r#"
1400 {
1401 "a": 1,
1402 "b": true,
1403 "c": {
1404 "d": "q",
1405 "e": [1, 2, 3.3]
1406 }
1407 }
1408 "#;
1409
1410 let value = serde_json::from_str::<serde_json::Value>(json).expect("json fail");
1411 let input: JsValueFacade = JsValueFacade::SerdeValue { value };
1412 let rt = init_test_rt();
1413
1414 let _ = rt.eval(None, Script::new("t.js", r#"
1415 function testSerde(input) {
1416 return "" + input.a + input.b + input.c.d + input.c.e[0] + input.c.e[1] + input.c.e[2];
1417 }
1418 "#)).await.expect("script failed");
1419
1420 let res = rt
1421 .invoke_function(None, &[], "testSerde", vec![input])
1422 .await
1423 .expect("func failed");
1424
1425 assert!(res.is_string());
1426 assert_eq!(res.get_str(), "1trueq123.3");
1427 }
1428
1429 #[derive(Serialize, Deserialize)]
1430 #[serde(rename_all = "camelCase")]
1431 struct User {
1432 name: String,
1433 last_name: String,
1434 }
1435
1436 #[tokio::test]
1437 async fn serde_tests_serialize() {
1438 let rtb: QuickJsRuntimeBuilder = QuickJsRuntimeBuilder::new();
1439 let rt = rtb.build();
1440
1441 rt.eval(
1443 None,
1444 Script::new(
1445 "test.js",
1446 r#"
1447 function myTest(user) {
1448 return {
1449 name: "proc_" + user.name,
1450 lastName: "proc_" + user.lastName
1451 }
1452 }
1453 "#,
1454 ),
1455 )
1456 .await
1457 .expect("script failed");
1458
1459 let test_user_input = User {
1461 last_name: "Anderson".to_string(),
1462 name: "Mister".to_string(),
1463 };
1464
1465 let args = vec![JsValueFacade::from_serializable(&test_user_input)
1466 .expect("could not serialize to JsValueFacade")];
1467
1468 let res: JsValueFacade = rt
1469 .invoke_function(None, &[], "myTest", args)
1470 .await
1471 .expect("func failed");
1472
1473 let json_result = res
1474 .to_json_string()
1475 .await
1476 .expect("could not serialize to json");
1477
1478 assert_eq!(
1479 json_result.as_str(),
1480 r#"{"name":"proc_Mister","lastName":"proc_Anderson"}"#
1481 );
1482
1483 let user_output: User = serde_json::from_str(json_result.as_str()).unwrap();
1485 assert_eq!(user_output.name.as_str(), "proc_Mister");
1486 assert_eq!(user_output.last_name.as_str(), "proc_Anderson");
1487 }
1488
1489 #[tokio::test]
1490 async fn serde_tests_value() {
1491 let rtb: QuickJsRuntimeBuilder = QuickJsRuntimeBuilder::new();
1492 let rt = rtb.build();
1493
1494 rt.eval(
1496 None,
1497 Script::new(
1498 "test.js",
1499 r#"
1500 function myTest(user) {
1501 return {
1502 name: "proc_" + user.name,
1503 lastName: "proc_" + user.lastName
1504 }
1505 }
1506 "#,
1507 ),
1508 )
1509 .await
1510 .expect("script failed");
1511
1512 let test_user_input = User {
1514 last_name: "Anderson".to_string(),
1515 name: "Mister".to_string(),
1516 };
1517
1518 let input_value: serde_json::Value =
1519 serde_json::to_value(test_user_input).expect("could not to_value");
1520 let args = vec![JsValueFacade::SerdeValue { value: input_value }];
1521
1522 let res: JsValueFacade = rt
1523 .invoke_function(None, &[], "myTest", args)
1524 .await
1525 .expect("func failed");
1526
1527 let value_result: serde_json::Value = res
1529 .to_serde_value()
1530 .await
1531 .expect("could not serialize to json");
1532
1533 assert!(value_result.is_object());
1534
1535 let user_output: User = serde_json::from_value(value_result).unwrap();
1537 assert_eq!(user_output.name.as_str(), "proc_Mister");
1538 assert_eq!(user_output.last_name.as_str(), "proc_Anderson");
1539 }
1540 }