use crate::js;
use crate::js::JsRuntime;
use crate::js::binding::set_exception_code;
use crate::js::binding::throw_type_error;
use crate::js::module::EsModuleFuture;
use crate::js::module::ModuleGraph;
use crate::js::module::ModuleStatus;
use crate::js::module::resolve_import;
use crate::js::pending;
use crate::prelude::*;
use crate::util::paths;
use std::cell::RefCell;
use std::rc::Rc;
pub fn module_resolve_cb<'a>(
context: v8::Local<'a, v8::Context>,
specifier: v8::Local<'a, v8::String>,
_import_attributes: v8::Local<'a, v8::FixedArray>,
referrer: v8::Local<'a, v8::Module>,
) -> Option<v8::Local<'a, v8::Module>> {
v8::callback_scope!(unsafe scope, context);
let state_rc = JsRuntime::state(scope);
let state = state_rc.borrow();
let import_map = state.options.import_map.clone();
let referrer_id = referrer.get_identity_hash().get();
let referrer = v8::Global::new(scope, referrer);
let dependant = state.module_map.get_path(referrer_id, referrer).unwrap();
let dependant = paths::maybe_parent(&dependant).to_string_lossy();
let specifier = specifier.to_rust_string_lossy(scope);
let specifier = resolve_import(&dependant, &specifier, import_map).unwrap();
trace!(
"|module_resolve_cb| dependant:{:?}, specifier:{:?}",
dependant, specifier
);
let module = state.module_map.get(&specifier).unwrap();
Some(v8::Local::new(scope, module))
}
pub extern "C" fn host_initialize_import_meta_object_cb(
context: v8::Local<v8::Context>,
module: v8::Local<v8::Module>,
meta: v8::Local<v8::Object>,
) {
v8::callback_scope!(unsafe scope, context);
let state_rc = JsRuntime::state(scope);
let state = state_rc.borrow();
let module_id = module.get_identity_hash().get();
let module = v8::Global::new(scope, module);
let filename = state.module_map.get_path(module_id, module).unwrap();
let is_main = state.module_map.main().as_ref() == Some(&filename);
trace!(
"|host_initialize_import_meta_object_cb| url:{:?}, is_main:{:?}",
filename, is_main
);
let key = v8::String::new(scope, "filename").unwrap();
let value = v8::String::new(scope, &filename).unwrap();
meta.create_data_property(scope, key.into(), value.into());
let filepath = Path::new(&filename).normalize().unwrap();
let dirname = paths::maybe_parent(&filepath);
let key = v8::String::new(scope, "dirname").unwrap();
let value = v8::String::new(scope, &dirname.to_string_lossy()).unwrap();
meta.create_data_property(scope, key.into(), value.into());
let url = format!("file://{}", filepath.as_path().to_string_lossy());
let key = v8::String::new(scope, "url").unwrap();
let value = v8::String::new(scope, &url).unwrap();
meta.create_data_property(scope, key.into(), value.into());
let key = v8::String::new(scope, "main").unwrap();
let value = v8::Boolean::new(scope, is_main);
meta.create_data_property(scope, key.into(), value.into());
let url = v8::String::new(scope, &filename).unwrap();
let builder = v8::FunctionBuilder::new(import_meta_resolve).data(url.into());
let key = v8::String::new(scope, "resolve").unwrap();
let value =
v8::FunctionBuilder::<v8::Function>::build(builder, scope).unwrap();
meta.set(scope, key.into(), value.into());
}
fn import_meta_resolve(
scope: &mut v8::PinScope,
args: v8::FunctionCallbackArguments,
mut rv: v8::ReturnValue,
) {
if args.length() == 0 {
throw_type_error(scope, &TheErr::ArgumentsNotEnough);
return;
}
let state_rc = JsRuntime::state(scope);
let state = state_rc.borrow();
let base = args.data().to_rust_string_lossy(scope);
let base = paths::maybe_parent(&base).to_string_lossy();
let specifier = args.get(0).to_rust_string_lossy(scope);
trace!(
"|import_meta_resolve| base:{:?}, specifier:{:?}",
base, specifier
);
let import_map = state.options.import_map.clone();
match resolve_import(&base, &specifier, import_map) {
Ok(path) => rv.set(v8::String::new(scope, &path).unwrap().into()),
Err(e) => throw_type_error(scope, &e),
};
}
pub extern "C" fn promise_reject_cb(message: v8::PromiseRejectMessage) {
v8::callback_scope!(unsafe scope, &message);
let undefined = v8::undefined(scope).into();
let event = message.get_event();
trace!("|promise_reject_cb| event:{event:?}, message:{message:?}");
use v8::PromiseRejectEvent::PromiseHandlerAddedAfterReject;
use v8::PromiseRejectEvent::PromiseRejectAfterResolved;
use v8::PromiseRejectEvent::PromiseRejectWithNoHandler;
use v8::PromiseRejectEvent::PromiseResolveAfterResolved;
let reason = match event {
PromiseHandlerAddedAfterReject
| PromiseRejectAfterResolved
| PromiseResolveAfterResolved => undefined,
PromiseRejectWithNoHandler => message.get_value().unwrap(),
};
let promise = message.get_promise();
let promise = v8::Global::new(scope, promise);
let state_rc = JsRuntime::state(scope);
let mut state = state_rc.borrow_mut();
match event {
PromiseRejectWithNoHandler => {
let reason = v8::Global::new(scope, reason);
state.exceptions.capture_promise_rejection(promise, reason);
}
PromiseHandlerAddedAfterReject => {
state.exceptions.remove_promise_rejection(&promise);
}
PromiseRejectAfterResolved | PromiseResolveAfterResolved => {}
}
}
pub fn host_import_module_dynamically_cb<'s>(
scope: &mut v8::PinScope<'s, '_>,
_host_defined_options: v8::Local<'s, v8::Data>,
base: v8::Local<'s, v8::Value>,
specifier: v8::Local<'s, v8::String>,
_import_attributes: v8::Local<v8::FixedArray>,
) -> Option<v8::Local<'s, v8::Promise>> {
let base = base.to_rust_string_lossy(scope);
let base = paths::maybe_parent(&base).to_string_lossy();
let specifier = specifier.to_rust_string_lossy(scope);
trace!(
"|host_import_module_dynamically_cb| base:{:?}, specifier:{:?}",
base, specifier
);
let promise_resolver = v8::PromiseResolver::new(scope).unwrap();
let promise = promise_resolver.get_promise(scope);
let state_rc = JsRuntime::state(scope);
let mut state = state_rc.borrow_mut();
let import_map = state.options.import_map.clone();
let specifier = match resolve_import(&base, &specifier, import_map) {
Ok(specifier) => specifier,
Err(e) => {
drop(state);
trace!("Failed to resolve import {specifier:?}(base:{base:?}): {e:?}");
let exception = v8::String::new(scope, &e.to_string()).unwrap();
let exception = v8::Exception::error(scope, exception);
set_exception_code(scope, exception, &e);
promise_resolver.reject(scope, exception);
return Some(promise);
}
};
let dynamic_import_being_fetched =
state.module_map.pending.iter().any(|graph_rc| {
*graph_rc.borrow().root_rc().borrow().path() == specifier
});
if state.module_map.contains(&specifier) && !dynamic_import_being_fetched {
let module = state.module_map.get(&specifier).unwrap();
let module = module.open(scope);
promise_resolver.resolve(scope, module.get_module_namespace());
return Some(promise);
}
let global_promise = v8::Global::new(scope, promise_resolver);
if dynamic_import_being_fetched {
state
.module_map
.pending
.iter()
.find(|graph_rc| {
*graph_rc.borrow().root_rc().borrow().path() == specifier
})
.unwrap()
.borrow_mut()
.same_origin_mut()
.push(global_promise);
return Some(promise);
}
let graph = ModuleGraph::dynamic_import(&specifier, global_promise);
let graph_rc = Rc::new(RefCell::new(graph));
let status = ModuleStatus::Fetching;
state.module_map.pending.push(Rc::clone(&graph_rc));
trace!(
"|host_import_module_dynamically_cb| ModuleMap pending {:?}",
specifier
);
state.module_map.seen.insert(specifier.clone(), status);
trace!(
"|host_import_module_dynamically_cb| ModuleMap seen {:?} {:?}",
specifier, status
);
let loader_cb = {
let state_rc = state_rc.clone();
let specifier = specifier.clone();
move |maybe_result: Option<TheResult<Vec<u8>>>| {
let fut = EsModuleFuture {
path: specifier.clone(),
module: graph_rc.borrow().root_rc(),
maybe_result,
};
let mut state = state_rc.borrow_mut();
state.pending_futures.push(Box::new(fut));
}
};
let task_id = js::TaskId::next();
pending::create_import_loader(
&mut state,
task_id,
&specifier,
Box::new(loader_cb),
);
Some(promise)
}