mirror of
https://github.com/denoland/deno.git
synced 2024-11-21 20:38:55 +00:00
refactor(core): remove ext: modules from the module map (#19040)
Rather than disallowing `ext:` resolution, clear the module map after initializing extensions so extension modules are anonymized. This operation is explicitly called in `deno_runtime`. Re-inject `node:` specifiers into the module map after doing this. Fixes #17717.
This commit is contained in:
parent
bb0676d3e2
commit
b6a3f8f722
@ -40,7 +40,7 @@ mod ts {
|
||||
|
||||
let node_built_in_module_names = SUPPORTED_BUILTIN_NODE_MODULES
|
||||
.iter()
|
||||
.map(|s| s.name)
|
||||
.map(|p| p.module_name())
|
||||
.collect::<Vec<&str>>();
|
||||
let build_libs = state.borrow::<Vec<&str>>();
|
||||
json!({
|
||||
|
@ -378,9 +378,8 @@ pub fn enhanced_resolution_error_message(error: &ResolutionError) -> String {
|
||||
pub fn get_resolution_error_bare_node_specifier(
|
||||
error: &ResolutionError,
|
||||
) -> Option<&str> {
|
||||
get_resolution_error_bare_specifier(error).filter(|specifier| {
|
||||
deno_node::resolve_builtin_node_module(specifier).is_ok()
|
||||
})
|
||||
get_resolution_error_bare_specifier(error)
|
||||
.filter(|specifier| deno_node::is_builtin_node_module(specifier))
|
||||
}
|
||||
|
||||
fn get_resolution_error_bare_specifier(
|
||||
|
@ -1023,7 +1023,7 @@ fn diagnose_resolution(
|
||||
}
|
||||
} else if let Some(module_name) = specifier.as_str().strip_prefix("node:")
|
||||
{
|
||||
if deno_node::resolve_builtin_node_module(module_name).is_err() {
|
||||
if !deno_node::is_builtin_node_module(module_name) {
|
||||
diagnostics
|
||||
.push(DenoDiagnostic::InvalidNodeSpecifier(specifier.clone()));
|
||||
} else if let Some(npm_resolver) = &snapshot.maybe_npm_resolver {
|
||||
|
@ -1090,7 +1090,7 @@ impl Documents {
|
||||
}
|
||||
}
|
||||
if let Some(module_name) = specifier.strip_prefix("node:") {
|
||||
if deno_node::resolve_builtin_node_module(module_name).is_ok() {
|
||||
if deno_node::is_builtin_node_module(module_name) {
|
||||
// return itself for node: specifiers because during type checking
|
||||
// we resolve to the ambient modules in the @types/node package
|
||||
// rather than deno_std/node
|
||||
|
@ -47,7 +47,6 @@ use deno_graph::Module;
|
||||
use deno_graph::Resolution;
|
||||
use deno_lockfile::Lockfile;
|
||||
use deno_runtime::deno_fs;
|
||||
use deno_runtime::deno_node;
|
||||
use deno_runtime::deno_node::NodeResolution;
|
||||
use deno_runtime::deno_node::NodeResolutionMode;
|
||||
use deno_runtime::deno_node::NodeResolver;
|
||||
@ -496,9 +495,7 @@ impl ModuleLoader for CliModuleLoader {
|
||||
.shared
|
||||
.npm_module_loader
|
||||
.resolve_nv_ref(&module.nv_reference, permissions),
|
||||
Some(Module::Node(module)) => {
|
||||
deno_node::resolve_builtin_node_module(&module.module_name)
|
||||
}
|
||||
Some(Module::Node(module)) => Ok(module.specifier.clone()),
|
||||
Some(Module::Esm(module)) => Ok(module.specifier.clone()),
|
||||
Some(Module::Json(module)) => Ok(module.specifier.clone()),
|
||||
Some(Module::External(module)) => {
|
||||
@ -517,11 +514,6 @@ impl ModuleLoader for CliModuleLoader {
|
||||
}
|
||||
}
|
||||
|
||||
// Built-in Node modules
|
||||
if let Some(module_name) = specifier.strip_prefix("node:") {
|
||||
return deno_node::resolve_builtin_node_module(module_name);
|
||||
}
|
||||
|
||||
// FIXME(bartlomieju): this is a hacky way to provide compatibility with REPL
|
||||
// and `Deno.core.evalContext` API. Ideally we should always have a referrer filled
|
||||
// but sadly that's not the case due to missing APIs in V8.
|
||||
@ -802,8 +794,6 @@ impl NpmModuleLoader {
|
||||
if let NodeResolution::CommonJs(specifier) = &response {
|
||||
// remember that this was a common js resolution
|
||||
self.cjs_resolutions.insert(specifier.clone());
|
||||
} else if let NodeResolution::BuiltIn(specifier) = &response {
|
||||
return deno_node::resolve_builtin_node_module(specifier);
|
||||
}
|
||||
Ok(response.into_url())
|
||||
}
|
||||
|
@ -39,7 +39,6 @@ use deno_core::ModuleType;
|
||||
use deno_core::ResolutionKind;
|
||||
use deno_npm::NpmSystemInfo;
|
||||
use deno_runtime::deno_fs;
|
||||
use deno_runtime::deno_node;
|
||||
use deno_runtime::deno_node::analyze::NodeCodeTranslator;
|
||||
use deno_runtime::deno_node::NodeResolver;
|
||||
use deno_runtime::deno_tls::rustls::RootCertStore;
|
||||
@ -128,11 +127,6 @@ impl ModuleLoader for EmbeddedModuleLoader {
|
||||
.resolve_req_reference(&reference, permissions);
|
||||
}
|
||||
|
||||
// Built-in Node modules
|
||||
if let Some(module_name) = specifier_text.strip_prefix("node:") {
|
||||
return deno_node::resolve_builtin_node_module(module_name);
|
||||
}
|
||||
|
||||
match maybe_mapped {
|
||||
Some(resolved) => Ok(resolved),
|
||||
None => deno_core::resolve_import(specifier, referrer.as_str())
|
||||
|
@ -1,4 +1,10 @@
|
||||
error: Uncaught (in promise) TypeError: Cannot load extension module from external code
|
||||
error: Uncaught (in promise) TypeError: Unsupported scheme "ext" for module "ext:runtime/01_errors.js". Supported schemes: [
|
||||
"data",
|
||||
"blob",
|
||||
"file",
|
||||
"http",
|
||||
"https",
|
||||
]
|
||||
await import("ext:runtime/01_errors.js");
|
||||
^
|
||||
at [WILDCARD]/extension_dynamic_import.ts:1:1
|
||||
at async [WILDCARD]/extension_dynamic_import.ts:1:1
|
||||
|
@ -5,4 +5,4 @@ error: Unsupported scheme "ext" for module "ext:runtime/01_errors.js". Supported
|
||||
"http",
|
||||
"https",
|
||||
]
|
||||
at [WILDCARD]
|
||||
at [WILDCARD]/extension_import.ts:1:8
|
||||
|
@ -538,7 +538,7 @@ fn op_resolve(
|
||||
};
|
||||
for specifier in args.specifiers {
|
||||
if let Some(module_name) = specifier.strip_prefix("node:") {
|
||||
if deno_node::resolve_builtin_node_module(module_name).is_ok() {
|
||||
if deno_node::is_builtin_node_module(module_name) {
|
||||
// return itself for node: specifiers because during type checking
|
||||
// we resolve to the ambient modules in the @types/node package
|
||||
// rather than deno_std/node
|
||||
|
@ -471,16 +471,6 @@ impl Extension {
|
||||
pub fn disable(self) -> Self {
|
||||
self.enabled(false)
|
||||
}
|
||||
|
||||
pub(crate) fn find_esm(
|
||||
&self,
|
||||
specifier: &str,
|
||||
) -> Option<&ExtensionFileSource> {
|
||||
self
|
||||
.get_esm_sources()?
|
||||
.iter()
|
||||
.find(|s| s.specifier == specifier)
|
||||
}
|
||||
}
|
||||
|
||||
// Provides a convenient builder pattern to declare Extensions
|
||||
|
@ -79,7 +79,6 @@ pub use crate::module_specifier::resolve_url;
|
||||
pub use crate::module_specifier::resolve_url_or_path;
|
||||
pub use crate::module_specifier::ModuleResolutionError;
|
||||
pub use crate::module_specifier::ModuleSpecifier;
|
||||
pub use crate::modules::ExtModuleLoader;
|
||||
pub use crate::modules::ExtModuleLoaderCb;
|
||||
pub use crate::modules::FsModuleLoader;
|
||||
pub use crate::modules::ModuleCode;
|
||||
|
238
core/modules.rs
238
core/modules.rs
@ -12,8 +12,8 @@ use crate::snapshot_util::SnapshottedData;
|
||||
use crate::Extension;
|
||||
use crate::JsRuntime;
|
||||
use crate::OpState;
|
||||
use anyhow::anyhow;
|
||||
use anyhow::Error;
|
||||
use core::panic;
|
||||
use futures::future::FutureExt;
|
||||
use futures::stream::FuturesUnordered;
|
||||
use futures::stream::Stream;
|
||||
@ -385,154 +385,90 @@ impl ModuleLoader for NoopModuleLoader {
|
||||
pub type ExtModuleLoaderCb =
|
||||
Box<dyn Fn(&ExtensionFileSource) -> Result<ModuleCode, Error>>;
|
||||
|
||||
pub struct ExtModuleLoader {
|
||||
module_loader: Rc<dyn ModuleLoader>,
|
||||
extensions: Rc<RefCell<Vec<Extension>>>,
|
||||
ext_resolution_allowed: RefCell<bool>,
|
||||
used_esm_sources: RefCell<HashMap<String, bool>>,
|
||||
maybe_load_callback: Option<ExtModuleLoaderCb>,
|
||||
}
|
||||
|
||||
impl Default for ExtModuleLoader {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
module_loader: Rc::new(NoopModuleLoader),
|
||||
extensions: Default::default(),
|
||||
ext_resolution_allowed: Default::default(),
|
||||
used_esm_sources: RefCell::new(HashMap::default()),
|
||||
maybe_load_callback: None,
|
||||
}
|
||||
}
|
||||
pub(crate) struct ExtModuleLoader {
|
||||
maybe_load_callback: Option<Rc<ExtModuleLoaderCb>>,
|
||||
sources: RefCell<HashMap<String, ExtensionFileSource>>,
|
||||
used_specifiers: RefCell<HashSet<String>>,
|
||||
}
|
||||
|
||||
impl ExtModuleLoader {
|
||||
pub fn new(
|
||||
module_loader: Option<Rc<dyn ModuleLoader>>,
|
||||
extensions: Rc<RefCell<Vec<Extension>>>,
|
||||
maybe_load_callback: Option<ExtModuleLoaderCb>,
|
||||
extensions: &[Extension],
|
||||
maybe_load_callback: Option<Rc<ExtModuleLoaderCb>>,
|
||||
) -> Self {
|
||||
let used_esm_sources: HashMap<String, bool> = extensions
|
||||
.borrow()
|
||||
.iter()
|
||||
.flat_map(|e| e.get_esm_sources())
|
||||
.flatten()
|
||||
.map(|file_source| (file_source.specifier.to_string(), false))
|
||||
.collect();
|
||||
|
||||
let mut sources = HashMap::new();
|
||||
sources.extend(
|
||||
extensions
|
||||
.iter()
|
||||
.flat_map(|e| e.get_esm_sources())
|
||||
.flatten()
|
||||
.map(|s| (s.specifier.to_string(), s.clone())),
|
||||
);
|
||||
ExtModuleLoader {
|
||||
module_loader: module_loader.unwrap_or_else(|| Rc::new(NoopModuleLoader)),
|
||||
extensions,
|
||||
ext_resolution_allowed: Default::default(),
|
||||
used_esm_sources: RefCell::new(used_esm_sources),
|
||||
maybe_load_callback,
|
||||
sources: RefCell::new(sources),
|
||||
used_specifiers: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resolve(
|
||||
impl ModuleLoader for ExtModuleLoader {
|
||||
fn resolve(
|
||||
&self,
|
||||
specifier: &str,
|
||||
referrer: &str,
|
||||
kind: ResolutionKind,
|
||||
_kind: ResolutionKind,
|
||||
) -> Result<ModuleSpecifier, Error> {
|
||||
if specifier.starts_with("ext:") {
|
||||
if !referrer.starts_with("ext:") && referrer != "."
|
||||
|| !*self.ext_resolution_allowed.borrow()
|
||||
{
|
||||
return Err(generic_error(
|
||||
"Cannot load extension module from external code",
|
||||
));
|
||||
}
|
||||
return Ok(ModuleSpecifier::parse(specifier)?);
|
||||
}
|
||||
self.module_loader.resolve(specifier, referrer, kind)
|
||||
Ok(resolve_import(specifier, referrer)?)
|
||||
}
|
||||
|
||||
pub fn load(
|
||||
fn load(
|
||||
&self,
|
||||
module_specifier: &ModuleSpecifier,
|
||||
maybe_referrer: Option<&ModuleSpecifier>,
|
||||
is_dyn_import: bool,
|
||||
specifier: &ModuleSpecifier,
|
||||
_maybe_referrer: Option<&ModuleSpecifier>,
|
||||
_is_dyn_import: bool,
|
||||
) -> Pin<Box<ModuleSourceFuture>> {
|
||||
if module_specifier.scheme() != "ext" {
|
||||
return self.module_loader.load(
|
||||
module_specifier,
|
||||
maybe_referrer,
|
||||
is_dyn_import,
|
||||
);
|
||||
}
|
||||
|
||||
let specifier = module_specifier.to_string();
|
||||
let extensions = self.extensions.borrow();
|
||||
let maybe_file_source = extensions
|
||||
.iter()
|
||||
.find_map(|e| e.find_esm(module_specifier.as_str()));
|
||||
|
||||
if let Some(file_source) = maybe_file_source {
|
||||
{
|
||||
let mut used_esm_sources = self.used_esm_sources.borrow_mut();
|
||||
let used = used_esm_sources.get_mut(file_source.specifier).unwrap();
|
||||
*used = true;
|
||||
}
|
||||
|
||||
let result = if let Some(load_callback) = &self.maybe_load_callback {
|
||||
load_callback(file_source)
|
||||
} else {
|
||||
file_source.load()
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok(code) => {
|
||||
let res =
|
||||
ModuleSource::new(ModuleType::JavaScript, code, module_specifier);
|
||||
return futures::future::ok(res).boxed_local();
|
||||
}
|
||||
Err(err) => return futures::future::err(err).boxed_local(),
|
||||
let sources = self.sources.borrow();
|
||||
let source = match sources.get(specifier.as_str()) {
|
||||
Some(source) => source,
|
||||
None => return futures::future::err(anyhow!("Specifier \"{}\" was not passed as an extension module and was not included in the snapshot.", specifier)).boxed_local(),
|
||||
};
|
||||
self
|
||||
.used_specifiers
|
||||
.borrow_mut()
|
||||
.insert(specifier.to_string());
|
||||
let result = if let Some(load_callback) = &self.maybe_load_callback {
|
||||
load_callback(source)
|
||||
} else {
|
||||
source.load()
|
||||
};
|
||||
match result {
|
||||
Ok(code) => {
|
||||
let res = ModuleSource::new(ModuleType::JavaScript, code, specifier);
|
||||
return futures::future::ok(res).boxed_local();
|
||||
}
|
||||
Err(err) => return futures::future::err(err).boxed_local(),
|
||||
}
|
||||
|
||||
async move {
|
||||
Err(generic_error(format!(
|
||||
"Cannot find extension module source for specifier {specifier}"
|
||||
)))
|
||||
}
|
||||
.boxed_local()
|
||||
}
|
||||
|
||||
pub fn prepare_load(
|
||||
fn prepare_load(
|
||||
&self,
|
||||
op_state: Rc<RefCell<OpState>>,
|
||||
module_specifier: &ModuleSpecifier,
|
||||
maybe_referrer: Option<String>,
|
||||
is_dyn_import: bool,
|
||||
_op_state: Rc<RefCell<OpState>>,
|
||||
_specifier: &ModuleSpecifier,
|
||||
_maybe_referrer: Option<String>,
|
||||
_is_dyn_import: bool,
|
||||
) -> Pin<Box<dyn Future<Output = Result<(), Error>>>> {
|
||||
if module_specifier.scheme() == "ext" {
|
||||
return async { Ok(()) }.boxed_local();
|
||||
}
|
||||
|
||||
self.module_loader.prepare_load(
|
||||
op_state,
|
||||
module_specifier,
|
||||
maybe_referrer,
|
||||
is_dyn_import,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn allow_ext_resolution(&self) {
|
||||
*self.ext_resolution_allowed.borrow_mut() = true;
|
||||
}
|
||||
|
||||
pub fn disallow_ext_resolution(&self) {
|
||||
*self.ext_resolution_allowed.borrow_mut() = false;
|
||||
async { Ok(()) }.boxed_local()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ExtModuleLoader {
|
||||
fn drop(&mut self) {
|
||||
let used_esm_sources = self.used_esm_sources.get_mut();
|
||||
let unused_modules: Vec<_> = used_esm_sources
|
||||
let sources = self.sources.get_mut();
|
||||
let used_specifiers = self.used_specifiers.get_mut();
|
||||
let unused_modules: Vec<_> = sources
|
||||
.iter()
|
||||
.filter(|(_s, v)| !*v)
|
||||
.map(|(s, _)| s)
|
||||
.filter(|(k, _)| !used_specifiers.contains(k.as_str()))
|
||||
.collect();
|
||||
|
||||
if !unused_modules.is_empty() {
|
||||
@ -541,7 +477,7 @@ impl Drop for ExtModuleLoader {
|
||||
.to_string();
|
||||
for m in unused_modules {
|
||||
msg.push_str(" - ");
|
||||
msg.push_str(m);
|
||||
msg.push_str(m.0);
|
||||
msg.push('\n');
|
||||
}
|
||||
panic!("{}", msg);
|
||||
@ -634,7 +570,7 @@ pub(crate) struct RecursiveModuleLoad {
|
||||
// These three fields are copied from `module_map_rc`, but they are cloned
|
||||
// ahead of time to avoid already-borrowed errors.
|
||||
op_state: Rc<RefCell<OpState>>,
|
||||
loader: Rc<ExtModuleLoader>,
|
||||
loader: Rc<dyn ModuleLoader>,
|
||||
}
|
||||
|
||||
impl RecursiveModuleLoad {
|
||||
@ -1060,7 +996,7 @@ pub(crate) struct ModuleMap {
|
||||
pub(crate) next_load_id: ModuleLoadId,
|
||||
|
||||
// Handling of futures for loading module sources
|
||||
pub loader: Rc<ExtModuleLoader>,
|
||||
pub loader: Rc<dyn ModuleLoader>,
|
||||
op_state: Rc<RefCell<OpState>>,
|
||||
pub(crate) dynamic_import_map:
|
||||
HashMap<ModuleLoadId, v8::Global<v8::PromiseResolver>>,
|
||||
@ -1369,7 +1305,7 @@ impl ModuleMap {
|
||||
}
|
||||
|
||||
pub(crate) fn new(
|
||||
loader: Rc<ExtModuleLoader>,
|
||||
loader: Rc<dyn ModuleLoader>,
|
||||
op_state: Rc<RefCell<OpState>>,
|
||||
) -> ModuleMap {
|
||||
Self {
|
||||
@ -1556,6 +1492,29 @@ impl ModuleMap {
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
pub(crate) fn clear(&mut self) {
|
||||
*self = Self::new(self.loader.clone(), self.op_state.clone())
|
||||
}
|
||||
|
||||
pub(crate) fn get_handle_by_name(
|
||||
&self,
|
||||
name: impl AsRef<str>,
|
||||
) -> Option<v8::Global<v8::Module>> {
|
||||
let id = self
|
||||
.get_id(name.as_ref(), AssertedModuleType::JavaScriptOrWasm)
|
||||
.or_else(|| self.get_id(name.as_ref(), AssertedModuleType::Json))?;
|
||||
self.get_handle(id)
|
||||
}
|
||||
|
||||
pub(crate) fn inject_handle(
|
||||
&mut self,
|
||||
name: ModuleName,
|
||||
module_type: ModuleType,
|
||||
handle: v8::Global<v8::Module>,
|
||||
) {
|
||||
self.create_module_info(name, module_type, handle, false, vec![]);
|
||||
}
|
||||
|
||||
fn create_module_info(
|
||||
&mut self,
|
||||
name: FastString,
|
||||
@ -3005,37 +2964,4 @@ if (import.meta.url != 'file:///main_with_code.js') throw Error();
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ext_resolution() {
|
||||
let loader = ExtModuleLoader::default();
|
||||
loader.allow_ext_resolution();
|
||||
loader
|
||||
.resolve("ext:core.js", "ext:referrer.js", ResolutionKind::Import)
|
||||
.unwrap();
|
||||
loader
|
||||
.resolve("ext:core.js", ".", ResolutionKind::Import)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ext_resolution_failure() {
|
||||
let loader = ExtModuleLoader::default();
|
||||
loader.allow_ext_resolution();
|
||||
assert_eq!(
|
||||
loader
|
||||
.resolve("ext:core.js", "file://bar", ResolutionKind::Import,)
|
||||
.err()
|
||||
.map(|e| e.to_string()),
|
||||
Some("Cannot load extension module from external code".to_string())
|
||||
);
|
||||
loader.disallow_ext_resolution();
|
||||
assert_eq!(
|
||||
loader
|
||||
.resolve("ext:core.js", "ext:referrer.js", ResolutionKind::Import,)
|
||||
.err()
|
||||
.map(|e| e.to_string()),
|
||||
Some("Cannot load extension module from external code".to_string())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
131
core/runtime.rs
131
core/runtime.rs
@ -9,6 +9,7 @@ use crate::extensions::OpEventLoopFn;
|
||||
use crate::inspector::JsRuntimeInspector;
|
||||
use crate::module_specifier::ModuleSpecifier;
|
||||
use crate::modules::AssertedModuleType;
|
||||
use crate::modules::ExtModuleLoader;
|
||||
use crate::modules::ExtModuleLoaderCb;
|
||||
use crate::modules::ModuleCode;
|
||||
use crate::modules::ModuleError;
|
||||
@ -16,6 +17,7 @@ use crate::modules::ModuleId;
|
||||
use crate::modules::ModuleLoadId;
|
||||
use crate::modules::ModuleLoader;
|
||||
use crate::modules::ModuleMap;
|
||||
use crate::modules::ModuleName;
|
||||
use crate::ops::*;
|
||||
use crate::realm::ContextState;
|
||||
use crate::realm::JsRealm;
|
||||
@ -24,6 +26,7 @@ use crate::snapshot_util;
|
||||
use crate::source_map::SourceMapCache;
|
||||
use crate::source_map::SourceMapGetter;
|
||||
use crate::Extension;
|
||||
use crate::ModuleType;
|
||||
use crate::NoopModuleLoader;
|
||||
use crate::OpMiddlewareFn;
|
||||
use crate::OpResult;
|
||||
@ -88,6 +91,7 @@ pub struct JsRuntime {
|
||||
// a safety issue with SnapshotCreator. See JsRuntime::drop.
|
||||
v8_isolate: Option<v8::OwnedIsolate>,
|
||||
snapshot_options: snapshot_util::SnapshotOptions,
|
||||
snapshot_module_load_cb: Option<Rc<ExtModuleLoaderCb>>,
|
||||
allocations: IsolateAllocations,
|
||||
extensions: Rc<RefCell<Vec<Extension>>>,
|
||||
event_loop_middlewares: Vec<Box<OpEventLoopFn>>,
|
||||
@ -506,13 +510,6 @@ impl JsRuntime {
|
||||
}
|
||||
}
|
||||
}
|
||||
let num_extensions = options.extensions.len();
|
||||
let extensions = Rc::new(RefCell::new(options.extensions));
|
||||
let ext_loader = Rc::new(crate::modules::ExtModuleLoader::new(
|
||||
Some(loader.clone()),
|
||||
extensions.clone(),
|
||||
options.snapshot_module_load_cb,
|
||||
));
|
||||
|
||||
{
|
||||
let global_realm = JsRealmInner::new(
|
||||
@ -530,8 +527,7 @@ impl JsRuntime {
|
||||
Self::STATE_DATA_OFFSET,
|
||||
Rc::into_raw(state_rc.clone()) as *mut c_void,
|
||||
);
|
||||
let module_map_rc =
|
||||
Rc::new(RefCell::new(ModuleMap::new(ext_loader, op_state)));
|
||||
let module_map_rc = Rc::new(RefCell::new(ModuleMap::new(loader, op_state)));
|
||||
if let Some(snapshotted_data) = maybe_snapshotted_data {
|
||||
let scope =
|
||||
&mut v8::HandleScope::with_context(&mut isolate, global_context);
|
||||
@ -546,11 +542,12 @@ impl JsRuntime {
|
||||
let mut js_runtime = Self {
|
||||
v8_isolate: Some(isolate),
|
||||
snapshot_options,
|
||||
snapshot_module_load_cb: options.snapshot_module_load_cb.map(Rc::new),
|
||||
allocations: IsolateAllocations::default(),
|
||||
event_loop_middlewares: Vec::with_capacity(num_extensions),
|
||||
extensions,
|
||||
event_loop_middlewares: Vec::with_capacity(options.extensions.len()),
|
||||
extensions: Rc::new(RefCell::new(options.extensions)),
|
||||
state: state_rc,
|
||||
module_map: Some(module_map_rc.clone()),
|
||||
module_map: Some(module_map_rc),
|
||||
is_main: options.is_main,
|
||||
};
|
||||
|
||||
@ -558,9 +555,7 @@ impl JsRuntime {
|
||||
// available during the initialization process.
|
||||
js_runtime.init_extension_ops().unwrap();
|
||||
let realm = js_runtime.global_realm();
|
||||
module_map_rc.borrow().loader.allow_ext_resolution();
|
||||
js_runtime.init_extension_js(&realm).unwrap();
|
||||
module_map_rc.borrow().loader.disallow_ext_resolution();
|
||||
|
||||
js_runtime
|
||||
}
|
||||
@ -666,21 +661,7 @@ impl JsRuntime {
|
||||
JsRealm::new(realm)
|
||||
};
|
||||
|
||||
self
|
||||
.module_map
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.borrow()
|
||||
.loader
|
||||
.allow_ext_resolution();
|
||||
self.init_extension_js(&realm)?;
|
||||
self
|
||||
.module_map
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.borrow()
|
||||
.loader
|
||||
.disallow_ext_resolution();
|
||||
Ok(realm)
|
||||
}
|
||||
|
||||
@ -735,6 +716,15 @@ impl JsRuntime {
|
||||
// 2. Iterate through all extensions:
|
||||
// a. If an extension has a `esm_entry_point`, execute it.
|
||||
|
||||
// TODO(nayeemrmn): Module maps should be per-realm.
|
||||
let module_map = self.module_map.as_ref().unwrap();
|
||||
let loader = module_map.borrow().loader.clone();
|
||||
let ext_loader = Rc::new(ExtModuleLoader::new(
|
||||
&self.extensions.borrow(),
|
||||
self.snapshot_module_load_cb.clone(),
|
||||
));
|
||||
module_map.borrow_mut().loader = ext_loader;
|
||||
|
||||
let mut esm_entrypoints = vec![];
|
||||
|
||||
// Take extensions to avoid double-borrow
|
||||
@ -816,6 +806,7 @@ impl JsRuntime {
|
||||
// Restore extensions
|
||||
self.extensions = extensions;
|
||||
|
||||
self.module_map.as_ref().unwrap().borrow_mut().loader = loader;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -1865,6 +1856,29 @@ impl JsRuntime {
|
||||
receiver
|
||||
}
|
||||
|
||||
/// Clear the module map, meant to be used after initializing extensions.
|
||||
/// Optionally pass a list of exceptions `(old_name, new_name)` representing
|
||||
/// specifiers which will be renamed and preserved in the module map.
|
||||
pub fn clear_module_map(
|
||||
&self,
|
||||
exceptions: impl Iterator<Item = (&'static str, &'static str)>,
|
||||
) {
|
||||
let mut module_map = self.module_map.as_ref().unwrap().borrow_mut();
|
||||
let handles = exceptions
|
||||
.map(|(old_name, new_name)| {
|
||||
(module_map.get_handle_by_name(old_name).unwrap(), new_name)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
module_map.clear();
|
||||
for (handle, new_name) in handles {
|
||||
module_map.inject_handle(
|
||||
ModuleName::from_static(new_name),
|
||||
ModuleType::JavaScript,
|
||||
handle,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn dynamic_import_reject(
|
||||
&mut self,
|
||||
id: ModuleLoadId,
|
||||
@ -4774,67 +4788,6 @@ Deno.core.opAsync("op_async_serialize_object_with_numbers_as_keys", {
|
||||
.is_ok());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn cant_load_internal_module_when_snapshot_is_loaded_and_not_snapshotting(
|
||||
) {
|
||||
#[derive(Default)]
|
||||
struct ModsLoader;
|
||||
|
||||
impl ModuleLoader for ModsLoader {
|
||||
fn resolve(
|
||||
&self,
|
||||
specifier: &str,
|
||||
referrer: &str,
|
||||
_kind: ResolutionKind,
|
||||
) -> Result<ModuleSpecifier, Error> {
|
||||
assert_eq!(specifier, "file:///main.js");
|
||||
assert_eq!(referrer, ".");
|
||||
let s = crate::resolve_import(specifier, referrer).unwrap();
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
fn load(
|
||||
&self,
|
||||
_module_specifier: &ModuleSpecifier,
|
||||
_maybe_referrer: Option<&ModuleSpecifier>,
|
||||
_is_dyn_import: bool,
|
||||
) -> Pin<Box<ModuleSourceFuture>> {
|
||||
let code = r#"
|
||||
// This module doesn't really exist, just verifying that we'll get
|
||||
// an error when specifier starts with "ext:".
|
||||
import { core } from "ext:core.js";
|
||||
"#;
|
||||
|
||||
async move { Ok(ModuleSource::for_test(code, "file:///main.js")) }
|
||||
.boxed_local()
|
||||
}
|
||||
}
|
||||
|
||||
let snapshot = {
|
||||
let runtime = JsRuntime::new(RuntimeOptions {
|
||||
will_snapshot: true,
|
||||
..Default::default()
|
||||
});
|
||||
let snap: &[u8] = &runtime.snapshot();
|
||||
Vec::from(snap).into_boxed_slice()
|
||||
};
|
||||
|
||||
let mut runtime2 = JsRuntime::new(RuntimeOptions {
|
||||
module_loader: Some(Rc::new(ModsLoader)),
|
||||
startup_snapshot: Some(Snapshot::Boxed(snapshot)),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let err = runtime2
|
||||
.load_main_module(&crate::resolve_url("file:///main.js").unwrap(), None)
|
||||
.await
|
||||
.unwrap_err();
|
||||
assert_eq!(
|
||||
err.to_string(),
|
||||
"Cannot load extension module from external code"
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
#[test]
|
||||
#[should_panic(expected = "Found ops with duplicate names:")]
|
||||
|
@ -33,7 +33,6 @@ mod resolution;
|
||||
pub use package_json::PackageJson;
|
||||
pub use path::PathClean;
|
||||
pub use polyfill::is_builtin_node_module;
|
||||
pub use polyfill::resolve_builtin_node_module;
|
||||
pub use polyfill::NodeModulePolyfill;
|
||||
pub use polyfill::SUPPORTED_BUILTIN_NODE_MODULES;
|
||||
pub use resolution::NodeModuleKind;
|
||||
|
@ -1,229 +1,217 @@
|
||||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
use deno_core::error::generic_error;
|
||||
use deno_core::error::AnyError;
|
||||
use deno_core::url::Url;
|
||||
use deno_core::ModuleSpecifier;
|
||||
|
||||
// TODO(bartlomieju): seems super wasteful to parse the specifier each time
|
||||
pub fn resolve_builtin_node_module(module_name: &str) -> Result<Url, AnyError> {
|
||||
if let Some(module) = find_builtin_node_module(module_name) {
|
||||
return Ok(ModuleSpecifier::parse(module.specifier).unwrap());
|
||||
}
|
||||
|
||||
Err(generic_error(format!(
|
||||
"Unknown built-in \"node:\" module: {module_name}"
|
||||
)))
|
||||
}
|
||||
|
||||
fn find_builtin_node_module(module_name: &str) -> Option<&NodeModulePolyfill> {
|
||||
/// e.g. `is_builtin_node_module("assert")`
|
||||
pub fn is_builtin_node_module(module_name: &str) -> bool {
|
||||
SUPPORTED_BUILTIN_NODE_MODULES
|
||||
.iter()
|
||||
.find(|m| m.name == module_name)
|
||||
}
|
||||
|
||||
pub fn is_builtin_node_module(module_name: &str) -> bool {
|
||||
find_builtin_node_module(module_name).is_some()
|
||||
.any(|m| m.module_name() == module_name)
|
||||
}
|
||||
|
||||
pub struct NodeModulePolyfill {
|
||||
/// Name of the module like "assert" or "timers/promises"
|
||||
pub name: &'static str,
|
||||
pub specifier: &'static str,
|
||||
pub ext_specifier: &'static str,
|
||||
}
|
||||
|
||||
impl NodeModulePolyfill {
|
||||
pub fn module_name(&self) -> &'static str {
|
||||
debug_assert!(self.specifier.starts_with("node:"));
|
||||
&self.specifier[5..]
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE(bartlomieju): keep this list in sync with `ext/node/polyfills/01_require.js`
|
||||
pub static SUPPORTED_BUILTIN_NODE_MODULES: &[NodeModulePolyfill] = &[
|
||||
NodeModulePolyfill {
|
||||
name: "assert",
|
||||
specifier: "ext:deno_node/assert.ts",
|
||||
specifier: "node:assert",
|
||||
ext_specifier: "ext:deno_node/assert.ts",
|
||||
},
|
||||
NodeModulePolyfill {
|
||||
name: "assert/strict",
|
||||
specifier: "ext:deno_node/assert/strict.ts",
|
||||
specifier: "node:assert/strict",
|
||||
ext_specifier: "ext:deno_node/assert/strict.ts",
|
||||
},
|
||||
NodeModulePolyfill {
|
||||
name: "async_hooks",
|
||||
specifier: "ext:deno_node/async_hooks.ts",
|
||||
specifier: "node:async_hooks",
|
||||
ext_specifier: "ext:deno_node/async_hooks.ts",
|
||||
},
|
||||
NodeModulePolyfill {
|
||||
name: "buffer",
|
||||
specifier: "ext:deno_node/buffer.ts",
|
||||
specifier: "node:buffer",
|
||||
ext_specifier: "ext:deno_node/buffer.ts",
|
||||
},
|
||||
NodeModulePolyfill {
|
||||
name: "child_process",
|
||||
specifier: "ext:deno_node/child_process.ts",
|
||||
specifier: "node:child_process",
|
||||
ext_specifier: "ext:deno_node/child_process.ts",
|
||||
},
|
||||
NodeModulePolyfill {
|
||||
name: "cluster",
|
||||
specifier: "ext:deno_node/cluster.ts",
|
||||
specifier: "node:cluster",
|
||||
ext_specifier: "ext:deno_node/cluster.ts",
|
||||
},
|
||||
NodeModulePolyfill {
|
||||
name: "console",
|
||||
specifier: "ext:deno_node/console.ts",
|
||||
specifier: "node:console",
|
||||
ext_specifier: "ext:deno_node/console.ts",
|
||||
},
|
||||
NodeModulePolyfill {
|
||||
name: "constants",
|
||||
specifier: "ext:deno_node/constants.ts",
|
||||
specifier: "node:constants",
|
||||
ext_specifier: "ext:deno_node/constants.ts",
|
||||
},
|
||||
NodeModulePolyfill {
|
||||
name: "crypto",
|
||||
specifier: "ext:deno_node/crypto.ts",
|
||||
specifier: "node:crypto",
|
||||
ext_specifier: "ext:deno_node/crypto.ts",
|
||||
},
|
||||
NodeModulePolyfill {
|
||||
name: "dgram",
|
||||
specifier: "ext:deno_node/dgram.ts",
|
||||
specifier: "node:dgram",
|
||||
ext_specifier: "ext:deno_node/dgram.ts",
|
||||
},
|
||||
NodeModulePolyfill {
|
||||
name: "diagnostics_channel",
|
||||
specifier: "ext:deno_node/diagnostics_channel.ts",
|
||||
specifier: "node:diagnostics_channel",
|
||||
ext_specifier: "ext:deno_node/diagnostics_channel.ts",
|
||||
},
|
||||
NodeModulePolyfill {
|
||||
name: "dns",
|
||||
specifier: "ext:deno_node/dns.ts",
|
||||
specifier: "node:dns",
|
||||
ext_specifier: "ext:deno_node/dns.ts",
|
||||
},
|
||||
NodeModulePolyfill {
|
||||
name: "dns/promises",
|
||||
specifier: "ext:deno_node/dns/promises.ts",
|
||||
specifier: "node:dns/promises",
|
||||
ext_specifier: "ext:deno_node/dns/promises.ts",
|
||||
},
|
||||
NodeModulePolyfill {
|
||||
name: "domain",
|
||||
specifier: "ext:deno_node/domain.ts",
|
||||
specifier: "node:domain",
|
||||
ext_specifier: "ext:deno_node/domain.ts",
|
||||
},
|
||||
NodeModulePolyfill {
|
||||
name: "events",
|
||||
specifier: "ext:deno_node/events.ts",
|
||||
specifier: "node:events",
|
||||
ext_specifier: "ext:deno_node/events.ts",
|
||||
},
|
||||
NodeModulePolyfill {
|
||||
name: "fs",
|
||||
specifier: "ext:deno_node/fs.ts",
|
||||
specifier: "node:fs",
|
||||
ext_specifier: "ext:deno_node/fs.ts",
|
||||
},
|
||||
NodeModulePolyfill {
|
||||
name: "fs/promises",
|
||||
specifier: "ext:deno_node/fs/promises.ts",
|
||||
specifier: "node:fs/promises",
|
||||
ext_specifier: "ext:deno_node/fs/promises.ts",
|
||||
},
|
||||
NodeModulePolyfill {
|
||||
name: "http",
|
||||
specifier: "ext:deno_node/http.ts",
|
||||
specifier: "node:http",
|
||||
ext_specifier: "ext:deno_node/http.ts",
|
||||
},
|
||||
NodeModulePolyfill {
|
||||
name: "http2",
|
||||
specifier: "ext:deno_node/http2.ts",
|
||||
specifier: "node:http2",
|
||||
ext_specifier: "ext:deno_node/http2.ts",
|
||||
},
|
||||
NodeModulePolyfill {
|
||||
name: "https",
|
||||
specifier: "ext:deno_node/https.ts",
|
||||
specifier: "node:https",
|
||||
ext_specifier: "ext:deno_node/https.ts",
|
||||
},
|
||||
NodeModulePolyfill {
|
||||
name: "module",
|
||||
specifier: "ext:deno_node/01_require.js",
|
||||
specifier: "node:module",
|
||||
ext_specifier: "ext:deno_node/01_require.js",
|
||||
},
|
||||
NodeModulePolyfill {
|
||||
name: "net",
|
||||
specifier: "ext:deno_node/net.ts",
|
||||
specifier: "node:net",
|
||||
ext_specifier: "ext:deno_node/net.ts",
|
||||
},
|
||||
NodeModulePolyfill {
|
||||
name: "os",
|
||||
specifier: "ext:deno_node/os.ts",
|
||||
specifier: "node:os",
|
||||
ext_specifier: "ext:deno_node/os.ts",
|
||||
},
|
||||
NodeModulePolyfill {
|
||||
name: "path",
|
||||
specifier: "ext:deno_node/path.ts",
|
||||
specifier: "node:path",
|
||||
ext_specifier: "ext:deno_node/path.ts",
|
||||
},
|
||||
NodeModulePolyfill {
|
||||
name: "path/posix",
|
||||
specifier: "ext:deno_node/path/posix.ts",
|
||||
specifier: "node:path/posix",
|
||||
ext_specifier: "ext:deno_node/path/posix.ts",
|
||||
},
|
||||
NodeModulePolyfill {
|
||||
name: "path/win32",
|
||||
specifier: "ext:deno_node/path/win32.ts",
|
||||
specifier: "node:path/win32",
|
||||
ext_specifier: "ext:deno_node/path/win32.ts",
|
||||
},
|
||||
NodeModulePolyfill {
|
||||
name: "perf_hooks",
|
||||
specifier: "ext:deno_node/perf_hooks.ts",
|
||||
specifier: "node:perf_hooks",
|
||||
ext_specifier: "ext:deno_node/perf_hooks.ts",
|
||||
},
|
||||
NodeModulePolyfill {
|
||||
name: "process",
|
||||
specifier: "ext:deno_node/process.ts",
|
||||
specifier: "node:process",
|
||||
ext_specifier: "ext:deno_node/process.ts",
|
||||
},
|
||||
NodeModulePolyfill {
|
||||
name: "punycode",
|
||||
specifier: "ext:deno_node/punycode.ts",
|
||||
specifier: "node:punycode",
|
||||
ext_specifier: "ext:deno_node/punycode.ts",
|
||||
},
|
||||
NodeModulePolyfill {
|
||||
name: "querystring",
|
||||
specifier: "ext:deno_node/querystring.ts",
|
||||
specifier: "node:querystring",
|
||||
ext_specifier: "ext:deno_node/querystring.ts",
|
||||
},
|
||||
NodeModulePolyfill {
|
||||
name: "readline",
|
||||
specifier: "ext:deno_node/readline.ts",
|
||||
specifier: "node:readline",
|
||||
ext_specifier: "ext:deno_node/readline.ts",
|
||||
},
|
||||
NodeModulePolyfill {
|
||||
name: "stream",
|
||||
specifier: "ext:deno_node/stream.ts",
|
||||
specifier: "node:stream",
|
||||
ext_specifier: "ext:deno_node/stream.ts",
|
||||
},
|
||||
NodeModulePolyfill {
|
||||
name: "stream/consumers",
|
||||
specifier: "ext:deno_node/stream/consumers.mjs",
|
||||
specifier: "node:stream/consumers",
|
||||
ext_specifier: "ext:deno_node/stream/consumers.mjs",
|
||||
},
|
||||
NodeModulePolyfill {
|
||||
name: "stream/promises",
|
||||
specifier: "ext:deno_node/stream/promises.mjs",
|
||||
specifier: "node:stream/promises",
|
||||
ext_specifier: "ext:deno_node/stream/promises.mjs",
|
||||
},
|
||||
NodeModulePolyfill {
|
||||
name: "stream/web",
|
||||
specifier: "ext:deno_node/stream/web.ts",
|
||||
specifier: "node:stream/web",
|
||||
ext_specifier: "ext:deno_node/stream/web.ts",
|
||||
},
|
||||
NodeModulePolyfill {
|
||||
name: "string_decoder",
|
||||
specifier: "ext:deno_node/string_decoder.ts",
|
||||
specifier: "node:string_decoder",
|
||||
ext_specifier: "ext:deno_node/string_decoder.ts",
|
||||
},
|
||||
NodeModulePolyfill {
|
||||
name: "sys",
|
||||
specifier: "ext:deno_node/sys.ts",
|
||||
specifier: "node:sys",
|
||||
ext_specifier: "ext:deno_node/sys.ts",
|
||||
},
|
||||
NodeModulePolyfill {
|
||||
name: "timers",
|
||||
specifier: "ext:deno_node/timers.ts",
|
||||
specifier: "node:timers",
|
||||
ext_specifier: "ext:deno_node/timers.ts",
|
||||
},
|
||||
NodeModulePolyfill {
|
||||
name: "timers/promises",
|
||||
specifier: "ext:deno_node/timers/promises.ts",
|
||||
specifier: "node:timers/promises",
|
||||
ext_specifier: "ext:deno_node/timers/promises.ts",
|
||||
},
|
||||
NodeModulePolyfill {
|
||||
name: "tls",
|
||||
specifier: "ext:deno_node/tls.ts",
|
||||
specifier: "node:tls",
|
||||
ext_specifier: "ext:deno_node/tls.ts",
|
||||
},
|
||||
NodeModulePolyfill {
|
||||
name: "tty",
|
||||
specifier: "ext:deno_node/tty.ts",
|
||||
specifier: "node:tty",
|
||||
ext_specifier: "ext:deno_node/tty.ts",
|
||||
},
|
||||
NodeModulePolyfill {
|
||||
name: "url",
|
||||
specifier: "ext:deno_node/url.ts",
|
||||
specifier: "node:url",
|
||||
ext_specifier: "ext:deno_node/url.ts",
|
||||
},
|
||||
NodeModulePolyfill {
|
||||
name: "util",
|
||||
specifier: "ext:deno_node/util.ts",
|
||||
specifier: "node:util",
|
||||
ext_specifier: "ext:deno_node/util.ts",
|
||||
},
|
||||
NodeModulePolyfill {
|
||||
name: "util/types",
|
||||
specifier: "ext:deno_node/util/types.ts",
|
||||
specifier: "node:util/types",
|
||||
ext_specifier: "ext:deno_node/util/types.ts",
|
||||
},
|
||||
NodeModulePolyfill {
|
||||
name: "v8",
|
||||
specifier: "ext:deno_node/v8.ts",
|
||||
specifier: "node:v8",
|
||||
ext_specifier: "ext:deno_node/v8.ts",
|
||||
},
|
||||
NodeModulePolyfill {
|
||||
name: "vm",
|
||||
specifier: "ext:deno_node/vm.ts",
|
||||
specifier: "node:vm",
|
||||
ext_specifier: "ext:deno_node/vm.ts",
|
||||
},
|
||||
NodeModulePolyfill {
|
||||
name: "worker_threads",
|
||||
specifier: "ext:deno_node/worker_threads.ts",
|
||||
specifier: "node:worker_threads",
|
||||
ext_specifier: "ext:deno_node/worker_threads.ts",
|
||||
},
|
||||
NodeModulePolyfill {
|
||||
name: "zlib",
|
||||
specifier: "ext:deno_node/zlib.ts",
|
||||
specifier: "node:zlib",
|
||||
ext_specifier: "ext:deno_node/zlib.ts",
|
||||
},
|
||||
];
|
||||
|
@ -4,6 +4,7 @@ use crate::inspector_server::InspectorServer;
|
||||
use crate::ops;
|
||||
use crate::permissions::PermissionsContainer;
|
||||
use crate::tokio_util::create_and_run_current_thread;
|
||||
use crate::worker::init_runtime_module_map;
|
||||
use crate::worker::FormatJsErrorFn;
|
||||
use crate::BootstrapOptions;
|
||||
use deno_broadcast_channel::InMemoryBroadcastChannel;
|
||||
@ -495,6 +496,7 @@ impl WebWorker {
|
||||
inspector: options.maybe_inspector_server.is_some(),
|
||||
..Default::default()
|
||||
});
|
||||
init_runtime_module_map(&mut js_runtime);
|
||||
|
||||
if let Some(server) = options.maybe_inspector_server.clone() {
|
||||
server.register_inspector(
|
||||
|
@ -57,6 +57,17 @@ impl ExitCode {
|
||||
self.0.store(code, Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
/// Clear extension modules from the module map, except preserve `ext:deno_node`
|
||||
/// modules as `node:` specifiers.
|
||||
pub fn init_runtime_module_map(js_runtime: &mut JsRuntime) {
|
||||
js_runtime.clear_module_map(
|
||||
deno_node::SUPPORTED_BUILTIN_NODE_MODULES
|
||||
.iter()
|
||||
.map(|p| (p.ext_specifier, p.specifier)),
|
||||
);
|
||||
}
|
||||
|
||||
/// This worker is created and used by almost all
|
||||
/// subcommands in Deno executable.
|
||||
///
|
||||
@ -319,6 +330,7 @@ impl MainWorker {
|
||||
is_main: true,
|
||||
..Default::default()
|
||||
});
|
||||
init_runtime_module_map(&mut js_runtime);
|
||||
|
||||
if let Some(server) = options.maybe_inspector_server.clone() {
|
||||
server.register_inspector(
|
||||
|
Loading…
Reference in New Issue
Block a user