mirror of
https://github.com/lexmount/moli.git
synced 2026-10-06 08:00:59 +00:00
fix(modules): retain parse and static request exception identity
Carry realm-owned exception tokens through the module tree, preserve V8 compile exceptions, and cache static dependency validation failures without caching direct import resolution or network errors. Keep values GC-traced and isolated across WindowProxy reuse. Add identity, shared-dependency, realm, GC, and pending-sibling regressions. Validated with fmt, full workspace Clippy, 17647 nextest passes, and a matched 148-case CLI/CDP WPT slice. The target now passes 11/11 subtests; smoke is 42/43 with the baseline missing-node Puppeteer failure.
This commit is contained in:
@@ -2978,7 +2978,6 @@ html/semantics/scripting-1/the-script-element/module/compilation-error-2.html
|
||||
html/semantics/scripting-1/the-script-element/module/crossorigin.html
|
||||
html/semantics/scripting-1/the-script-element/module/dynamic-import/alpha/base-url-worker-importScripts.html
|
||||
html/semantics/scripting-1/the-script-element/module/dynamic-import/code-cache-base-url.html
|
||||
html/semantics/scripting-1/the-script-element/module/dynamic-import/dynamic-imports-script-error.html
|
||||
html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-nonce-classic.html
|
||||
html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-nonce-module.html
|
||||
html/semantics/scripting-1/the-script-element/module/error-type-3.html
|
||||
|
||||
@@ -6498,6 +6498,7 @@ html/semantics/scripting-1/the-script-element/module/duplicated-imports-1.html
|
||||
html/semantics/scripting-1/the-script-element/module/duplicated-imports-2.html
|
||||
html/semantics/scripting-1/the-script-element/module/dynamic-import/code-cache-nonce.html
|
||||
html/semantics/scripting-1/the-script-element/module/dynamic-import/delay-load-event.html
|
||||
html/semantics/scripting-1/the-script-element/module/dynamic-import/dynamic-imports-script-error.html
|
||||
html/semantics/scripting-1/the-script-element/module/dynamic-import/dynamic-imports.html
|
||||
html/semantics/scripting-1/the-script-element/module/dynamic-import/inline-event-handler.html
|
||||
html/semantics/scripting-1/the-script-element/module/dynamic-import/propagate-nonce-external-classic.html
|
||||
|
||||
@@ -41,4 +41,13 @@ pub trait ModuleScriptTreeHost {
|
||||
) -> Result<ModuleGraphHandle, ModuleLoadError>;
|
||||
|
||||
fn mark_module_failed(&mut self, key: ModuleMapKey, error: ModuleLoadError) -> ModuleEntryId;
|
||||
|
||||
/// Static requested-module validation errors belong to the module script,
|
||||
/// unlike failures to resolve the specifier passed directly to import().
|
||||
/// Retain the actual exception and cache it for subsequent graph loads.
|
||||
fn cache_module_request_error(
|
||||
&mut self,
|
||||
key: ModuleMapKey,
|
||||
error: ModuleLoadError,
|
||||
) -> ModuleLoadError;
|
||||
}
|
||||
|
||||
@@ -303,7 +303,12 @@ impl ModuleScriptTreeJob {
|
||||
) -> ModuleScriptTreePoll {
|
||||
let snapshot = match host.module_dependencies(entry) {
|
||||
Ok(snapshot) => snapshot,
|
||||
Err(error) => return self.fail(error),
|
||||
Err(error) => {
|
||||
if let Some(key) = error.key.as_deref().cloned() {
|
||||
return self.finish_module_load_error(host, key, error);
|
||||
}
|
||||
return self.fail(error);
|
||||
}
|
||||
};
|
||||
let snapshot = self.dependency_snapshot_with_tree_context(snapshot);
|
||||
if snapshot.requested_modules.is_empty() {
|
||||
@@ -318,7 +323,11 @@ impl ModuleScriptTreeJob {
|
||||
};
|
||||
let candidates = match self.collect_dependency_candidates(host, &snapshot) {
|
||||
Ok(candidates) => candidates,
|
||||
Err(error) => return self.fail(error),
|
||||
Err(error) => {
|
||||
let error = host.cache_module_request_error(snapshot.key.clone(), error);
|
||||
self.record_parse_error(snapshot.key, error);
|
||||
return self.finish_after_parse_error(host);
|
||||
}
|
||||
};
|
||||
|
||||
for candidate in candidates {
|
||||
@@ -689,5 +698,6 @@ struct DependencyCandidate {
|
||||
}
|
||||
|
||||
fn is_parse_error(error: &ModuleLoadError) -> bool {
|
||||
error.error_constructor == Some(ModuleErrorConstructorKind::SyntaxError)
|
||||
error.exception_id.is_some()
|
||||
|| error.error_constructor == Some(ModuleErrorConstructorKind::SyntaxError)
|
||||
}
|
||||
|
||||
@@ -520,12 +520,18 @@ pub enum ModuleSource {
|
||||
Binary(Vec<u8>),
|
||||
}
|
||||
|
||||
/// Identifies a JavaScript exception retained by the host in its owning realm.
|
||||
/// The tree transports this token without owning or reconstructing the value.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct ModuleExceptionId(pub u64);
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ModuleLoadError {
|
||||
pub stage: ModuleLoadStage,
|
||||
pub key: Option<Box<ModuleMapKey>>,
|
||||
pub message: String,
|
||||
pub error_constructor: Option<ModuleErrorConstructorKind>,
|
||||
pub exception_id: Option<ModuleExceptionId>,
|
||||
}
|
||||
|
||||
impl ModuleLoadError {
|
||||
@@ -535,6 +541,7 @@ impl ModuleLoadError {
|
||||
key: None,
|
||||
message: message.into(),
|
||||
error_constructor: None,
|
||||
exception_id: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -547,6 +554,11 @@ impl ModuleLoadError {
|
||||
self.error_constructor = Some(constructor);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_exception_id(mut self, exception_id: ModuleExceptionId) -> Self {
|
||||
self.exception_id = Some(exception_id);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
|
||||
@@ -85,9 +85,10 @@ impl ModuleScriptTreeHost for FakeHost {
|
||||
attributes: &ModuleAttributesKey,
|
||||
requested_phase: ModuleImportPhase,
|
||||
) -> Result<ResolvedModuleRequest, ModuleLoadError> {
|
||||
let source_url = base_url
|
||||
.join(specifier)
|
||||
.map_err(|error| ModuleLoadError::new(ModuleLoadStage::Resolve, error.to_string()))?;
|
||||
let source_url = base_url.join(specifier).map_err(|error| {
|
||||
ModuleLoadError::new(ModuleLoadStage::Resolve, error.to_string())
|
||||
.with_error_constructor(ModuleErrorConstructorKind::TypeError)
|
||||
})?;
|
||||
let kind = if attributes
|
||||
.attributes
|
||||
.iter()
|
||||
@@ -246,6 +247,25 @@ impl ModuleScriptTreeHost for FakeHost {
|
||||
fn mark_module_failed(&mut self, _key: ModuleMapKey, _error: ModuleLoadError) -> ModuleEntryId {
|
||||
ModuleEntryId(0)
|
||||
}
|
||||
|
||||
fn cache_module_request_error(
|
||||
&mut self,
|
||||
key: ModuleMapKey,
|
||||
error: ModuleLoadError,
|
||||
) -> ModuleLoadError {
|
||||
let error = error.with_key(key.clone()).with_exception_id(
|
||||
moli_module_script_tree::ModuleExceptionId(self.next_entry_id as u64),
|
||||
);
|
||||
self.next_entry_id += 1;
|
||||
self.entries.insert(
|
||||
key,
|
||||
FakeEntry::Failed {
|
||||
error: error.clone(),
|
||||
phase: ModuleImportPhase::Evaluation,
|
||||
},
|
||||
);
|
||||
error
|
||||
}
|
||||
}
|
||||
|
||||
fn url(input: &str) -> Url {
|
||||
@@ -967,6 +987,91 @@ fn parse_error_result_uses_module_discovery_order_not_completion_order() {
|
||||
assert_eq!(host.link_calls, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn static_request_error_is_cached_before_any_dependency_fetch_starts() {
|
||||
let root_key = key("https://example.test/app/root.mjs");
|
||||
let mut host = FakeHost::new();
|
||||
host.ready(
|
||||
root_key.clone(),
|
||||
ModuleEntryId(1),
|
||||
vec![
|
||||
request("./valid.mjs", ModuleImportPhase::Evaluation),
|
||||
request("http://[", ModuleImportPhase::Evaluation),
|
||||
],
|
||||
);
|
||||
let mut first = job(inline_root(root_key.clone(), ModuleEntryId(1)));
|
||||
let ModuleScriptTreePoll::Failed(error) = first.poll(&mut host) else {
|
||||
panic!("invalid static request must fail the graph");
|
||||
};
|
||||
assert!(
|
||||
host.started.is_empty(),
|
||||
"all requests must be validated before fetching any"
|
||||
);
|
||||
assert_eq!(error.key.as_deref(), Some(&root_key));
|
||||
assert!(error.exception_id.is_some());
|
||||
assert_eq!(
|
||||
error.error_constructor,
|
||||
Some(ModuleErrorConstructorKind::TypeError)
|
||||
);
|
||||
let mut second = job(external_root(
|
||||
root_key.url.clone(),
|
||||
ModuleImportPhase::Evaluation,
|
||||
));
|
||||
assert_eq!(
|
||||
second.drive(&mut host),
|
||||
ModuleScriptTreeDrive::Failed(error)
|
||||
);
|
||||
assert!(host.started.is_empty());
|
||||
assert_eq!(host.link_calls, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn static_request_type_error_waits_for_pending_siblings_and_preserves_network_failure() {
|
||||
let root_key = key("https://example.test/app/root.mjs");
|
||||
let bad_key = key("https://example.test/app/bad.mjs");
|
||||
let mut host = FakeHost::new();
|
||||
host.ready(
|
||||
root_key.clone(),
|
||||
ModuleEntryId(1),
|
||||
vec![
|
||||
request("./bad.mjs", ModuleImportPhase::Evaluation),
|
||||
request("./network.mjs", ModuleImportPhase::Evaluation),
|
||||
],
|
||||
);
|
||||
host.ready(
|
||||
bad_key.clone(),
|
||||
ModuleEntryId(2),
|
||||
vec![request("http://[", ModuleImportPhase::Evaluation)],
|
||||
);
|
||||
let mut tree = job(inline_root(root_key, ModuleEntryId(1)));
|
||||
let ModuleScriptTreePoll::NeedFetches(mut fetches) = tree.poll(&mut host) else {
|
||||
panic!("root must start its uncached sibling fetch");
|
||||
};
|
||||
assert_eq!(fetches.len(), 1);
|
||||
let ModuleScriptTreeDrive::WaitingForSingleModuleClients(wait) = tree.drive(&mut host) else {
|
||||
panic!("static request failure must wait for the outstanding network fetch");
|
||||
};
|
||||
assert_eq!(wait.client_count, 1);
|
||||
assert!(
|
||||
matches!(host.entries.get(&bad_key), Some(FakeEntry::Failed { error, .. })
|
||||
if error.exception_id.is_some())
|
||||
);
|
||||
let fetch = fetches.pop().unwrap();
|
||||
let error = ModuleLoadError::new(ModuleLoadStage::Fetch, "network failure");
|
||||
let result = tree.resume_single_module(
|
||||
&mut host,
|
||||
fetch.client,
|
||||
ModuleFetchResult {
|
||||
key: fetch.key,
|
||||
client: fetch.client,
|
||||
requested_phase: ModuleImportPhase::Evaluation,
|
||||
outcome: ModuleFetchOutcome::Failed(error.clone()),
|
||||
},
|
||||
);
|
||||
assert_eq!(result, ModuleScriptTreePoll::Failed(error));
|
||||
assert_eq!(host.link_calls, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn evaluation_phase_upgrades_source_phase_for_same_key() {
|
||||
let root_key = key("https://example.test/app/root.mjs");
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::types::ScriptErrorConstructorKind;
|
||||
use moli_module_script_tree::ModuleExceptionId;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(crate) enum ModuleLoadStage {
|
||||
@@ -14,6 +15,7 @@ pub(crate) struct ModuleLoadError {
|
||||
stage: ModuleLoadStage,
|
||||
message: String,
|
||||
error_constructor: Option<ScriptErrorConstructorKind>,
|
||||
exception_id: Option<ModuleExceptionId>,
|
||||
top_level_module_load_failure: bool,
|
||||
}
|
||||
|
||||
@@ -23,6 +25,7 @@ impl ModuleLoadError {
|
||||
stage,
|
||||
message: message.into(),
|
||||
error_constructor: None,
|
||||
exception_id: None,
|
||||
top_level_module_load_failure: false,
|
||||
}
|
||||
}
|
||||
@@ -40,6 +43,15 @@ impl ModuleLoadError {
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn with_exception_id(mut self, exception_id: ModuleExceptionId) -> Self {
|
||||
self.exception_id = Some(exception_id);
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn exception_id(&self) -> Option<ModuleExceptionId> {
|
||||
self.exception_id
|
||||
}
|
||||
|
||||
pub(crate) fn stage(&self) -> ModuleLoadStage {
|
||||
self.stage
|
||||
}
|
||||
|
||||
@@ -1539,6 +1539,9 @@ impl<O: NativeModuleTreeDocumentOwnerAdapter> module_tree::ModuleScriptTreeHost
|
||||
{
|
||||
let entry = local_entry_id(entry);
|
||||
let key = self.owner.module_entry_key(entry);
|
||||
if let Some(error) = self.owner.module_failure(entry) {
|
||||
return Err(chromium_error(error).with_key(chromium_module_key(&key)));
|
||||
}
|
||||
let base_url = self.owner.module_entry_url(entry);
|
||||
let effective_fetch_metadata = self.owner.module_effective_fetch_metadata(entry);
|
||||
let requested_modules = self
|
||||
@@ -1598,6 +1601,23 @@ impl<O: NativeModuleTreeDocumentOwnerAdapter> module_tree::ModuleScriptTreeHost
|
||||
let entry = self.owner.mark_module_failed(local_key, local_error(error));
|
||||
chromium_entry_id(entry)
|
||||
}
|
||||
|
||||
fn cache_module_request_error(
|
||||
&mut self,
|
||||
key: module_tree::ModuleMapKey,
|
||||
error: module_tree::ModuleLoadError,
|
||||
) -> module_tree::ModuleLoadError {
|
||||
let local_key = match local_module_key(&key) {
|
||||
Ok(key) => key,
|
||||
Err(error) => return chromium_error(error),
|
||||
};
|
||||
let error = match self.owner.preserve_module_load_error(local_error(error)) {
|
||||
Ok(error) => error,
|
||||
Err(error) => return chromium_error(error),
|
||||
};
|
||||
self.owner.mark_module_failed(local_key, error.clone());
|
||||
chromium_error(error).with_key(key)
|
||||
}
|
||||
}
|
||||
|
||||
fn module_map_fetch_outcome_for_key_with_owner<O>(
|
||||
@@ -1899,6 +1919,9 @@ fn local_graph(graph: module_tree::ModuleGraphHandle) -> ModuleGraphHandle {
|
||||
fn chromium_error(error: ModuleLoadError) -> module_tree::ModuleLoadError {
|
||||
let mut converted =
|
||||
module_tree::ModuleLoadError::new(chromium_load_stage(error.stage()), error.message());
|
||||
if let Some(exception_id) = error.exception_id() {
|
||||
converted = converted.with_exception_id(exception_id);
|
||||
}
|
||||
if let Some(constructor) = error.error_constructor() {
|
||||
let constructor = match constructor {
|
||||
ScriptErrorConstructorKind::SyntaxError => {
|
||||
@@ -1918,6 +1941,9 @@ fn chromium_error(error: ModuleLoadError) -> module_tree::ModuleLoadError {
|
||||
|
||||
fn local_error(error: module_tree::ModuleLoadError) -> ModuleLoadError {
|
||||
let mut converted = ModuleLoadError::new(local_load_stage(error.stage), error.message);
|
||||
if let Some(exception_id) = error.exception_id {
|
||||
converted = converted.with_exception_id(exception_id);
|
||||
}
|
||||
if let Some(constructor) = error.error_constructor {
|
||||
converted = converted.with_error_constructor(local_error_constructor(constructor));
|
||||
}
|
||||
|
||||
@@ -105,6 +105,11 @@ pub(crate) trait NativeModuleTreeDocumentOwnerAdapter {
|
||||
|
||||
fn mark_module_failed(&mut self, key: ModuleMapKey, error: ModuleLoadError) -> ModuleEntryId;
|
||||
|
||||
fn preserve_module_load_error(
|
||||
&mut self,
|
||||
error: ModuleLoadError,
|
||||
) -> std::result::Result<ModuleLoadError, ModuleLoadError>;
|
||||
|
||||
fn record_runtime_warning(&mut self, message: fmt::Arguments<'_>);
|
||||
}
|
||||
|
||||
@@ -239,6 +244,13 @@ impl<T: NativeModuleTreeDocumentOwnerAdapter + ?Sized> NativeModuleTreeDocumentO
|
||||
(*self).mark_module_failed(key, error)
|
||||
}
|
||||
|
||||
fn preserve_module_load_error(
|
||||
&mut self,
|
||||
error: ModuleLoadError,
|
||||
) -> std::result::Result<ModuleLoadError, ModuleLoadError> {
|
||||
(*self).preserve_module_load_error(error)
|
||||
}
|
||||
|
||||
fn record_runtime_warning(&mut self, message: fmt::Arguments<'_>) {
|
||||
(*self).record_runtime_warning(message);
|
||||
}
|
||||
@@ -279,6 +291,14 @@ impl<'a> NativeModuleTreeFrameDocumentOwner<'a> {
|
||||
}
|
||||
|
||||
impl NativeModuleTreeDocumentOwnerAdapter for NativeModuleTreeDocumentOwner<'_> {
|
||||
fn preserve_module_load_error(
|
||||
&mut self,
|
||||
error: ModuleLoadError,
|
||||
) -> std::result::Result<ModuleLoadError, ModuleLoadError> {
|
||||
self.vm
|
||||
.preserve_native_module_load_error(self.compile_frame_realm, error)
|
||||
}
|
||||
|
||||
fn compile_module_record(
|
||||
&mut self,
|
||||
key: ModuleMapKey,
|
||||
@@ -447,6 +467,14 @@ impl NativeModuleTreeDocumentOwnerAdapter for NativeModuleTreeDocumentOwner<'_>
|
||||
}
|
||||
|
||||
impl NativeModuleTreeDocumentOwnerAdapter for NativeModuleTreeFrameDocumentOwner<'_> {
|
||||
fn preserve_module_load_error(
|
||||
&mut self,
|
||||
error: ModuleLoadError,
|
||||
) -> std::result::Result<ModuleLoadError, ModuleLoadError> {
|
||||
self.vm
|
||||
.preserve_native_module_load_error(Some(self.realm_id), error)
|
||||
}
|
||||
|
||||
fn compile_module_record(
|
||||
&mut self,
|
||||
key: ModuleMapKey,
|
||||
|
||||
@@ -84,7 +84,9 @@ mod child_dynamic_import;
|
||||
mod child_parser_module;
|
||||
mod child_ready_document_script;
|
||||
mod dynamic_import_selected_task_body;
|
||||
mod load_error;
|
||||
mod main_selected_task;
|
||||
use load_error::{module_load_error_value, retain_module_exception};
|
||||
pub(crate) use main_selected_task::{
|
||||
MainDynamicImportGraphFetchBodySettlement, MainNativeModuleSelectedTaskApplication,
|
||||
MainNativeModuleSelectedTaskBodyActivity,
|
||||
@@ -3332,6 +3334,7 @@ impl ScriptVm {
|
||||
source_url: &Url,
|
||||
fetch_metadata: &crate::module_runtime::ModuleFetchMetadata,
|
||||
) -> std::result::Result<(ModuleRecordEntry, ModuleIdentityHash), ModuleLoadError> {
|
||||
let mut exception_id = None;
|
||||
self.renderer_document_isolate
|
||||
.with_entered_renderer_document_isolate(|isolate| {
|
||||
let scope = pin!(v8::HandleScope::new(isolate));
|
||||
@@ -3352,6 +3355,12 @@ impl ScriptVm {
|
||||
v8::script_compiler::Source::new(source_string, Some(&origin));
|
||||
let module = v8::script_compiler::compile_module(&scope, &mut compiler_source)
|
||||
.ok_or_else(|| {
|
||||
if let Some(exception) = scope.exception() {
|
||||
match retain_module_exception(&mut scope, exception) {
|
||||
Ok(id) => exception_id = Some(id),
|
||||
Err(error) => return error,
|
||||
}
|
||||
}
|
||||
let exception = scope
|
||||
.exception()
|
||||
.and_then(|exception| exception.to_detail_string(&scope))
|
||||
@@ -3384,7 +3393,10 @@ impl ScriptVm {
|
||||
})
|
||||
.map_err(|error| {
|
||||
let message = error.to_string();
|
||||
let load_error = ModuleLoadError::new(ModuleLoadStage::Compile, message.clone());
|
||||
let mut load_error = ModuleLoadError::new(ModuleLoadStage::Compile, message.clone());
|
||||
if let Some(exception_id) = exception_id {
|
||||
load_error = load_error.with_exception_id(exception_id);
|
||||
}
|
||||
if message.starts_with("v8 failed to compile WebAssembly module `") {
|
||||
load_error
|
||||
.with_error_constructor(ScriptErrorConstructorKind::WebAssemblyCompileError)
|
||||
@@ -4101,7 +4113,10 @@ impl ScriptVm {
|
||||
request: PendingDynamicModuleImport,
|
||||
message: &str,
|
||||
) -> std::result::Result<(), ModuleLoadError> {
|
||||
self.reject_native_dynamic_module_import_with_constructor(request, message, None)
|
||||
self.reject_native_dynamic_module_import_and_checkpoint(
|
||||
request,
|
||||
&ModuleLoadError::new(ModuleLoadStage::Fetch, message),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -4110,18 +4125,13 @@ impl ScriptVm {
|
||||
request: PendingDynamicModuleImport,
|
||||
error: &ModuleLoadError,
|
||||
) -> std::result::Result<(), ModuleLoadError> {
|
||||
self.reject_native_dynamic_module_import_with_constructor(
|
||||
request,
|
||||
error.message(),
|
||||
error.error_constructor(),
|
||||
)
|
||||
self.reject_native_dynamic_module_import_and_checkpoint(request, error)
|
||||
}
|
||||
|
||||
fn reject_native_dynamic_module_import_with_constructor(
|
||||
fn reject_native_dynamic_module_import_and_checkpoint(
|
||||
&mut self,
|
||||
request: PendingDynamicModuleImport,
|
||||
message: &str,
|
||||
error_constructor: Option<ScriptErrorConstructorKind>,
|
||||
error: &ModuleLoadError,
|
||||
) -> std::result::Result<(), ModuleLoadError> {
|
||||
self.renderer_document_isolate
|
||||
.with_entered_renderer_document_isolate(|isolate| {
|
||||
@@ -4130,14 +4140,7 @@ impl ScriptVm {
|
||||
let context = v8::Local::new(scope, request.context());
|
||||
let scope = &mut v8::ContextScope::new(scope, context);
|
||||
let resolver = v8::Local::new(scope, request.resolver());
|
||||
let message = v8_string(scope, message);
|
||||
let exception = message
|
||||
.and_then(|message| {
|
||||
error_constructor
|
||||
.and_then(|kind| script_error_value(scope, kind, message))
|
||||
.or_else(|| Some(v8::Exception::type_error(scope, message)))
|
||||
})
|
||||
.unwrap_or_else(|| v8::undefined(scope).into());
|
||||
let exception = module_load_error_value(scope, error)?;
|
||||
let _ = resolver.reject(scope, exception);
|
||||
Self::perform_microtask_checkpoints(scope, None)?;
|
||||
Ok(())
|
||||
@@ -5066,6 +5069,7 @@ fn module_import_phase(phase: v8::ModuleImportPhase) -> ModuleImportPhase {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
mod parse_errors;
|
||||
use std::pin::pin;
|
||||
|
||||
use super::{
|
||||
|
||||
@@ -90,8 +90,6 @@ impl ScriptVm {
|
||||
request: PendingDynamicModuleImport,
|
||||
error: &ModuleLoadError,
|
||||
) -> std::result::Result<(), ModuleLoadError> {
|
||||
let message = error.message();
|
||||
let error_constructor = error.error_constructor();
|
||||
self.renderer_document_isolate
|
||||
.with_entered_renderer_document_isolate(|isolate| {
|
||||
let scope = pin!(v8::HandleScope::new(isolate));
|
||||
@@ -99,14 +97,7 @@ impl ScriptVm {
|
||||
let context = v8::Local::new(scope, request.context());
|
||||
let scope = &mut v8::ContextScope::new(scope, context);
|
||||
let resolver = v8::Local::new(scope, request.resolver());
|
||||
let message = v8_string(scope, message);
|
||||
let exception = message
|
||||
.and_then(|message| {
|
||||
error_constructor
|
||||
.and_then(|kind| script_error_value(scope, kind, message))
|
||||
.or_else(|| Some(v8::Exception::type_error(scope, message)))
|
||||
})
|
||||
.unwrap_or_else(|| v8::undefined(scope).into());
|
||||
let exception = module_load_error_value(scope, error)?;
|
||||
let _ = resolver.reject(scope, exception);
|
||||
Ok(())
|
||||
})
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
|
||||
use moli_module_script_tree::ModuleExceptionId;
|
||||
|
||||
use super::*;
|
||||
use crate::util::private_key;
|
||||
|
||||
const MODULE_EXCEPTIONS_SLOT: &str = "__moliModuleExceptions";
|
||||
static NEXT_EXCEPTION_ID: AtomicU64 = AtomicU64::new(1);
|
||||
|
||||
/// Keep the original exception in the realm's GC-traced heap, not in a Rust
|
||||
/// context slot containing a Global that would keep a retired realm alive.
|
||||
/// The module map survives document.open(), as does this private registry.
|
||||
pub(super) fn retain_module_exception(
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
exception: v8::Local<'_, v8::Value>,
|
||||
) -> Result<ModuleExceptionId> {
|
||||
let global = scope.get_current_context().global(scope);
|
||||
let map = match get_private_value(scope, global, MODULE_EXCEPTIONS_SLOT)
|
||||
.and_then(|value| v8::Local::<v8::Map>::try_from(value).ok())
|
||||
{
|
||||
Some(map) => map,
|
||||
None => {
|
||||
let map = v8::Map::new(scope);
|
||||
let key = private_key(scope, MODULE_EXCEPTIONS_SLOT).ok_or_else(|| {
|
||||
anyhow::anyhow!("failed to allocate module exception registry key")
|
||||
})?;
|
||||
anyhow::ensure!(
|
||||
global.set_private(scope, key, map.into()) == Some(true),
|
||||
"failed to retain module exception registry"
|
||||
);
|
||||
map
|
||||
}
|
||||
};
|
||||
let id = NEXT_EXCEPTION_ID
|
||||
.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |id| id.checked_add(1))
|
||||
.map_err(|_| anyhow::anyhow!("module exception identifiers exhausted"))?;
|
||||
let key = v8::BigInt::new_from_u64(scope, id);
|
||||
map.set(scope, key.into(), exception)
|
||||
.ok_or_else(|| anyhow::anyhow!("failed to retain module exception"))?;
|
||||
Ok(ModuleExceptionId(id))
|
||||
}
|
||||
|
||||
pub(super) fn module_load_error_value<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
error: &ModuleLoadError,
|
||||
) -> Result<v8::Local<'s, v8::Value>> {
|
||||
if let Some(id) = error.exception_id() {
|
||||
let global = scope.get_current_context().global(scope);
|
||||
let map = get_private_value(scope, global, MODULE_EXCEPTIONS_SLOT)
|
||||
.and_then(|value| v8::Local::<v8::Map>::try_from(value).ok())
|
||||
.ok_or_else(|| {
|
||||
anyhow::anyhow!("module exception registry missing from request realm")
|
||||
})?;
|
||||
let key = v8::BigInt::new_from_u64(scope, id.0);
|
||||
anyhow::ensure!(
|
||||
map.has(scope, key.into()) == Some(true),
|
||||
"module exception {id:?} does not belong to request realm"
|
||||
);
|
||||
return map
|
||||
.get(scope, key.into())
|
||||
.ok_or_else(|| anyhow::anyhow!("failed to read retained module exception"));
|
||||
}
|
||||
let message = v8_string(scope, error.message())
|
||||
.ok_or_else(|| anyhow::anyhow!("failed to allocate module error message"))?;
|
||||
script_error_value(
|
||||
scope,
|
||||
error
|
||||
.error_constructor()
|
||||
.unwrap_or(ScriptErrorConstructorKind::TypeError),
|
||||
message,
|
||||
)
|
||||
.ok_or_else(|| anyhow::anyhow!("failed to create module error"))
|
||||
}
|
||||
|
||||
impl ScriptVm {
|
||||
pub(crate) fn preserve_native_module_load_error(
|
||||
&mut self,
|
||||
realm_id: Option<FrameRealmId>,
|
||||
error: ModuleLoadError,
|
||||
) -> std::result::Result<ModuleLoadError, ModuleLoadError> {
|
||||
if error.exception_id().is_some() {
|
||||
return Ok(error);
|
||||
}
|
||||
let context_ptr = if let Some(realm_id) = realm_id {
|
||||
self.frame_realm_context_ptr(realm_id).map_err(|error| {
|
||||
ModuleLoadError::new(ModuleLoadStage::Resolve, error.to_string())
|
||||
})?
|
||||
} else {
|
||||
self.native_module_default_context_ptr()
|
||||
};
|
||||
self.renderer_document_isolate
|
||||
.with_entered_renderer_document_isolate(|isolate| {
|
||||
let scope = pin!(v8::HandleScope::new(isolate));
|
||||
let scope = &mut scope.init();
|
||||
// SAFETY: the selected realm is owned by this VM during entry.
|
||||
let context = unsafe { v8::Local::new(scope, &*context_ptr) };
|
||||
let scope = &mut v8::ContextScope::new(scope, context);
|
||||
let exception = module_load_error_value(scope, &error)?;
|
||||
let id = retain_module_exception(scope, exception)?;
|
||||
Ok(error.with_exception_id(id))
|
||||
})
|
||||
.map_err(|error: anyhow::Error| {
|
||||
ModuleLoadError::new(ModuleLoadStage::Resolve, error.to_string())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,403 @@
|
||||
use super::super::{module_load_error_value, retain_module_exception};
|
||||
use super::*;
|
||||
|
||||
fn compile_parse_error(vm: &mut ScriptVm, path: &str) -> ModuleLoadError {
|
||||
let url = Url::parse(path).unwrap();
|
||||
vm.compile_native_module_record(
|
||||
ModuleMapKey::java_script(url.clone()),
|
||||
&ModuleSource::text("export const = ;".to_owned()),
|
||||
&url,
|
||||
&ModuleFetchMetadata::default(),
|
||||
)
|
||||
.expect_err("the module must have a syntax error")
|
||||
}
|
||||
|
||||
fn install_module(vm: &mut ScriptVm, path: &str, source: &str) {
|
||||
let url = Url::parse(path).unwrap();
|
||||
let key = ModuleMapKey::java_script(url.clone());
|
||||
let source = ModuleSource::text(source.to_owned());
|
||||
let metadata = ModuleFetchMetadata::default();
|
||||
let (record, identity) = vm
|
||||
.compile_native_module_record(key.clone(), &source, &url, &metadata)
|
||||
.unwrap();
|
||||
vm.document_runtime
|
||||
.insert_native_module_source(key.clone(), source);
|
||||
vm.document_runtime
|
||||
.insert_native_compiled_module_record_with_metadata(key, record, identity, metadata);
|
||||
}
|
||||
|
||||
fn graph_error(vm: &mut ScriptVm, specifier: &str) -> ModuleLoadError {
|
||||
let mut job = dynamic_import_job_in_vm(
|
||||
vm,
|
||||
specifier,
|
||||
Url::parse("https://module-errors.test/page.html").unwrap(),
|
||||
ModuleImportPhase::Evaluation,
|
||||
);
|
||||
job.advance_dynamic_import_owner_lane(vm)
|
||||
.err()
|
||||
.expect("module graph should fail")
|
||||
}
|
||||
|
||||
fn reject_with_error(vm: &mut ScriptVm, error: &ModuleLoadError) -> v8::Global<v8::Value> {
|
||||
let request = dynamic_import_request_in_vm(
|
||||
vm,
|
||||
"./bad.mjs",
|
||||
Url::parse("https://module-errors.test/page.html").unwrap(),
|
||||
ModuleImportPhase::Evaluation,
|
||||
);
|
||||
let resolver = request.resolver().clone();
|
||||
vm.renderer_document_isolate
|
||||
.with_entered_renderer_document_isolate(|isolate| {
|
||||
let scope = pin!(v8::HandleScope::new(isolate));
|
||||
let scope = &mut scope.init();
|
||||
let context = v8::Local::new(scope, &vm.page_default_context);
|
||||
let scope = &mut v8::ContextScope::new(scope, context);
|
||||
v8::Local::new(scope, &resolver)
|
||||
.get_promise(scope)
|
||||
.mark_as_handled();
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
vm.reject_native_dynamic_module_import_with_error_selected_task_body(request, error)
|
||||
.unwrap();
|
||||
vm.renderer_document_isolate
|
||||
.with_entered_renderer_document_isolate(|isolate| {
|
||||
let scope = pin!(v8::HandleScope::new(isolate));
|
||||
let scope = &mut scope.init();
|
||||
let context = v8::Local::new(scope, &vm.page_default_context);
|
||||
let scope = &mut v8::ContextScope::new(scope, context);
|
||||
let promise = v8::Local::new(scope, &resolver).get_promise(scope);
|
||||
assert_eq!(promise.state(), v8::PromiseState::Rejected);
|
||||
Ok(v8::Global::new(scope, promise.result(scope)))
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn module_parse_error_preserves_exception_identity_without_merging_equal_messages() {
|
||||
let mut vm = new_test_vm("https://module-errors.test/page.html");
|
||||
let error = compile_parse_error(&mut vm, "https://module-errors.test/first.mjs");
|
||||
let other_error = compile_parse_error(&mut vm, "https://module-errors.test/second.mjs");
|
||||
let first = reject_with_error(&mut vm, &error);
|
||||
let again = reject_with_error(&mut vm, &error.clone());
|
||||
let other = reject_with_error(&mut vm, &other_error);
|
||||
vm.with_default_context_scope_and_checkpoint_for_test(|scope, _| {
|
||||
let first = v8::Local::new(scope, &first);
|
||||
let again = v8::Local::new(scope, &again);
|
||||
let other = v8::Local::new(scope, &other);
|
||||
assert!(first.is_native_error());
|
||||
assert!(
|
||||
first.strict_equals(again),
|
||||
"cloned load errors must retain the JS exception"
|
||||
);
|
||||
assert!(
|
||||
!first.strict_equals(other),
|
||||
"separate modules must retain separate exceptions"
|
||||
);
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn module_parse_error_identity_survives_document_open_in_the_same_realm() {
|
||||
let mut vm = new_test_vm("https://module-errors.test/page.html");
|
||||
let error = compile_parse_error(&mut vm, "https://module-errors.test/bad.mjs");
|
||||
let first = reject_with_error(&mut vm, &error);
|
||||
vm.eval("document.open(); document.write('<p>replacement</p>'); document.close(); 'done'")
|
||||
.unwrap();
|
||||
let again = reject_with_error(&mut vm, &error);
|
||||
vm.with_default_context_scope_and_checkpoint_for_test(|scope, _| {
|
||||
let first = v8::Local::new(scope, &first);
|
||||
let again = v8::Local::new(scope, &again);
|
||||
assert!(
|
||||
first.strict_equals(again),
|
||||
"document.open must retain the realm's module errors"
|
||||
);
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn module_parse_error_static_resolution_is_shared_across_roots_but_not_modules() {
|
||||
let mut vm = new_test_vm("https://module-errors.test/page.html");
|
||||
for (name, source) in [
|
||||
("bad.mjs", "import 'unmapped';"),
|
||||
("other-bad.mjs", "import 'unmapped';"),
|
||||
("first.mjs", "import './bad.mjs';"),
|
||||
("second.mjs", "import './bad.mjs';"),
|
||||
] {
|
||||
install_module(
|
||||
&mut vm,
|
||||
&format!("https://module-errors.test/{name}"),
|
||||
source,
|
||||
);
|
||||
}
|
||||
let first = graph_error(&mut vm, "./first.mjs");
|
||||
let again = graph_error(&mut vm, "./first.mjs");
|
||||
let second = graph_error(&mut vm, "./second.mjs");
|
||||
let dependency = graph_error(&mut vm, "./bad.mjs");
|
||||
let other = graph_error(&mut vm, "./other-bad.mjs");
|
||||
assert!(first.exception_id().is_some());
|
||||
assert_eq!(
|
||||
first.error_constructor(),
|
||||
Some(ScriptErrorConstructorKind::TypeError)
|
||||
);
|
||||
assert_eq!(first.exception_id(), again.exception_id());
|
||||
assert_eq!(first.exception_id(), second.exception_id());
|
||||
assert_eq!(first.exception_id(), dependency.exception_id());
|
||||
assert_ne!(first.exception_id(), other.exception_id());
|
||||
let first = reject_with_error(&mut vm, &first);
|
||||
let second = reject_with_error(&mut vm, &second);
|
||||
vm.with_default_context_scope_and_checkpoint_for_test(|scope, _| {
|
||||
assert!(v8::Local::new(scope, &first).strict_equals(v8::Local::new(scope, &second)));
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn module_parse_error_cache_does_not_capture_direct_resolution_fetch_or_link_errors() {
|
||||
let mut vm = new_test_vm("https://module-errors.test/page.html");
|
||||
let direct = graph_error(&mut vm, "unmapped");
|
||||
for error in [
|
||||
direct,
|
||||
ModuleLoadError::new(ModuleLoadStage::Fetch, "network failure"),
|
||||
ModuleLoadError::new(ModuleLoadStage::Instantiate, "missing export")
|
||||
.with_error_constructor(ScriptErrorConstructorKind::SyntaxError),
|
||||
] {
|
||||
assert!(error.exception_id().is_none());
|
||||
let first = reject_with_error(&mut vm, &error);
|
||||
let second = reject_with_error(&mut vm, &error);
|
||||
vm.with_default_context_scope_and_checkpoint_for_test(|scope, _| {
|
||||
assert!(!v8::Local::new(scope, &first).strict_equals(v8::Local::new(scope, &second)));
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn module_parse_error_registry_bypasses_public_constructors_and_collection_methods() {
|
||||
let mut vm = new_test_vm("https://module-errors.test/page.html");
|
||||
vm.eval(
|
||||
r#"
|
||||
globalThis.__intrinsicSyntaxError = SyntaxError;
|
||||
const forbidden = () => { throw new Error('public hook must not run'); };
|
||||
Map.prototype.set = Map.prototype.get = Map.prototype.has = forbidden;
|
||||
globalThis.Map = globalThis.SyntaxError = globalThis.TypeError = forbidden;
|
||||
'installed'
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
let error = compile_parse_error(&mut vm, "https://module-errors.test/bad.mjs");
|
||||
let first = reject_with_error(&mut vm, &error);
|
||||
let second = reject_with_error(&mut vm, &error);
|
||||
vm.with_default_context_scope_and_checkpoint_for_test(|scope, _| {
|
||||
let first = v8::Local::new(scope, &first);
|
||||
assert!(first.strict_equals(v8::Local::new(scope, &second)));
|
||||
let global = scope.get_current_context().global(scope);
|
||||
assert_eq!(
|
||||
global.set(scope, v8str(scope, "__originalException").into(), first),
|
||||
Some(true)
|
||||
);
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
vm.eval("__originalException instanceof __intrinsicSyntaxError")
|
||||
.unwrap(),
|
||||
"true"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn module_parse_error_registry_is_realm_local_and_does_not_root_retired_realms() {
|
||||
ensure_v8();
|
||||
let mut isolate = v8::Isolate::new(Default::default());
|
||||
let (weak_context, weak_exception, error) = {
|
||||
let scope = pin!(v8::HandleScope::new(&mut isolate));
|
||||
let scope = &mut scope.init();
|
||||
let context = v8::Context::new(scope, Default::default());
|
||||
let scope = &mut v8::ContextScope::new(scope, context);
|
||||
let message = v8str(scope, "bad module");
|
||||
let exception = v8::Exception::syntax_error(scope, message);
|
||||
let id = retain_module_exception(scope, exception).unwrap();
|
||||
let error =
|
||||
ModuleLoadError::new(ModuleLoadStage::Compile, "bad module").with_exception_id(id);
|
||||
assert!(
|
||||
module_load_error_value(scope, &error)
|
||||
.unwrap()
|
||||
.strict_equals(exception)
|
||||
);
|
||||
let weak_context = v8::Weak::new(scope, context);
|
||||
let weak_exception = v8::Weak::new(scope, exception);
|
||||
let other_context = v8::Context::new(scope, Default::default());
|
||||
let scope = &mut v8::ContextScope::new(scope, other_context);
|
||||
assert!(module_load_error_value(scope, &error).is_err());
|
||||
let other = v8::Exception::syntax_error(scope, message);
|
||||
let other_id = retain_module_exception(scope, other).unwrap();
|
||||
assert_ne!(id, other_id);
|
||||
assert!(module_load_error_value(scope, &error).is_err());
|
||||
(weak_context, weak_exception, error)
|
||||
};
|
||||
isolate.low_memory_notification();
|
||||
assert!(
|
||||
weak_context.is_empty(),
|
||||
"retained module errors must not root the realm"
|
||||
);
|
||||
assert!(
|
||||
weak_exception.is_empty(),
|
||||
"retiring the realm must release its exceptions"
|
||||
);
|
||||
assert!(
|
||||
error.exception_id().is_some(),
|
||||
"the portable token may outlive its realm"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn module_parse_error_compilation_and_resolution_use_the_child_realm() {
|
||||
let mut vm = new_test_vm("https://module-errors.test/page.html");
|
||||
vm.eval(
|
||||
r#"
|
||||
const root = document.appendChild(document.createElement('html'));
|
||||
const body = root.appendChild(document.createElement('body'));
|
||||
globalThis.frame = document.createElement('iframe');
|
||||
frame.srcdoc = '<script>parent.__moduleErrorFrameReady = true;<\/script>';
|
||||
body.appendChild(frame);
|
||||
'ready'
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
commit_child_document_and_run_parser_script_for_dynamic_import_test(
|
||||
&mut vm,
|
||||
"module error child",
|
||||
)
|
||||
.await;
|
||||
finish_child_document_after_parser_script_for_dynamic_import_test(
|
||||
&mut vm,
|
||||
"module error child",
|
||||
)
|
||||
.await;
|
||||
let realm_id = vm
|
||||
.child_frame_realm_store
|
||||
.values()
|
||||
.next()
|
||||
.unwrap()
|
||||
.owner_realm_id;
|
||||
let url = Url::parse("https://module-errors.test/bad.mjs").unwrap();
|
||||
let syntax_error = vm
|
||||
.compile_native_module_record_for_frame_realm(
|
||||
realm_id,
|
||||
ModuleMapKey::java_script(url.clone()),
|
||||
&ModuleSource::text("export const = ;".to_owned()),
|
||||
&url,
|
||||
&ModuleFetchMetadata::default(),
|
||||
)
|
||||
.unwrap_err();
|
||||
let resolution_error = vm
|
||||
.preserve_native_module_load_error(
|
||||
Some(realm_id),
|
||||
ModuleLoadError::new(ModuleLoadStage::Resolve, "unmapped dependency")
|
||||
.with_error_constructor(ScriptErrorConstructorKind::TypeError),
|
||||
)
|
||||
.unwrap();
|
||||
let (syntax, resolution) = vm
|
||||
.with_frame_realm_scope_and_checkpoint_for_test(realm_id, |scope, _| {
|
||||
let syntax = module_load_error_value(scope, &syntax_error)?;
|
||||
let resolution = module_load_error_value(scope, &resolution_error)?;
|
||||
Ok((
|
||||
v8::Global::new(scope, syntax),
|
||||
v8::Global::new(scope, resolution),
|
||||
))
|
||||
})
|
||||
.unwrap();
|
||||
vm.with_default_context_scope_and_checkpoint_for_test(|scope, _| {
|
||||
assert!(module_load_error_value(scope, &syntax_error).is_err());
|
||||
assert!(module_load_error_value(scope, &resolution_error).is_err());
|
||||
let global = scope.get_current_context().global(scope);
|
||||
let syntax = v8::Local::new(scope, &syntax);
|
||||
let resolution = v8::Local::new(scope, &resolution);
|
||||
assert_eq!(
|
||||
global.set(scope, v8str(scope, "__childSyntaxError").into(), syntax),
|
||||
Some(true)
|
||||
);
|
||||
assert_eq!(
|
||||
global.set(
|
||||
scope,
|
||||
v8str(scope, "__childResolutionError").into(),
|
||||
resolution
|
||||
),
|
||||
Some(true)
|
||||
);
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
vm.eval(
|
||||
r#"
|
||||
__childSyntaxError instanceof frame.contentWindow.SyntaxError &&
|
||||
!(__childSyntaxError instanceof SyntaxError) &&
|
||||
__childResolutionError instanceof frame.contentWindow.TypeError &&
|
||||
!(__childResolutionError instanceof TypeError)
|
||||
"#
|
||||
)
|
||||
.unwrap(),
|
||||
"true"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn module_parse_error_registry_does_not_follow_a_reused_window_proxy() {
|
||||
ensure_v8();
|
||||
let mut isolate = v8::Isolate::new(Default::default());
|
||||
let (current_context, old_context, old_exception) = {
|
||||
let scope = pin!(v8::HandleScope::new(&mut isolate));
|
||||
let scope = &mut scope.init();
|
||||
let context = v8::Context::new(scope, Default::default());
|
||||
let (proxy, old_context, old_exception, error) = {
|
||||
let scope = &mut v8::ContextScope::new(scope, context);
|
||||
let exception = v8::Exception::syntax_error(scope, v8str(scope, "old module"));
|
||||
let id = retain_module_exception(scope, exception).unwrap();
|
||||
let error =
|
||||
ModuleLoadError::new(ModuleLoadStage::Compile, "old module").with_exception_id(id);
|
||||
(
|
||||
context.global(scope),
|
||||
v8::Weak::new(scope, context),
|
||||
v8::Weak::new(scope, exception),
|
||||
error,
|
||||
)
|
||||
};
|
||||
context.detach_global();
|
||||
let replacement = v8::Context::new(
|
||||
scope,
|
||||
v8::ContextOptions {
|
||||
global_object: Some(proxy.into()),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
let scope = &mut v8::ContextScope::new(scope, replacement);
|
||||
assert!(replacement.global(scope).strict_equals(proxy.into()));
|
||||
assert!(
|
||||
module_load_error_value(scope, &error).is_err(),
|
||||
"a new realm must not inherit the previous module cache via WindowProxy"
|
||||
);
|
||||
(
|
||||
v8::Global::new(scope, replacement),
|
||||
old_context,
|
||||
old_exception,
|
||||
)
|
||||
};
|
||||
isolate.low_memory_notification();
|
||||
assert!(
|
||||
old_context.is_empty(),
|
||||
"the current WindowProxy must not root the old realm"
|
||||
);
|
||||
assert!(
|
||||
old_exception.is_empty(),
|
||||
"the current realm must not retain old module errors"
|
||||
);
|
||||
drop(current_context);
|
||||
}
|
||||
Reference in New Issue
Block a user