1use crate::facades::QuickjsRuntimeFacadeInner;
4use crate::jsutils::modules::{CompiledModuleLoader, NativeModuleLoader, ScriptModuleLoader};
5use crate::jsutils::{JsError, Script, ScriptPreProcessor};
6use crate::quickjs_utils::compile::from_bytecode;
7use crate::quickjs_utils::modules::{
8 add_module_export, compile_module, get_module_def, get_module_name, new_module,
9 set_module_export,
10};
11use crate::quickjs_utils::runtime::new_class_id;
12use crate::quickjs_utils::{gc, interrupthandler, modules, promises};
13use crate::quickjsrealmadapter::QuickJsRealmAdapter;
14use libquickjs_sys as q;
15use serde::Serialize;
16use std::cell::RefCell;
17use std::collections::HashMap;
18use std::ffi::CString;
19use std::fmt::{Debug, Formatter};
20use std::os::raw::c_int;
21use std::panic;
22use std::sync::{Arc, Weak};
23
24pub trait ModuleLoader {
26 fn normalize_path(
29 &self,
30 q_ctx: &QuickJsRealmAdapter,
31 ref_path: &str,
32 path: &str,
33 ) -> Option<String>;
34 fn load_module(
36 &self,
37 q_ctx: &QuickJsRealmAdapter,
38 absolute_path: &str,
39 ) -> Result<*mut q::JSModuleDef, JsError>;
40 fn has_module(&self, q_ctx: &QuickJsRealmAdapter, absolute_path: &str) -> bool;
42 unsafe fn init_module(
46 &self,
47 q_ctx: &QuickJsRealmAdapter,
48 module: *mut q::JSModuleDef,
49 ) -> Result<(), JsError>;
50}
51
52pub struct CompiledModuleLoaderAdapter {
55 inner: Box<dyn CompiledModuleLoader>,
56}
57
58impl CompiledModuleLoaderAdapter {
59 pub fn new(loader: Box<dyn CompiledModuleLoader>) -> Self {
60 Self { inner: loader }
61 }
62}
63
64pub struct ScriptModuleLoaderAdapter {
65 inner: Box<dyn ScriptModuleLoader>,
66}
67
68impl ScriptModuleLoaderAdapter {
69 pub fn new(loader: Box<dyn ScriptModuleLoader>) -> Self {
70 Self { inner: loader }
71 }
72}
73
74impl ModuleLoader for CompiledModuleLoaderAdapter {
75 fn normalize_path(
76 &self,
77 q_ctx: &QuickJsRealmAdapter,
78 ref_path: &str,
79 path: &str,
80 ) -> Option<String> {
81 self.inner.normalize_path(q_ctx, ref_path, path)
82 }
83
84 fn load_module(
85 &self,
86 q_ctx: &QuickJsRealmAdapter,
87 absolute_path: &str,
88 ) -> Result<*mut q::JSModuleDef, JsError> {
89 let bytes = self.inner.load_module(q_ctx, absolute_path);
90
91 let compiled_module = unsafe { from_bytecode(q_ctx.context, &bytes)? };
92 Ok(get_module_def(&compiled_module))
93 }
94
95 fn has_module(&self, q_ctx: &QuickJsRealmAdapter, absolute_path: &str) -> bool {
96 self.normalize_path(q_ctx, absolute_path, absolute_path)
97 .is_some()
98 }
99
100 unsafe fn init_module(
101 &self,
102 _q_ctx: &QuickJsRealmAdapter,
103 _module: *mut q::JSModuleDef,
104 ) -> Result<(), JsError> {
105 Ok(())
106 }
107}
108
109impl ModuleLoader for ScriptModuleLoaderAdapter {
110 fn normalize_path(
111 &self,
112 realm: &QuickJsRealmAdapter,
113 ref_path: &str,
114 path: &str,
115 ) -> Option<String> {
116 self.inner.normalize_path(realm, ref_path, path)
117 }
118
119 fn load_module(
120 &self,
121 realm: &QuickJsRealmAdapter,
122 absolute_path: &str,
123 ) -> Result<*mut q::JSModuleDef, JsError> {
124 log::trace!("load_module");
125 let code = self.inner.load_module(realm, absolute_path);
126
127 let mut script = Script::new(absolute_path, code.as_str());
128 script = QuickJsRuntimeAdapter::pre_process(script)?;
129 log::trace!("load_module / 2");
130 let compiled_module = unsafe { compile_module(realm.context, script)? };
131 log::trace!("load_module / 3");
132 Ok(get_module_def(&compiled_module))
133 }
134
135 fn has_module(&self, q_ctx: &QuickJsRealmAdapter, absolute_path: &str) -> bool {
136 self.normalize_path(q_ctx, absolute_path, absolute_path)
137 .is_some()
138 }
139
140 unsafe fn init_module(
141 &self,
142 _q_ctx: &QuickJsRealmAdapter,
143 _module: *mut q::JSModuleDef,
144 ) -> Result<(), JsError> {
145 Ok(())
146 }
147}
148
149pub struct NativeModuleLoaderAdapter {
150 inner: Box<dyn NativeModuleLoader>,
151}
152
153impl NativeModuleLoaderAdapter {
154 pub fn new(loader: Box<dyn NativeModuleLoader>) -> Self {
155 Self { inner: loader }
156 }
157}
158
159impl ModuleLoader for NativeModuleLoaderAdapter {
160 fn normalize_path(
161 &self,
162 q_ctx: &QuickJsRealmAdapter,
163 _ref_path: &str,
164 path: &str,
165 ) -> Option<String> {
166 if self.inner.has_module(q_ctx, path) {
167 Some(path.to_string())
168 } else {
169 None
170 }
171 }
172
173 fn load_module(
174 &self,
175 q_ctx: &QuickJsRealmAdapter,
176 absolute_path: &str,
177 ) -> Result<*mut q::JSModuleDef, JsError> {
178 let module = unsafe { new_module(q_ctx.context, absolute_path, Some(native_module_init))? };
180
181 for name in self.inner.get_module_export_names(q_ctx, absolute_path) {
182 unsafe { add_module_export(q_ctx.context, module, name)? }
183 }
184
185 Ok(module)
187 }
188
189 fn has_module(&self, q_ctx: &QuickJsRealmAdapter, absolute_path: &str) -> bool {
190 self.inner.has_module(q_ctx, absolute_path)
191 }
192
193 unsafe fn init_module(
194 &self,
195 q_ctx: &QuickJsRealmAdapter,
196 module: *mut q::JSModuleDef,
197 ) -> Result<(), JsError> {
198 let module_name = get_module_name(q_ctx.context, module)?;
199
200 for (name, val) in self.inner.get_module_exports(q_ctx, module_name.as_str()) {
201 set_module_export(q_ctx.context, module, name, val)?;
202 }
203 Ok(())
204 }
205}
206
207unsafe extern "C" fn native_module_init(
208 ctx: *mut q::JSContext,
209 module: *mut q::JSModuleDef,
210) -> c_int {
211 let module_name = get_module_name(ctx, module).expect("could not get name");
212 log::trace!("native_module_init: {}", module_name);
213
214 QuickJsRuntimeAdapter::do_with(|q_js_rt| {
215 QuickJsRealmAdapter::with_context(ctx, |q_ctx| {
216 q_js_rt
217 .with_all_module_loaders(|module_loader| {
218 if module_loader.has_module(q_ctx, module_name.as_str()) {
219 match module_loader.init_module(q_ctx, module) {
220 Ok(_) => {
221 Some(0) }
223 Err(e) => {
224 q_ctx.report_ex(
225 format!(
226 "Failed to init native module: {module_name} caused by {e}"
227 )
228 .as_str(),
229 );
230 Some(1)
231 }
232 }
233 } else {
234 None
235 }
236 })
237 .unwrap_or(0)
238 })
239 })
240}
241
242thread_local! {
243 pub(crate) static QJS_RT: RefCell<Option<QuickJsRuntimeAdapter >> = const { RefCell::new(None) };
248
249}
250
251pub type ContextInitHooks =
252 Vec<Box<dyn Fn(&QuickJsRuntimeAdapter, &QuickJsRealmAdapter) -> Result<(), JsError>>>;
253
254pub struct QuickJsRuntimeAdapter {
255 pub(crate) runtime: *mut q::JSRuntime,
256 pub(crate) contexts: HashMap<String, QuickJsRealmAdapter>,
257 rti_ref: Option<Weak<QuickjsRuntimeFacadeInner>>,
258 id: String,
259 pub(crate) context_init_hooks: RefCell<ContextInitHooks>,
260 script_module_loaders: Vec<ScriptModuleLoaderAdapter>,
261 native_module_loaders: Vec<NativeModuleLoaderAdapter>,
262 compiled_module_loaders: Vec<CompiledModuleLoaderAdapter>,
263 pub(crate) script_pre_processors: Vec<Box<dyn ScriptPreProcessor + Send>>,
265 #[allow(clippy::type_complexity)]
266 pub(crate) interrupt_handler: Option<Box<dyn Fn(&QuickJsRuntimeAdapter) -> bool>>,
267}
268
269thread_local! {
270 static NESTED: RefCell<bool> = const {RefCell::new(false)};
271}
272
273#[derive(Serialize)]
274pub struct MemoryUsage {
275 pub realm_ct: usize,
276 pub malloc_size: i64,
277 pub malloc_limit: i64,
278 pub memory_used_size: i64,
279 pub malloc_count: i64,
280 pub memory_used_count: i64,
281 pub atom_count: i64,
282 pub atom_size: i64,
283 pub str_count: i64,
284 pub str_size: i64,
285 pub obj_count: i64,
286 pub obj_size: i64,
287 pub prop_count: i64,
288 pub prop_size: i64,
289 pub shape_count: i64,
290 pub shape_size: i64,
291 pub js_func_count: i64,
292 pub js_func_size: i64,
293 pub js_func_code_size: i64,
294 pub js_func_pc2line_count: i64,
295 pub js_func_pc2line_size: i64,
296 pub c_func_count: i64,
297 pub array_count: i64,
298 pub fast_array_count: i64,
299 pub fast_array_elements: i64,
300 pub binary_object_count: i64,
301 pub binary_object_size: i64,
302}
303
304impl Debug for MemoryUsage {
305 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
306 f.write_str("MemoryUsage:")?;
307 f.write_str(format!("\n realm_ct: {}", self.realm_ct).as_str())?;
308 f.write_str(format!("\n malloc_size: {}", self.malloc_size).as_str())?;
309 f.write_str(format!("\n malloc_limit: {}", self.malloc_limit).as_str())?;
310 f.write_str(format!("\n memory_used_size: {}", self.memory_used_size).as_str())?;
311 f.write_str(format!("\n malloc_count: {}", self.malloc_count).as_str())?;
312 f.write_str(format!("\n memory_used_count: {}", self.memory_used_count).as_str())?;
313 f.write_str(format!("\n atom_count: {}", self.atom_count).as_str())?;
314 f.write_str(format!("\n atom_size: {}", self.atom_size).as_str())?;
315 f.write_str(format!("\n str_count: {}", self.str_count).as_str())?;
316 f.write_str(format!("\n str_size: {}", self.str_size).as_str())?;
317 f.write_str(format!("\n obj_count: {}", self.obj_count).as_str())?;
318 f.write_str(format!("\n obj_size: {}", self.obj_size).as_str())?;
319 f.write_str(format!("\n prop_count: {}", self.prop_count).as_str())?;
320 f.write_str(format!("\n prop_size: {}", self.prop_size).as_str())?;
321 f.write_str(format!("\n shape_count: {}", self.shape_count).as_str())?;
322 f.write_str(format!("\n shape_size: {}", self.shape_size).as_str())?;
323 f.write_str(format!("\n js_func_count: {}", self.js_func_count).as_str())?;
324 f.write_str(format!("\n js_func_size: {}", self.js_func_size).as_str())?;
325 f.write_str(format!("\n js_func_code_size: {}", self.js_func_code_size).as_str())?;
326 f.write_str(format!("\n js_func_pc2line_count: {}", self.js_func_pc2line_count).as_str())?;
327 f.write_str(format!("\n js_func_pc2line_size: {}", self.js_func_pc2line_size).as_str())?;
328 f.write_str(format!("\n c_func_count: {}", self.c_func_count).as_str())?;
329 f.write_str(format!("\n array_count: {}", self.array_count).as_str())?;
330 f.write_str(format!("\n fast_array_count: {}", self.fast_array_count).as_str())?;
331 f.write_str(format!("\n fast_array_elements: {}", self.fast_array_elements).as_str())?;
332 f.write_str(format!("\n binary_object_count: {}", self.binary_object_count).as_str())?;
333 f.write_str(format!("\n binary_object_size: {}", self.binary_object_size).as_str())?;
334 Ok(())
335 }
336}
337
338impl QuickJsRuntimeAdapter {
339 pub(crate) fn init_rt_for_current_thread(rt: QuickJsRuntimeAdapter) {
340 QJS_RT.with(|rc| {
341 let opt = &mut *rc.borrow_mut();
342 opt.replace(rt);
343 })
344 }
345
346 pub fn new_class_id(&self) -> u32 {
347 unsafe { new_class_id(self.runtime) }
348 }
349
350 pub fn print_stats(&self) {
351 for ctx in &self.contexts {
352 println!("> ----- ctx: {}", ctx.0);
353 ctx.1.print_stats();
354 println!("< ----- ctx: {}", ctx.0);
355 }
356
357 crate::quickjs_utils::typedarrays::BUFFERS.with(|rc| {
359 let map = &*rc.borrow();
360 println!("typedarrays::BUFFERS.len() = {}", map.len());
361 });
362 crate::quickjs_utils::functions::CALLBACK_REGISTRY.with(|rc| {
363 let map = &*rc.borrow();
364 println!("functions::CALLBACK_REGISTRY.len() = {}", map.len());
365 });
366 crate::quickjs_utils::functions::CALLBACK_IDS.with(|rc| {
367 let map = &*rc.borrow();
368 println!("functions::CALLBACK_IDS.len() = {}", map.len());
369 });
370
371 let mu = self.memory_usage();
372 println!("MemoryUsage: {mu:?}");
373 }
374
375 pub fn memory_usage(&self) -> MemoryUsage {
377 let mu: q::JSMemoryUsage = unsafe { crate::quickjs_utils::get_memory_usage(self.runtime) };
378
379 MemoryUsage {
380 realm_ct: self.contexts.len(),
381 malloc_size: mu.malloc_size,
382 malloc_limit: mu.malloc_limit,
383 memory_used_size: mu.memory_used_size,
384 malloc_count: mu.malloc_count,
385 memory_used_count: mu.memory_used_count,
386 atom_count: mu.atom_count,
387 atom_size: mu.atom_size,
388 str_count: mu.str_count,
389 str_size: mu.str_size,
390 obj_count: mu.obj_count,
391 obj_size: mu.obj_size,
392 prop_count: mu.prop_count,
393 prop_size: mu.prop_size,
394 shape_count: mu.shape_count,
395 shape_size: mu.shape_size,
396 js_func_count: mu.js_func_count,
397 js_func_size: mu.js_func_size,
398 js_func_code_size: mu.js_func_code_size,
399 js_func_pc2line_count: mu.js_func_pc2line_count,
400 js_func_pc2line_size: mu.js_func_pc2line_size,
401 c_func_count: mu.c_func_count,
402 array_count: mu.array_count,
403 fast_array_count: mu.fast_array_count,
404 fast_array_elements: mu.fast_array_elements,
405 binary_object_count: mu.binary_object_count,
406 binary_object_size: mu.binary_object_size,
407 }
408 }
409
410 pub(crate) fn pre_process(mut script: Script) -> Result<Script, JsError> {
411 Self::do_with(|q_js_rt| {
412 for pp in &q_js_rt.script_pre_processors {
413 pp.process(&mut script)?;
414 }
415 #[cfg(feature = "typescript")]
416 crate::typescript::transpile_serverside(q_js_rt, &mut script)?;
417
418 Ok(script)
419 })
420 }
421
422 pub fn add_context_init_hook<H>(&self, hook: H) -> Result<(), JsError>
423 where
424 H: Fn(&QuickJsRuntimeAdapter, &QuickJsRealmAdapter) -> Result<(), JsError> + 'static,
425 {
426 let i = {
427 let hooks = &mut *self.context_init_hooks.borrow_mut();
428 hooks.push(Box::new(hook));
429 hooks.len() - 1
430 };
431
432 let hooks = &*self.context_init_hooks.borrow();
433 let hook = hooks.get(i).expect("invalid state");
434 for ctx in self.contexts.values() {
435 if let Err(e) = hook(self, ctx) {
436 panic!("hook failed {}", e);
437 }
438 }
439
440 Ok(())
441 }
442
443 pub fn create_context(id: &str) -> Result<(), JsError> {
444 let ctx = Self::do_with(|q_js_rt| {
445 assert!(!q_js_rt.has_context(id));
446 QuickJsRealmAdapter::new(id.to_string(), q_js_rt)
447 });
448
449 QuickJsRuntimeAdapter::do_with_mut(|q_js_rt| {
450 q_js_rt.contexts.insert(id.to_string(), ctx);
451 });
452
453 Self::do_with(|q_js_rt| {
454 let ctx = q_js_rt.get_context(id);
455 let hooks = &*q_js_rt.context_init_hooks.borrow();
456 for hook in hooks {
457 hook(q_js_rt, ctx)?;
458 }
459 Ok(())
460 })
461 }
462 pub fn list_contexts(&self) -> Vec<&str> {
463 self.contexts.keys().map(|k| k.as_str()).collect()
464 }
465 pub fn remove_context(id: &str) -> anyhow::Result<()> {
466 log::debug!("QuickJsRuntime::drop_context: {}", id);
467
468 QuickJsRuntimeAdapter::do_with(|rt| {
469 let q_ctx = rt.get_context(id);
470 log::trace!("QuickJsRuntime::q_ctx.free: {}", id);
471 q_ctx.free();
472 log::trace!("after QuickJsRuntime::q_ctx.free: {}", id);
473 rt.gc();
474 });
475
476 let ctx = QuickJsRuntimeAdapter::do_with_mut(|m_rt| m_rt.contexts.remove(id));
477
478 match ctx {
479 None => {
480 anyhow::bail!("no such context");
481 }
482 Some(ctx) => {
483 drop(ctx);
484 }
485 }
486
487 Ok(())
488 }
489 pub(crate) fn get_context_ids() -> Vec<String> {
490 QuickJsRuntimeAdapter::do_with(|q_js_rt| {
491 q_js_rt.contexts.iter().map(|c| c.0.clone()).collect()
492 })
493 }
494 pub fn get_context(&self, id: &str) -> &QuickJsRealmAdapter {
495 self.contexts.get(id).expect("no such context")
496 }
497 pub fn opt_context(&self, id: &str) -> Option<&QuickJsRealmAdapter> {
498 self.contexts.get(id)
499 }
500 pub fn has_context(&self, id: &str) -> bool {
501 self.contexts.contains_key(id)
502 }
503 pub(crate) fn init_rti_ref(&mut self, el_ref: Weak<QuickjsRuntimeFacadeInner>) {
504 self.rti_ref = Some(el_ref);
505 }
506 pub unsafe fn get_quickjs_context(&self, context: *mut q::JSContext) -> &QuickJsRealmAdapter {
509 let id = QuickJsRealmAdapter::get_id(context);
510 self.get_context(id)
511 }
512
513 pub fn get_rti_ref(&self) -> Option<Arc<QuickjsRuntimeFacadeInner>> {
514 if let Some(rt_ref) = &self.rti_ref {
515 rt_ref.upgrade()
516 } else {
517 None
518 }
519 }
520
521 pub(crate) fn new(runtime: *mut q::JSRuntime) -> Self {
522 log::trace!("creating new QuickJsRuntime");
523
524 if runtime.is_null() {
525 panic!("RuntimeCreationFailed");
526 }
527
528 let id = format!("q_{}", thread_id::get());
537
538 let mut q_rt = Self {
539 runtime,
540 contexts: Default::default(),
541
542 rti_ref: None,
543 id,
544 context_init_hooks: RefCell::new(vec![]),
545 script_module_loaders: vec![],
546 native_module_loaders: vec![],
547 compiled_module_loaders: vec![],
548 script_pre_processors: vec![],
549 interrupt_handler: None,
550 };
551
552 modules::set_module_loader(&q_rt);
553 promises::init_promise_rejection_tracker(&q_rt);
554
555 let main_ctx = QuickJsRealmAdapter::new("__main__".to_string(), &q_rt);
556 q_rt.contexts.insert("__main__".to_string(), main_ctx);
557
558 q_rt
559 }
560
561 pub fn set_interrupt_handler<I: Fn(&QuickJsRuntimeAdapter) -> bool + 'static>(
562 &mut self,
563 interrupt_handler: I,
564 ) -> &mut Self {
565 self.interrupt_handler = Some(Box::new(interrupt_handler));
566 interrupthandler::init(self);
567 self
568 }
569
570 pub fn add_script_module_loader(&mut self, sml: ScriptModuleLoaderAdapter) {
571 self.script_module_loaders.push(sml);
572 }
573
574 pub fn add_compiled_module_loader(&mut self, cml: CompiledModuleLoaderAdapter) {
575 self.compiled_module_loaders.push(cml);
576 }
577
578 pub fn add_native_module_loader(&mut self, nml: NativeModuleLoaderAdapter) {
579 self.native_module_loaders.push(nml);
580 }
581
582 pub fn get_main_realm(&self) -> &QuickJsRealmAdapter {
583 self.get_context("__main__")
585 }
586
587 pub fn with_all_module_loaders<C, R>(&self, consumer: C) -> Option<R>
588 where
589 C: Fn(&dyn ModuleLoader) -> Option<R>,
590 {
591 for loader in &self.compiled_module_loaders {
592 let res = consumer(loader);
593 if res.is_some() {
594 return res;
595 }
596 }
597 for loader in &self.native_module_loaders {
598 let res = consumer(loader);
599 if res.is_some() {
600 return res;
601 }
602 }
603 for loader in &self.script_module_loaders {
604 let res = consumer(loader);
605 if res.is_some() {
606 return res;
607 }
608 }
609 None
610 }
611
612 pub fn gc(&self) {
614 gc(self);
615 }
616
617 pub fn do_with<C, R>(task: C) -> R
618 where
619 C: FnOnce(&QuickJsRuntimeAdapter) -> R,
620 {
621 let most_outer = NESTED.with(|rc| {
622 if *rc.borrow() {
623 false
624 } else {
625 *rc.borrow_mut() = true;
626 true
627 }
628 });
629
630 let res = QJS_RT.with(|qjs_rc| {
631 let qjs_rt_opt = &*qjs_rc.borrow();
632 let q_js_rt = qjs_rt_opt
633 .as_ref()
634 .expect("runtime was not yet initialized for this thread");
635 if most_outer {
636 unsafe { libquickjs_sys::JS_UpdateStackTop(q_js_rt.runtime) };
637 }
638 task(q_js_rt)
639 });
640
641 if most_outer {
642 NESTED.with(|rc| {
643 *rc.borrow_mut() = false;
644 });
645 }
646
647 res
648 }
649
650 pub fn do_with_mut<C, R>(task: C) -> R
651 where
652 C: FnOnce(&mut QuickJsRuntimeAdapter) -> R,
653 {
654 let most_outer = NESTED.with(|rc| {
655 if *rc.borrow() {
656 false
657 } else {
658 *rc.borrow_mut() = true;
659 true
660 }
661 });
662
663 let res = QJS_RT.with(|qjs_rc| {
664 let qjs_rt_opt = &mut *qjs_rc.borrow_mut();
665 let qjs_rt = qjs_rt_opt
666 .as_mut()
667 .expect("runtime was not yet initialized for this thread");
668 if most_outer {
669 unsafe { libquickjs_sys::JS_UpdateStackTop(qjs_rt.runtime) };
670 }
671 task(qjs_rt)
672 });
673
674 if most_outer {
675 NESTED.with(|rc| {
676 *rc.borrow_mut() = false;
677 });
678 }
679
680 res
681 }
682
683 pub fn run_pending_jobs_if_any(&self) {
687 log::trace!("quick_js_rt.run_pending_jobs_if_any");
688 while self.has_pending_jobs() {
689 log::trace!("quick_js_rt.has_pending_jobs!");
690 let res = self.run_pending_job();
691 match res {
692 Ok(_) => {
693 log::trace!("run_pending_job OK!");
694 }
695 Err(e) => {
696 log::error!("run_pending_job failed: {}", e);
697 }
698 }
699 }
700 }
701
702 pub fn has_pending_jobs(&self) -> bool {
703 let flag = unsafe { q::JS_IsJobPending(self.runtime) };
704 #[cfg(feature = "bellard")]
705 {
706 flag > 0
707 }
708 #[cfg(feature = "quickjs-ng")]
709 {
710 flag
711 }
712 }
713
714 pub fn run_pending_job(&self) -> Result<(), JsError> {
715 let mut ctx: *mut q::JSContext = std::ptr::null_mut();
716 let flag = unsafe {
717 q::JS_ExecutePendingJob(self.runtime, &mut ctx)
719 };
720 if flag < 0 {
721 let e = unsafe { QuickJsRealmAdapter::get_exception(ctx) }
722 .unwrap_or_else(|| JsError::new_str("Unknown exception while running pending job"));
723 return Err(e);
724 }
725 Ok(())
726 }
727
728 pub fn get_id(&self) -> &str {
729 self.id.as_str()
730 }
731
732 pub fn load_module_script_opt(&self, ref_path: &str, path: &str) -> Option<Script> {
734 let realm = self.get_main_realm();
735 for loader in &self.script_module_loaders {
736 let i = &loader.inner;
737 if let Some(normalized) = i.normalize_path(realm, ref_path, path) {
738 let code = i.load_module(realm, normalized.as_str());
739 return Some(Script::new(normalized.as_str(), code.as_str()));
740 }
741 }
742
743 None
744 }
745}
746
747impl Drop for QuickJsRuntimeAdapter {
748 fn drop(&mut self) {
749 log::trace!("drop QuickJsRuntime, dropping contexts");
751
752 self.contexts.clear();
753 log::trace!("drop QuickJsRuntime, after dropping contexts");
754
755 log::trace!("before JS_FreeRuntime");
756 unsafe { q::JS_FreeRuntime(self.runtime) };
757 log::trace!("after JS_FreeRuntime");
758 }
759}
760
761impl QuickJsRuntimeAdapter {
762 pub fn load_module_script(&self, ref_path: &str, path: &str) -> Option<Script> {
763 self.load_module_script_opt(ref_path, path)
764 }
765
766 pub fn get_realm(&self, id: &str) -> Option<&QuickJsRealmAdapter> {
767 if self.has_context(id) {
768 Some(self.get_context(id))
769 } else {
770 None
771 }
772 }
773
774 pub fn add_realm_init_hook<H>(&self, hook: H) -> Result<(), JsError>
775 where
776 H: Fn(&Self, &QuickJsRealmAdapter) -> Result<(), JsError> + 'static,
777 {
778 self.add_context_init_hook(hook)
779 }
780}
781
782pub(crate) fn make_cstring(value: &str) -> Result<CString, JsError> {
784 let res = CString::new(value);
785 match res {
786 Ok(val) => Ok(val),
787 Err(_) => Err(JsError::new_string(format!(
788 "could not create cstring from {value}"
789 ))),
790 }
791}
792
793#[cfg(test)]
794pub mod tests {
795 use crate::builder::QuickJsRuntimeBuilder;
796 use crate::quickjsrealmadapter::QuickJsRealmAdapter;
797 use crate::quickjsruntimeadapter::QuickJsRuntimeAdapter;
798
799 use crate::facades::tests::init_test_rt;
800 use std::panic;
801
802 use crate::jsutils::modules::ScriptModuleLoader;
803 use crate::jsutils::Script;
804
805 struct FooScriptModuleLoader {}
806 impl ScriptModuleLoader for FooScriptModuleLoader {
807 fn normalize_path(
808 &self,
809 _realm: &QuickJsRealmAdapter,
810 _ref_path: &str,
811 path: &str,
812 ) -> Option<String> {
813 Some(path.to_string())
814 }
815
816 fn load_module(&self, _realm: &QuickJsRealmAdapter, _absolute_path: &str) -> String {
817 log::debug!("load_module");
818 "{}".to_string()
819 }
820 }
821
822 #[test]
823 fn test_mem_usage() {
824 let rt = QuickJsRuntimeBuilder::new()
825 .memory_limit(1024 * 1024)
826 .build();
827 rt.eval_sync(None, Script::new("", "globalThis.f = function(){};"))
828 .expect("script failed");
829
830 rt.loop_realm_sync(None, |rt, _realm| {
831 let mu = rt.memory_usage();
832 println!("mu: {mu:?}");
833 });
834 }
835
836 #[test]
837 fn test_script_load() {
838 log::debug!("testing1");
839 let rt = QuickJsRuntimeBuilder::new()
840 .script_module_loader(FooScriptModuleLoader {})
841 .build();
842 rt.exe_rt_task_in_event_loop(|q_js_rt| {
843 log::debug!("testing2");
844 let script = q_js_rt.load_module_script_opt("", "test.mjs").unwrap();
845 assert_eq!(script.get_runnable_code(), "{}");
846 log::debug!("tested");
847 });
848 }
849
850 #[test]
851 fn test_eval() {
852 let rt = init_test_rt();
853 rt.eval_sync(
854 None,
855 Script::new(
856 "eval.js",
857 "console.log('euc: ' + encodeURIComponent('hello world'));",
858 ),
859 )
860 .expect("script failed to compile");
861 }
862
863 #[test]
864 fn test_realm_init() {
865 let rt = QuickJsRuntimeBuilder::new().build();
881
882 rt.exe_task_in_event_loop(|| {
883 QuickJsRuntimeAdapter::do_with(|rt| {
884 rt.add_realm_init_hook(|_rt, realm| {
885 realm
886 .install_function(
887 &["utils"],
888 "doSomething",
889 |_rt, realm, _this, _args| realm.create_null(),
890 0,
891 )
892 .expect("failed to install function");
893 match realm.eval(Script::new("t.js", "1+1")) {
894 Ok(_) => {}
895 Err(e) => {
896 panic!("script failed {}", e);
897 }
898 }
899 Ok(())
900 })
901 .expect("init hook addition failed");
902 })
903 });
904
905 rt.loop_realm_void(Some("testrealm1"), |_rt, realm| {
906 realm
907 .eval(Script::new("test.js", "console.log(utils.doSomething());"))
908 .expect("script failed");
909 });
910 }
911}