diff --git a/build.rs b/build.rs index 777c7e39..87b8063a 100644 --- a/build.rs +++ b/build.rs @@ -154,6 +154,7 @@ fn build_binding() { .clang_args(args) .generate_cstr(true) .rustified_enum(".*UseCounterFeature") + .rustified_enum(".*ModuleImportPhase") .allowlist_item("v8__.*") .allowlist_item("cppgc__.*") .allowlist_item("RustObj") diff --git a/src/binding.cc b/src/binding.cc index 1bce7c85..e63c0b72 100644 --- a/src/binding.cc +++ b/src/binding.cc @@ -272,6 +272,12 @@ void v8__Isolate__SetHostImportModuleDynamicallyCallback( isolate->SetHostImportModuleDynamicallyCallback(callback); } +void v8__Isolate__SetHostImportModuleWithPhaseDynamicallyCallback( + v8::Isolate* isolate, + v8::HostImportModuleWithPhaseDynamicallyCallback callback) { + isolate->SetHostImportModuleWithPhaseDynamicallyCallback(callback); +} + void v8__Isolate__SetHostCreateShadowRealmContextCallback( v8::Isolate* isolate, v8::HostCreateShadowRealmContextCallback callback) { isolate->SetHostCreateShadowRealmContextCallback(callback); diff --git a/src/binding.hpp b/src/binding.hpp index fb9670e3..4c0d837e 100644 --- a/src/binding.hpp +++ b/src/binding.hpp @@ -39,6 +39,7 @@ using v8__CFunctionInfo = v8::CFunctionInfo; using v8__FastOneByteString = v8::FastOneByteString; using v8__Isolate__UseCounterFeature = v8::Isolate::UseCounterFeature; using v8__String__WriteFlags = v8::String::WriteFlags; +using v8__ModuleImportPhase = v8::ModuleImportPhase; static uint32_t v8__MAJOR_VERSION = V8_MAJOR_VERSION; static uint32_t v8__MINOR_VERSION = V8_MINOR_VERSION; diff --git a/src/isolate.rs b/src/isolate.rs index 723d0b24..555cbb55 100644 --- a/src/isolate.rs +++ b/src/isolate.rs @@ -1,5 +1,6 @@ // Copyright 2019-2021 the Deno authors. All rights reserved. MIT license. use crate::binding::v8__Isolate__UseCounterFeature; +pub use crate::binding::v8__ModuleImportPhase as ModuleImportPhase; use crate::cppgc::Heap; use crate::function::FunctionCallbackInfo; use crate::gc::GCCallbackFlags; @@ -340,6 +341,171 @@ where } } +/// HostImportModuleWithPhaseDynamicallyCallback is called when we +/// require the embedder to load a module with a specific phase. 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 phase is the phase of the import requested. +/// +/// The import_attributes are import attributes for this request in the form: +/// [key1, value1, key2, value2, ...] where the keys and values are of type +/// v8::String. Note, unlike the FixedArray passed to ResolveModuleCallback and +/// returned from ModuleRequest::GetImportAttributes(), this array does not +/// contain the source Locations of the attributes. +/// +/// The Promise returned from this function is forwarded to userland +/// JavaScript. The embedder must resolve this promise according to the phase +/// requested: +/// - For ModuleImportPhase::kSource, the promise must be resolved with a +/// compiled ModuleSource object, or rejected with a SyntaxError if the +/// module does not support source representation. +/// - For ModuleImportPhase::kEvaluation, the promise must be resolved with a +/// ModuleNamespace object of a module that has been compiled, instantiated, +/// and evaluated. +/// +/// 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. +/// +/// This callback is still experimental and is only invoked for source phase +/// imports. +pub trait HostImportModuleWithPhaseDynamicallyCallback: + UnitType + + for<'s> FnOnce( + &mut HandleScope<'s>, + Local<'s, Data>, + Local<'s, Value>, + Local<'s, String>, + ModuleImportPhase, + Local<'s, FixedArray>, + ) -> Option> +{ + fn to_c_fn(self) -> RawHostImportModuleWithPhaseDynamicallyCallback; +} + +#[cfg(target_family = "unix")] +pub(crate) type RawHostImportModuleWithPhaseDynamicallyCallback = + for<'s> extern "C" fn( + Local<'s, Context>, + Local<'s, Data>, + Local<'s, Value>, + Local<'s, String>, + ModuleImportPhase, + Local<'s, FixedArray>, + ) -> *mut Promise; + +#[cfg(all(target_family = "windows", target_arch = "x86_64"))] +pub type RawHostImportModuleWithPhaseDynamicallyCallback = + for<'s> extern "C" fn( + *mut *mut Promise, + Local<'s, Context>, + Local<'s, Data>, + Local<'s, Value>, + Local<'s, String>, + ModuleImportPhase, + Local<'s, FixedArray>, + ) -> *mut *mut Promise; + +impl HostImportModuleWithPhaseDynamicallyCallback for F +where + F: UnitType + + for<'s> FnOnce( + &mut HandleScope<'s>, + Local<'s, Data>, + Local<'s, Value>, + Local<'s, String>, + ModuleImportPhase, + Local<'s, FixedArray>, + ) -> Option>, +{ + #[inline(always)] + fn to_c_fn(self) -> RawHostImportModuleWithPhaseDynamicallyCallback { + #[inline(always)] + fn scope_adapter<'s, F: HostImportModuleWithPhaseDynamicallyCallback>( + context: Local<'s, Context>, + host_defined_options: Local<'s, Data>, + resource_name: Local<'s, Value>, + specifier: Local<'s, String>, + import_phase: ModuleImportPhase, + import_attributes: Local<'s, FixedArray>, + ) -> Option> { + let scope = &mut unsafe { CallbackScope::new(context) }; + (F::get())( + scope, + host_defined_options, + resource_name, + specifier, + import_phase, + import_attributes, + ) + } + + #[cfg(target_family = "unix")] + #[inline(always)] + extern "C" fn abi_adapter< + 's, + F: HostImportModuleWithPhaseDynamicallyCallback, + >( + context: Local<'s, Context>, + host_defined_options: Local<'s, Data>, + resource_name: Local<'s, Value>, + specifier: Local<'s, String>, + import_phase: ModuleImportPhase, + import_attributes: Local<'s, FixedArray>, + ) -> *mut Promise { + scope_adapter::( + context, + host_defined_options, + resource_name, + specifier, + import_phase, + import_attributes, + ) + .map_or_else(null_mut, |return_value| return_value.as_non_null().as_ptr()) + } + + #[cfg(all(target_family = "windows", target_arch = "x86_64"))] + #[inline(always)] + extern "C" fn abi_adapter< + 's, + F: HostImportModuleWithPhaseDynamicallyCallback, + >( + return_value: *mut *mut Promise, + context: Local<'s, Context>, + host_defined_options: Local<'s, Data>, + resource_name: Local<'s, Value>, + specifier: Local<'s, String>, + import_phase: ModuleImportPhase, + import_attributes: Local<'s, FixedArray>, + ) -> *mut *mut Promise { + unsafe { + std::ptr::write( + return_value, + scope_adapter::( + context, + host_defined_options, + resource_name, + specifier, + import_phase, + import_attributes, + ) + .map(|return_value| return_value.as_non_null().as_ptr()) + .unwrap_or_else(null_mut), + ); + return_value + } + } + + abi_adapter:: + } +} + /// `HostCreateShadowRealmContextCallback` is called each time a `ShadowRealm` /// is being constructed. You can use [`HandleScope::get_current_context`] to /// get the [`Context`] in which the constructor is being run. @@ -497,6 +663,10 @@ extern "C" { isolate: *mut Isolate, callback: RawHostImportModuleDynamicallyCallback, ); + fn v8__Isolate__SetHostImportModuleWithPhaseDynamicallyCallback( + isolate: *mut Isolate, + callback: RawHostImportModuleWithPhaseDynamicallyCallback, + ); #[cfg(not(target_os = "windows"))] fn v8__Isolate__SetHostCreateShadowRealmContextCallback( isolate: *mut Isolate, @@ -1088,6 +1258,26 @@ impl Isolate { } } + /// This specifies the callback called by the upcoming dynamic + /// import() and import.source() language feature to load modules. + /// + /// This API is experimental and is expected to be changed or removed in the + /// future. The callback is currently only called when for source-phase + /// imports. Evaluation-phase imports use the existing + /// HostImportModuleDynamicallyCallback callback. + #[inline(always)] + pub fn set_host_import_module_with_phase_dynamically_callback( + &mut self, + callback: impl HostImportModuleWithPhaseDynamicallyCallback, + ) { + unsafe { + v8__Isolate__SetHostImportModuleWithPhaseDynamicallyCallback( + self, + callback.to_c_fn(), + ); + } + } + /// This specifies the callback called by the upcoming `ShadowRealm` /// construction language feature to retrieve host created globals. pub fn set_host_create_shadow_realm_context_callback( diff --git a/src/lib.rs b/src/lib.rs index 48d5a3aa..e40acccd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -107,6 +107,7 @@ pub use isolate::GarbageCollectionType; pub use isolate::HeapStatistics; pub use isolate::HostCreateShadowRealmContextCallback; pub use isolate::HostImportModuleDynamicallyCallback; +pub use isolate::HostImportModuleWithPhaseDynamicallyCallback; pub use isolate::HostInitializeImportMetaObjectCallback; pub use isolate::Isolate; pub use isolate::IsolateHandle; @@ -114,6 +115,7 @@ pub use isolate::MemoryPressureLevel; pub use isolate::MessageCallback; pub use isolate::MessageErrorLevel; pub use isolate::MicrotasksPolicy; +pub use isolate::ModuleImportPhase; pub use isolate::NearHeapLimitCallback; pub use isolate::OomDetails; pub use isolate::OomErrorCallback; diff --git a/tests/test_api.rs b/tests/test_api.rs index 1bd44b32..3c6686d3 100644 --- a/tests/test_api.rs +++ b/tests/test_api.rs @@ -7801,6 +7801,62 @@ fn static_source_phase_import() { assert_eq!(obj, ns.get(scope, default.into()).unwrap()); } +#[test] +fn dynamic_source_phase_import() { + let _setup_guard = setup::parallel_test(); + let isolate = &mut v8::Isolate::new(Default::default()); + + let scope = &mut v8::HandleScope::new(isolate); + + let context = v8::Context::new(scope, Default::default()); + let scope = &mut v8::ContextScope::new(scope, context); + + let obj = eval(scope, "new WebAssembly.Module(new Uint8Array([0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00]))").unwrap().cast::(); + + context.set_slot(v8::Global::new(scope, obj)); + + fn dynamic_import_cb<'s>( + _scope: &mut v8::HandleScope<'s>, + _host_defined_options: v8::Local<'s, v8::Data>, + _resource_name: v8::Local<'s, v8::Value>, + _specifier: v8::Local<'s, v8::String>, + _import_attributes: v8::Local<'s, v8::FixedArray>, + ) -> Option> { + unreachable!() + } + + fn dynamic_import_phase_cb<'s>( + scope: &mut v8::HandleScope<'s>, + _host_defined_options: v8::Local<'s, v8::Data>, + _resource_name: v8::Local<'s, v8::Value>, + _specifier: v8::Local<'s, v8::String>, + phase: v8::ModuleImportPhase, + _import_attributes: v8::Local<'s, v8::FixedArray>, + ) -> Option> { + assert_eq!(phase, v8::ModuleImportPhase::kSource); + + let resolver = v8::PromiseResolver::new(scope).unwrap(); + + let context = scope.get_current_context(); + let global = context.get_slot::>().unwrap(); + let obj = v8::Local::new(scope, global); + resolver.resolve(scope, obj.into()); + + Some(resolver.get_promise(scope)) + } + + scope.set_host_import_module_dynamically_callback(dynamic_import_cb); + scope.set_host_import_module_with_phase_dynamically_callback( + dynamic_import_phase_cb, + ); + + let promise = eval(scope, "import.source('a')") + .unwrap() + .cast::(); + eprintln!("{}", promise.result(scope).to_rust_string_lossy(scope)); + assert_eq!(obj, promise.result(scope)); +} + #[allow(clippy::float_cmp)] #[test] fn date() {