Skip to content

Commit 6adec23

Browse files
committed
Add test_dyn_import, integrate into core
1 parent 88b0c87 commit 6adec23

File tree

6 files changed

+309
-7
lines changed

6 files changed

+309
-7
lines changed

core/isolate.rs

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use crate::js_errors::JSError;
88
use crate::libdeno;
99
use crate::libdeno::deno_buf;
10+
use crate::libdeno::deno_dyn_import_id;
1011
use crate::libdeno::deno_mod;
1112
use crate::libdeno::deno_pinned_buf;
1213
use crate::libdeno::PinnedBuf;
@@ -19,6 +20,7 @@ use futures::task;
1920
use futures::Async::*;
2021
use futures::Future;
2122
use futures::Poll;
23+
use libc::c_char;
2224
use libc::c_void;
2325
use std::ffi::CStr;
2426
use std::ffi::CString;
@@ -52,9 +54,33 @@ pub enum StartupData<'a> {
5254

5355
type DispatchFn = Fn(&[u8], Option<PinnedBuf>) -> Op;
5456

57+
pub type DynImportFuture = Box<dyn Future<Item = deno_mod, Error = ()> + Send>;
58+
type DynImportFn = Fn(&str, &str) -> DynImportFuture;
59+
60+
/// Wraps DynImportFuture to include the deno_dyn_import_id, so that it doesn't
61+
/// need to be exposed.
62+
struct DynImport {
63+
id: deno_dyn_import_id,
64+
inner: DynImportFuture,
65+
}
66+
67+
impl Future for DynImport {
68+
type Item = (deno_dyn_import_id, deno_mod);
69+
type Error = ();
70+
fn poll(&mut self) -> Poll<Self::Item, ()> {
71+
match self.inner.poll() {
72+
Ok(Ready(mod_id)) => Ok(Ready((self.id, mod_id))),
73+
Ok(NotReady) => Ok(NotReady),
74+
// Note that mod_id 0 indicates error.
75+
Err(()) => Ok(Ready((self.id, 0))),
76+
}
77+
}
78+
}
79+
5580
#[derive(Default)]
5681
pub struct Config {
5782
dispatch: Option<Arc<DispatchFn>>,
83+
dyn_import: Option<Arc<DynImportFn>>,
5884
pub will_snapshot: bool,
5985
}
6086

@@ -68,6 +94,13 @@ impl Config {
6894
{
6995
self.dispatch = Some(Arc::new(f));
7096
}
97+
98+
pub fn dyn_import<F>(&mut self, f: F)
99+
where
100+
F: Fn(&str, &str) -> DynImportFuture + Send + Sync + 'static,
101+
{
102+
self.dyn_import = Some(Arc::new(f));
103+
}
71104
}
72105

73106
/// A single execution context of JavaScript. Corresponds roughly to the "Web
@@ -85,6 +118,7 @@ pub struct Isolate {
85118
needs_init: bool,
86119
shared: SharedQueue,
87120
pending_ops: FuturesUnordered<OpAsyncFuture>,
121+
pending_dyn_imports: FuturesUnordered<DynImport>,
88122
have_unpolled_ops: bool,
89123
}
90124

@@ -121,6 +155,7 @@ impl Isolate {
121155
load_snapshot: Snapshot2::empty(),
122156
shared: shared.as_deno_buf(),
123157
recv_cb: Self::pre_dispatch,
158+
dyn_import_cb: Self::dyn_import,
124159
};
125160

126161
// Seperate into Option values for each startup type
@@ -147,6 +182,7 @@ impl Isolate {
147182
needs_init,
148183
pending_ops: FuturesUnordered::new(),
149184
have_unpolled_ops: false,
185+
pending_dyn_imports: FuturesUnordered::new(),
150186
};
151187

152188
// If we want to use execute this has to happen here sadly.
@@ -174,6 +210,27 @@ impl Isolate {
174210
}
175211
}
176212

213+
extern "C" fn dyn_import(
214+
user_data: *mut c_void,
215+
specifier: *const c_char,
216+
referrer: *const c_char,
217+
id: deno_dyn_import_id,
218+
) {
219+
assert_ne!(user_data, std::ptr::null_mut());
220+
let isolate = unsafe { Isolate::from_raw_ptr(user_data) };
221+
let specifier = unsafe { CStr::from_ptr(specifier).to_str().unwrap() };
222+
let referrer = unsafe { CStr::from_ptr(referrer).to_str().unwrap() };
223+
debug!("dyn_import specifier {} referrer {} ", specifier, referrer);
224+
225+
if let Some(ref f) = isolate.config.dyn_import {
226+
let inner = f(specifier, referrer);
227+
let fut = DynImport { inner, id };
228+
isolate.pending_dyn_imports.push(fut);
229+
} else {
230+
panic!("dyn_import callback not set")
231+
}
232+
}
233+
177234
extern "C" fn pre_dispatch(
178235
user_data: *mut c_void,
179236
control_argv0: deno_buf,
@@ -340,6 +397,26 @@ impl Isolate {
340397
assert_ne!(snapshot.data_len, 0);
341398
Ok(snapshot)
342399
}
400+
401+
fn dyn_import_done(
402+
&self,
403+
id: libdeno::deno_dyn_import_id,
404+
mod_id: deno_mod,
405+
) -> Result<(), JSError> {
406+
unsafe {
407+
libdeno::deno_dyn_import(
408+
self.libdeno_isolate,
409+
self.as_raw_ptr(),
410+
id,
411+
mod_id,
412+
)
413+
};
414+
if let Some(js_error) = self.last_exception() {
415+
assert_eq!(id, 0);
416+
return Err(js_error);
417+
}
418+
Ok(())
419+
}
343420
}
344421

345422
/// Called during mod_instantiate() to resolve imports.
@@ -440,6 +517,21 @@ impl Future for Isolate {
440517
let mut overflow_response: Option<Buf> = None;
441518

442519
loop {
520+
// If there are any pending dyn_import futures, do those first.
521+
if !self.pending_dyn_imports.is_empty() {
522+
match self.pending_dyn_imports.poll() {
523+
Err(()) => panic!("unexpected dyn_import error"),
524+
Ok(Ready(None)) => break,
525+
Ok(NotReady) => break,
526+
Ok(Ready(Some((dyn_import_id, mod_id)))) => {
527+
debug!("dyn_import_done {} {}", dyn_import_id, mod_id);
528+
self.dyn_import_done(dyn_import_id, mod_id)?;
529+
}
530+
}
531+
}
532+
533+
// Now handle actual ops,
534+
443535
self.have_unpolled_ops = false;
444536
#[allow(clippy::match_wild_err_arm)]
445537
match self.pending_ops.poll() {
@@ -764,6 +856,96 @@ pub mod tests {
764856
});
765857
}
766858

859+
#[test]
860+
fn dyn_import_err() {
861+
// Test an erroneous dynamic import where the specified module isn't found.
862+
run_in_task(|| {
863+
let count = Arc::new(AtomicUsize::new(0));
864+
let count_ = count.clone();
865+
let mut config = Config::default();
866+
config.dyn_import(move |specifier, referrer| {
867+
count_.fetch_add(1, Ordering::Relaxed);
868+
assert_eq!(specifier, "foo.js");
869+
assert_eq!(referrer, "dyn_import2.js");
870+
Box::new(futures::future::err(()))
871+
});
872+
let mut isolate = Isolate::new(StartupData::None, config);
873+
js_check(isolate.execute(
874+
"dyn_import2.js",
875+
r#"
876+
(async () => {
877+
await import("foo.js");
878+
})();
879+
"#,
880+
));
881+
assert_eq!(count.load(Ordering::Relaxed), 1);
882+
883+
// We should get an error here.
884+
let result = isolate.poll();
885+
assert!(result.is_err());
886+
})
887+
}
888+
889+
#[test]
890+
fn dyn_import_ok() {
891+
// Test an erroneous dynamic import where the specified module isn't found.
892+
run_in_task(|| {
893+
let count = Arc::new(AtomicUsize::new(0));
894+
let count_ = count.clone();
895+
896+
// Sometimes Rust is really annoying.
897+
use std::sync::Mutex;
898+
let mod_b = Arc::new(Mutex::new(0));
899+
let mod_b2 = mod_b.clone();
900+
901+
let mut config = Config::default();
902+
config.dyn_import(move |_specifier, referrer| {
903+
count_.fetch_add(1, Ordering::Relaxed);
904+
// assert_eq!(specifier, "foo.js");
905+
assert_eq!(referrer, "dyn_import3.js");
906+
let mod_id = mod_b2.lock().unwrap();
907+
Box::new(futures::future::ok(*mod_id))
908+
});
909+
910+
let mut isolate = Isolate::new(StartupData::None, config);
911+
912+
// Instantiate mod_b
913+
{
914+
let mut mod_id = mod_b.lock().unwrap();
915+
*mod_id = isolate
916+
.mod_new(false, "b.js", "export function b() { return 'b' }")
917+
.unwrap();
918+
let mut resolve = move |_specifier: &str,
919+
_referrer: deno_mod|
920+
-> deno_mod { unreachable!() };
921+
js_check(isolate.mod_instantiate(*mod_id, &mut resolve));
922+
}
923+
// Dynamically import mod_b
924+
js_check(isolate.execute(
925+
"dyn_import3.js",
926+
r#"
927+
(async () => {
928+
let mod = await import("foo1.js");
929+
if (mod.b() !== 'b') {
930+
throw Error("bad1");
931+
}
932+
// And again!
933+
mod = await import("foo2.js");
934+
if (mod.b() !== 'b') {
935+
throw Error("bad2");
936+
}
937+
})();
938+
"#,
939+
));
940+
941+
assert_eq!(count.load(Ordering::Relaxed), 1);
942+
assert_eq!(Ok(Ready(())), isolate.poll());
943+
assert_eq!(count.load(Ordering::Relaxed), 2);
944+
assert_eq!(Ok(Ready(())), isolate.poll());
945+
assert_eq!(count.load(Ordering::Relaxed), 2);
946+
})
947+
}
948+
767949
#[test]
768950
fn terminate_execution() {
769951
let (tx, rx) = std::sync::mpsc::channel::<bool>();

core/libdeno.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,9 +192,23 @@ type deno_recv_cb = unsafe extern "C" fn(
192192
zero_copy_buf: deno_pinned_buf,
193193
);
194194

195+
/// Called when dynamic import is called in JS: import('foo')
196+
/// Embedder must call deno_dyn_import() with the specified id and
197+
/// the module.
198+
#[allow(non_camel_case_types)]
199+
type deno_dyn_import_cb = unsafe extern "C" fn(
200+
user_data: *mut c_void,
201+
specifier: *const c_char,
202+
referrer: *const c_char,
203+
id: deno_dyn_import_id,
204+
);
205+
195206
#[allow(non_camel_case_types)]
196207
pub type deno_mod = i32;
197208

209+
#[allow(non_camel_case_types)]
210+
pub type deno_dyn_import_id = i32;
211+
198212
#[allow(non_camel_case_types)]
199213
type deno_resolve_cb = unsafe extern "C" fn(
200214
user_data: *mut c_void,
@@ -208,6 +222,7 @@ pub struct deno_config<'a> {
208222
pub load_snapshot: Snapshot2<'a>,
209223
pub shared: deno_buf,
210224
pub recv_cb: deno_recv_cb,
225+
pub dyn_import_cb: deno_dyn_import_cb,
211226
}
212227

213228
#[cfg(not(windows))]
@@ -292,6 +307,14 @@ extern "C" {
292307
id: deno_mod,
293308
);
294309

310+
/// Call exactly once for every deno_dyn_import_cb.
311+
pub fn deno_dyn_import(
312+
i: *const isolate,
313+
user_data: *const c_void,
314+
id: deno_dyn_import_id,
315+
mod_id: deno_mod,
316+
);
317+
295318
pub fn deno_snapshot_new(i: *const isolate) -> Snapshot1<'static>;
296319

297320
#[allow(dead_code)]

core/libdeno/binding.cc

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,8 @@ v8::MaybeLocal<v8::Promise> HostImportModuleDynamicallyCallback(
521521
auto* isolate = context->GetIsolate();
522522
DenoIsolate* d = DenoIsolate::FromIsolate(isolate);
523523
v8::Isolate::Scope isolate_scope(isolate);
524+
v8::Context::Scope context_scope(context);
525+
v8::EscapableHandleScope handle_scope(isolate);
524526

525527
v8::String::Utf8Value specifier_str(isolate, specifier);
526528

@@ -544,7 +546,9 @@ v8::MaybeLocal<v8::Promise> HostImportModuleDynamicallyCallback(
544546

545547
d->dyn_import_cb_(d->user_data_, *specifier_str, *referrer_name_str,
546548
import_id);
547-
return resolver->GetPromise();
549+
550+
auto promise = resolver->GetPromise();
551+
return handle_scope.Escape(promise);
548552
}
549553

550554
void DenoIsolate::AddIsolate(v8::Isolate* isolate) {

core/libdeno/deno.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,9 @@ void deno_mod_instantiate(Deno* d, void* user_data, deno_mod id,
126126
void deno_mod_evaluate(Deno* d, void* user_data, deno_mod id);
127127

128128
// Call exactly once for every deno_dyn_import_cb.
129-
void deno_dyn_import(Deno* d, deno_dyn_import_id id, deno_mod mod_id);
129+
// Note this call will execute JS.
130+
void deno_dyn_import(Deno* d, void* user_data, deno_dyn_import_id id,
131+
deno_mod mod_id);
130132

131133
#ifdef __cplusplus
132134
} // extern "C"

core/libdeno/modules.cc

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -151,13 +151,19 @@ void deno_mod_evaluate(Deno* d_, void* user_data, deno_mod id) {
151151
}
152152
}
153153

154-
void deno_dyn_import(Deno* d_, deno_dyn_import_id import_id, deno_mod mod_id) {
154+
void deno_dyn_import(Deno* d_, void* user_data, deno_dyn_import_id import_id,
155+
deno_mod mod_id) {
155156
auto* d = unwrap(d_);
157+
deno::UserDataScope user_data_scope(d, user_data);
158+
156159
auto* isolate = d->isolate_;
157160
v8::Isolate::Scope isolate_scope(isolate);
158161
v8::Locker locker(isolate);
159162
v8::HandleScope handle_scope(isolate);
160163
auto context = d->context_.Get(d->isolate_);
164+
v8::Context::Scope context_scope(context);
165+
166+
v8::TryCatch try_catch(isolate);
161167

162168
auto it = d->dyn_import_map_.find(import_id);
163169
if (it == d->dyn_import_map_.end()) {
@@ -168,9 +174,13 @@ void deno_dyn_import(Deno* d_, deno_dyn_import_id import_id, deno_mod mod_id) {
168174
/// Resolve.
169175
auto persistent_promise = &it->second;
170176
auto promise = persistent_promise->Get(isolate);
171-
persistent_promise->Reset();
172177

173178
auto* info = d->GetModuleInfo(mod_id);
179+
180+
// Do the following callback into JS?? Is user_data_scope needed?
181+
persistent_promise->Reset();
182+
d->dyn_import_map_.erase(it);
183+
174184
if (info == nullptr) {
175185
// Resolution error.
176186
promise->Reject(context, v8::Null(isolate)).ToChecked();
@@ -181,7 +191,10 @@ void deno_dyn_import(Deno* d_, deno_dyn_import_id import_id, deno_mod mod_id) {
181191
Local<Value> module_namespace = module->GetModuleNamespace();
182192
promise->Resolve(context, module_namespace).ToChecked();
183193
}
184-
d->dyn_import_map_.erase(it);
194+
195+
if (try_catch.HasCaught()) {
196+
HandleException(context, try_catch.Exception());
197+
}
185198
}
186199

187200
} // extern "C"

0 commit comments

Comments
 (0)