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}