Skip to content

Commit e043697

Browse files
authored
Expose dynamic import in core (#2472)
1 parent 88b0c87 commit e043697

File tree

6 files changed

+307
-7
lines changed

6 files changed

+307
-7
lines changed

core/isolate.rs

Lines changed: 180 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+
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+
task::current().notify();
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,27 @@ 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+
debug!("dyn_import_done {} {}", id, mod_id);
408+
unsafe {
409+
libdeno::deno_dyn_import(
410+
self.libdeno_isolate,
411+
self.as_raw_ptr(),
412+
id,
413+
mod_id,
414+
)
415+
};
416+
if let Some(js_error) = self.last_exception() {
417+
assert_eq!(id, 0);
418+
return Err(js_error);
419+
}
420+
Ok(())
421+
}
343422
}
344423

345424
/// Called during mod_instantiate() to resolve imports.
@@ -440,6 +519,18 @@ impl Future for Isolate {
440519
let mut overflow_response: Option<Buf> = None;
441520

442521
loop {
522+
// If there are any pending dyn_import futures, do those first.
523+
match self.pending_dyn_imports.poll() {
524+
Err(()) => unreachable!(),
525+
Ok(NotReady) => unreachable!(),
526+
Ok(Ready(None)) => (),
527+
Ok(Ready(Some((dyn_import_id, mod_id)))) => {
528+
self.dyn_import_done(dyn_import_id, mod_id)?;
529+
continue;
530+
}
531+
}
532+
533+
// Now handle actual ops.
443534
self.have_unpolled_ops = false;
444535
#[allow(clippy::match_wild_err_arm)]
445536
match self.pending_ops.poll() {
@@ -764,6 +855,95 @@ pub mod tests {
764855
});
765856
}
766857

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