use std::borrow::Cow;
use std::cell::{OnceCell, RefCell};
use std::collections::hash_map::Entry;
use std::ffi::CStr;
use std::fmt::Debug;
use std::ptr::NonNull;
use std::rc::Rc;
use std::{mem, ptr};
use encoding_rs::UTF_8;
use headers::{HeaderMapExt, ReferrerPolicy as ReferrerPolicyHeader};
use hyper_serde::Serde;
use indexmap::IndexMap;
use indexmap::map::Entry as IndexMapEntry;
use js::context::JSContext;
use js::conversions::{ToJSValConvertible, jsstr_to_string};
use js::gc::{HandleObject, MutableHandleValue};
use js::jsapi::{
CallArgs, ExceptionStackBehavior, GetFunctionNativeReserved, GetModuleResolveHook,
Handle as RawHandle, HandleValue as RawHandleValue, Heap, JS_GetFunctionObject,
JSContext as RawJSContext, JSObject, JSPROP_ENUMERATE, JSRuntime, ModuleErrorBehaviour,
ModuleType, SetFunctionNativeReserved, SetModuleDynamicImportHook, SetModuleMetadataHook,
SetModulePrivate, SetModuleResolveHook, SetScriptPrivateReferenceHooks, Value,
};
use js::jsval::{JSVal, PrivateValue, UndefinedValue};
use js::realm::{AutoRealm, CurrentRealm};
use js::rust::wrappers2::{
CompileJsonModule1, CompileModule1, DefineFunctionWithReserved, GetModuleRequestSpecifier,
GetModuleRequestType, JS_ClearPendingException, JS_DefineProperty4, JS_GetPendingException,
JS_NewStringCopyN, JS_SetPendingException, ModuleEvaluate, ModuleLink,
ThrowOnModuleEvaluationFailure,
};
use js::rust::{Handle, HandleValue, ToString, transform_str_to_source_text};
use mime::Mime;
use net_traits::blob_url_store::UrlWithBlobClaim;
use net_traits::http_status::HttpStatus;
use net_traits::mime_classifier::MimeClassifier;
use net_traits::policy_container::PolicyContainer;
use net_traits::request::{
CredentialsMode, Destination, ParserMetadata, Referrer, RequestBuilder, RequestClient,
RequestId, RequestMode,
};
use net_traits::{FetchMetadata, Metadata, NetworkError, ReferrerPolicy, ResourceFetchTiming};
use script_bindings::cell::DomRefCell;
use script_bindings::error::Fallible;
use script_bindings::reflector::DomObject;
use script_bindings::settings_stack::run_a_callback;
use script_bindings::trace::CustomTraceable;
use servo_config::pref;
use servo_url::ServoUrl;
use crate::DomTypeHolder;
use crate::dom::bindings::error::{Error, ErrorToJsval, report_pending_exception};
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::refcounted::Trusted;
use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::str::DOMString;
use crate::dom::bindings::trace::RootedTraceableBox;
use crate::dom::csp::{GlobalCspReporting, Violation};
use crate::dom::globalscope::GlobalScope;
use crate::dom::globalscope::script_execution::{ErrorReporting, fill_compile_options};
use crate::dom::html::htmlscriptelement::{SCRIPT_JS_MIMES, substitute_with_local_script};
use crate::dom::performance::performanceresourcetiming::InitiatorType;
use crate::dom::promise::Promise;
use crate::dom::promisenativehandler::{Callback, PromiseNativeHandler};
use crate::dom::types::{
DedicatedWorkerGlobalScope, SharedWorkerGlobalScope, WorkerGlobalScope, WorkletGlobalScope,
};
use crate::dom::window::Window;
use crate::modules::import_map::{ModuleSpecifierMap, resolve_url_like_module_specifier};
use crate::modules::module_loading::{
LoadState, Payload, host_load_imported_module, load_requested_modules,
};
use crate::network_listener::{self, FetchResponseListener, ResourceTimingListener};
use crate::realms::enter_auto_realm;
use crate::script_runtime::IntroductionType;
use crate::tasks::task::NonSendTaskBox;
pub(crate) fn gen_type_error(
cx: &mut JSContext,
global: &GlobalScope,
error: Error,
) -> RethrowError {
rooted!(&in(cx) let mut thrown = UndefinedValue());
error.to_jsval(cx, global, thrown.handle_mut());
RethrowError(RootedTraceableBox::from_box(Heap::boxed(thrown.get())))
}
#[derive(JSTraceable)]
pub(crate) struct ModuleObject(RootedTraceableBox<Heap<*mut JSObject>>);
impl ModuleObject {
pub(crate) fn new(obj: HandleObject) -> ModuleObject {
ModuleObject(RootedTraceableBox::from_box(Heap::boxed(obj.get())))
}
pub(crate) fn handle(&'_ self) -> HandleObject<'_> {
self.0.handle()
}
}
#[derive(JSTraceable)]
pub(crate) struct RethrowError(RootedTraceableBox<Heap<JSVal>>);
impl RethrowError {
pub(crate) fn new(val: Box<Heap<JSVal>>) -> Self {
Self(RootedTraceableBox::from_box(val))
}
#[expect(unsafe_code)]
pub(crate) fn from_pending_exception(cx: &mut JSContext) -> Self {
rooted!(&in(cx) let mut exception = UndefinedValue());
assert!(unsafe { JS_GetPendingException(cx, exception.handle_mut()) });
unsafe { JS_ClearPendingException(cx) };
Self::new(Heap::boxed(exception.get()))
}
pub(crate) fn handle(&self) -> Handle<'_, JSVal> {
self.0.handle()
}
}
impl Debug for RethrowError {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
"RethrowError(...)".fmt(fmt)
}
}
impl Clone for RethrowError {
fn clone(&self) -> Self {
Self(RootedTraceableBox::from_box(Heap::boxed(self.0.get())))
}
}
pub(crate) struct ModuleScript {
pub(crate) base_url: ServoUrl,
pub(crate) options: ScriptFetchOptions,
pub(crate) owner: Option<Trusted<GlobalScope>>,
}
impl ModuleScript {
pub(crate) fn new(
base_url: ServoUrl,
options: ScriptFetchOptions,
owner: Option<Trusted<GlobalScope>>,
) -> Self {
ModuleScript {
base_url,
options,
owner,
}
}
}
pub(crate) type ModuleRequest = (ServoUrl, ModuleType);
#[derive(JSTraceable)]
pub(crate) enum ModuleStatus {
#[expect(clippy::type_complexity)]
Fetching(#[no_trace] Vec<Box<dyn FnOnce(&mut JSContext, Option<Rc<ModuleTree>>)>>),
Loaded(Rc<ModuleTree>),
}
#[derive(JSTraceable, MallocSizeOf)]
pub(crate) struct ModuleTree {
#[no_trace]
url: ServoUrl,
#[ignore_malloc_size_of = "mozjs"]
record: OnceCell<ModuleObject>,
#[ignore_malloc_size_of = "mozjs"]
parse_error: OnceCell<RethrowError>,
#[ignore_malloc_size_of = "mozjs"]
rethrow_error: DomRefCell<Option<RethrowError>>,
#[no_trace]
loaded_modules: DomRefCell<IndexMap<String, ServoUrl>>,
}
impl ModuleTree {
pub(crate) fn get_url(&self) -> ServoUrl {
self.url.clone()
}
pub(crate) fn get_record(&self) -> Option<&ModuleObject> {
self.record.get()
}
pub(crate) fn get_parse_error(&self) -> Option<&RethrowError> {
self.parse_error.get()
}
pub(crate) fn get_rethrow_error(&self) -> &DomRefCell<Option<RethrowError>> {
&self.rethrow_error
}
pub(crate) fn set_rethrow_error(&self, rethrow_error: RethrowError) {
*self.rethrow_error.borrow_mut() = Some(rethrow_error);
}
pub(crate) fn find_descendant_inside_module_map(
&self,
global: &GlobalScope,
specifier: &String,
module_type: ModuleType,
) -> Option<Rc<ModuleTree>> {
self.loaded_modules
.borrow()
.get(specifier)
.and_then(|url| global.module_tree_for_request_if_loaded(&(url.clone(), module_type)))
}
pub(crate) fn insert_module_dependency(
&self,
module: &Rc<ModuleTree>,
module_request_specifier: String,
) {
let url = module.url.clone();
match self
.loaded_modules
.borrow_mut()
.entry(module_request_specifier)
{
IndexMapEntry::Occupied(entry) => {
assert_eq!(*entry.get(), url);
},
IndexMapEntry::Vacant(entry) => {
entry.insert(url);
},
}
}
}
pub(crate) struct ModuleSource<'a> {
pub source: Cow<'a, str>,
pub unminified_dir: Option<String>,
pub external: bool,
pub url: ServoUrl,
}
impl<'a> crate::unminify::ScriptSource for ModuleSource<'a> {
fn unminified_dir(&self) -> Option<String> {
self.unminified_dir.clone()
}
fn extract_bytes(&self) -> &[u8] {
self.source.as_bytes()
}
fn rewrite_source(&mut self, source: String) {
self.source = source.into();
}
fn url(&self) -> ServoUrl {
self.url.clone()
}
fn is_external(&self) -> bool {
self.external
}
}
impl ModuleTree {
#[expect(unsafe_code)]
#[expect(clippy::too_many_arguments)]
fn create_a_javascript_module_script(
cx: &mut JSContext,
source: Cow<'_, str>,
global: &GlobalScope,
url: &ServoUrl,
options: ScriptFetchOptions,
external: bool,
line_number: u32,
introduction_type: Option<&'static CStr>,
) -> Self {
let mut realm = AutoRealm::new(
cx,
NonNull::new(global.reflector().get_jsobject().get()).unwrap(),
);
let cx = &mut *realm;
let owner = Trusted::new(global);
let module = ModuleTree {
url: url.clone(),
record: OnceCell::new(),
parse_error: OnceCell::new(),
rethrow_error: DomRefCell::new(None),
loaded_modules: DomRefCell::new(IndexMap::new()),
};
let compile_options = fill_compile_options(
cx,
url.as_str(),
introduction_type,
ErrorReporting::Unmuted,
true, line_number,
);
let mut source = if global.unminified_js_dir().is_some() {
let mut module_source = ModuleSource {
source,
unminified_dir: global.unminified_js_dir(),
external,
url: url.clone(),
};
crate::unminify::unminify_js(&mut module_source);
transform_str_to_source_text(&module_source.source)
} else {
transform_str_to_source_text(&source)
};
unsafe {
rooted!(&in(cx) let mut module_script: *mut JSObject = std::ptr::null_mut());
module_script.set(CompileModule1(cx, compile_options.ptr, &mut source));
if module_script.is_null() {
warn!("fail to compile module script of {}", url);
let _ = module
.parse_error
.set(RethrowError::from_pending_exception(cx));
return module;
}
let module_script_data = Rc::new(ModuleScript::new(url.clone(), options, Some(owner)));
SetModulePrivate(
module_script.get(),
&PrivateValue(Rc::into_raw(module_script_data) as *const _),
);
let _ = module.record.set(ModuleObject::new(module_script.handle()));
}
module
}
#[expect(unsafe_code)]
fn create_a_json_module_script(
cx: &mut JSContext,
source: &str,
global: &GlobalScope,
url: &ServoUrl,
introduction_type: Option<&'static CStr>,
) -> Self {
let mut realm = AutoRealm::new(
cx,
NonNull::new(global.reflector().get_jsobject().get()).unwrap(),
);
let cx = &mut *realm;
let module = ModuleTree {
url: url.clone(),
record: OnceCell::new(),
parse_error: OnceCell::new(),
rethrow_error: DomRefCell::new(None),
loaded_modules: DomRefCell::new(IndexMap::new()),
};
let compile_options = fill_compile_options(
cx,
url.as_str(),
introduction_type,
ErrorReporting::Unmuted,
true, 1, );
rooted!(&in(cx) let mut module_script: *mut JSObject = std::ptr::null_mut());
unsafe {
module_script.set(CompileJsonModule1(
cx,
compile_options.ptr,
&mut transform_str_to_source_text(source),
));
}
if module_script.is_null() {
warn!("fail to compile module script of {}", url);
let _ = module
.parse_error
.set(RethrowError::from_pending_exception(cx));
return module;
}
let _ = module.record.set(ModuleObject::new(module_script.handle()));
module
}
#[expect(unsafe_code)]
pub(crate) fn execute_module(
&self,
cx: &mut JSContext,
global: &GlobalScope,
module_record: HandleObject,
mut eval_result: MutableHandleValue,
) -> Result<(), RethrowError> {
let mut realm = AutoRealm::new(
cx,
NonNull::new(global.reflector().get_jsobject().get()).unwrap(),
);
let cx = &mut *realm;
unsafe {
let ok = ModuleEvaluate(cx, module_record, eval_result.reborrow());
assert!(ok, "module evaluation failed");
rooted!(&in(cx) let mut evaluation_promise = ptr::null_mut::<JSObject>());
if eval_result.is_object() {
evaluation_promise.set(eval_result.to_object());
}
let throw_result = ThrowOnModuleEvaluationFailure(
cx,
evaluation_promise.handle(),
ModuleErrorBehaviour::ThrowModuleErrorsSync,
);
if !throw_result {
warn!("fail to evaluate module");
Err(RethrowError::from_pending_exception(cx))
} else {
debug!("module evaluated successfully");
Ok(())
}
}
}
#[expect(unsafe_code)]
pub(crate) fn report_error(&self, cx: &mut JSContext, global: &GlobalScope) {
let module_error = self.rethrow_error.borrow();
if let Some(exception) = &*module_error {
let mut realm = enter_auto_realm(cx, global);
let cx = &mut realm.current_realm();
unsafe {
JS_SetPendingException(cx, exception.handle(), ExceptionStackBehavior::Capture);
}
report_pending_exception(cx);
}
}
pub(crate) fn resolve_module_specifier(
global: &GlobalScope,
script: Option<&ModuleScript>,
specifier: DOMString,
) -> Fallible<ServoUrl> {
let script_global = script.and_then(|s| s.owner.as_ref().map(|o| o.root()));
let (global, base_url): (&GlobalScope, &ServoUrl) = match script {
Some(s) => (script_global.as_ref().map_or(global, |g| g), &s.base_url),
None => (global, &global.api_base_url()),
};
let import_map = if global.is::<Window>() {
Some(global.import_map())
} else {
None
};
let specifier = &specifier.str();
let serialized_base_url = base_url.as_str();
let as_url = resolve_url_like_module_specifier(specifier, base_url);
let normalized_specifier = match &as_url {
Some(url) => url.as_str(),
None => specifier,
};
let mut result = None;
if let Some(map) = import_map {
for (prefix, imports) in &map.scopes {
let prefix = prefix.as_str();
if prefix == serialized_base_url ||
(serialized_base_url.starts_with(prefix) && prefix.ends_with('\u{002f}'))
{
let scope_imports_match =
resolve_imports_match(normalized_specifier, as_url.as_ref(), imports)?;
if scope_imports_match.is_some() {
result = scope_imports_match;
break;
}
}
}
if result.is_none() {
result =
resolve_imports_match(normalized_specifier, as_url.as_ref(), &map.imports)?;
}
}
if result.is_none() {
result = as_url.clone();
}
match result {
Some(result) => {
global.add_module_to_resolved_module_set(
serialized_base_url,
normalized_specifier,
as_url.clone(),
);
Ok(result)
},
None => Err(Error::Type(
c"Specifier was a bare specifier, but was not remapped to anything by importMap."
.to_owned(),
)),
}
}
}
#[derive(JSTraceable, MallocSizeOf)]
pub(crate) struct ModuleHandler {
#[ignore_malloc_size_of = "Measuring trait objects is hard"]
task: DomRefCell<Option<Box<dyn NonSendTaskBox>>>,
}
impl ModuleHandler {
pub(crate) fn new_boxed(task: Box<dyn NonSendTaskBox>) -> Box<dyn Callback> {
Box::new(Self {
task: DomRefCell::new(Some(task)),
})
}
}
impl Callback for ModuleHandler {
fn callback(&self, cx: &mut CurrentRealm, _v: HandleValue) {
let task = self.task.borrow_mut().take().unwrap();
task.run_box(cx);
}
}
struct ModuleContext {
owner: Trusted<GlobalScope>,
data: Vec<u8>,
metadata: Option<Metadata>,
module_request: ModuleRequest,
options: ScriptFetchOptions,
status: Result<(), NetworkError>,
introduction_type: Option<&'static CStr>,
policy_container: Option<PolicyContainer>,
}
impl FetchResponseListener for ModuleContext {
fn process_request_body(&mut self, _: RequestId) {}
fn process_response(
&mut self,
_: &mut js::context::JSContext,
_: RequestId,
metadata: Result<FetchMetadata, NetworkError>,
) {
self.metadata = metadata.ok().map(|meta| match meta {
FetchMetadata::Unfiltered(m) => m,
FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
});
let status = self
.metadata
.as_ref()
.map(|m| m.status.clone())
.unwrap_or_else(HttpStatus::new_error);
self.status = {
if status.is_error() {
Err(NetworkError::ResourceLoadError(
"No http status code received".to_owned(),
))
} else if status.is_success() {
Ok(())
} else {
Err(NetworkError::ResourceLoadError(format!(
"HTTP error code {}",
status.code()
)))
}
};
}
fn process_response_chunk(
&mut self,
_: &mut js::context::JSContext,
_: RequestId,
mut chunk: Vec<u8>,
) {
if self.status.is_ok() {
self.data.append(&mut chunk);
}
}
fn process_response_eof(
mut self,
cx: &mut js::context::JSContext,
_: RequestId,
response: Result<(), NetworkError>,
timing: ResourceFetchTiming,
) {
let global = self.owner.root();
let (_url, module_type) = &self.module_request;
if !global.is::<WorkletGlobalScope>() {
network_listener::submit_timing(cx, &self, &response, &timing);
}
let module_map = global.module_map();
if let (Err(error), _) | (_, Err(error)) = (response.as_ref(), self.status.as_ref()) {
error!("Fetching module script failed {:?}", error);
let Some(ModuleStatus::Fetching(callbacks)) =
module_map.safe_borrow_mut(cx).remove(&self.module_request)
else {
return error!("Processing response for a non pending module request");
};
for callback in callbacks {
(callback)(cx, None);
}
return;
}
let metadata = self.metadata.take().unwrap();
if let Some(policy_container) = self.policy_container {
let workerscope = global.downcast::<WorkerGlobalScope>().expect(
"We only need a policy container when initializing a worker's globalscope.",
);
workerscope.process_response_for_workerscope(&metadata, &policy_container);
}
let final_url = metadata.final_url;
let mime_type: Option<Mime> = metadata.content_type.map(Serde::into_inner).map(Into::into);
let mut module_script = None;
let referrer_policy = metadata
.headers
.and_then(|headers| headers.typed_get::<ReferrerPolicyHeader>())
.into();
if referrer_policy != ReferrerPolicy::EmptyString {
self.options.referrer_policy = referrer_policy;
}
if let Some(mime) = mime_type {
let (mut source_text, _) = UTF_8.decode_with_bom_removal(&self.data);
if SCRIPT_JS_MIMES.contains(&mime.essence_str()) &&
matches!(module_type, ModuleType::JavaScript)
{
if let Some(window) = global.downcast::<Window>() &&
let Some(script_souce) = window.local_script_source()
{
substitute_with_local_script(script_souce, &mut source_text, final_url.clone());
}
let module_tree = Rc::new(ModuleTree::create_a_javascript_module_script(
cx,
source_text,
&global,
&final_url,
self.options,
true,
1,
self.introduction_type,
));
module_script = Some(module_tree);
} else if MimeClassifier::is_json(&mime) && matches!(module_type, ModuleType::JSON) {
let module_tree = Rc::new(ModuleTree::create_a_json_module_script(
cx,
&source_text,
&global,
&final_url,
self.introduction_type,
));
module_script = Some(module_tree);
}
}
let callbacks = match module_map
.safe_borrow_mut(cx)
.entry(self.module_request.clone())
{
Entry::Occupied(mut entry) => {
let old_value = match module_script.as_ref() {
None => entry.remove(),
Some(module_script) => {
entry.insert(ModuleStatus::Loaded(module_script.clone()))
},
};
match old_value {
ModuleStatus::Loaded(_) => {
return error!("Processing response for a non pending module request");
},
ModuleStatus::Fetching(callbacks) => callbacks,
}
},
Entry::Vacant(_) => {
return error!("Processing response for a non pending module request");
},
};
for callback in callbacks {
(callback)(cx, module_script.clone());
}
}
fn process_csp_violations(
&mut self,
cx: &mut js::context::JSContext,
_request_id: RequestId,
violations: Vec<Violation>,
) {
let global = self.owner.root();
if let Some(scope) = global.downcast::<DedicatedWorkerGlobalScope>() {
scope.report_csp_violations(violations);
} else if let Some(scope) = global.downcast::<SharedWorkerGlobalScope>() {
scope.report_csp_violations(violations);
} else {
global.report_csp_violations(cx, violations, None, None);
}
}
fn process_content_length(&mut self, _request_id: RequestId, size: usize) {
self.data.reserve(size - self.data.len());
}
}
impl ResourceTimingListener for ModuleContext {
fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
let initiator_type = InitiatorType::LocalName("module".to_string());
let (url, _) = &self.module_request;
(initiator_type, url.clone())
}
fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
self.owner.root()
}
}
#[expect(unsafe_code)]
#[expect(non_snake_case)]
pub(crate) unsafe fn EnsureModuleHooksInitialized(rt: *mut JSRuntime) {
unsafe {
if GetModuleResolveHook(rt).is_some() {
return;
}
SetModuleResolveHook(rt, Some(HostResolveImportedModule));
SetModuleMetadataHook(rt, Some(HostPopulateImportMeta));
SetScriptPrivateReferenceHooks(
rt,
Some(host_add_ref_top_level_script),
Some(host_release_top_level_script),
);
SetModuleDynamicImportHook(rt, Some(host_import_module_dynamically));
}
}
#[expect(unsafe_code)]
unsafe extern "C" fn host_add_ref_top_level_script(value: *const Value) {
let val = unsafe { Rc::from_raw((*value).to_private() as *const ModuleScript) };
mem::forget(val.clone());
mem::forget(val);
}
#[expect(unsafe_code)]
unsafe extern "C" fn host_release_top_level_script(value: *const Value) {
let _val = unsafe { Rc::from_raw((*value).to_private() as *const ModuleScript) };
}
#[expect(unsafe_code)]
pub(crate) unsafe extern "C" fn host_import_module_dynamically(
cx: *mut RawJSContext,
reference_private: RawHandleValue,
specifier: RawHandle<*mut JSObject>,
promise: RawHandle<*mut JSObject>,
) -> bool {
let mut cx = unsafe { JSContext::from_ptr(NonNull::new(cx).unwrap()) };
let cx = &mut cx;
let promise = Promise::new_with_js_promise(cx, unsafe { Handle::from_raw(promise) });
let jsstr = unsafe { GetModuleRequestSpecifier(cx, Handle::from_raw(specifier)) };
let module_type = unsafe { GetModuleRequestType(cx, Handle::from_raw(specifier)) };
let specifier = unsafe { jsstr_to_string(cx, NonNull::new(jsstr).unwrap()) };
let mut realm = CurrentRealm::assert(cx);
let payload = Payload::PromiseRecord(promise);
host_load_imported_module(
&mut realm,
None,
reference_private,
specifier,
module_type,
None,
payload,
);
true
}
#[derive(Clone, Debug, JSTraceable, MallocSizeOf)]
pub(crate) struct ScriptFetchOptions {
pub(crate) integrity_metadata: String,
#[no_trace]
pub(crate) credentials_mode: CredentialsMode,
pub(crate) cryptographic_nonce: String,
#[no_trace]
pub(crate) parser_metadata: ParserMetadata,
#[no_trace]
pub(crate) referrer_policy: ReferrerPolicy,
pub(crate) render_blocking: bool,
}
impl ScriptFetchOptions {
pub(crate) fn default_classic_script() -> ScriptFetchOptions {
Self {
cryptographic_nonce: String::new(),
integrity_metadata: String::new(),
parser_metadata: ParserMetadata::NotParserInserted,
credentials_mode: CredentialsMode::CredentialsSameOrigin,
referrer_policy: ReferrerPolicy::EmptyString,
render_blocking: false,
}
}
pub(crate) fn descendant_fetch_options(
&self,
url: &ServoUrl,
global: &GlobalScope,
) -> ScriptFetchOptions {
let integrity = global.import_map().resolve_a_module_integrity_metadata(url);
Self {
integrity_metadata: integrity,
cryptographic_nonce: self.cryptographic_nonce.clone(),
credentials_mode: self.credentials_mode,
parser_metadata: self.parser_metadata,
referrer_policy: self.referrer_policy,
render_blocking: self.render_blocking,
}
}
}
#[expect(unsafe_code)]
pub(crate) unsafe fn module_script_from_reference_private(
reference_private: &RawHandle<JSVal>,
) -> Option<&ModuleScript> {
if reference_private.get().is_undefined() {
return None;
}
unsafe { (reference_private.get().to_private() as *const ModuleScript).as_ref() }
}
#[expect(unsafe_code)]
#[expect(non_snake_case)]
unsafe extern "C" fn HostResolveImportedModule(
cx: *mut RawJSContext,
reference_private: RawHandleValue,
specifier: RawHandle<*mut JSObject>,
) -> *mut JSObject {
let mut cx = unsafe { JSContext::from_ptr(NonNull::new(cx).unwrap()) };
let mut realm = CurrentRealm::assert(&mut cx);
let global_scope = GlobalScope::from_current_realm(&mut realm);
let cx = &mut realm;
let module_data = unsafe { module_script_from_reference_private(&reference_private) };
let jsstr = unsafe { GetModuleRequestSpecifier(cx, Handle::from_raw(specifier)) };
let module_type = unsafe { GetModuleRequestType(cx, Handle::from_raw(specifier)) };
let specifier = unsafe { jsstr_to_string(cx, NonNull::new(jsstr).unwrap()) };
let url = ModuleTree::resolve_module_specifier(
&global_scope,
module_data,
DOMString::from(specifier),
);
assert!(url.is_ok());
let parsed_url = url.unwrap();
let module_tree = global_scope.module_tree_for_request_if_loaded(&(parsed_url, module_type));
let module = module_tree.expect("Attempted to link a module not found inside module map");
let fetched_module_object = module.get_record();
assert!(fetched_module_object.is_some());
if let Some(record) = fetched_module_object {
return record.handle().get();
}
unreachable!()
}
const SLOT_MODULEPRIVATE: usize = 0;
#[expect(unsafe_code)]
#[expect(non_snake_case)]
unsafe extern "C" fn HostPopulateImportMeta(
cx: *mut RawJSContext,
reference_private: RawHandleValue,
meta_object: RawHandle<*mut JSObject>,
) -> bool {
let mut cx = unsafe { JSContext::from_ptr(NonNull::new(cx).unwrap()) };
let mut realm = CurrentRealm::assert(&mut cx);
let global_scope = GlobalScope::from_current_realm(&mut realm);
let base_url = match unsafe { module_script_from_reference_private(&reference_private) } {
Some(module_data) => module_data.base_url.clone(),
None => global_scope.api_base_url(),
};
unsafe {
let url_string = JS_NewStringCopyN(
&mut cx,
base_url.as_str().as_ptr() as *const _,
base_url.as_str().len(),
);
rooted!(&in(cx) let url_string = url_string);
if !JS_DefineProperty4(
&mut cx,
Handle::from_raw(meta_object),
c"url".as_ptr(),
url_string.handle(),
JSPROP_ENUMERATE.into(),
) {
return false;
}
let resolve_function = DefineFunctionWithReserved(
&mut cx,
meta_object.get(),
c"resolve".as_ptr(),
Some(import_meta_resolve),
1,
JSPROP_ENUMERATE.into(),
);
rooted!(&in(cx) let obj = JS_GetFunctionObject(resolve_function));
assert!(!obj.is_null());
SetFunctionNativeReserved(
obj.get(),
SLOT_MODULEPRIVATE,
&reference_private.get() as *const _,
);
}
true
}
#[expect(unsafe_code)]
unsafe extern "C" fn import_meta_resolve(cx: *mut RawJSContext, argc: u32, vp: *mut JSVal) -> bool {
let mut cx = unsafe { JSContext::from_ptr(ptr::NonNull::new(cx).unwrap()) };
let mut realm = CurrentRealm::assert(&mut cx);
let global_scope = GlobalScope::from_current_realm(&mut realm);
let cx = &mut realm;
let args = unsafe { CallArgs::from_vp(vp, argc) };
rooted!(&in(cx) let module_private = unsafe { *GetFunctionNativeReserved(args.callee(), SLOT_MODULEPRIVATE) });
let reference_private = module_private.handle().into();
let module_data = unsafe { module_script_from_reference_private(&reference_private) };
let specifier = unsafe {
let value = HandleValue::from_raw(args.get(0));
match NonNull::new(ToString(cx, value)) {
Some(jsstr) => jsstr_to_string(cx, jsstr).into(),
None => return false,
}
};
let url = ModuleTree::resolve_module_specifier(&global_scope, module_data, specifier);
match url {
Ok(url) => {
url.as_str()
.safe_to_jsval(cx, unsafe { MutableHandleValue::from_raw(args.rval()) });
true
},
Err(error) => {
let resolution_error = gen_type_error(cx, &global_scope, error);
unsafe {
JS_SetPendingException(
cx,
resolution_error.handle(),
ExceptionStackBehavior::Capture,
);
}
false
},
}
}
#[expect(clippy::too_many_arguments)]
pub(crate) fn fetch_a_module_script_graph(
cx: &mut JSContext,
global: &GlobalScope,
url: UrlWithBlobClaim,
fetch_client: RequestClient,
destination: Destination,
referrer: Referrer,
credentials_mode: CredentialsMode,
introduction_type: Option<&'static CStr>,
on_complete: impl FnOnce(&mut JSContext, Option<Rc<ModuleTree>>) + Clone + 'static,
) {
let global_scope = DomRoot::from_ref(global);
let options = ScriptFetchOptions {
integrity_metadata: "".into(),
credentials_mode,
cryptographic_nonce: "".into(),
parser_metadata: ParserMetadata::NotParserInserted,
referrer_policy: ReferrerPolicy::EmptyString,
render_blocking: false,
};
fetch_a_single_module_script(
cx,
url,
fetch_client.clone(),
global,
destination,
options,
referrer,
None,
true,
introduction_type,
move |cx, module_tree| {
let Some(module) = module_tree else {
return on_complete(cx, None);
};
fetch_the_descendants_and_link_module_script(
cx,
&global_scope,
module,
fetch_client,
destination,
on_complete,
);
},
);
}
pub(crate) fn fetch_an_external_module_script(
cx: &mut JSContext,
url: UrlWithBlobClaim,
global: &GlobalScope,
options: ScriptFetchOptions,
on_complete: impl FnOnce(&mut JSContext, Option<Rc<ModuleTree>>) + Clone + 'static,
) {
let referrer = global.get_referrer();
let fetch_client = global.request_client(Some(cx.no_gc()));
let global_scope = DomRoot::from_ref(global);
fetch_a_single_module_script(
cx,
url,
fetch_client.clone(),
global,
Destination::Script,
options,
referrer,
None,
true,
Some(IntroductionType::SRC_SCRIPT),
move |cx, module_tree| {
let Some(module) = module_tree else {
return on_complete(cx, None);
};
fetch_the_descendants_and_link_module_script(
cx,
&global_scope,
module,
fetch_client,
Destination::Script,
on_complete,
);
},
);
}
pub(crate) fn fetch_a_modulepreload_module(
cx: &mut JSContext,
url: UrlWithBlobClaim,
destination: Destination,
global: &GlobalScope,
options: ScriptFetchOptions,
on_complete: impl FnOnce(&mut JSContext, bool) + 'static,
) {
let referrer = global.get_referrer();
let fetch_client = global.request_client(Some(cx.no_gc()));
let global_scope = DomRoot::from_ref(global);
let module_type = if let Destination::Json = destination {
Some(ModuleType::JSON)
} else {
None
};
fetch_a_single_module_script(
cx,
url,
fetch_client.clone(),
global,
destination,
options,
referrer,
module_type,
true,
Some(IntroductionType::SRC_SCRIPT),
move |cx, result| {
on_complete(cx, result.is_none());
assert!(global_scope.is::<Window>());
if pref!(dom_allow_preloading_module_descendants) &&
let Some(module) = result
{
fetch_the_descendants_and_link_module_script(
cx,
&global_scope,
module,
fetch_client,
destination,
|_, _| {},
);
}
},
);
}
#[expect(clippy::too_many_arguments)]
pub(crate) fn fetch_inline_module_script(
cx: &mut JSContext,
global: &GlobalScope,
module_script_text: Cow<'_, str>,
url: ServoUrl,
options: ScriptFetchOptions,
line_number: u32,
introduction_type: Option<&'static CStr>,
on_complete: impl FnOnce(&mut JSContext, Option<Rc<ModuleTree>>) + Clone + 'static,
) {
let module_tree = Rc::new(ModuleTree::create_a_javascript_module_script(
cx,
module_script_text,
global,
&url,
options,
false,
line_number,
introduction_type,
));
let fetch_client = global.request_client(Some(cx.no_gc()));
fetch_the_descendants_and_link_module_script(
cx,
global,
module_tree,
fetch_client,
Destination::Script,
on_complete,
);
}
#[expect(unsafe_code)]
fn fetch_the_descendants_and_link_module_script(
cx: &mut JSContext,
global: &GlobalScope,
module_script: Rc<ModuleTree>,
fetch_client: RequestClient,
destination: Destination,
on_complete: impl FnOnce(&mut JSContext, Option<Rc<ModuleTree>>) + Clone + 'static,
) {
if module_script.get_record().is_none() {
let parse_error = module_script.get_parse_error().cloned();
module_script.set_rethrow_error(parse_error.unwrap());
on_complete(cx, Some(module_script));
return;
}
let state = Rc::new(LoadState {
error_to_rethrow: RefCell::new(None),
destination,
fetch_client,
});
let mut realm = enter_auto_realm(cx, global);
let cx = &mut realm.current_realm();
let loading_promise = load_requested_modules(cx, module_script.clone(), Some(state.clone()));
let global_scope = DomRoot::from_ref(global);
let fulfilled_module = module_script.clone();
let fulfilled_on_complete = on_complete.clone();
let loading_promise_fulfillment = ModuleHandler::new_boxed(Box::new(
task!(fulfilled_steps: |cx, global_scope: DomRoot<GlobalScope>| {
let mut realm = AutoRealm::new(
cx,
NonNull::new(global_scope.reflector().get_jsobject().get()).unwrap(),
);
let cx = &mut *realm;
let handle = fulfilled_module.get_record().map(|module| module.handle()).unwrap();
let link = unsafe { ModuleLink(cx, handle) };
if !link {
let exception = RethrowError::from_pending_exception(cx);
fulfilled_module.set_rethrow_error(exception);
}
fulfilled_on_complete(cx, Some(fulfilled_module));
}),
));
let loading_promise_rejection =
ModuleHandler::new_boxed(Box::new(task!(rejected_steps: |cx, state: Rc<LoadState>| {
if let Some(error) = state.error_to_rethrow.borrow().as_ref() {
module_script.set_rethrow_error(error.clone());
on_complete(cx, Some(module_script));
} else {
on_complete(cx, None);
}
})));
let handler = PromiseNativeHandler::new(
cx,
global,
Some(loading_promise_fulfillment),
Some(loading_promise_rejection),
);
run_a_callback::<DomTypeHolder, _>(global, || {
loading_promise.append_native_handler(cx, &handler);
});
}
#[expect(clippy::too_many_arguments)]
pub(crate) fn fetch_a_single_module_script(
cx: &mut JSContext,
url: UrlWithBlobClaim,
fetch_client: RequestClient,
global: &GlobalScope,
destination: Destination,
options: ScriptFetchOptions,
referrer: Referrer,
module_type: Option<ModuleType>,
is_top_level: bool,
introduction_type: Option<&'static CStr>,
on_complete: impl FnOnce(&mut JSContext, Option<Rc<ModuleTree>>) + 'static,
) {
let module_type = module_type.unwrap_or(ModuleType::JavaScript);
let module_request = (url.url(), module_type);
let module_map = global.module_map();
let mut module_map_borrow = module_map.safe_borrow_mut(cx);
let entry = module_map_borrow.entry(module_request.clone());
match entry {
Entry::Occupied(mut entry) => match entry.get_mut() {
ModuleStatus::Loaded(module_tree) => {
let module = module_tree.clone();
drop(module_map_borrow);
return on_complete(cx, Some(module));
},
ModuleStatus::Fetching(callbacks) => return callbacks.push(Box::new(on_complete)),
},
Entry::Vacant(entry) => {
entry.insert(ModuleStatus::Fetching(vec![Box::new(on_complete)]));
},
}
let policy_container = (is_top_level && global.is::<WorkerGlobalScope>())
.then(|| fetch_client.policy_container.clone());
let mode = match destination {
Destination::Worker | Destination::SharedWorker if is_top_level => RequestMode::SameOrigin,
_ => RequestMode::CorsMode,
};
let destination = match module_type {
ModuleType::JSON => Destination::Json,
ModuleType::JavaScript | ModuleType::Unknown => destination,
};
let request = RequestBuilder::new(global.webview_id(), url, referrer)
.destination(destination)
.parser_metadata(options.parser_metadata)
.integrity_metadata(options.integrity_metadata.clone())
.credentials_mode(options.credentials_mode)
.referrer_policy(options.referrer_policy)
.mode(mode)
.cryptographic_nonce_metadata(options.cryptographic_nonce.clone())
.client(fetch_client)
.pipeline_id(Some(global.pipeline_id()));
let context = ModuleContext {
owner: Trusted::new(global),
data: vec![],
metadata: None,
module_request,
options,
status: Ok(()),
introduction_type,
policy_container,
};
let task_source = global.task_manager().networking_task_source().to_sendable();
global.fetch(request, context, task_source);
}
#[derive(Default, Eq, Hash, JSTraceable, MallocSizeOf, PartialEq)]
pub(crate) struct ResolvedModule {
pub(crate) base_url: String,
pub(crate) specifier: String,
#[no_trace]
pub(crate) specifier_url: Option<ServoUrl>,
}
impl ResolvedModule {
pub(crate) fn new(
base_url: String,
specifier: String,
specifier_url: Option<ServoUrl>,
) -> Self {
Self {
base_url,
specifier,
specifier_url,
}
}
}
fn resolve_imports_match(
normalized_specifier: &str,
as_url: Option<&ServoUrl>,
specifier_map: &ModuleSpecifierMap,
) -> Fallible<Option<ServoUrl>> {
for (specifier_key, resolution_result) in specifier_map {
if specifier_key == normalized_specifier {
if let Some(resolution_result) = resolution_result {
return Ok(Some(resolution_result.clone()));
} else {
return Err(Error::Type(
c"Resolution of specifierKey was blocked by a null entry.".to_owned(),
));
}
}
if specifier_key.ends_with('\u{002f}') &&
normalized_specifier.starts_with(specifier_key) &&
(as_url.is_none() || as_url.is_some_and(|u| u.is_special_scheme()))
{
let Some(resolution_result) = resolution_result else {
return Err(Error::Type(
c"Resolution of specifierKey was blocked by a null entry.".to_owned(),
));
};
let after_prefix = normalized_specifier
.strip_prefix(specifier_key)
.expect("specifier_key should be the prefix of normalized_specifier");
debug_assert!(resolution_result.as_str().ends_with('\u{002f}'));
let url = ServoUrl::parse_with_base(Some(resolution_result), after_prefix);
let Ok(url) = url else {
return Err(Error::Type(
c"Resolution of normalizedSpecifier was blocked since
the afterPrefix portion could not be URL-parsed relative to
the resolutionResult mapped to by the specifierKey prefix."
.to_owned(),
));
};
if !url.as_str().starts_with(resolution_result.as_str()) {
return Err(Error::Type(
c"Resolution of normalizedSpecifier was blocked due to
it backtracking above its prefix specifierKey."
.to_owned(),
));
}
return Ok(Some(url));
}
}
Ok(None)
}