rqjs_ext/
vm.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3use std::{
4    cmp::min,
5    collections::{HashMap, HashSet},
6    ffi::CStr,
7    future::Future,
8    str::FromStr,
9    sync::{Arc, Mutex},
10};
11
12// use once_cell::sync::Lazy;
13
14// use chrono::Utc;
15// use ring::rand::SecureRandom;
16use rquickjs::{
17    atom::PredefinedAtom, context::EvalOptions, function::Opt, prelude::Func, qjs, CatchResultExt,
18    CaughtError, Ctx, Error, Function, IntoJs, Module, Object, Promise, Result, String as JsString,
19    Value,
20};
21// use tokio::sync::oneshot::{self, Receiver};
22// use tracing::trace;
23// use zstd::{bulk::Decompressor, dict::DecoderDictionary};
24
25// use crate::modules::{
26//     console,
27//     crypto::SYSTEM_RANDOM,
28//     path::{dirname, join_path, resolve_path},
29// };
30
31// use crate::{
32//     bytecode::{BYTECODE_COMPRESSED, BYTECODE_UNCOMPRESSED, BYTECODE_VERSION, SIGNATURE_LENGTH},
33//     environment,
34//     json::{parse::json_parse, stringify::json_stringify_replacer_space},
35//     number::number_to_string,
36//     utils::{
37//         clone::structured_clone,
38//         io::get_js_path,
39//         object::{get_bytes, ObjectExt},
40//     },
41// };
42
43// pub static TIME_ORIGIN: AtomicUsize = AtomicUsize::new(0);
44
45// #[inline]
46// pub fn uncompressed_size(input: &[u8]) -> StdResult<(usize, &[u8]), io::Error> {
47//     let size = input.get(..4).ok_or(io::ErrorKind::InvalidInput)?;
48//     let size: &[u8; 4] = size.try_into().map_err(|_| io::ErrorKind::InvalidInput)?;
49//     let uncompressed_size = u32::from_le_bytes(*size) as usize;
50//     let rest = &input[4..];
51//     Ok((uncompressed_size, rest))
52// }
53
54// pub(crate) static COMPRESSION_DICT: &[u8] = include_bytes!("../../bundle/lrt/compression.dict");
55
56// static DECOMPRESSOR_DICT: Lazy<DecoderDictionary> =
57//     Lazy::new(|| DecoderDictionary::copy(COMPRESSION_DICT));
58
59fn print(value: String, stdout: Opt<bool>) {
60    if stdout.0.unwrap_or_default() {
61        println!("{value}");
62    } else {
63        eprintln!("{value}")
64    }
65}
66
67// #[derive(Debug)]
68// pub struct BinaryResolver {
69//     paths: Vec<PathBuf>,
70//     cwd: PathBuf,
71// }
72// impl BinaryResolver {
73//     pub fn add_path<P: Into<PathBuf>>(&mut self, path: P) -> &mut Self {
74//         self.paths.push(path.into());
75//         self
76//     }
77
78//     pub fn get_bin_path(path: &Path) -> PathBuf {
79//         path.with_extension("lrt")
80//     }
81
82//     pub fn normalize<P: AsRef<Path>>(path: P) -> PathBuf {
83//         let ends_with_slash = path.as_ref().to_str().map_or(false, |s| s.ends_with('/'));
84//         let mut normalized = PathBuf::new();
85//         for component in path.as_ref().components() {
86//             match &component {
87//                 Component::ParentDir => {
88//                     if !normalized.pop() {
89//                         normalized.push(component);
90//                     }
91//                 },
92//                 _ => {
93//                     normalized.push(component);
94//                 },
95//             }
96//         }
97//         if ends_with_slash {
98//             normalized.push("");
99//         }
100//         normalized
101//     }
102// }
103
104// impl Default for BinaryResolver {
105//     fn default() -> Self {
106//         let cwd = env::current_dir().unwrap();
107//         Self {
108//             cwd,
109//             paths: Vec::with_capacity(10),
110//         }
111//     }
112// }
113
114// #[allow(clippy::manual_strip)]
115// impl Resolver for BinaryResolver {
116//     fn resolve(&mut self, _ctx: &Ctx, base: &str, name: &str) -> Result<String> {
117//         trace!("Try resolve \"{}\" from \"{}\"", name, base);
118
119//         if BYTECODE_CACHE.contains_key(name) {
120//             return Ok(name.to_string());
121//         }
122
123//         let base_path = Path::new(base);
124//         let base_path = if base_path.is_dir() {
125//             if base_path == self.cwd {
126//                 Path::new(".")
127//             } else {
128//                 base_path
129//             }
130//         } else {
131//             base_path.parent().unwrap_or(base_path)
132//         };
133
134//         let normalized_path = base_path.join(name);
135
136//         let normalized_path = BinaryResolver::normalize(normalized_path);
137//         let mut normalized_path = normalized_path.to_str().unwrap();
138//         let cache_path = if normalized_path.starts_with("./") {
139//             &normalized_path[2..]
140//         } else {
141//             normalized_path
142//         };
143
144//         let cache_key = Path::new(cache_path).with_extension("js");
145//         let cache_key = cache_key.to_str().unwrap();
146
147//         trace!("Normalized path: {}, key: {}", normalized_path, cache_key);
148
149//         if BYTECODE_CACHE.contains_key(cache_key) {
150//             return Ok(cache_key.to_string());
151//         }
152
153//         if BYTECODE_CACHE.contains_key(base) {
154//             normalized_path = name;
155//             if Path::new(normalized_path).exists() {
156//                 return Ok(normalized_path.to_string());
157//             }
158//         }
159
160//         if Path::new(normalized_path).exists() {
161//             return Ok(normalized_path.to_string());
162//         }
163
164//         let path = self
165//             .paths
166//             .iter()
167//             .find_map(|path| {
168//                 let path = path.join(normalized_path);
169//                 let bin_path = BinaryResolver::get_bin_path(&path);
170//                 if bin_path.exists() {
171//                     return Some(bin_path);
172//                 }
173//                 get_js_path(path.to_str().unwrap())
174//             })
175//             .ok_or_else(|| Error::new_resolving(base, name))?;
176
177//         Ok(path.into_os_string().into_string().unwrap())
178//     }
179// }
180
181// #[derive(Debug)]
182// pub struct BinaryLoader;
183
184// impl Default for BinaryLoader {
185//     fn default() -> Self {
186//         Self
187//     }
188// }
189
190// struct RawLoaderContainer<T>
191// where
192//     T: RawLoader + 'static,
193// {
194//     loader: T,
195//     cwd: String,
196// }
197// impl<T> RawLoaderContainer<T>
198// where
199//     T: RawLoader + 'static,
200// {
201//     fn new(loader: T) -> Self {
202//         Self {
203//             loader,
204//             cwd: std::env::current_dir()
205//                 .unwrap()
206//                 .to_string_lossy()
207//                 .to_string(),
208//         }
209//     }
210// }
211
212// unsafe impl<T> RawLoader for RawLoaderContainer<T>
213// where
214//     T: RawLoader + 'static,
215// {
216//     #[allow(clippy::manual_strip)]
217//     unsafe fn raw_load<'js>(&mut self, ctx: &Ctx<'js>, name: &str) -> Result<Module<'js>> {
218//         let res = self.loader.raw_load(ctx, name)?;
219
220//         let name = if name.starts_with("./") {
221//             &name[2..]
222//         } else {
223//             name
224//         };
225
226//         if name.starts_with('/') {
227//             set_import_meta(&res, name)?;
228//         } else {
229//             set_import_meta(&res, &format!("{}/{}", &self.cwd, name))?;
230//         };
231
232//         Ok(res)
233//     }
234// }
235
236// impl Loader for BinaryLoader {
237//     fn load(&mut self, _ctx: &Ctx<'_>, name: &str) -> Result<ModuleData> {
238//         trace!("Loading module: {}", name);
239//         if let Some(bytes) = BYTECODE_CACHE.get(name) {
240//             trace!("Loading embedded module: {}", name);
241
242//             return load_bytecode_module(name, bytes);
243//         }
244//         let path = PathBuf::from(name);
245//         let mut bytes: &[u8] = &std::fs::read(path)?;
246
247//         if name.ends_with(".lrt") {
248//             trace!("Loading binary module: {}", name);
249//             return load_bytecode_module(name, bytes);
250//         }
251//         if bytes.starts_with(b"#!") {
252//             bytes = bytes.splitn(2, |&c| c == b'\n').nth(1).unwrap_or(bytes);
253//         }
254//         Ok(ModuleData::source(name, bytes))
255//     }
256// }
257
258// pub fn load_bytecode_module(name: &str, buf: &[u8]) -> Result<ModuleData> {
259//     let bytes = load_module(buf)?;
260//     Ok(unsafe { ModuleData::bytecode(name, bytes) })
261// }
262
263// fn load_module(input: &[u8]) -> Result<Vec<u8>> {
264//     let (_, compressed, input) = get_bytecode_signature(input)?;
265
266//     if compressed {
267//         let (size, input) = uncompressed_size(input)?;
268//         let mut buf = Vec::with_capacity(size);
269//         let mut decompressor = Decompressor::with_prepared_dictionary(&DECOMPRESSOR_DICT)?;
270//         decompressor.decompress_to_buffer(input, &mut buf)?;
271//         return Ok(buf);
272//     }
273
274//     Ok(input.to_vec())
275// }
276
277// fn get_bytecode_signature(input: &[u8]) -> StdResult<(&[u8], bool, &[u8]), io::Error> {
278//     let raw_signature = input
279//         .get(..SIGNATURE_LENGTH)
280//         .ok_or(io::Error::new::<String>(
281//             io::ErrorKind::InvalidInput,
282//             "Invalid bytecode signature length".into(),
283//         ))?;
284
285//     let (last, signature) = raw_signature.split_last().unwrap();
286
287//     if signature != BYTECODE_VERSION.as_bytes() {
288//         return Err(io::Error::new::<String>(
289//             io::ErrorKind::InvalidInput,
290//             "Invalid bytecode version".into(),
291//         ));
292//     }
293
294//     let mut compressed = None;
295//     if *last == BYTECODE_COMPRESSED {
296//         compressed = Some(true)
297//     } else if *last == BYTECODE_UNCOMPRESSED {
298//         compressed = Some(false)
299//     }
300
301//     let rest = &input[SIGNATURE_LENGTH..];
302//     Ok((
303//         signature,
304//         compressed.ok_or(io::Error::new::<String>(
305//             io::ErrorKind::InvalidInput,
306//             "Invalid bytecode signature".into(),
307//         ))?,
308//         rest,
309//     ))
310// }
311
312// pub struct Vm {
313//     pub runtime: AsyncRuntime,
314//     pub ctx: AsyncContext,
315// }
316
317struct LifetimeArgs<'js>(Ctx<'js>);
318
319#[allow(dead_code)]
320struct ExportArgs<'js>(Ctx<'js>, Object<'js>, Value<'js>, Value<'js>);
321
322// pub struct VmOptions {
323//     pub module_builder: crate::module_builder::ModuleBuilder,
324//     pub max_stack_size: usize,
325//     pub gc_threshold_mb: usize,
326// }
327
328// impl Default for VmOptions {
329//     fn default() -> Self {
330//         Self {
331//             module_builder: crate::module_builder::ModuleBuilder::with_default(),
332//             max_stack_size: 512 * 1024,
333//             gc_threshold_mb: {
334//                 const DEFAULT_GC_THRESHOLD_MB: usize = 20;
335
336//                 let gc_threshold_mb: usize = env::var(environment::ENV_LLRT_GC_THRESHOLD_MB)
337//                     .map(|threshold| threshold.parse().unwrap_or(DEFAULT_GC_THRESHOLD_MB))
338//                     .unwrap_or(DEFAULT_GC_THRESHOLD_MB);
339
340//                 gc_threshold_mb * 1024 * 1024
341//             },
342//         }
343//     }
344// }
345
346// impl Vm {
347//     pub const ENV_LAMBDA_TASK_ROOT: &'static str = "LAMBDA_TASK_ROOT";
348
349//     pub async fn from_options(
350//         vm_options: VmOptions,
351//     ) -> StdResult<Self, Box<dyn std::error::Error + Send + Sync>> {
352//         if TIME_ORIGIN.load(Ordering::Relaxed) == 0 {
353//             let time_origin = Utc::now().timestamp_nanos_opt().unwrap_or_default() as usize;
354//             TIME_ORIGIN.store(time_origin, Ordering::Relaxed)
355//         }
356
357//         SYSTEM_RANDOM
358//             .fill(&mut [0; 8])
359//             .expect("Failed to initialize SystemRandom");
360
361//         let mut file_resolver = FileResolver::default();
362//         let mut binary_resolver = BinaryResolver::default();
363//         let mut paths: Vec<&str> = Vec::with_capacity(10);
364
365//         paths.push(".");
366
367//         let task_root = env::var(Self::ENV_LAMBDA_TASK_ROOT).unwrap_or_else(|_| String::from(""));
368//         let task_root = task_root.as_str();
369//         if cfg!(debug_assertions) {
370//             paths.push("bundle");
371//         } else {
372//             paths.push("/opt");
373//         }
374
375//         if !task_root.is_empty() {
376//             paths.push(task_root);
377//         }
378
379//         for path in paths.iter() {
380//             file_resolver.add_path(*path);
381//             binary_resolver.add_path(*path);
382//         }
383
384//         let (builtin_resolver, module_loader, module_names, init_globals) =
385//             vm_options.module_builder.build();
386
387//         let resolver = (builtin_resolver, binary_resolver, file_resolver);
388
389//         let loader = RawLoaderContainer::new((
390//             module_loader,
391//             BinaryLoader,
392//             BuiltinLoader::default(),
393//             ScriptLoader::default()
394//                 .with_extension("mjs")
395//                 .with_extension("cjs"),
396//         ));
397
398//         let runtime = AsyncRuntime::new()?;
399//         runtime.set_max_stack_size(vm_options.max_stack_size).await;
400//         runtime.set_gc_threshold(vm_options.gc_threshold_mb).await;
401//         runtime.set_loader(resolver, loader).await;
402//         let ctx = AsyncContext::full(&runtime).await?;
403//         ctx.with(|ctx| {
404//             for init_global in init_globals {
405//                 init_global(&ctx)?;
406//             }
407//             init(&ctx, module_names)?;
408//             Ok::<_, Error>(())
409//         })
410//         .await?;
411
412//         Ok(Vm { runtime, ctx })
413//     }
414
415//     pub async fn new() -> StdResult<Self, Box<dyn std::error::Error + Send + Sync>> {
416//         let vm = Self::from_options(VmOptions::default()).await?;
417//         Ok(vm)
418//     }
419
420//     pub fn load_module<'js>(ctx: &Ctx<'js>, filename: PathBuf) -> Result<Object<'js>> {
421//         Module::import(ctx, filename.to_string_lossy().to_string())
422//     }
423
424//     pub async fn run_module(ctx: &AsyncContext, filename: &Path) {
425//         Self::run_and_handle_exceptions(ctx, |ctx| {
426//             let _res = Vm::load_module(&ctx, filename.to_path_buf())?;
427//             Ok(())
428//         })
429//         .await
430//     }
431
432//     pub async fn run_and_handle_exceptions<'js, F>(ctx: &AsyncContext, f: F)
433//     where
434//         F: FnOnce(Ctx) -> rquickjs::Result<()> + Send,
435//     {
436//         ctx.with(|ctx| {
437//             f(ctx.clone())
438//                 .catch(&ctx)
439//                 .unwrap_or_else(|err| Self::print_error_and_exit(&ctx, err));
440//         })
441//         .await;
442//     }
443
444//     pub fn print_error_and_exit<'js>(ctx: &Ctx<'js>, err: CaughtError<'js>) -> ! {
445//         let mut error_str = String::new();
446//         write!(error_str, "Error: {:?}", err).unwrap();
447//         if let Ok(error) = err.into_value(ctx) {
448//             if console::log_std_err(ctx, Rest(vec![error.clone()]), console::LogLevel::Fatal)
449//                 .is_err()
450//             {
451//                 eprintln!("{}", error_str);
452//             };
453//             exit(1)
454//         } else {
455//             eprintln!("{}", error_str);
456//             exit(1)
457//         };
458//     }
459
460//     pub async fn idle(self) -> StdResult<(), Box<dyn std::error::Error + Sync + Send>> {
461//         self.runtime.idle().await;
462
463//         drop(self.ctx);
464//         drop(self.runtime);
465//         Ok(())
466//     }
467// }
468
469use tokio::sync::oneshot::Receiver;
470
471use crate::{
472    json::{parse::json_parse, stringify::json_stringify_replacer_space},
473    modules::path::{dirname, join_path, resolve_path},
474    number::number_to_string,
475    utils::object::{get_bytes, ObjectExt},
476};
477
478fn json_parse_string<'js>(ctx: Ctx<'js>, value: Value<'js>) -> Result<Value<'js>> {
479    let bytes = get_bytes(&ctx, value)?;
480    json_parse(&ctx, bytes)
481}
482
483fn run_gc(ctx: Ctx<'_>) {
484    // trace!("Running GC");
485
486    unsafe {
487        let rt = qjs::JS_GetRuntime(ctx.as_raw().as_ptr());
488        qjs::JS_RunGC(rt);
489    };
490}
491
492use crate::utils::clone::structured_clone;
493fn init(ctx: &Ctx<'_>, module_names: HashSet<&'static str>) -> Result<()> {
494    let globals = ctx.globals();
495
496    globals.set("__gc", Func::from(run_gc))?;
497
498    let number: Function = globals.get(PredefinedAtom::Number)?;
499    let number_proto: Object = number.get(PredefinedAtom::Prototype)?;
500    number_proto.set(PredefinedAtom::ToString, Func::from(number_to_string))?;
501
502    globals.set("global", ctx.globals())?;
503    globals.set("self", ctx.globals())?;
504    globals.set("load", Func::from(load))?;
505    globals.set("print", Func::from(print))?;
506    globals.set(
507        "structuredClone",
508        Func::from(|ctx, value, options| structured_clone(&ctx, value, options)),
509    )?;
510
511    let json_module: Object = globals.get(PredefinedAtom::JSON)?;
512    json_module.set("parse", Func::from(json_parse_string))?;
513    json_module.set(
514        "stringify",
515        Func::from(|ctx, value, replacer, space| {
516            struct StringifyArgs<'js>(Ctx<'js>, Value<'js>, Opt<Value<'js>>, Opt<Value<'js>>);
517            let StringifyArgs(ctx, value, replacer, space) =
518                StringifyArgs(ctx, value, replacer, space);
519
520            let mut space_value = None;
521            let mut replacer_value = None;
522
523            if let Some(replacer) = replacer.0 {
524                if let Some(space) = space.0 {
525                    if let Some(space) = space.as_string() {
526                        let mut space = space.clone().to_string()?;
527                        space.truncate(20);
528                        space_value = Some(space);
529                    }
530                    if let Some(number) = space.as_int() {
531                        if number > 0 {
532                            space_value = Some(" ".repeat(min(10, number as usize)));
533                        }
534                    }
535                }
536                replacer_value = Some(replacer);
537            }
538
539            json_stringify_replacer_space(&ctx, value, replacer_value, space_value)
540                .map(|v| v.into_js(&ctx))?
541        }),
542    )?;
543
544    #[allow(clippy::arc_with_non_send_sync)]
545    let require_in_progress: Arc<Mutex<HashMap<String, Object>>> =
546        Arc::new(Mutex::new(HashMap::new()));
547
548    #[allow(clippy::arc_with_non_send_sync)]
549    let require_exports: Arc<Mutex<Option<Value>>> = Arc::new(Mutex::new(None));
550    let require_exports_ref = require_exports.clone();
551    let require_exports_ref_2 = require_exports.clone();
552
553    let js_bootstrap = Object::new(ctx.clone())?;
554    js_bootstrap.set(
555        "moduleExport",
556        Func::from(move |ctx, obj, prop, value| {
557            let ExportArgs(_ctx, _, _, value) = ExportArgs(ctx, obj, prop, value);
558            let mut exports = require_exports.lock().unwrap();
559            exports.replace(value);
560            Result::Ok(true)
561        }),
562    )?;
563    js_bootstrap.set(
564        "exports",
565        Func::from(move |ctx, obj, prop, value| {
566            let ExportArgs(ctx, _, prop, value) = ExportArgs(ctx, obj, prop, value);
567            let mut exports = require_exports_ref.lock().unwrap();
568            let exports = if exports.is_some() {
569                exports.as_ref().unwrap()
570            } else {
571                exports.replace(Object::new(ctx.clone())?.into_value());
572                exports.as_ref().unwrap()
573            };
574            exports.as_object().unwrap().set(prop, value)?;
575            Result::Ok(true)
576        }),
577    )?;
578    globals.set("__bootstrap", js_bootstrap)?;
579
580    globals.set(
581        "require",
582        Func::from(move |ctx, specifier: String| -> Result<Value> {
583            let LifetimeArgs(ctx) = LifetimeArgs(ctx);
584            let specifier = if let Some(striped_specifier) = &specifier.strip_prefix("node:") {
585                striped_specifier.to_string()
586            } else {
587                specifier
588            };
589            let import_name = if module_names.contains(specifier.as_str())
590                // || BYTECODE_CACHE.contains_key(&specifier)
591                || specifier.starts_with('/')
592            {
593                specifier
594            } else {
595                let module_name = get_script_or_module_name(ctx.clone());
596                let abs_path = resolve_path([module_name].iter());
597                let import_directory = dirname(abs_path);
598                join_path(vec![import_directory, specifier])
599            };
600
601            let mut map = require_in_progress.lock().unwrap();
602            if let Some(obj) = map.get(&import_name) {
603                return Ok(obj.clone().into_value());
604            }
605
606            let obj = Object::new(ctx.clone())?;
607            map.insert(import_name.clone(), obj.clone());
608            drop(map);
609
610            // trace!("Require: {}", import_name);
611
612            let imported_object: Promise = Module::import(&ctx, import_name.clone()).unwrap();
613            require_in_progress.lock().unwrap().remove(&import_name);
614
615            if let Some(exports) = require_exports_ref_2.lock().unwrap().take() {
616                if let Some(exports) = exports.as_object() {
617                    for prop in exports.props::<Value, Value>() {
618                        let (key, value) = prop?;
619                        obj.set(key, value)?;
620                    }
621                } else {
622                    return Ok(exports);
623                }
624            }
625
626            for prop in imported_object.props::<String, Value>() {
627                let (key, value) = prop?;
628                obj.set(key, value)?;
629            }
630
631            Ok(obj.into_value())
632        }),
633    )?;
634
635    Module::import(ctx, "@llrt/std")?;
636
637    Ok(())
638}
639
640fn load<'js>(ctx: Ctx<'js>, filename: String, options: Opt<Object<'js>>) -> Result<Value<'js>> {
641    let mut eval_options = EvalOptions::default();
642    eval_options.global = true;
643    eval_options.strict = false;
644    eval_options.backtrace_barrier = false;
645    eval_options.promise = true;
646
647    // let mut eval_options = EvalOptions {
648    //     global: true,
649    //     strict: false,
650    //     backtrace_barrier: false,
651    //     promise: false,
652    // };
653
654    if let Some(options) = options.0 {
655        if let Some(global) = options.get_optional("global")? {
656            eval_options.global = global;
657        }
658
659        if let Some(strict) = options.get_optional("strict")? {
660            eval_options.strict = strict;
661        }
662    }
663
664    ctx.eval_file_with_options(
665        std::path::PathBuf::from_str(&filename).unwrap(),
666        eval_options,
667    )
668}
669
670fn get_script_or_module_name(ctx: Ctx<'_>) -> String {
671    unsafe {
672        let ctx_ptr = ctx.as_raw().as_ptr();
673        let atom = qjs::JS_GetScriptOrModuleName(ctx_ptr, 0);
674        let c_str = qjs::JS_AtomToCString(ctx_ptr, atom);
675        if c_str.is_null() {
676            qjs::JS_FreeCString(ctx_ptr, c_str);
677            return String::from(".");
678        }
679        let bytes = CStr::from_ptr(c_str).to_bytes();
680        let res = std::str::from_utf8_unchecked(bytes).to_string();
681        qjs::JS_FreeCString(ctx_ptr, c_str);
682        res
683    }
684}
685
686fn set_import_meta(module: &Module<'_>, filepath: &str) -> Result<()> {
687    let meta: Object = module.meta()?;
688    meta.prop("url", format!("file://{}", filepath))?;
689    Ok(())
690}
691
692pub trait ErrorExtensions<'js> {
693    fn into_value(self, ctx: &Ctx<'js>) -> Result<Value<'js>>;
694}
695
696impl<'js> ErrorExtensions<'js> for Error {
697    fn into_value(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
698        Err::<(), _>(self).catch(ctx).unwrap_err().into_value(ctx)
699    }
700}
701
702impl<'js> ErrorExtensions<'js> for CaughtError<'js> {
703    fn into_value(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
704        Ok(match self {
705            CaughtError::Error(err) => {
706                JsString::from_str(ctx.clone(), &err.to_string())?.into_value()
707            }
708            CaughtError::Exception(ex) => ex.into_value(),
709            CaughtError::Value(val) => val,
710        })
711    }
712}
713
714pub trait CtxExtension<'js> {
715    fn spawn_exit<F, R>(&self, future: F) -> Result<Receiver<R>>
716    where
717        F: Future<Output = Result<R>> + 'js,
718        R: 'js;
719}
720
721impl<'js> CtxExtension<'js> for Ctx<'js> {
722    fn spawn_exit<F, R>(&self, future: F) -> Result<Receiver<R>>
723    where
724        F: Future<Output = Result<R>> + 'js,
725        R: 'js,
726    {
727        todo!();
728        // let ctx = self.clone();
729
730        // let type_error_ctor: Constructor = ctx.globals().get(PredefinedAtom::TypeError)?;
731        // let type_error: Object = type_error_ctor.construct(())?;
732        // let stack: Option<String> = type_error.get(PredefinedAtom::Stack).ok();
733
734        // let (join_channel_tx, join_channel_rx) = oneshot::channel();
735
736        // self.spawn(async move {
737        //     match future.await.catch(&ctx) {
738        //         Ok(res) => {
739        //             //result here dosn't matter if receiver has dropped
740        //             let _ = join_channel_tx.send(res);
741        //         }
742        //         Err(err) => {
743        //             // if let CaughtError::Exception(err) = err {
744        //             //     if err.stack().is_none() {
745        //             //         if let Some(stack) = stack {
746        //             //             err.set(PredefinedAtom::Stack, stack).unwrap();
747        //             //         }
748        //             //     }
749        //             //     Vm::print_error_and_exit(&ctx, CaughtError::Exception(err));
750        //             // } else {
751        //             //     Vm::print_error_and_exit(&ctx, err);
752        //             // }
753        //         }
754        //     }
755        // });
756        // Ok(join_channel_rx)
757    }
758}