From ce38f674f703294da9a82a7db9143731f0a57f4e Mon Sep 17 00:00:00 2001 From: Ry Dahl Date: Thu, 26 Dec 2019 10:45:55 -0500 Subject: [PATCH] Support dynamic import (#136) --- src/binding.cc | 36 +++++++++++++++++- src/isolate.rs | 56 ++++++++++++++++++++++++++-- src/lib.rs | 2 +- tests/test_api.rs | 93 ++++++++++++++++++++++++++++++++++++----------- 4 files changed, 160 insertions(+), 27 deletions(-) diff --git a/src/binding.cc b/src/binding.cc index 221d5c09..77bf9dd5 100644 --- a/src/binding.cc +++ b/src/binding.cc @@ -8,6 +8,8 @@ #include "v8/include/v8-platform.h" #include "v8/include/v8.h" +const uint32_t kSlotDynamicImport = 1000; + using namespace support; static_assert(sizeof(v8::ScriptOrigin) == sizeof(size_t) * 7, @@ -39,6 +41,27 @@ static_assert(sizeof(v8::Location) == sizeof(size_t) * 1, static_assert(sizeof(v8::SnapshotCreator) == sizeof(size_t) * 1, "SnapshotCreator size mismatch"); +// This is an extern C calling convention compatible version of +// v8::HostImportModuleDynamicallyCallback +typedef v8::Promise* (*v8__HostImportModuleDynamicallyCallback)( + v8::Local context, v8::Local referrer, + v8::Local specifier); + +v8::MaybeLocal HostImportModuleDynamicallyCallback( + v8::Local context, v8::Local referrer, + v8::Local specifier) { + auto* isolate = context->GetIsolate(); + auto* callback = reinterpret_cast( + isolate->GetData(kSlotDynamicImport)); + assert(callback != nullptr); + auto* promise_ptr = callback(context, referrer, specifier); + if (promise_ptr == nullptr) { + return v8::MaybeLocal(); + } else { + return v8::MaybeLocal(ptr_to_local(promise_ptr)); + } +} + extern "C" { void v8__V8__SetFlagsFromCommandLine(int* argc, char** argv) { @@ -102,6 +125,13 @@ void v8__Isolate__SetHostInitializeImportMetaObjectCallback( isolate->SetHostInitializeImportMetaObjectCallback(callback); } +void v8__Isolate__SetHostImportModuleDynamicallyCallback( + v8::Isolate* isolate, v8__HostImportModuleDynamicallyCallback callback) { + isolate->SetData(kSlotDynamicImport, reinterpret_cast(callback)); + isolate->SetHostImportModuleDynamicallyCallback( + HostImportModuleDynamicallyCallback); +} + bool v8__Isolate__AddMessageListener(v8::Isolate& isolate, v8::MessageCallback callback) { return isolate.AddMessageListener(callback); @@ -546,8 +576,10 @@ void v8__TryCatch__SetCaptureMessage(v8::TryCatch& self, bool value) { self.SetCaptureMessage(value); } -v8::Uint8Array* v8__Uint8Array__New(v8::ArrayBuffer* buf_ptr, size_t byte_offset, size_t length) { - return local_to_ptr(v8::Uint8Array::New(ptr_to_local(buf_ptr), byte_offset, length)); +v8::Uint8Array* v8__Uint8Array__New(v8::ArrayBuffer* buf_ptr, + size_t byte_offset, size_t length) { + return local_to_ptr( + v8::Uint8Array::New(ptr_to_local(buf_ptr), byte_offset, length)); } v8::Script* v8__Script__Compile(v8::Context* context, v8::String* source, diff --git a/src/isolate.rs b/src/isolate.rs index 18da1b4d..cf1ba45f 100644 --- a/src/isolate.rs +++ b/src/isolate.rs @@ -9,20 +9,55 @@ use crate::Local; use crate::Message; use crate::Module; use crate::Object; +use crate::Promise; +use crate::ScriptOrModule; use crate::StartupData; +use crate::String; use crate::Value; use std::ffi::c_void; use std::ops::Deref; use std::ops::DerefMut; use std::ptr::NonNull; -type MessageCallback = extern "C" fn(Local, Local); +pub type MessageCallback = extern "C" fn(Local, Local); -type PromiseRejectCallback = extern "C" fn(PromiseRejectMessage); +pub type PromiseRejectCallback = extern "C" fn(PromiseRejectMessage); -type HostInitializeImportMetaObjectCallback = +/// HostInitializeImportMetaObjectCallback is called the first time import.meta +/// is accessed for a module. Subsequent access will reuse the same value. +/// +/// The method combines two implementation-defined abstract operations into one: +/// HostGetImportMetaProperties and HostFinalizeImportMeta. +/// +/// The embedder should use v8::Object::CreateDataProperty to add properties on +/// the meta object. +pub type HostInitializeImportMetaObjectCallback = extern "C" fn(Local, Local, Local); +/// HostImportModuleDynamicallyCallback is called when we require the +/// embedder to load a module. This is used as part of the dynamic +/// import syntax. +/// +/// The referrer contains metadata about the script/module that calls +/// import. +/// +/// The specifier is the name of the module that should be imported. +/// +/// The embedder must compile, instantiate, evaluate the Module, and +/// obtain it's namespace object. +/// +/// The Promise returned from this function is forwarded to userland +/// JavaScript. The embedder must resolve this promise with the module +/// namespace object. In case of an exception, the embedder must reject +/// this promise with the exception. If the promise creation itself +/// fails (e.g. due to stack overflow), the embedder must propagate +/// that exception by returning an empty MaybeLocal. +pub type HostImportModuleDynamicallyCallback = extern "C" fn( + Local, + Local, + Local, +) -> *mut Promise; + extern "C" { fn v8__Isolate__New(params: *mut CreateParams) -> *mut Isolate; fn v8__Isolate__Dispose(this: *mut Isolate); @@ -48,6 +83,10 @@ extern "C" { isolate: *mut Isolate, callback: HostInitializeImportMetaObjectCallback, ); + fn v8__Isolate__SetHostImportModuleDynamicallyCallback( + isolate: *mut Isolate, + callback: HostImportModuleDynamicallyCallback, + ); fn v8__Isolate__ThrowException( isolate: &Isolate, exception: &Value, @@ -174,6 +213,17 @@ impl Isolate { } } + /// This specifies the callback called by the upcoming dynamic + /// import() language feature to load modules. + pub fn set_host_import_module_dynamically_callback( + &mut self, + callback: HostImportModuleDynamicallyCallback, + ) { + unsafe { + v8__Isolate__SetHostImportModuleDynamicallyCallback(self, callback) + } + } + /// Schedules an exception to be thrown when returning to JavaScript. When an /// exception has been scheduled it is illegal to invoke any JavaScript /// operation; the caller must return immediately and only after the exception diff --git a/src/lib.rs b/src/lib.rs index 0b05d191..a8fea20b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -55,7 +55,7 @@ pub use function::{ }; pub use global::Global; pub use handle_scope::{EscapableHandleScope, HandleScope, ToLocal}; -pub use isolate::{InIsolate, Isolate, OwnedIsolate}; +pub use isolate::*; pub use local::Local; pub use locker::Locker; pub use module::*; diff --git a/tests/test_api.rs b/tests/test_api.rs index 0c156e2d..3f50bbde 100644 --- a/tests/test_api.rs +++ b/tests/test_api.rs @@ -5,6 +5,7 @@ extern crate lazy_static; use rusty_v8 as v8; use rusty_v8::{new_null, FunctionCallbackInfo, InIsolate, Local, ToLocal}; +use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Mutex; lazy_static! { @@ -339,7 +340,6 @@ fn add_message_listener() { let mut isolate = v8::Isolate::new(params); isolate.set_capture_stack_trace_for_uncaught_exceptions(true, 32); - use std::sync::atomic::{AtomicUsize, Ordering}; static CALL_COUNT: AtomicUsize = AtomicUsize::new(0); extern "C" fn check_message_0( @@ -389,7 +389,6 @@ fn set_host_initialize_import_meta_object_callback() { params.set_array_buffer_allocator(v8::new_default_allocator()); let mut isolate = v8::Isolate::new(params); - use std::sync::atomic::{AtomicUsize, Ordering}; static CALL_COUNT: AtomicUsize = AtomicUsize::new(0); extern "C" fn callback( @@ -791,25 +790,6 @@ fn promise_rejected() { drop(locker); } -extern "C" fn fn_callback(info: &FunctionCallbackInfo) { - assert_eq!(info.length(), 0); - { - let rv = &mut info.get_return_value(); - #[allow(mutable_transmutes)] - #[allow(clippy::transmute_ptr_to_ptr)] - let info: &mut FunctionCallbackInfo = unsafe { std::mem::transmute(info) }; - { - let mut hs = v8::HandleScope::new(info); - let scope = hs.enter(); - let s = v8::String::new(scope, "Hello callback!").unwrap(); - let value: Local = s.into(); - let rv_value = rv.get(scope); - assert!(rv_value.is_undefined()); - rv.set(value); - } - } -} - #[test] fn function() { setup(); @@ -817,6 +797,27 @@ fn function() { params.set_array_buffer_allocator(v8::new_default_allocator()); let isolate = v8::Isolate::new(params); let mut locker = v8::Locker::new(&isolate); + + extern "C" fn fn_callback(info: &FunctionCallbackInfo) { + assert_eq!(info.length(), 0); + { + let rv = &mut info.get_return_value(); + #[allow(mutable_transmutes)] + #[allow(clippy::transmute_ptr_to_ptr)] + let info: &mut FunctionCallbackInfo = + unsafe { std::mem::transmute(info) }; + { + let mut hs = v8::HandleScope::new(info); + let scope = hs.enter(); + let s = v8::String::new(scope, "Hello callback!").unwrap(); + let value: Local = s.into(); + let rv_value = rv.get(scope); + assert!(rv_value.is_undefined()); + rv.set(value); + } + } + } + { let mut hs = v8::HandleScope::new(&mut locker); let scope = hs.enter(); @@ -1305,3 +1306,53 @@ fn uint8_array() { isolate.exit(); drop(g); } + +#[test] +fn dynamic_import() { + let g = setup(); + let mut params = v8::Isolate::create_params(); + params.set_array_buffer_allocator(v8::new_default_allocator()); + let mut isolate = v8::Isolate::new(params); + + static CALL_COUNT: AtomicUsize = AtomicUsize::new(0); + + extern "C" fn dynamic_import_cb( + context: v8::Local, + _referrer: v8::Local, + specifier: v8::Local, + ) -> *mut v8::Promise { + let mut cbs = v8::CallbackScope::new(context); + let mut hs = v8::HandleScope::new(cbs.enter()); + let scope = hs.enter(); + assert!(specifier.strict_equals(v8_str(scope, "bar.js").into())); + let e = v8_str(scope, "boom"); + scope.isolate().throw_exception(e.into()); + CALL_COUNT.fetch_add(1, Ordering::SeqCst); + std::ptr::null_mut() + } + isolate.set_host_import_module_dynamically_callback(dynamic_import_cb); + + isolate.enter(); + let mut locker = v8::Locker::new(&isolate); + { + let mut hs = v8::HandleScope::new(&mut locker); + let s = hs.enter(); + let mut context = v8::Context::new(s); + context.enter(); + + let result = eval( + s, + context, + "(async function () {\n\ + let x = await import('bar.js');\n\ + })();", + ); + assert!(result.is_some()); + assert_eq!(CALL_COUNT.load(Ordering::SeqCst), 1); + + context.exit(); + } + drop(locker); + isolate.exit(); + drop(g); +}