Skip to content

Commit 43eaad0

Browse files
committed
Add test_dyn_import, integrate into core
1 parent a115340 commit 43eaad0

File tree

6 files changed

+335
-7
lines changed

6 files changed

+335
-7
lines changed

core/isolate.rs

Lines changed: 208 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,28 @@ impl Isolate {
174210
}
175211
}
176212

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

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

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

860+
#[test]
861+
fn test_dyn_import1() {
862+
// Just check that we properly get the callback with the right specifier and
863+
// referrer.
864+
run_in_task(|| {
865+
let count = Arc::new(AtomicUsize::new(0));
866+
let count_ = count.clone();
867+
let mut config = Config::default();
868+
config.dyn_import(move |specifier, referrer| {
869+
count_.fetch_add(1, Ordering::Relaxed);
870+
assert_eq!(specifier, "foo.js");
871+
assert_eq!(referrer, "dyn_import1.js");
872+
// We will never poll the isolate in this test, so just return a fake
873+
// mod_id value.
874+
Box::new(futures::future::ok(123))
875+
});
876+
let mut isolate = Isolate::new(StartupData::None, config);
877+
js_check(isolate.execute(
878+
"dyn_import1.js",
879+
r#"
880+
(async () => {
881+
await import("foo.js");
882+
})();
883+
"#,
884+
));
885+
assert_eq!(count.load(Ordering::Relaxed), 1);
886+
})
887+
}
888+
889+
#[test]
890+
fn test_dyn_import2() {
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+
let mut config = Config::default();
896+
config.dyn_import(move |specifier, referrer| {
897+
count_.fetch_add(1, Ordering::Relaxed);
898+
assert_eq!(specifier, "foo.js");
899+
assert_eq!(referrer, "dyn_import2.js");
900+
Box::new(futures::future::err(()))
901+
});
902+
let mut isolate = Isolate::new(StartupData::None, config);
903+
js_check(isolate.execute(
904+
"dyn_import2.js",
905+
r#"
906+
(async () => {
907+
await import("foo.js");
908+
})();
909+
"#,
910+
));
911+
assert_eq!(count.load(Ordering::Relaxed), 1);
912+
913+
// We should get an error here.
914+
let result = isolate.poll();
915+
assert!(result.is_err());
916+
})
917+
}
918+
919+
#[test]
920+
fn test_dyn_import3() {
921+
// Test an erroneous dynamic import where the specified module isn't found.
922+
run_in_task(|| {
923+
let count = Arc::new(AtomicUsize::new(0));
924+
let count_ = count.clone();
925+
926+
// Sometimes Rust is really annoying.
927+
use std::sync::Mutex;
928+
let mod_b = Arc::new(Mutex::new(0));
929+
let mod_b2 = mod_b.clone();
930+
931+
let mut config = Config::default();
932+
config.dyn_import(move |_specifier, referrer| {
933+
count_.fetch_add(1, Ordering::Relaxed);
934+
// assert_eq!(specifier, "foo.js");
935+
assert_eq!(referrer, "dyn_import3.js");
936+
let mod_id = mod_b2.lock().unwrap();
937+
Box::new(futures::future::ok(*mod_id))
938+
});
939+
940+
let mut isolate = Isolate::new(StartupData::None, config);
941+
942+
// Instantiate mod_b
943+
{
944+
let mut mod_id = mod_b.lock().unwrap();
945+
*mod_id = isolate
946+
.mod_new(false, "b.js", "export function b() { return 'b' }")
947+
.unwrap();
948+
let mut resolve = move |_specifier: &str,
949+
_referrer: deno_mod|
950+
-> deno_mod { unreachable!() };
951+
js_check(isolate.mod_instantiate(*mod_id, &mut resolve));
952+
}
953+
// Dynamically import mod_b
954+
js_check(isolate.execute(
955+
"dyn_import3.js",
956+
r#"
957+
(async () => {
958+
let mod = await import("foo1.js");
959+
if (mod.b() !== 'b') {
960+
throw Error("bad1");
961+
}
962+
// And again!
963+
mod = await import("foo2.js");
964+
if (mod.b() !== 'b') {
965+
throw Error("bad2");
966+
}
967+
})();
968+
"#,
969+
));
970+
assert_eq!(count.load(Ordering::Relaxed), 1);
971+
assert_eq!(Ok(Ready(())), isolate.poll());
972+
})
973+
}
974+
767975
#[test]
768976
fn terminate_execution() {
769977
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"

0 commit comments

Comments
 (0)