use std::borrow::Cow;
use std::cell::RefCell;
use std::collections::HashMap;
use std::collections::HashSet;
use std::rc::Rc;
use super::RequestedModuleType;
use crate::ModuleCodeString;
use crate::ModuleSource;
use crate::fast_string::FastString;
use crate::modules::ModuleId;
use crate::modules::ModuleInfo;
use crate::modules::ModuleLoadId;
use crate::modules::ModuleName;
use crate::modules::ModuleReference;
use crate::modules::ModuleRequest;
use crate::modules::ModuleType;
#[derive(Debug, PartialEq)]
pub(crate) enum SymbolicModule {
Alias(ModuleName),
Mod(ModuleId),
}
struct ModuleNameTypeMap<T> {
submaps: Vec<HashMap<ModuleName, T>>,
map_index: HashMap<RequestedModuleType, usize>,
len: usize,
}
impl<T> Default for ModuleNameTypeMap<T> {
fn default() -> Self {
Self {
submaps: Default::default(),
map_index: Default::default(),
len: 0,
}
}
}
impl<T> ModuleNameTypeMap<T> {
pub fn len(&self) -> usize {
self.len
}
fn map_index(&self, ty: &RequestedModuleType) -> Option<usize> {
self.map_index.get(ty).copied()
}
pub fn get<Q>(&self, ty: &RequestedModuleType, name: &Q) -> Option<&T>
where
ModuleName: std::borrow::Borrow<Q>,
Q: std::cmp::Eq + std::hash::Hash + std::fmt::Debug + ?Sized,
{
let index = self.map_index(ty)?;
let map = self.submaps.get(index)?;
map.get(name)
}
pub fn insert(
&mut self,
module_type: &RequestedModuleType,
name: FastString,
module: T,
) {
let index = match self.map_index(module_type) {
Some(index) => index,
None => {
let index = self.submaps.len();
self.map_index.insert(module_type.clone(), index);
self.submaps.push(Default::default());
index
}
};
if self
.submaps
.get_mut(index)
.unwrap()
.insert(name, module)
.is_none()
{
self.len += 1;
}
}
pub fn drain(
mut self,
mut f: impl FnMut(usize, &RequestedModuleType, ModuleName, T),
) {
let mut i = 0;
for (ty, value) in self.map_index.into_iter() {
for (key, value) in self.submaps.get_mut(value).unwrap().drain() {
f(i, &ty, key, value);
i += 1;
}
}
}
}
pub(crate) type SyntheticModuleExports =
Vec<(v8::Global<v8::String>, v8::Global<v8::Value>)>;
pub(crate) type SyntheticModuleExportsStore =
HashMap<v8::Global<v8::Module>, SyntheticModuleExports>;
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub(crate) enum ModuleSourceKind {
Wasm,
}
impl ModuleSourceKind {
pub fn from_module_type(module_type: &ModuleType) -> Option<Self> {
match module_type {
ModuleType::Wasm => Some(Self::Wasm),
_ => None,
}
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
enum ModuleSourceType {
Loaded(ModuleType),
Requested(RequestedModuleType),
}
#[derive(Debug, Eq, PartialEq, Hash)]
pub(crate) struct ModuleSourceKey {
pub name: ModuleName,
typ: ModuleSourceType,
}
impl ModuleSourceKey {
pub fn from_reference(module_reference: &ModuleReference) -> Self {
Self {
name: module_reference.specifier.to_string().into(),
typ: ModuleSourceType::Requested(
module_reference.requested_module_type.clone(),
),
}
}
pub fn from_loaded_source(loaded_source: &mut ModuleSource) -> Self {
let name = if let Some(module_url_found) =
loaded_source.cheap_copy_module_url_found()
{
module_url_found
} else {
loaded_source.cheap_copy_module_url_specified()
};
Self {
name,
typ: ModuleSourceType::Loaded(loaded_source.module_type.clone()),
}
}
}
#[derive(Default)]
pub(crate) struct ModuleMapData {
pub(crate) handles_inverted: HashMap<v8::Global<v8::Module>, usize>,
pub(crate) handles: Vec<v8::Global<v8::Module>>,
pub(crate) main_module_callbacks: Vec<v8::Global<v8::Function>>,
pub(crate) info: Vec<ModuleInfo>,
by_name: ModuleNameTypeMap<SymbolicModule>,
pub(crate) next_load_id: ModuleLoadId,
pub(crate) main_module_id: Option<ModuleId>,
pub(crate) synthetic_module_exports_store: SyntheticModuleExportsStore,
pub(crate) lazy_esm_sources:
Rc<RefCell<HashMap<ModuleName, ModuleCodeString>>>,
pub(crate) known_lazy_esm: RefCell<HashSet<Cow<'static, str>>>,
pub(crate) lazy_script_sources:
Rc<RefCell<HashMap<ModuleName, ModuleCodeString>>>,
pub(crate) loaded_script_results:
Rc<RefCell<HashMap<ModuleName, v8::Global<v8::Value>>>>,
pub(crate) synthetic_esm_modules:
Rc<RefCell<HashMap<ModuleName, ModuleName>>>,
pub(crate) lazy_script_loading: Rc<RefCell<HashSet<ModuleName>>>,
pub(crate) captured_bootstrap: RefCell<Option<v8::Global<v8::Value>>>,
pub(crate) sources: HashMap<ModuleSourceKey, v8::Global<v8::Object>>,
#[cfg(test)]
pub(crate) dropped_requests: HashMap<ModuleId, Vec<ModuleRequest>>,
pub(crate) consumed_lazy_specifiers: RefCell<HashSet<String>>,
}
impl ModuleMapData {
pub fn create_module_info(
&mut self,
name: FastString,
module_type: ModuleType,
handle: v8::Global<v8::Module>,
main: bool,
requests: Vec<ModuleRequest>,
) -> ModuleId {
let data = self;
let id = data.handles.len();
let is_internal =
crate::modules::map::is_internal_module_specifier(name.as_str());
{
let mut sources = data.lazy_esm_sources.borrow_mut();
if !sources.is_empty() {
sources.remove(name.as_str());
}
}
let (name1, name2) = name.into_cheap_copy();
data.handles_inverted.insert(handle.clone(), id);
data.handles.push(handle);
if main {
data.main_module_id = Some(id);
}
let requested_module_type = RequestedModuleType::from(module_type.clone());
data
.by_name
.insert(&requested_module_type, name1, SymbolicModule::Mod(id));
data.info.push(ModuleInfo {
id,
main,
name: name2,
requests,
module_type,
is_internal,
});
id
}
pub fn get_id(
&self,
name: &str,
requested_module_type: impl AsRef<RequestedModuleType>,
) -> Option<ModuleId> {
let map = &self.by_name;
let first_symbolic_module =
map.get(requested_module_type.as_ref(), name)?;
let mut mod_name = match first_symbolic_module {
SymbolicModule::Mod(mod_id) => return Some(*mod_id),
SymbolicModule::Alias(target) => target,
};
loop {
let symbolic_module =
map.get(requested_module_type.as_ref(), mod_name)?;
match symbolic_module {
SymbolicModule::Alias(target) => {
debug_assert!(mod_name != target);
mod_name = target;
}
SymbolicModule::Mod(mod_id) => return Some(*mod_id),
}
}
}
pub(crate) fn alias(
&mut self,
name: FastString,
requested_module_type: &RequestedModuleType,
target: FastString,
) {
debug_assert_ne!(name, target);
self.by_name.insert(
requested_module_type,
name,
SymbolicModule::Alias(target),
);
}
pub(crate) fn register_under_type(
&mut self,
name: FastString,
requested_module_type: &RequestedModuleType,
module_id: ModuleId,
) {
self.by_name.insert(
requested_module_type,
name,
SymbolicModule::Mod(module_id),
);
}
#[cfg(test)]
pub(crate) fn is_alias(
&self,
name: &str,
requested_module_type: impl AsRef<RequestedModuleType>,
) -> bool {
let map = &self.by_name;
let entry = map.get(requested_module_type.as_ref(), name);
matches!(entry, Some(SymbolicModule::Alias(_)))
}
pub(crate) fn get_handle(
&self,
id: ModuleId,
) -> Option<v8::Global<v8::Module>> {
self.handles.get(id).cloned()
}
pub(crate) fn get_name_by_module(
&self,
global: &v8::Global<v8::Module>,
) -> Option<String> {
match self.handles_inverted.get(global) {
Some(id) => self.get_name_by_id(*id),
_ => None,
}
}
pub(crate) fn get_type_by_module(
&self,
global: &v8::Global<v8::Module>,
) -> Option<ModuleType> {
match self.handles_inverted.get(global) {
Some(id) => {
let info = self.info.get(*id).unwrap();
Some(info.module_type.clone())
}
_ => None,
}
}
pub(crate) fn get_info_by_module(
&self,
global: &v8::Global<v8::Module>,
) -> Option<&ModuleInfo> {
match self.handles_inverted.get(global) {
Some(id) => Some(self.info.get(*id).unwrap()),
_ => None,
}
}
pub(crate) fn is_main_module(&self, global: &v8::Global<v8::Module>) -> bool {
self
.main_module_id
.map(|id| self.handles_inverted.get(global) == Some(&id))
.unwrap_or_default()
}
pub(crate) fn get_name_by_id(&self, id: ModuleId) -> Option<String> {
self.info.get(id).map(|info| info.name.as_str().to_owned())
}
#[cfg(test)]
pub fn assert_module_map(
&self,
modules: &Vec<ModuleInfo>,
restored_from_snapshot: usize,
) {
use crate::runtime::NO_OF_BUILTIN_MODULES;
let data = self;
assert_eq!(data.handles.len(), modules.len() + NO_OF_BUILTIN_MODULES);
assert_eq!(data.info.len(), modules.len() + NO_OF_BUILTIN_MODULES);
assert_eq!(data.next_load_id as usize, modules.len());
assert_eq!(data.by_name.len(), modules.len() + NO_OF_BUILTIN_MODULES);
for info in modules {
assert!(data.handles.get(info.id).is_some());
let actual = data.info.get(info.id).unwrap();
assert_eq!(actual.id, info.id);
assert_eq!(actual.main, info.main);
assert_eq!(actual.name, info.name);
assert_eq!(actual.module_type, info.module_type);
assert_eq!(actual.is_internal, info.is_internal);
if info.id < restored_from_snapshot {
assert!(
actual.requests.is_empty(),
"import edges of snapshot-instantiated module {} should be dropped",
info.name
);
} else {
let actual_requests = if actual.requests.is_empty() {
self
.dropped_requests
.get(&info.id)
.map(|r| r.as_slice())
.unwrap_or_default()
} else {
actual.requests.as_slice()
};
assert_eq!(actual_requests, info.requests.as_slice());
}
let requested_module_type =
RequestedModuleType::from(info.module_type.clone());
assert_eq!(
data.by_name.get(&requested_module_type, &info.name),
Some(&SymbolicModule::Mod(info.id))
);
}
}
}
#[cfg(test)]
mod tests {
use url::Url;
use super::*;
use crate::ascii_str;
#[test]
fn module_name_map_test() {
let mut data: ModuleNameTypeMap<usize> = ModuleNameTypeMap::default();
data.insert(
&RequestedModuleType::Json,
ascii_str!("http://example.com/").into(),
1,
);
assert_eq!(
Some(&1),
data.get(
&RequestedModuleType::Json,
Url::parse("http://example.com/").unwrap().as_str()
)
);
}
}