use std::collections::HashMap;
use std::future::Future;
use std::rc::Rc;
use std::task::Context;
use std::task::Poll;
use deno_core::error::CoreError;
use futures::StreamExt;
use futures::future::FutureExt;
use v8::Function;
use super::ModuleMap;
use crate::error::CoreErrorKind;
use crate::modules::ModuleError;
use crate::modules::ModuleId;
use crate::modules::ModuleImportPhase;
use crate::modules::ModuleLoadId;
use crate::modules::RequestedModuleType;
use crate::modules::ResolutionKind;
use crate::modules::module_map_data::ModuleSourceKey;
use crate::modules::recursive_load::RecursiveModuleLoad;
use crate::modules::recursive_load::RegisterOutcome;
use crate::runtime::JsRealm;
pub(super) type PrepareLoadFuture =
dyn Future<Output = (ModuleLoadId, Result<RecursiveModuleLoad, CoreError>)>;
pub(super) struct DynImportModEvaluate {
load_id: ModuleLoadId,
module_id: ModuleId,
promise: v8::Global<v8::Promise>,
}
#[derive(Debug, Clone)]
pub(super) struct DynImportState {
resolver: v8::Global<v8::PromiseResolver>,
cped: v8::Global<v8::Value>,
phase: ModuleImportPhase,
}
impl ModuleMap {
#[allow(clippy::too_many_arguments, reason = "internal code")]
pub(crate) fn load_dynamic_import(
self: Rc<Self>,
scope: &mut v8::PinScope,
specifier: String,
referrer: String,
requested_module_type: RequestedModuleType,
phase: ModuleImportPhase,
resolver_handle: v8::Global<v8::PromiseResolver>,
cped_handle: v8::Global<v8::Value>,
) -> bool {
let resolve_response = self.resolve_with_scope(
scope,
&specifier,
&referrer,
ResolutionKind::DynamicImport,
&HashMap::new(),
);
if phase == ModuleImportPhase::Evaluation
&& let ref resolve_result = resolve_response
&& let Ok(module_specifier) = resolve_result
&& let Some(id) = self
.data
.borrow()
.get_id(module_specifier.as_str(), &requested_module_type)
{
let module = self
.data
.borrow()
.get_handle(id)
.map(|handle| v8::Local::new(scope, handle))
.expect("Dyn import module info not found");
if module.get_status() == v8::ModuleStatus::Evaluated {
let has_pending_tla = self
.pending_dyn_mod_evaluations
.borrow()
.iter()
.any(|pending| pending.module_id == id);
if has_pending_tla {
self
.pending_tla_waiters
.borrow_mut()
.entry(id)
.or_default()
.push(resolver_handle);
return false;
}
let resolver = resolver_handle.open(scope);
let module_namespace = module.get_module_namespace();
resolver.resolve(scope, module_namespace).unwrap();
return false;
}
}
if phase == ModuleImportPhase::Evaluation
&& let ref resolve_result = resolve_response
&& let Ok(module_specifier) = resolve_result
&& self.has_lazy_esm_source(module_specifier.as_str())
{
match self.lazy_load_esm_module(scope, module_specifier.as_str()) {
Ok(module_ns) => {
let resolver = resolver_handle.open(scope);
let module_ns_local = v8::Local::new(scope, module_ns);
resolver.resolve(scope, module_ns_local).unwrap();
return false;
}
Err(e) => {
let exception = e.to_v8_error(scope);
let exception_local = v8::Local::new(scope, exception);
let resolver = resolver_handle.open(scope);
resolver.reject(scope, exception_local).unwrap();
return false;
}
}
}
if phase == ModuleImportPhase::Evaluation
&& let ref resolve_result = resolve_response
&& let Ok(module_specifier) = resolve_result
&& self.has_synthetic_esm_module(module_specifier.as_str())
&& !self
.loader
.borrow()
.should_load_synthetic_esm(module_specifier.as_str())
{
match self
.lazy_load_synthetic_esm_module(scope, module_specifier.as_str())
{
Ok(module_ns) => {
let resolver = resolver_handle.open(scope);
let module_ns_local = v8::Local::new(scope, module_ns);
resolver.resolve(scope, module_ns_local).unwrap();
return false;
}
Err(e) => {
let exception = e.to_v8_error(scope);
let exception_local = v8::Local::new(scope, exception);
let resolver = resolver_handle.open(scope);
resolver.reject(scope, exception_local).unwrap();
return false;
}
}
}
let load = RecursiveModuleLoad::new_dynamic_import(
specifier,
referrer,
requested_module_type,
phase,
self.clone(),
resolve_response,
);
self.dynamic_import_map.borrow_mut().insert(
load.id(),
DynImportState {
resolver: resolver_handle,
cped: cped_handle,
phase,
},
);
let load_id = load.id();
let fut = async move {
let mut load = load;
(load_id, load.prepare().await.map(|()| load))
}
.boxed_local();
self.preparing_dynamic_imports.push(fut);
true
}
pub(crate) fn has_pending_dynamic_imports(&self) -> bool {
self.preparing_dynamic_imports.is_pending()
|| self.pending_dynamic_imports.is_pending()
}
pub(crate) fn has_pending_dyn_module_evaluation(&self) -> bool {
self.pending_dyn_mod_evaluations.is_pending()
}
fn dynamic_import_module_evaluate(
&self,
scope: &mut v8::PinScope,
id: ModuleId,
load_id: ModuleLoadId,
state: DynImportState,
) -> Result<(), CoreError> {
let module_handle = self.get_handle(id).expect("ModuleInfo not found");
let status = {
let module = module_handle.open(scope);
module.get_status()
};
match status {
v8::ModuleStatus::Instantiated | v8::ModuleStatus::Evaluated => {}
_ => return Ok(()),
}
v8::tc_scope!(let tc_scope, scope);
let cped = v8::Local::new(tc_scope, state.cped);
tc_scope.set_continuation_preserved_embedder_data(cped);
let module = v8::Local::new(tc_scope, &module_handle);
self.evaluating_top_level.set(true);
let maybe_value = module.evaluate(tc_scope);
self.evaluating_top_level.set(false);
let status = module.get_status();
if let Some(value) = maybe_value {
debug_assert!(
status == v8::ModuleStatus::Evaluated
|| status == v8::ModuleStatus::Errored
);
fn wake_module(
scope: &mut v8::PinScope<'_, '_>,
_args: v8::FunctionCallbackArguments<'_>,
_rv: v8::ReturnValue,
) {
let module_map = JsRealm::module_map_from(scope);
module_map.module_waker.wake();
}
let promise = v8::Local::<v8::Promise>::try_from(value)
.expect("Expected to get promise as module evaluation result");
let wake_module_cb = Function::builder(wake_module).build(tc_scope);
if let Some(wake_module_cb) = wake_module_cb {
promise.then2(tc_scope, wake_module_cb, wake_module_cb);
} else {
}
let dyn_import_mod_evaluate = DynImportModEvaluate {
load_id,
module_id: id,
promise: v8::Global::new(tc_scope, promise),
};
self
.pending_dyn_mod_evaluations
.push(dyn_import_mod_evaluate);
} else if tc_scope.has_terminated() || tc_scope.is_execution_terminating() {
return Err(CoreErrorKind::EvaluateDynamicImportedModule.into_box());
} else {
assert_eq!(status, v8::ModuleStatus::Errored);
}
Ok(())
}
fn evaluate_dyn_imports(&self, scope: &mut v8::PinScope) -> bool {
if !self.pending_dyn_mod_evaluations.is_pending() {
return false;
}
let pending = self.pending_dyn_mod_evaluations.take();
let mut resolved_any = false;
let mut still_pending = vec![];
for eval in pending {
let promise = eval.promise.open(scope);
match promise.state() {
v8::PromiseState::Pending => {
still_pending.push(eval);
}
v8::PromiseState::Fulfilled => {
resolved_any = true;
self.dynamic_import_resolve(scope, eval.load_id, eval.module_id);
self.resolve_tla_waiters(scope, eval.module_id);
}
v8::PromiseState::Rejected => {
resolved_any = true;
let exception = v8::Global::new(scope, promise.result(scope));
self.dynamic_import_reject(scope, eval.load_id, exception.clone());
self.reject_tla_waiters(scope, eval.module_id, exception);
}
}
}
self.pending_dyn_mod_evaluations.set(still_pending);
resolved_any
}
fn resolve_tla_waiters(&self, scope: &mut v8::PinScope, module_id: ModuleId) {
let waiters = self.pending_tla_waiters.borrow_mut().remove(&module_id);
if let Some(waiters) = waiters
&& let Some(module) = self
.data
.borrow()
.get_handle(module_id)
.map(|handle| v8::Local::new(scope, handle))
{
let module_namespace = module.get_module_namespace();
for resolver_handle in waiters {
let resolver = resolver_handle.open(scope);
resolver.resolve(scope, module_namespace).unwrap();
}
if !JsRealm::state_from_scope(scope).has_tick_scheduled() {
scope.perform_microtask_checkpoint();
}
}
}
fn reject_tla_waiters(
&self,
scope: &mut v8::PinScope,
module_id: ModuleId,
exception: v8::Global<v8::Value>,
) {
let waiters = self.pending_tla_waiters.borrow_mut().remove(&module_id);
if let Some(waiters) = waiters {
let exception = v8::Local::new(scope, exception);
for resolver_handle in waiters {
let resolver = resolver_handle.open(scope);
resolver.reject(scope, exception).unwrap();
}
if !JsRealm::state_from_scope(scope).has_tick_scheduled() {
scope.perform_microtask_checkpoint();
}
}
}
pub(crate) fn dynamic_import_reject(
&self,
scope: &mut v8::PinScope,
id: ModuleLoadId,
exception: v8::Global<v8::Value>,
) {
let resolver_handle = self
.dynamic_import_map
.borrow_mut()
.remove(&id)
.expect("Invalid dynamic import id")
.resolver;
let resolver = resolver_handle.open(scope);
let exception = v8::Local::new(scope, exception);
resolver.reject(scope, exception).unwrap();
if !JsRealm::state_from_scope(scope).has_tick_scheduled() {
scope.perform_microtask_checkpoint();
}
}
pub(crate) fn dynamic_import_resolve(
&self,
scope: &mut v8::PinScope,
id: ModuleLoadId,
mod_id: ModuleId,
) {
let resolver_handle = self
.dynamic_import_map
.borrow_mut()
.remove(&id)
.expect("Invalid dynamic import id")
.resolver;
let resolver = resolver_handle.open(scope);
let module = self
.data
.borrow()
.get_handle(mod_id)
.map(|handle| v8::Local::new(scope, handle))
.expect("Dyn import module info not found");
assert_eq!(module.get_status(), v8::ModuleStatus::Evaluated);
let module_namespace = module.get_module_namespace();
resolver.resolve(scope, module_namespace).unwrap();
self.dyn_module_evaluate_idle_counter.set(0);
if !JsRealm::state_from_scope(scope).has_tick_scheduled() {
scope.perform_microtask_checkpoint();
}
}
pub(crate) fn poll_progress(
&self,
cx: &mut Context,
scope: &mut v8::PinScope,
) -> Result<(), CoreError> {
let mut has_evaluated = true;
self.module_waker.register(cx.waker());
while has_evaluated {
has_evaluated = false;
loop {
self.drain_prepare_dyn_imports(cx, scope);
self.drain_dyn_imports(cx, scope)?;
self.drain_code_cache_ready(cx);
if self.evaluate_dyn_imports(scope) {
has_evaluated = true;
} else {
break;
}
}
}
Ok(())
}
fn drain_prepare_dyn_imports(
&self,
cx: &mut Context,
scope: &mut v8::PinScope,
) {
if !self.preparing_dynamic_imports.is_pending() {
return;
}
while let Poll::Ready(Some((dyn_import_id, prepare_result))) =
self.preparing_dynamic_imports.poll_next_unpin(cx)
{
match prepare_result {
Ok(load) => {
self
.pending_dynamic_imports
.push(StreamExt::into_future(load));
}
Err(err) => {
let exception = err.to_v8_error(scope);
self.dynamic_import_reject(scope, dyn_import_id, exception);
}
}
}
}
fn drain_dyn_imports(
&self,
cx: &mut Context,
scope: &mut v8::PinScope,
) -> Result<(), CoreError> {
if !self.pending_dynamic_imports.is_pending() {
return Ok(());
}
while let Poll::Ready(Some((maybe_result, mut load))) =
self.pending_dynamic_imports.poll_next_unpin(cx)
{
let dyn_import_id = load.id();
match maybe_result {
Some(Ok((request, info))) => {
match load.register_and_recurse(scope, &request, info) {
Ok(RegisterOutcome::Done) => {
self
.pending_dynamic_imports
.push(StreamExt::into_future(load));
}
Err(err) => {
let exception = match err {
ModuleError::Exception(e) => e,
ModuleError::Core(e) => e.to_v8_error(scope),
ModuleError::Concrete(e) => {
CoreErrorKind::Module(e).to_v8_error(scope)
}
};
self.dynamic_import_reject(scope, dyn_import_id, exception);
}
}
}
Some(Err(err)) => {
let exception = err.to_v8_error(scope);
self.dynamic_import_reject(scope, dyn_import_id, exception);
}
None => {
let state = self
.dynamic_import_map
.borrow()
.get(&dyn_import_id)
.unwrap()
.clone();
match state.phase {
ModuleImportPhase::Evaluation => {
let module_id =
load.root_module_id().expect("Root module should be loaded");
let result = self.instantiate_module(scope, module_id);
if let Err(exception) = result {
self.dynamic_import_reject(scope, dyn_import_id, exception);
}
self.dynamic_import_module_evaluate(
scope,
module_id,
dyn_import_id,
state,
)?;
}
ModuleImportPhase::Defer => {
let module_id =
load.root_module_id().expect("Root module should be loaded");
let result = self.instantiate_module(scope, module_id);
if let Err(exception) = result {
self.dynamic_import_reject(scope, dyn_import_id, exception);
continue;
}
let module_handle =
self.get_handle(module_id).expect("ModuleInfo not found");
v8::tc_scope!(let tc_scope, scope);
let cped = v8::Local::new(tc_scope, state.cped.clone());
tc_scope.set_continuation_preserved_embedder_data(cped);
let module = v8::Local::new(tc_scope, &module_handle);
let maybe_promise = module.evaluate_for_import_defer(tc_scope);
let Some(promise_val) = maybe_promise else {
let exception = tc_scope.exception().unwrap();
let exception = v8::Global::new(tc_scope, exception);
self.dynamic_import_reject(tc_scope, dyn_import_id, exception);
continue;
};
let module_namespace = module
.get_module_namespace_with_phase(v8::ModuleImportPhase::kDefer);
let promise = v8::Local::<v8::Promise>::try_from(promise_val)
.expect("evaluate_for_import_defer should return a promise");
match promise.state() {
v8::PromiseState::Fulfilled => {
let resolver_handle = self
.dynamic_import_map
.borrow_mut()
.remove(&dyn_import_id)
.expect("Invalid dynamic import id")
.resolver;
let resolver = resolver_handle.open(tc_scope);
resolver.resolve(tc_scope, module_namespace).unwrap();
tc_scope.perform_microtask_checkpoint();
}
v8::PromiseState::Rejected => {
let err = promise.result(tc_scope);
let err = v8::Global::new(tc_scope, err);
self.dynamic_import_reject(tc_scope, dyn_import_id, err);
}
v8::PromiseState::Pending => {
fn wake_module(
scope: &mut v8::PinScope<'_, '_>,
_args: v8::FunctionCallbackArguments<'_>,
_rv: v8::ReturnValue,
) {
let module_map = JsRealm::module_map_from(scope);
module_map.module_waker.wake();
}
let wake_module_cb =
v8::Function::builder(wake_module).build(tc_scope);
if let Some(wake_module_cb) = wake_module_cb {
promise.then2(tc_scope, wake_module_cb, wake_module_cb);
}
let dyn_import_mod_evaluate = DynImportModEvaluate {
load_id: dyn_import_id,
module_id,
promise: v8::Global::new(tc_scope, promise),
};
self
.pending_dyn_mod_evaluations
.push(dyn_import_mod_evaluate);
}
}
}
ModuleImportPhase::Source => {
let module_reference = load.root_module_reference().expect(
"Root module reference had to have been resolved to get here.",
);
let key = ModuleSourceKey::from_reference(module_reference);
let source = {
let data = self.data.borrow();
let source = data.sources.get(&key).expect("Source had to have been inserted successfully, or recursion would error.");
v8::Local::new(scope, source).into()
};
let resolver = state.resolver.open(scope);
resolver.resolve(scope, source).unwrap();
}
}
}
}
}
Ok(())
}
}