diff options
Diffstat (limited to 'core/es_isolate.rs')
| -rw-r--r-- | core/es_isolate.rs | 716 |
1 files changed, 378 insertions, 338 deletions
diff --git a/core/es_isolate.rs b/core/es_isolate.rs index 0e9bb5b5b..655b42a06 100644 --- a/core/es_isolate.rs +++ b/core/es_isolate.rs @@ -1,9 +1,8 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. -// Do not add any dependency to modules.rs! -// modules.rs is complex and should remain decoupled from isolate.rs to keep the -// Isolate struct from becoming too bloating for users who do not need -// asynchronous module loading. +// This module provides higher level implementation of Isolate that +// supports asynchronous loading and executution of ES Modules. +// The isolate.rs should never depend on this module. use rusty_v8 as v8; @@ -13,16 +12,11 @@ use futures::future::Future; use futures::future::FutureExt; use futures::ready; use futures::stream::FuturesUnordered; -use futures::stream::IntoStream; -use futures::stream::Stream; use futures::stream::StreamExt; use futures::stream::StreamFuture; -use futures::stream::TryStream; -use futures::stream::TryStreamExt; use futures::task::AtomicWaker; use libc::c_void; use std::collections::HashMap; -use std::fmt; use std::ops::{Deref, DerefMut}; use std::option::Option; use std::pin::Pin; @@ -32,6 +26,11 @@ use std::task::Poll; use crate::isolate::Isolate; use crate::isolate::StartupData; +use crate::module_specifier::ModuleSpecifier; +use crate::modules::LoadState; +use crate::modules::Loader; +use crate::modules::Modules; +use crate::modules::RecursiveModuleLoad; pub type ModuleId = i32; pub type DynImportId = i32; @@ -48,78 +47,6 @@ pub struct SourceCodeInfo { pub module_url_found: String, } -#[derive(Debug, Eq, PartialEq)] -pub enum RecursiveLoadEvent { - Fetch(SourceCodeInfo), - Instantiate(ModuleId), -} - -pub trait ImportStream: TryStream { - fn register( - &mut self, - source_code_info: SourceCodeInfo, - isolate: &mut EsIsolate, - ) -> Result<(), ErrBox>; -} - -type DynImportStream = Box< - dyn ImportStream< - Ok = RecursiveLoadEvent, - Error = ErrBox, - Item = Result<RecursiveLoadEvent, ErrBox>, - > + Send - + Unpin, ->; - -type DynImportFn = dyn Fn(DynImportId, &str, &str) -> DynImportStream; - -/// Wraps DynImportStream to include the DynImportId, so that it doesn't -/// need to be exposed. -#[derive(Debug)] -struct DynImport { - pub id: DynImportId, - pub inner: DynImportStream, -} - -impl fmt::Debug for DynImportStream { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "DynImportStream(..)") - } -} - -impl Stream for DynImport { - type Item = Result<(DynImportId, RecursiveLoadEvent), (DynImportId, ErrBox)>; - - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context, - ) -> Poll<Option<Self::Item>> { - let self_inner = self.get_mut(); - let result = ready!(self_inner.inner.try_poll_next_unpin(cx)).unwrap(); - match result { - Ok(event) => Poll::Ready(Some(Ok((self_inner.id, event)))), - Err(e) => Poll::Ready(Some(Err((self_inner.id, e)))), - } - } -} - -impl ImportStream for DynImport { - fn register( - &mut self, - source_code_info: SourceCodeInfo, - isolate: &mut EsIsolate, - ) -> Result<(), ErrBox> { - self.inner.register(source_code_info, isolate) - } -} - -pub struct ModuleInfo { - pub main: bool, - pub name: String, - pub handle: v8::Global<v8::Module>, - pub import_specifiers: Vec<String>, -} - /// A single execution context of JavaScript. Corresponds roughly to the "Web /// Worker" concept in the DOM. An Isolate is a Future that can be used with /// Tokio. The Isolate future complete when there is an error or when all @@ -130,15 +57,13 @@ pub struct ModuleInfo { /// as arguments. An async Op corresponds exactly to a Promise in JavaScript. pub struct EsIsolate { core_isolate: Box<Isolate>, - - mods_: HashMap<ModuleId, ModuleInfo>, + loader: Arc<Box<dyn Loader + Unpin>>, + pub modules: Modules, pub(crate) next_dyn_import_id: DynImportId, pub(crate) dyn_import_map: HashMap<DynImportId, v8::Global<v8::PromiseResolver>>, - pub(crate) resolve_context: *mut c_void, - pending_dyn_imports: FuturesUnordered<StreamFuture<IntoStream<DynImport>>>, - dyn_import: Option<Arc<DynImportFn>>, + pending_dyn_imports: FuturesUnordered<StreamFuture<RecursiveModuleLoad>>, waker: AtomicWaker, } @@ -166,7 +91,7 @@ impl Drop for EsIsolate { let mut locker = v8::Locker::new(&isolate); let mut hs = v8::HandleScope::new(&mut locker); let scope = hs.enter(); - for module in self.mods_.values_mut() { + for module in self.modules.info.values_mut() { module.handle.reset(scope); } for handle in self.dyn_import_map.values_mut() { @@ -176,30 +101,12 @@ impl Drop for EsIsolate { } } -/// Called during mod_instantiate() to resolve imports. -type ResolveFn<'a> = dyn FnMut(&str, ModuleId) -> ModuleId + 'a; - -/// Used internally by Isolate::mod_instantiate to wrap ResolveFn and -/// encapsulate pointer casts. -pub struct ResolveContext<'a> { - pub resolve_fn: &'a mut ResolveFn<'a>, -} - -impl<'a> ResolveContext<'a> { - #[inline] - fn as_raw_ptr(&mut self) -> *mut c_void { - self as *mut _ as *mut c_void - } - - #[allow(clippy::missing_safety_doc)] - #[inline] - pub(crate) unsafe fn from_raw_ptr(ptr: *mut c_void) -> &'a mut Self { - &mut *(ptr as *mut _) - } -} - impl EsIsolate { - pub fn new(startup_data: StartupData, will_snapshot: bool) -> Box<Self> { + pub fn new( + loader: Box<dyn Loader + Unpin>, + startup_data: StartupData, + will_snapshot: bool, + ) -> Box<Self> { let mut core_isolate = Isolate::new(startup_data, will_snapshot); { let isolate = core_isolate.v8_isolate.as_mut().unwrap(); @@ -212,12 +119,11 @@ impl EsIsolate { } let es_isolate = Self { + modules: Modules::new(), + loader: Arc::new(loader), core_isolate, - mods_: HashMap::new(), next_dyn_import_id: 0, dyn_import_map: HashMap::new(), - resolve_context: std::ptr::null_mut(), - dyn_import: None, pending_dyn_imports: FuturesUnordered::new(), waker: AtomicWaker::new(), }; @@ -276,15 +182,9 @@ impl EsIsolate { let mut handle = v8::Global::<v8::Module>::new(); handle.set(scope, module); - self.mods_.insert( - id, - ModuleInfo { - main, - name: name.to_string(), - import_specifiers, - handle, - }, - ); + self + .modules + .register(id, name, main, handle, import_specifiers); context.exit(); id } @@ -301,19 +201,18 @@ impl EsIsolate { } pub fn mod_get_imports(&self, id: ModuleId) -> Vec<String> { - let info = self.get_module_info(id).unwrap(); + let info = self.modules.get_info(id).unwrap(); let len = info.import_specifiers.len(); let mut out = Vec::new(); for i in 0..len { - let info = self.get_module_info(id).unwrap(); + let info = self.modules.get_info(id).unwrap(); let specifier = info.import_specifiers.get(i).unwrap().to_string(); out.push(specifier); } out } - fn mod_instantiate2(&mut self, mut ctx: ResolveContext<'_>, id: ModuleId) { - self.resolve_context = ctx.as_raw_ptr(); + fn mod_instantiate2(&mut self, id: ModuleId) { let isolate = self.core_isolate.v8_isolate.as_ref().unwrap(); let mut locker = v8::Locker::new(isolate); let mut hs = v8::HandleScope::new(&mut locker); @@ -324,9 +223,10 @@ impl EsIsolate { let mut try_catch = v8::TryCatch::new(scope); let tc = try_catch.enter(); - let maybe_info = self.get_module_info(id); + let maybe_info = self.modules.get_info(id); if maybe_info.is_none() { + context.exit(); return; } @@ -334,6 +234,7 @@ impl EsIsolate { let mut module = module_handle.get(scope).unwrap(); if module.get_status() == v8::ModuleStatus::Errored { + context.exit(); return; } @@ -350,30 +251,19 @@ impl EsIsolate { } context.exit(); - self.resolve_context = std::ptr::null_mut(); } + /// Instanciates a ES module /// /// ErrBox can be downcast to a type that exposes additional information about /// the V8 exception. By default this type is CoreJSError, however it may be a /// different type if Isolate::set_js_error_create() has been used. - pub fn mod_instantiate( - &mut self, - id: ModuleId, - resolve_fn: &mut ResolveFn, - ) -> Result<(), ErrBox> { - let ctx = ResolveContext { resolve_fn }; - self.mod_instantiate2(ctx, id); + fn mod_instantiate(&mut self, id: ModuleId) -> Result<(), ErrBox> { + self.mod_instantiate2(id); self.core_isolate.check_last_exception() } - /// Evaluates an already instantiated ES module. - /// - /// ErrBox can be downcast to a type that exposes additional information about - /// the V8 exception. By default this type is CoreJSError, however it may be a - /// different type if Isolate::set_js_error_create() has been used. - pub fn mod_evaluate(&mut self, id: ModuleId) -> Result<(), ErrBox> { - self.shared_init(); + fn mod_evaluate2(&mut self, id: ModuleId) { let isolate = self.core_isolate.v8_isolate.as_ref().unwrap(); let mut locker = v8::Locker::new(isolate); let mut hs = v8::HandleScope::new(&mut locker); @@ -382,7 +272,7 @@ impl EsIsolate { let mut context = self.core_isolate.global_context.get(scope).unwrap(); context.enter(); - let info = self.get_module_info(id).expect("ModuleInfo not found"); + let info = self.modules.get_info(id).expect("ModuleInfo not found"); let mut module = info.handle.get(scope).expect("Empty module handle"); let mut status = module.get_status(); @@ -416,22 +306,32 @@ impl EsIsolate { }; context.exit(); - - self.core_isolate.check_last_exception() } - pub fn get_module_info(&self, id: ModuleId) -> Option<&ModuleInfo> { - if id == 0 { - return None; - } - self.mods_.get(&id) + /// Evaluates an already instantiated ES module. + /// + /// ErrBox can be downcast to a type that exposes additional information about + /// the V8 exception. By default this type is CoreJSError, however it may be a + /// different type if Isolate::set_js_error_create() has been used. + pub fn mod_evaluate(&mut self, id: ModuleId) -> Result<(), ErrBox> { + self.shared_init(); + self.mod_evaluate2(id); + self.core_isolate.check_last_exception() } - pub fn set_dyn_import<F>(&mut self, f: F) - where - F: Fn(DynImportId, &str, &str) -> DynImportStream + Send + Sync + 'static, - { - self.dyn_import = Some(Arc::new(f)); + pub fn module_resolve_cb( + &mut self, + specifier: &str, + referrer_id: ModuleId, + ) -> ModuleId { + let referrer = self.modules.get_name(referrer_id).unwrap(); + // We should have already resolved and Ready this module, so + // resolve() will not fail this time. + let specifier = self + .modules + .get_cached_specifier(specifier, &referrer) + .expect("Module should already be resolved"); + self.modules.get_id(specifier.as_str()).unwrap_or(0) } pub fn dyn_import_cb( @@ -442,16 +342,14 @@ impl EsIsolate { ) { debug!("dyn_import specifier {} referrer {} ", specifier, referrer); - if let Some(ref f) = self.dyn_import { - let inner = f(id, specifier, referrer); - let stream = DynImport { inner, id }; - self.waker.wake(); - self - .pending_dyn_imports - .push(stream.into_stream().into_future()); - } else { - panic!("dyn_import callback not set") - } + let load = RecursiveModuleLoad::dynamic_import( + id, + specifier, + referrer, + self.loader.clone(), + ); + self.waker.wake(); + self.pending_dyn_imports.push(load.into_future()); } fn dyn_import_done( @@ -486,7 +384,7 @@ impl EsIsolate { let mut resolver = resolver_handle.get(scope).unwrap(); resolver_handle.reset(scope); - let maybe_info = self.get_module_info(mod_id); + let maybe_info = self.modules.get_info(mod_id); if let Some(info) = maybe_info { // Resolution success @@ -518,47 +416,151 @@ impl EsIsolate { } fn poll_dyn_imports(&mut self, cx: &mut Context) -> Poll<Result<(), ErrBox>> { - use RecursiveLoadEvent::*; loop { match self.pending_dyn_imports.poll_next_unpin(cx) { Poll::Pending | Poll::Ready(None) => { // There are no active dynamic import loaders, or none are ready. return Poll::Ready(Ok(())); } - Poll::Ready(Some(( - Some(Ok((dyn_import_id, Fetch(source_code_info)))), - mut stream, - ))) => { - // A module (not necessarily the one dynamically imported) has been - // fetched. Create and register it, and if successful, poll for the - // next recursive-load event related to this dynamic import. - match stream.get_mut().register(source_code_info, self) { - Ok(()) => self.pending_dyn_imports.push(stream.into_future()), - Err(err) => { - self.dyn_import_done(dyn_import_id, Err(Some(err.to_string())))? + Poll::Ready(Some(load_stream_poll)) => { + let maybe_result = load_stream_poll.0; + let mut load = load_stream_poll.1; + let dyn_import_id = load.dyn_import_id.unwrap(); + + if let Some(load_stream_result) = maybe_result { + match load_stream_result { + Ok(info) => { + // A module (not necessarily the one dynamically imported) has been + // fetched. Create and register it, and if successful, poll for the + // next recursive-load event related to this dynamic import. + match self.register_during_load(info, &mut load) { + Ok(()) => { + // Keep importing until it's fully drained + self.pending_dyn_imports.push(load.into_future()); + } + Err(err) => self.dyn_import_done( + dyn_import_id, + Err(Some(err.to_string())), + )?, + } + } + Err(err) => { + // A non-javascript error occurred; this could be due to a an invalid + // module specifier, or a problem with the source map, or a failure + // to fetch the module source code. + self + .dyn_import_done(dyn_import_id, Err(Some(err.to_string())))? + } + } + } else { + // The top-level module from a dynamic import has been instantiated. + // Load is done. + let module_id = load.root_module_id.unwrap(); + self.mod_instantiate(module_id)?; + if let Ok(()) = self.mod_evaluate(module_id) { + self.dyn_import_done(dyn_import_id, Ok(module_id))? + } else { + self.dyn_import_done(dyn_import_id, Err(None))? } } } - Poll::Ready(Some(( - Some(Ok((dyn_import_id, Instantiate(module_id)))), - _, - ))) => { - // The top-level module from a dynamic import has been instantiated. - match self.mod_evaluate(module_id) { - Ok(()) => self.dyn_import_done(dyn_import_id, Ok(module_id))?, - Err(..) => self.dyn_import_done(dyn_import_id, Err(None))?, - } - } - Poll::Ready(Some((Some(Err((dyn_import_id, err))), _))) => { - // A non-javascript error occurred; this could be due to a an invalid - // module specifier, or a problem with the source map, or a failure - // to fetch the module source code. - self.dyn_import_done(dyn_import_id, Err(Some(err.to_string())))? - } - Poll::Ready(Some((None, _))) => unreachable!(), } } } + + fn register_during_load( + &mut self, + info: SourceCodeInfo, + load: &mut RecursiveModuleLoad, + ) -> Result<(), ErrBox> { + let SourceCodeInfo { + code, + module_url_specified, + module_url_found, + } = info; + + let is_main = + load.state == LoadState::LoadingRoot && !load.is_dynamic_import(); + let referrer_name = &module_url_found.to_string(); + let referrer_specifier = + ModuleSpecifier::resolve_url(referrer_name).unwrap(); + + // #A There are 3 cases to handle at this moment: + // 1. Source code resolved result have the same module name as requested + // and is not yet registered + // -> register + // 2. Source code resolved result have a different name as requested: + // 2a. The module with resolved module name has been registered + // -> alias + // 2b. The module with resolved module name has not yet been registerd + // -> register & alias + + // If necessary, register an alias. + if module_url_specified != module_url_found { + self.modules.alias(&module_url_specified, &module_url_found); + } + + let module_id = match self.modules.get_id(&module_url_found) { + Some(id) => { + // Module has already been registered. + debug!( + "Already-registered module fetched again: {}", + module_url_found + ); + id + } + // Module not registered yet, do it now. + None => self.mod_new(is_main, &module_url_found, &code)?, + }; + + // Now we must iterate over all imports of the module and load them. + let imports = self.mod_get_imports(module_id); + for import in imports { + let module_specifier = self.loader.resolve( + &import, + referrer_name, + false, + load.is_dynamic_import(), + )?; + self + .modules + .cache_specifier(&import, referrer_name, &module_specifier); + let module_name = module_specifier.as_str(); + + if !self.modules.is_registered(module_name) { + load.add_import(module_specifier, referrer_specifier.clone()); + } + } + + // If we just finished loading the root module, store the root module id. + if load.state == LoadState::LoadingRoot { + load.root_module_id = Some(module_id); + load.state = LoadState::LoadingImports; + } + + if load.pending.is_empty() { + load.state = LoadState::Done; + } + + Ok(()) + } + + pub async fn load_module( + &mut self, + specifier: &str, + code: Option<String>, + ) -> Result<ModuleId, ErrBox> { + let mut load = + RecursiveModuleLoad::main(specifier, code, self.loader.clone()); + + while let Some(info_result) = load.next().await { + let info = info_result?; + self.register_during_load(info, &mut load)?; + } + + let root_id = load.root_module_id.expect("Root module id empty"); + self.mod_instantiate(root_id).map(|_| root_id) + } } impl Future for EsIsolate { @@ -594,16 +596,48 @@ pub mod tests { use crate::isolate::js_check; use crate::isolate::tests::run_in_task; use crate::isolate::PinnedBuf; + use crate::modules::SourceCodeInfoFuture; use crate::ops::*; use std::io; use std::sync::atomic::{AtomicUsize, Ordering}; - use std::sync::Mutex; - pub fn setup() -> (Box<EsIsolate>, Arc<AtomicUsize>) { + #[test] + fn test_mods() { + #[derive(Clone, Default)] + struct ModsLoader { + pub count: Arc<AtomicUsize>, + } + + impl Loader for ModsLoader { + fn resolve( + &self, + specifier: &str, + referrer: &str, + _is_main: bool, + _is_dyn_import: bool, + ) -> Result<ModuleSpecifier, ErrBox> { + self.count.fetch_add(1, Ordering::Relaxed); + assert_eq!(specifier, "./b.js"); + assert_eq!(referrer, "file:///a.js"); + let s = ModuleSpecifier::resolve_import(specifier, referrer).unwrap(); + Ok(s) + } + + fn load( + &self, + _module_specifier: &ModuleSpecifier, + _maybe_referrer: Option<ModuleSpecifier>, + ) -> Pin<Box<SourceCodeInfoFuture>> { + unreachable!() + } + } + + let loader = Box::new(ModsLoader::default()); + let resolve_count = loader.count.clone(); let dispatch_count = Arc::new(AtomicUsize::new(0)); let dispatch_count_ = dispatch_count.clone(); - let mut isolate = EsIsolate::new(StartupData::None, false); + let mut isolate = EsIsolate::new(loader, StartupData::None, false); let dispatcher = move |control: &[u8], _zero_copy: Option<PinnedBuf>| -> CoreOp { @@ -626,19 +660,16 @@ pub mod tests { } "#, )); + assert_eq!(dispatch_count.load(Ordering::Relaxed), 0); - (isolate, dispatch_count) - } - #[test] - fn test_mods() { - let (mut isolate, dispatch_count) = setup(); + let specifier_a = "file:///a.js".to_string(); let mod_a = isolate .mod_new( true, - "a.js", + &specifier_a, r#" - import { b } from 'b.js' + import { b } from './b.js' if (b() != 'b') throw Error(); let control = new Uint8Array([42]); Deno.core.send(1, control); @@ -648,223 +679,231 @@ pub mod tests { assert_eq!(dispatch_count.load(Ordering::Relaxed), 0); let imports = isolate.mod_get_imports(mod_a); - assert_eq!(imports, vec!["b.js".to_string()]); + let specifier_b = "./b.js".to_string(); + assert_eq!(imports, vec![specifier_b.clone()]); let mod_b = isolate - .mod_new(false, "b.js", "export function b() { return 'b' }") + .mod_new(false, "file:///b.js", "export function b() { return 'b' }") .unwrap(); let imports = isolate.mod_get_imports(mod_b); assert_eq!(imports.len(), 0); - let resolve_count = Arc::new(AtomicUsize::new(0)); - let resolve_count_ = resolve_count.clone(); - - let mut resolve = move |specifier: &str, _referrer: ModuleId| -> ModuleId { - resolve_count_.fetch_add(1, Ordering::SeqCst); - assert_eq!(specifier, "b.js"); - mod_b - }; - - js_check(isolate.mod_instantiate(mod_b, &mut resolve)); + let module_specifier = + ModuleSpecifier::resolve_import(&specifier_b, &specifier_a).unwrap(); + isolate.modules.cache_specifier( + &specifier_b, + &specifier_a, + &module_specifier, + ); + js_check(isolate.mod_instantiate(mod_b)); assert_eq!(dispatch_count.load(Ordering::Relaxed), 0); assert_eq!(resolve_count.load(Ordering::SeqCst), 0); - js_check(isolate.mod_instantiate(mod_a, &mut resolve)); + js_check(isolate.mod_instantiate(mod_a)); assert_eq!(dispatch_count.load(Ordering::Relaxed), 0); - assert_eq!(resolve_count.load(Ordering::SeqCst), 1); js_check(isolate.mod_evaluate(mod_a)); assert_eq!(dispatch_count.load(Ordering::Relaxed), 1); - assert_eq!(resolve_count.load(Ordering::SeqCst), 1); } - struct MockImportStream(Vec<Result<RecursiveLoadEvent, ErrBox>>); - - impl Stream for MockImportStream { - type Item = Result<RecursiveLoadEvent, ErrBox>; - - fn poll_next( - self: Pin<&mut Self>, - _cx: &mut Context, - ) -> Poll<Option<Self::Item>> { - let inner = self.get_mut(); - let event = if inner.0.is_empty() { - None - } else { - Some(inner.0.remove(0)) - }; - Poll::Ready(event) + #[test] + fn dyn_import_err() { + #[derive(Clone, Default)] + struct DynImportErrLoader { + pub count: Arc<AtomicUsize>, } - } - impl ImportStream for MockImportStream { - fn register( - &mut self, - module_data: SourceCodeInfo, - isolate: &mut EsIsolate, - ) -> Result<(), ErrBox> { - let id = isolate.mod_new( - false, - &module_data.module_url_found, - &module_data.code, - )?; - println!( - "MockImportStream register {} {}", - id, module_data.module_url_found - ); - Ok(()) + impl Loader for DynImportErrLoader { + fn resolve( + &self, + specifier: &str, + referrer: &str, + _is_main: bool, + _is_dyn_import: bool, + ) -> Result<ModuleSpecifier, ErrBox> { + self.count.fetch_add(1, Ordering::Relaxed); + assert_eq!(specifier, "/foo.js"); + assert_eq!(referrer, "file:///dyn_import2.js"); + let s = ModuleSpecifier::resolve_import(specifier, referrer).unwrap(); + Ok(s) + } + + fn load( + &self, + _module_specifier: &ModuleSpecifier, + _maybe_referrer: Option<ModuleSpecifier>, + ) -> Pin<Box<SourceCodeInfoFuture>> { + async { Err(ErrBox::from(io::Error::from(io::ErrorKind::NotFound))) } + .boxed() + } } - } - #[test] - fn dyn_import_err() { // Test an erroneous dynamic import where the specified module isn't found. run_in_task(|cx| { - let count = Arc::new(AtomicUsize::new(0)); - let count_ = count.clone(); - let mut isolate = EsIsolate::new(StartupData::None, false); - isolate.set_dyn_import(move |_, specifier, referrer| { - count_.fetch_add(1, Ordering::Relaxed); - assert_eq!(specifier, "foo.js"); - assert_eq!(referrer, "dyn_import2.js"); - let err = io::Error::from(io::ErrorKind::NotFound); - let stream = MockImportStream(vec![Err(err.into())]); - Box::new(stream) - }); + let loader = Box::new(DynImportErrLoader::default()); + let count = loader.count.clone(); + let mut isolate = EsIsolate::new(loader, StartupData::None, false); + js_check(isolate.execute( - "dyn_import2.js", + "file:///dyn_import2.js", r#" (async () => { - await import("foo.js"); + await import("/foo.js"); })(); "#, )); - assert_eq!(count.load(Ordering::Relaxed), 1); + assert_eq!(count.load(Ordering::Relaxed), 0); // We should get an error here. let result = isolate.poll_unpin(cx); if let Poll::Ready(Ok(_)) = result { unreachable!(); } + assert_eq!(count.load(Ordering::Relaxed), 1); }) } #[test] fn dyn_import_err2() { - use std::convert::TryInto; - // Import multiple modules to demonstrate that after failed dynamic import - // another dynamic import can still be run - run_in_task(|cx| { - let count = Arc::new(AtomicUsize::new(0)); - let count_ = count.clone(); - let mut isolate = EsIsolate::new(StartupData::None, false); - isolate.set_dyn_import(move |_, specifier, referrer| { - let c = count_.fetch_add(1, Ordering::Relaxed); + #[derive(Clone, Default)] + struct DynImportErr2Loader { + pub count: Arc<AtomicUsize>, + } + + impl Loader for DynImportErr2Loader { + fn resolve( + &self, + specifier: &str, + referrer: &str, + _is_main: bool, + _is_dyn_import: bool, + ) -> Result<ModuleSpecifier, ErrBox> { + let c = self.count.fetch_add(1, Ordering::Relaxed); match c { - 0 => assert_eq!(specifier, "foo1.js"), - 1 => assert_eq!(specifier, "foo2.js"), - 2 => assert_eq!(specifier, "foo3.js"), + 0 => assert_eq!(specifier, "/foo1.js"), + 1 => assert_eq!(specifier, "/foo2.js"), + 2 => assert_eq!(specifier, "/foo3.js"), _ => unreachable!(), } - assert_eq!(referrer, "dyn_import_error.js"); + assert_eq!(referrer, "file:///dyn_import_error.js"); + let s = ModuleSpecifier::resolve_import(specifier, referrer).unwrap(); + Ok(s) + } - let source_code_info = SourceCodeInfo { - module_url_specified: specifier.to_owned(), - module_url_found: specifier.to_owned(), + fn load( + &self, + specifier: &ModuleSpecifier, + _maybe_referrer: Option<ModuleSpecifier>, + ) -> Pin<Box<SourceCodeInfoFuture>> { + let info = SourceCodeInfo { + module_url_specified: specifier.to_string(), + module_url_found: specifier.to_string(), code: "# not valid JS".to_owned(), }; - let stream = MockImportStream(vec![ - Ok(RecursiveLoadEvent::Fetch(source_code_info)), - Ok(RecursiveLoadEvent::Instantiate(c.try_into().unwrap())), - ]); - Box::new(stream) - }); + async move { Ok(info) }.boxed() + } + } + + // Import multiple modules to demonstrate that after failed dynamic import + // another dynamic import can still be run + run_in_task(|cx| { + let loader = Box::new(DynImportErr2Loader::default()); + let loader1 = loader.clone(); + let mut isolate = EsIsolate::new(loader, StartupData::None, false); js_check(isolate.execute( - "dyn_import_error.js", + "file:///dyn_import_error.js", r#" (async () => { - await import("foo1.js"); + await import("/foo1.js"); })(); (async () => { - await import("foo2.js"); + await import("/foo2.js"); })(); (async () => { - await import("foo3.js"); + await import("/foo3.js"); })(); "#, )); - assert_eq!(count.load(Ordering::Relaxed), 3); + assert_eq!(loader1.count.load(Ordering::Relaxed), 0); // Now each poll should return error assert!(match isolate.poll_unpin(cx) { Poll::Ready(Err(_)) => true, _ => false, }); + assert_eq!(loader1.count.load(Ordering::Relaxed), 1); assert!(match isolate.poll_unpin(cx) { Poll::Ready(Err(_)) => true, _ => false, }); + assert_eq!(loader1.count.load(Ordering::Relaxed), 2); assert!(match isolate.poll_unpin(cx) { Poll::Ready(Err(_)) => true, _ => false, }); + assert_eq!(loader1.count.load(Ordering::Relaxed), 3); }) } #[test] fn dyn_import_ok() { - run_in_task(|cx| { - let count = Arc::new(AtomicUsize::new(0)); - let count_ = count.clone(); - - // Sometimes Rust is really annoying. - let mod_b = Arc::new(Mutex::new(0)); - let mod_b2 = mod_b.clone(); + #[derive(Clone, Default)] + struct DynImportOkLoader { + pub resolve_count: Arc<AtomicUsize>, + pub load_count: Arc<AtomicUsize>, + } - let mut isolate = EsIsolate::new(StartupData::None, false); - isolate.set_dyn_import(move |_id, specifier, referrer| { - let c = count_.fetch_add(1, Ordering::Relaxed); + impl Loader for DynImportOkLoader { + fn resolve( + &self, + specifier: &str, + referrer: &str, + _is_main: bool, + _is_dyn_import: bool, + ) -> Result<ModuleSpecifier, ErrBox> { + let c = self.resolve_count.fetch_add(1, Ordering::Relaxed); match c { - 0 => assert_eq!(specifier, "foo1.js"), - 1 => assert_eq!(specifier, "foo2.js"), + 0 => assert_eq!(specifier, "./b.js"), + 1 => assert_eq!(specifier, "./b.js"), _ => unreachable!(), } - assert_eq!(referrer, "dyn_import3.js"); - let mod_id = *mod_b2.lock().unwrap(); - let source_code_info = SourceCodeInfo { - module_url_specified: "foo.js".to_owned(), - module_url_found: "foo.js".to_owned(), - code: "".to_owned(), - }; - let stream = MockImportStream(vec![ - Ok(RecursiveLoadEvent::Fetch(source_code_info)), - Ok(RecursiveLoadEvent::Instantiate(mod_id)), - ]); - Box::new(stream) - }); + assert_eq!(referrer, "file:///dyn_import3.js"); + let s = ModuleSpecifier::resolve_import(specifier, referrer).unwrap(); + Ok(s) + } - // Instantiate mod_b - { - let mut mod_id = mod_b.lock().unwrap(); - *mod_id = isolate - .mod_new(false, "b.js", "export function b() { return 'b' }") - .unwrap(); - let mut resolve = move |_specifier: &str, - _referrer: ModuleId| - -> ModuleId { unreachable!() }; - js_check(isolate.mod_instantiate(*mod_id, &mut resolve)); + fn load( + &self, + specifier: &ModuleSpecifier, + _maybe_referrer: Option<ModuleSpecifier>, + ) -> Pin<Box<SourceCodeInfoFuture>> { + self.load_count.fetch_add(1, Ordering::Relaxed); + let info = SourceCodeInfo { + module_url_specified: specifier.to_string(), + module_url_found: specifier.to_string(), + code: "export function b() { return 'b' }".to_owned(), + }; + async move { Ok(info) }.boxed() } + } + + run_in_task(|cx| { + let loader = Box::new(DynImportOkLoader::default()); + let resolve_count = loader.resolve_count.clone(); + let load_count = loader.load_count.clone(); + let mut isolate = EsIsolate::new(loader, StartupData::None, false); + // Dynamically import mod_b js_check(isolate.execute( - "dyn_import3.js", + "file:///dyn_import3.js", r#" (async () => { - let mod = await import("foo1.js"); + let mod = await import("./b.js"); if (mod.b() !== 'b') { throw Error("bad1"); } // And again! - mod = await import("foo2.js"); + mod = await import("./b.js"); if (mod.b() !== 'b') { throw Error("bad2"); } @@ -872,17 +911,18 @@ pub mod tests { "#, )); - assert_eq!(count.load(Ordering::Relaxed), 1); assert!(match isolate.poll_unpin(cx) { Poll::Ready(Ok(_)) => true, _ => false, }); - assert_eq!(count.load(Ordering::Relaxed), 2); + assert_eq!(resolve_count.load(Ordering::Relaxed), 2); + assert_eq!(load_count.load(Ordering::Relaxed), 2); assert!(match isolate.poll_unpin(cx) { Poll::Ready(Ok(_)) => true, _ => false, }); - assert_eq!(count.load(Ordering::Relaxed), 2); + assert_eq!(resolve_count.load(Ordering::Relaxed), 2); + assert_eq!(load_count.load(Ordering::Relaxed), 2); }) } } |
