fix(modules): preserve original exceptions through error reporting

This commit is contained in:
ldm0
2026-09-27 07:59:14 +08:00
parent 37cd9ae930
commit 425f575a67
39 changed files with 652 additions and 318 deletions
@@ -6,10 +6,13 @@
window.addEventListener("error", (event) => {
window.dynamicModuleTlaPayloadOrder.push("window-error");
window.dynamicModuleTlaPayloadErrorMessageMatches = Boolean(
event.error &&
typeof event.error.message === "string" &&
event.error.message.length > 0,
typeof event.message === "string" && event.message.length > 0,
);
window.dynamicModuleTlaPayloadErrorIdentityMatches =
event.error === window.dynamicModuleTlaPayloadReason &&
Object.getPrototypeOf(event.error) === null &&
!Object.hasOwn(event.error, "message") &&
!Object.hasOwn(event.error, "fileName");
window.dynamicModuleTlaPayloadFilenameMatches =
typeof event.filename === "string" &&
event.filename.includes(
@@ -23,7 +26,7 @@
window.dynamicModuleTlaPayloadOrder.push("script-error");
};
broken.text =
'const reason = Object.create(null); await Promise.reject(reason);';
'const reason = window.dynamicModuleTlaPayloadReason = Object.create(null); await Promise.reject(reason);';
document.body.appendChild(broken);
const later = document.createElement("script");
@@ -6,10 +6,13 @@
window.addEventListener("error", (event) => {
window.parserOwnedModuleTlaPayloadOrder.push("window-error");
window.parserOwnedModuleTlaPayloadErrorMessageMatches = Boolean(
event.error &&
typeof event.error.message === "string" &&
event.error.message.length > 0,
typeof event.message === "string" && event.message.length > 0,
);
window.parserOwnedModuleTlaPayloadErrorIdentityMatches =
event.error === window.parserOwnedModuleTlaPayloadReason &&
Object.getPrototypeOf(event.error) === null &&
!Object.hasOwn(event.error, "message") &&
!Object.hasOwn(event.error, "fileName");
window.parserOwnedModuleTlaPayloadFilenameMatches =
typeof event.filename === "string" &&
event.filename.includes(
@@ -21,7 +24,7 @@
type="module"
onerror="window.parserOwnedModuleTlaPayloadOrder.push('script-error');"
>
const reason = Object.create(null);
const reason = window.parserOwnedModuleTlaPayloadReason = Object.create(null);
await Promise.reject(reason);
</script>
<script type="module">
+9 -1
View File
@@ -3151,6 +3151,10 @@ async fn dynamic_module_tla_exotic_rejection_reports_window_error_payload_withou
diagnostic_global(&page, "dynamicModuleTlaPayloadErrorMessageMatches"),
Some(&JsValueSnapshot::Bool(true))
);
assert_eq!(
diagnostic_global(&page, "dynamicModuleTlaPayloadErrorIdentityMatches"),
Some(&JsValueSnapshot::Bool(true))
);
assert_eq!(
diagnostic_global(&page, "dynamicModuleTlaPayloadFilenameMatches"),
Some(&JsValueSnapshot::Bool(true))
@@ -3839,6 +3843,10 @@ async fn parser_owned_module_tla_exotic_rejection_reports_window_error_payload_w
diagnostic_global(&page, "parserOwnedModuleTlaPayloadErrorMessageMatches"),
Some(&JsValueSnapshot::Bool(true))
);
assert_eq!(
diagnostic_global(&page, "parserOwnedModuleTlaPayloadErrorIdentityMatches"),
Some(&JsValueSnapshot::Bool(true))
);
assert_eq!(
diagnostic_global(&page, "parserOwnedModuleTlaPayloadFilenameMatches"),
Some(&JsValueSnapshot::Bool(true))
@@ -5377,7 +5385,7 @@ async fn module_wasm_js_cycle_reports_guard_without_crashing() -> Result<()> {
.await?;
assert_eq!(
diagnostic_global(&page, "moduleWasmJsCycleConstructor"),
Some(&JsValueSnapshot::String("Error".to_owned())),
Some(&JsValueSnapshot::String("TypeError".to_owned())),
"message: {:?}",
diagnostic_global(&page, "moduleWasmJsCycleMessage")
);
+25 -1
View File
@@ -698,6 +698,30 @@ struct DependencyCandidate {
}
fn is_parse_error(error: &ModuleLoadError) -> bool {
error.exception_id.is_some()
(error.exception_id.is_some()
&& matches!(
error.stage,
ModuleLoadStage::Compile | ModuleLoadStage::Resolve
))
|| error.error_constructor == Some(ModuleErrorConstructorKind::SyntaxError)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn retained_values_do_not_turn_link_or_evaluation_failures_into_parse_errors() {
for (stage, expected) in [
(ModuleLoadStage::Compile, true),
(ModuleLoadStage::Resolve, true),
(ModuleLoadStage::Fetch, false),
(ModuleLoadStage::Instantiate, false),
(ModuleLoadStage::Evaluate, false),
] {
let error = ModuleLoadError::new(stage, "retained error")
.with_exception_id(crate::ModuleExceptionId(1));
assert_eq!(is_parse_error(&error), expected, "{stage:?}");
}
}
}
@@ -1,4 +1,4 @@
use crate::types::ScriptErrorConstructorKind;
use crate::types::{ScriptErrorConstructorKind, ScriptErrorValue};
use moli_module_script_tree::ModuleExceptionId;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -60,6 +60,17 @@ impl ModuleLoadError {
&self.message
}
pub(crate) fn with_message(mut self, message: String) -> Self {
self.message = message;
self
}
pub(crate) fn error_value(&self) -> Option<ScriptErrorValue> {
self.exception_id
.map(ScriptErrorValue::Retained)
.or_else(|| self.error_constructor.map(ScriptErrorValue::Constructor))
}
pub(crate) fn error_constructor(&self) -> Option<ScriptErrorConstructorKind> {
self.error_constructor
}
@@ -8,7 +8,7 @@ use crate::host::{
};
use crate::page_task_queue::PostParseLifecycleWork;
use crate::planning::PreparedScript;
use crate::types::{ScriptErrorConstructorKind, ScriptKind};
use crate::types::{ScriptErrorValue, ScriptKind};
use tracing::warn;
impl DocumentRuntime {
@@ -156,7 +156,7 @@ impl DocumentRuntime {
script: &PreparedScript,
message: &str,
module_failure_policy: Option<ModuleFailurePolicy>,
error_constructor: Option<ScriptErrorConstructorKind>,
error_value: Option<ScriptErrorValue>,
) -> Vec<PostParseLifecycleWork> {
if script.host_script_handle.is_none()
&& matches!(script.kind, ScriptKind::Module | ScriptKind::ImportMap)
@@ -186,7 +186,7 @@ impl DocumentRuntime {
message,
Some(script.url.as_str()),
module_failure_policy,
error_constructor,
error_value,
)
}
@@ -196,17 +196,12 @@ impl DocumentRuntime {
script: &PreparedScript,
message: &str,
module_failure_policy: Option<ModuleFailurePolicy>,
error_constructor: Option<ScriptErrorConstructorKind>,
error_value: Option<ScriptErrorValue>,
) -> Vec<PageTask> {
self.plan_script_failure_lifecycle_work(
script,
message,
module_failure_policy,
error_constructor,
)
.into_iter()
.map(PostParseLifecycleWork::into_page_task)
.collect()
self.plan_script_failure_lifecycle_work(script, message, module_failure_policy, error_value)
.into_iter()
.map(PostParseLifecycleWork::into_page_task)
.collect()
}
pub(crate) fn enqueue_parser_boundary_lifecycle_work(&mut self, work: PostParseLifecycleWork) {
@@ -6,7 +6,7 @@ use crate::{
PreparedScript, PreparedScriptSourceLoadOutcome, SharedScriptSourceLoad,
prepared_script_with_loaded_source,
},
types::{ScriptErrorConstructorKind, SharedNavigationResponseResult},
types::{ScriptErrorValue, SharedNavigationResponseResult},
};
use super::{DocumentScriptExecutionLane, DocumentScriptSourceFailureLane};
@@ -21,7 +21,7 @@ use super::{DocumentScriptExecutionLane, DocumentScriptSourceFailureLane};
pub(crate) struct PageOwnedDocumentScriptSourceFailure {
message: String,
module_failure_policy: Option<ModuleFailurePolicy>,
error_constructor: Option<ScriptErrorConstructorKind>,
error_value: Option<ScriptErrorValue>,
}
impl PageOwnedDocumentScriptSourceFailure {
@@ -29,19 +29,19 @@ impl PageOwnedDocumentScriptSourceFailure {
Self {
message,
module_failure_policy: None,
error_constructor: None,
error_value: None,
}
}
pub(crate) fn runtime_terminal(
message: String,
module_failure_policy: Option<ModuleFailurePolicy>,
error_constructor: Option<ScriptErrorConstructorKind>,
error_value: Option<ScriptErrorValue>,
) -> Self {
Self {
message,
module_failure_policy,
error_constructor,
error_value,
}
}
@@ -55,13 +55,9 @@ impl PageOwnedDocumentScriptSourceFailure {
) -> (
String,
Option<ModuleFailurePolicy>,
Option<ScriptErrorConstructorKind>,
Option<ScriptErrorValue>,
) {
(
self.message,
self.module_failure_policy,
self.error_constructor,
)
(self.message, self.module_failure_policy, self.error_value)
}
}
@@ -19,7 +19,7 @@ use crate::parser_module_evaluation::{
use crate::parser_script::action::ParserClassicScriptNextOwnerAction;
use crate::planning::{PreparedScript, SharedScriptSourceLoad};
use crate::stylesheet_blocking::DocumentBlockingStylesheetSignature;
use crate::types::ScriptErrorConstructorKind;
use crate::types::ScriptErrorValue;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) struct ParserModuleEvaluationReactionUpdate {
@@ -620,7 +620,7 @@ where
&mut self,
reaction_id: u64,
reason: String,
error_constructor: Option<ScriptErrorConstructorKind>,
error_value: Option<ScriptErrorValue>,
mut convert: Convert,
) -> Option<ParserModuleEvaluationReactionUpdate>
where
@@ -631,7 +631,7 @@ where
{
let root_entry =
self.parser_module_evaluations
.mark_rejected(reaction_id, reason, error_constructor)?;
.mark_rejected(reaction_id, reason, error_value)?;
let queued_ready_action_count = self.promote_ready_parser_module_evaluations(&mut convert);
Some(ParserModuleEvaluationReactionUpdate {
root_entry,
+36 -42
View File
@@ -24,8 +24,7 @@ use super::{
prepared_script_with_loaded_source,
},
types::{
ScriptErrorConstructorKind, ScriptKind, ScriptMode, ScriptSourceKind,
SharedNavigationResponseResult,
ScriptErrorValue, ScriptKind, ScriptMode, ScriptSourceKind, SharedNavigationResponseResult,
},
};
use crate::frame_owner_model::MainDocumentScriptLoadDelayLease;
@@ -165,7 +164,7 @@ struct DynamicScriptFailure {
kind: DynamicScriptFailureKind,
module_failure_policy: Option<ModuleFailurePolicy>,
source_network_result: Option<SharedNavigationResponseResult>,
error_constructor: Option<ScriptErrorConstructorKind>,
error_value: Option<ScriptErrorValue>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -187,7 +186,7 @@ impl DynamicScriptFailure {
kind,
module_failure_policy,
source_network_result: None,
error_constructor: None,
error_value: None,
}
}
@@ -199,11 +198,8 @@ impl DynamicScriptFailure {
self
}
fn with_error_constructor(
mut self,
error_constructor: Option<ScriptErrorConstructorKind>,
) -> Self {
self.error_constructor = error_constructor;
fn with_error_value(mut self, error_value: Option<ScriptErrorValue>) -> Self {
self.error_value = error_value;
self
}
@@ -335,7 +331,7 @@ pub(super) enum DynamicScriptRunnable {
kind: DynamicScriptFailureKind,
module_failure_policy: Option<ModuleFailurePolicy>,
source_network_result: Option<SharedNavigationResponseResult>,
error_constructor: Option<ScriptErrorConstructorKind>,
error_value: Option<ScriptErrorValue>,
},
}
@@ -354,7 +350,7 @@ pub(super) struct DynamicScriptFailureTerminal {
pub(super) kind: DynamicScriptFailureKind,
pub(super) module_failure_policy: Option<ModuleFailurePolicy>,
pub(super) source_network_result: Option<SharedNavigationResponseResult>,
pub(super) error_constructor: Option<ScriptErrorConstructorKind>,
pub(super) error_value: Option<ScriptErrorValue>,
}
#[derive(Debug)]
@@ -582,7 +578,7 @@ impl DynamicScriptOwner {
kind,
module_failure_policy,
source_network_result,
error_constructor,
error_value,
}) = self.followup_work.pop_front()
else {
unreachable!("owned error terminal changed after an immutable front check")
@@ -594,7 +590,7 @@ impl DynamicScriptOwner {
kind,
module_failure_policy,
source_network_result,
error_constructor,
error_value,
})
}
@@ -642,33 +638,33 @@ impl DynamicScriptOwner {
message: String,
kind: DynamicScriptFailureKind,
module_failure_policy: Option<ModuleFailurePolicy>,
error_constructor: Option<ScriptErrorConstructorKind>,
error_value: Option<ScriptErrorValue>,
) {
self.note_script_failed_with_kind_and_error_constructor(
self.note_script_failed_with_kind_and_error_value(
id,
script,
message,
kind,
module_failure_policy,
error_constructor,
error_value,
);
}
pub(super) fn note_script_failed_with_kind_and_error_constructor(
pub(super) fn note_script_failed_with_kind_and_error_value(
&mut self,
id: DynamicScriptOwnerId,
script: &PreparedScript,
message: String,
kind: DynamicScriptFailureKind,
module_failure_policy: Option<ModuleFailurePolicy>,
error_constructor: Option<ScriptErrorConstructorKind>,
error_value: Option<ScriptErrorValue>,
) {
assert!(
script.host_script_handle.is_some(),
"runtime dynamic script should carry host handle before failure dispatch planning"
);
let failure = DynamicScriptFailure::with_kind(message, kind, module_failure_policy)
.with_error_constructor(error_constructor);
.with_error_value(error_value);
if failure.is_deferrable_module() {
self.note_script_failed_in_queue_or_enqueue(id, script.clone(), failure);
self.refresh_followup_work();
@@ -1189,13 +1185,13 @@ impl DynamicScriptOwner {
&mut self,
reaction_id: u64,
reason: String,
error_constructor: Option<ScriptErrorConstructorKind>,
error_value: Option<ScriptErrorValue>,
) -> Option<ModuleScriptEvaluationUpdate> {
let update = self.mark_module_script_evaluation_reaction(
reaction_id,
ModuleScriptEvaluationReactionState::Rejected {
reason,
error_constructor,
error_value,
},
);
if update.is_some() {
@@ -1279,21 +1275,21 @@ impl DynamicScriptOwner {
message: String,
kind: DynamicScriptFailureKind,
module_failure_policy: Option<ModuleFailurePolicy>,
error_constructor: Option<ScriptErrorConstructorKind>,
error_value: Option<ScriptErrorValue>,
source_network_result: Option<SharedNavigationResponseResult>,
) {
self.requeue_failed_script_front_with_error_constructor(
self.requeue_failed_script_front_with_error_value(
id,
script,
message,
kind,
module_failure_policy,
source_network_result,
error_constructor,
error_value,
);
}
pub(super) fn requeue_failed_script_front_with_error_constructor(
pub(super) fn requeue_failed_script_front_with_error_value(
&mut self,
id: DynamicScriptOwnerId,
script: PreparedScript,
@@ -1301,11 +1297,11 @@ impl DynamicScriptOwner {
kind: DynamicScriptFailureKind,
module_failure_policy: Option<ModuleFailurePolicy>,
source_network_result: Option<SharedNavigationResponseResult>,
error_constructor: Option<ScriptErrorConstructorKind>,
error_value: Option<ScriptErrorValue>,
) {
let failure = DynamicScriptFailure::with_kind(message, kind, module_failure_policy)
.with_source_network_result(source_network_result)
.with_error_constructor(error_constructor);
.with_error_value(error_value);
self.requeue_script_failure_front(id, script, failure);
}
@@ -1952,11 +1948,11 @@ impl DynamicScriptOwner {
kind,
module_failure_policy,
source_network_result,
error_constructor,
error_value,
} => {
let failure = DynamicScriptFailure::with_kind(message, kind, module_failure_policy)
.with_source_network_result(source_network_result)
.with_error_constructor(error_constructor);
.with_error_value(error_value);
if failure.is_deferrable_module() {
self.enqueue_script_failure_with_id(id, script, failure);
return;
@@ -2166,7 +2162,7 @@ impl DynamicScriptOwner {
kind: failure.kind,
module_failure_policy: failure.module_failure_policy,
source_network_result: failure.source_network_result,
error_constructor: failure.error_constructor,
error_value: failure.error_value,
})
}
DynamicScriptReadyState::Loading
@@ -3482,7 +3478,7 @@ mod tests {
"opaque typed module graph failure".to_owned(),
DynamicScriptFailureKind::ModuleResolve,
Some(ModuleFailurePolicy::GraphFailure),
Some(ScriptErrorConstructorKind::SyntaxError),
Some(crate::types::ScriptErrorConstructorKind::SyntaxError.into()),
);
let second = owner
@@ -3499,7 +3495,7 @@ mod tests {
let DynamicScriptRunnable::DispatchError {
id: failure_id,
message,
error_constructor,
error_value,
..
} = failure
else {
@@ -3508,8 +3504,8 @@ mod tests {
assert_eq!(failure_id, first_id);
assert_eq!(message, "opaque typed module graph failure");
assert_eq!(
error_constructor,
Some(ScriptErrorConstructorKind::SyntaxError),
error_value,
Some(crate::types::ScriptErrorConstructorKind::SyntaxError.into()),
"deferred dynamic module failures must retain their original error constructor"
);
}
@@ -3815,7 +3811,7 @@ mod tests {
}
#[test]
fn owner_terminal_failure_preserves_error_constructor() {
fn owner_terminal_failure_preserves_error_value() {
let mut owner = DynamicScriptOwner::default();
owner.enqueue_batch(DynamicScriptBatch {
async_scripts: VecDeque::from([prepared_script(0, ScriptMode::Async)]),
@@ -3828,26 +3824,24 @@ mod tests {
panic!("expected executable dynamic script");
};
owner.note_script_failed_with_kind_and_error_constructor(
owner.note_script_failed_with_kind_and_error_value(
id,
&script,
"typed failure".to_owned(),
DynamicScriptFailureKind::Immediate,
None,
Some(ScriptErrorConstructorKind::SyntaxError),
Some(crate::types::ScriptErrorConstructorKind::SyntaxError.into()),
);
let DynamicScriptRunnable::DispatchError {
error_constructor, ..
} = owner
let DynamicScriptRunnable::DispatchError { error_value, .. } = owner
.next_runnable_script()
.expect("typed failure should become owner terminal work")
else {
panic!("expected dynamic script error dispatch");
};
assert_eq!(
error_constructor,
Some(ScriptErrorConstructorKind::SyntaxError)
error_value,
Some(crate::types::ScriptErrorConstructorKind::SyntaxError.into())
);
}
+8 -8
View File
@@ -21,7 +21,7 @@ use crate::{
{
module_runtime::ModuleOwnerState,
planning::{PreparedScript, ScriptSource},
types::{ScriptErrorConstructorKind, ScriptKind, ScriptMode, ScriptSourceKind},
types::{ScriptErrorValue, ScriptKind, ScriptMode, ScriptSourceKind},
},
};
use std::collections::HashMap;
@@ -1077,13 +1077,13 @@ impl HostScriptScheduler {
&self,
message: &str,
filename: Option<&str>,
error_constructor: Option<ScriptErrorConstructorKind>,
error_value: Option<ScriptErrorValue>,
) -> PostParseLifecycleWork {
PostParseLifecycleWork::ReportWindowScriptFailure(
WindowScriptFailureReportTask::new_with_error_constructor(
WindowScriptFailureReportTask::new_with_error_value(
message,
filename.map(std::borrow::ToOwned::to_owned),
error_constructor,
error_value,
),
)
}
@@ -1096,7 +1096,7 @@ impl HostScriptScheduler {
message: &str,
filename: Option<&str>,
module_failure_policy: Option<ModuleFailurePolicy>,
error_constructor: Option<ScriptErrorConstructorKind>,
error_value: Option<ScriptErrorValue>,
) -> Vec<PostParseLifecycleWork> {
let mut tasks = Vec::new();
let message = normalize_module_link_failure_message(message, filename);
@@ -1130,7 +1130,7 @@ impl HostScriptScheduler {
tasks.push(self.plan_window_script_failure_report_lifecycle_work(
&message,
filename,
error_constructor,
error_value,
));
}
@@ -1166,7 +1166,7 @@ impl HostScriptScheduler {
message: &str,
filename: Option<&str>,
module_failure_policy: Option<ModuleFailurePolicy>,
error_constructor: Option<ScriptErrorConstructorKind>,
error_value: Option<ScriptErrorValue>,
) -> Vec<PageTask> {
self.plan_script_failure_lifecycle_work(
kind,
@@ -1175,7 +1175,7 @@ impl HostScriptScheduler {
message,
filename,
module_failure_policy,
error_constructor,
error_value,
)
.into_iter()
.map(PostParseLifecycleWork::into_page_task)
+12 -16
View File
@@ -131,22 +131,18 @@ impl ScriptFailurePageTaskPolicy {
report_window_failure: false,
load_event_after_window_failure: false,
},
ModuleFailurePolicy::GraphFailure => Self {
load_event: event_policy.dispatch_policy(ScriptEventKind::Load),
error_event: ScriptEventDispatchPolicy::Skip(
ScriptEventSkipReason::ModuleGraphFailure,
),
report_window_failure: true,
load_event_after_window_failure: true,
},
ModuleFailurePolicy::EvaluationFailure => Self {
load_event: event_policy.dispatch_policy(ScriptEventKind::Load),
error_event: ScriptEventDispatchPolicy::Skip(
ScriptEventSkipReason::ModuleGraphFailure,
),
report_window_failure: true,
load_event_after_window_failure: false,
},
// A script with a fetched module graph still completes its
// external-script load step when running that graph rejects.
ModuleFailurePolicy::GraphFailure | ModuleFailurePolicy::EvaluationFailure => {
Self {
load_event: event_policy.dispatch_policy(ScriptEventKind::Load),
error_event: ScriptEventDispatchPolicy::Skip(
ScriptEventSkipReason::ModuleGraphFailure,
),
report_window_failure: true,
load_event_after_window_failure: true,
}
}
};
}
Self {
+1 -1
View File
@@ -2473,7 +2473,7 @@ fn explicit_module_failure_policy_overrides_opaque_message_text() {
load_event: ScriptEventDispatchPolicy::Dispatch,
error_event: ScriptEventDispatchPolicy::Skip(ScriptEventSkipReason::ModuleGraphFailure),
report_window_failure: true,
load_event_after_window_failure: false,
load_event_after_window_failure: true,
}
);
}
@@ -13,7 +13,7 @@ use crate::dom::NodeId;
use crate::dynamic_script_owner::DynamicScriptOwnerId;
use crate::frame_owner_model::{FrameDocumentTaskOwner, MainDocumentScriptLoadDelayLease};
use crate::planning::PreparedScript;
use crate::types::ScriptErrorConstructorKind;
use crate::types::ScriptErrorValue;
pub(crate) struct ModuleScriptContinuation {
pub(crate) script: PreparedScript,
@@ -830,7 +830,7 @@ pub(crate) enum ModuleScriptEvaluationReactionState {
Fulfilled,
Rejected {
reason: String,
error_constructor: Option<ScriptErrorConstructorKind>,
error_value: Option<ScriptErrorValue>,
},
}
@@ -1132,14 +1132,14 @@ impl JsContextHost {
document_owner: FrameDocumentTaskOwner,
reaction_id: u64,
reason: String,
error_constructor: Option<ScriptErrorConstructorKind>,
error_value: Option<crate::types::ScriptErrorValue>,
) {
self.queue_page_module_reaction(
RendererPageModuleReactionEvent::DocumentModuleScriptEvaluationRejected {
document_owner,
reaction_id,
reason,
error_constructor,
error_value,
},
);
}
@@ -1165,7 +1165,7 @@ impl JsContextHost {
realm_id: crate::frame_owner_model::FrameRealmId,
reaction_id: u64,
reason: String,
error_constructor: Option<ScriptErrorConstructorKind>,
error_value: Option<crate::types::ScriptErrorValue>,
) {
self.queue_page_module_reaction(
RendererPageModuleReactionEvent::ChildParserModuleEvaluationRejected {
@@ -1173,7 +1173,7 @@ impl JsContextHost {
realm_id,
reaction_id,
reason,
error_constructor,
error_value,
},
);
}
@@ -41,8 +41,8 @@ use crate::{
InFlightWorkerSubresourceFetchState, MessagePortId, NetworkBodySourceId,
PendingSubresourceAuthState, PendingSubresourceFetchState,
PendingSubresourceResponseState, PendingWebSocketResponseState,
RunningSubresourceFetchState, ScriptErrorConstructorKind, ScriptNetworkOutputItem,
StreamingSubresourceFetchState, SubresourceResourceType,
RunningSubresourceFetchState, ScriptNetworkOutputItem, StreamingSubresourceFetchState,
SubresourceResourceType,
},
util::{
string_from_utf16_units_lossy, utf16_units, utf16_units_contain_unpaired_surrogate,
@@ -7,7 +7,7 @@ use crate::{
module_runtime::DynamicModuleImportOwner,
resource_ready::{ReadyPageTask, RendererPageTaskReadyMetadata},
runtime::{PageOwnerTurnOutcome, RendererDocumentToken},
types::ScriptErrorConstructorKind,
types::ScriptErrorValue,
};
use super::{RendererOwnerWakeSender, RendererOwnerWakeSource, RendererPageTaskReadySignal};
@@ -43,7 +43,7 @@ pub(crate) enum RendererPageModuleReactionEvent {
document_owner: FrameDocumentTaskOwner,
reaction_id: u64,
reason: String,
error_constructor: Option<ScriptErrorConstructorKind>,
error_value: Option<ScriptErrorValue>,
},
ChildParserModuleEvaluationFulfilled {
document_owner: FrameDocumentTaskOwner,
@@ -55,7 +55,7 @@ pub(crate) enum RendererPageModuleReactionEvent {
realm_id: FrameRealmId,
reaction_id: u64,
reason: String,
error_constructor: Option<ScriptErrorConstructorKind>,
error_value: Option<ScriptErrorValue>,
},
DynamicModuleEvaluationFulfilled {
import_owner: DynamicModuleImportOwner,
@@ -120,17 +120,17 @@ impl fmt::Debug for RendererPageModuleReactionEvent {
match self {
Self::DocumentModuleScriptEvaluationRejected {
reason,
error_constructor,
error_value,
..
}
| Self::ChildParserModuleEvaluationRejected {
reason,
error_constructor,
error_value,
..
} => {
debug
.field("reason", reason)
.field("error_constructor", error_constructor);
.field("error_value", error_value);
}
Self::DynamicModuleEvaluationRejected { .. } => {
debug.field("reason", &"<v8::Global<Value>>");
@@ -6,7 +6,7 @@ use crate::{
host::ScriptEventTask,
planning::PreparedScript,
stylesheet_blocking::DocumentOwnedBlockingStylesheetDiscoveryInput,
types::ScriptErrorConstructorKind,
types::ScriptErrorValue,
types::ScriptRun,
};
use url::Url;
@@ -70,23 +70,23 @@ pub(crate) enum PageOwnedInternalLoadingTaskEffect {
pub(crate) struct WindowScriptFailureReportTask {
pub(crate) message: String,
pub(crate) filename: Option<String>,
pub(crate) error_constructor: Option<ScriptErrorConstructorKind>,
pub(crate) error_value: Option<ScriptErrorValue>,
}
impl WindowScriptFailureReportTask {
pub(crate) fn new(message: impl Into<String>, filename: Option<String>) -> Self {
Self::new_with_error_constructor(message, filename, None)
Self::new_with_error_value(message, filename, None)
}
pub(crate) fn new_with_error_constructor(
pub(crate) fn new_with_error_value(
message: impl Into<String>,
filename: Option<String>,
error_constructor: Option<ScriptErrorConstructorKind>,
error_value: Option<ScriptErrorValue>,
) -> Self {
Self {
message: message.into(),
filename,
error_constructor,
error_value,
}
}
}
@@ -4,7 +4,7 @@ use crate::document_script_scheduler::{
use crate::document_task_lane::DocumentTaskQueue;
use crate::module_runtime::ModuleEntryId;
use crate::module_script_continuation::ModuleScriptEvaluationReactionState;
use crate::types::ScriptErrorConstructorKind;
use crate::types::ScriptErrorValue;
#[derive(Debug)]
pub(crate) struct ParserModuleEvaluationContinuation<Work> {
@@ -150,7 +150,7 @@ impl<Work> ParserModuleEvaluationStore<Work> {
&mut self,
reaction_id: u64,
reason: String,
error_constructor: Option<ScriptErrorConstructorKind>,
error_value: Option<ScriptErrorValue>,
) -> Option<ModuleEntryId> {
let evaluation = self
.evaluations
@@ -158,7 +158,7 @@ impl<Work> ParserModuleEvaluationStore<Work> {
.find(|evaluation| evaluation.reaction_id == reaction_id)?;
evaluation.reaction_state = ModuleScriptEvaluationReactionState::Rejected {
reason,
error_constructor,
error_value,
};
Some(evaluation.root_entry)
}
+34 -41
View File
@@ -36,7 +36,7 @@ use crate::script_vm::{
PostParseLifecycleAdvance, PostParsePageOwnedTask, RendererDocumentIsolateHandle,
};
use crate::script_vm::{PreparedScriptExecutionOutcome, RendererDocumentIsolateBootstrap};
use crate::types::ScriptErrorConstructorKind;
use crate::types::ScriptErrorValue;
use crate::types::ScriptSkipReason;
use moli_page_types::{
ContentSecurityPolicyIssueSnapshot, ContentSecurityPolicyViolationType, InspectorIssueSnapshot,
@@ -424,14 +424,8 @@ fn wrap_native_esm_module_load_error(
prefix: &str,
error: crate::module_runtime::ModuleLoadError,
) -> crate::module_runtime::ModuleLoadError {
let wrapped = crate::module_runtime::ModuleLoadError::new(
error.stage(),
format!("{prefix}: {}", error.message()),
);
match error.error_constructor() {
Some(error_constructor) => wrapped.with_error_constructor(error_constructor),
None => wrapped,
}
let message = format!("{prefix}: {}", error.message());
error.with_message(message)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
@@ -484,7 +478,7 @@ enum PageOwnedScriptFailureClassification {
Typed {
dynamic_kind: crate::dynamic_script_owner::DynamicScriptFailureKind,
module_failure_policy: Option<crate::host::ModuleFailurePolicy>,
error_constructor: Option<ScriptErrorConstructorKind>,
error_value: Option<ScriptErrorValue>,
},
}
@@ -501,7 +495,7 @@ impl PageOwnedScriptFailureClassification {
script, stage,
),
module_failure_policy: error.module_failure_policy(),
error_constructor: error.error_constructor(),
error_value: error.error_value(),
},
)
}
@@ -517,7 +511,7 @@ impl PageOwnedScriptFailureClassification {
error.stage(),
),
module_failure_policy: Some(module_failure_policy),
error_constructor: error.error_constructor(),
error_value: error.error_value(),
}
}
}
@@ -595,14 +589,14 @@ fn complete_prepared_script_execution_failure(
PageOwnedScriptFailureClassification::Typed {
dynamic_kind,
module_failure_policy,
error_constructor,
error_value,
} => vm.finish_runtime_owned_script_failure_with_kind(
dynamic_script_owner_id,
&script,
&error,
dynamic_kind,
module_failure_policy,
error_constructor,
error_value,
),
PageOwnedScriptFailureClassification::LegacyMessageText => {
vm.finish_runtime_owned_script_failure(dynamic_script_owner_id, &script, &error);
@@ -614,34 +608,34 @@ fn complete_prepared_script_execution_failure(
Some(script.url.as_str()),
);
} else if vm.parser_owned_module_reports_failure_immediately(&script) {
let (module_failure_policy, error_constructor) = match failure_classification {
let (module_failure_policy, error_value) = match failure_classification {
PageOwnedScriptFailureClassification::Typed {
module_failure_policy,
error_constructor,
error_value,
..
} => (module_failure_policy, error_constructor),
} => (module_failure_policy, error_value),
PageOwnedScriptFailureClassification::LegacyMessageText => (None, None),
};
vm.dispatch_parser_owned_module_failure_and_finish_settlement_best_effort(
&script,
&error,
module_failure_policy,
error_constructor,
error_value,
);
} else {
let (module_failure_policy, error_constructor) = match failure_classification {
let (module_failure_policy, error_value) = match failure_classification {
PageOwnedScriptFailureClassification::Typed {
module_failure_policy,
error_constructor,
error_value,
..
} => (module_failure_policy, error_constructor),
} => (module_failure_policy, error_value),
PageOwnedScriptFailureClassification::LegacyMessageText => (None, None),
};
vm.enqueue_script_failure_lifecycle_work_best_effort(
&script,
&error,
module_failure_policy,
error_constructor,
error_value,
);
}
complete_prepared_script_execution_failure_report(script, error)
@@ -657,13 +651,12 @@ fn complete_page_owned_prepared_script_execution_failure_body(
prepared_script_activity: crate::script_vm::PreparedScriptBodyActivity,
) -> PageOwnedScriptExecutionOutcome {
let terminal_activity = if completion_owner.is_runtime_owned() {
let (dynamic_kind, module_failure_policy, error_constructor) = match failure_classification
{
let (dynamic_kind, module_failure_policy, error_value) = match failure_classification {
PageOwnedScriptFailureClassification::Typed {
dynamic_kind,
module_failure_policy,
error_constructor,
} => (dynamic_kind, module_failure_policy, error_constructor),
error_value,
} => (dynamic_kind, module_failure_policy, error_value),
PageOwnedScriptFailureClassification::LegacyMessageText => (
crate::dynamic_script_owner::DynamicScriptOwner::legacy_message_failure_kind(
&script, &error,
@@ -678,40 +671,40 @@ fn complete_page_owned_prepared_script_execution_failure_body(
&error,
dynamic_kind,
module_failure_policy,
error_constructor,
error_value,
)
} else if vm.parser_owned_inline_importmap_reports_window_error_immediately(&script) {
vm.report_window_error_body_best_effort(&error, Some(script.url.as_str()), None);
crate::script_vm::ScriptTerminalBodyActivity::EventDispatchAttempted
} else if vm.parser_owned_module_reports_failure_immediately(&script) {
let (module_failure_policy, error_constructor) = match failure_classification {
let (module_failure_policy, error_value) = match failure_classification {
PageOwnedScriptFailureClassification::Typed {
module_failure_policy,
error_constructor,
error_value,
..
} => (module_failure_policy, error_constructor),
} => (module_failure_policy, error_value),
PageOwnedScriptFailureClassification::LegacyMessageText => (None, None),
};
vm.dispatch_current_prepared_script_error_body_best_effort(
&script,
&error,
module_failure_policy,
error_constructor,
error_value,
)
} else {
let (module_failure_policy, error_constructor) = match failure_classification {
let (module_failure_policy, error_value) = match failure_classification {
PageOwnedScriptFailureClassification::Typed {
module_failure_policy,
error_constructor,
error_value,
..
} => (module_failure_policy, error_constructor),
} => (module_failure_policy, error_value),
PageOwnedScriptFailureClassification::LegacyMessageText => (None, None),
};
vm.enqueue_script_failure_lifecycle_work_best_effort(
&script,
&error,
module_failure_policy,
error_constructor,
error_value,
);
crate::script_vm::ScriptTerminalBodyActivity::NoEventDispatch
};
@@ -800,13 +793,13 @@ async fn execute_prepared_script_on_script_execution_lane(
let failure_classification =
PageOwnedScriptFailureClassification::from_prepared_script_error(&script, &error);
if let Some(claim) = runtime_script_claim.take() {
let (module_failure_policy, error_constructor) = match failure_classification {
let (module_failure_policy, error_value) = match failure_classification {
PageOwnedScriptFailureClassification::LegacyMessageText => (None, None),
PageOwnedScriptFailureClassification::Typed {
module_failure_policy,
error_constructor,
error_value,
..
} => (module_failure_policy, error_constructor),
} => (module_failure_policy, error_value),
};
let message = error.into_message();
let terminal_activity = vm.finish_claimed_runtime_owned_script_failure_body(
@@ -814,7 +807,7 @@ async fn execute_prepared_script_on_script_execution_lane(
&script,
&message,
module_failure_policy,
error_constructor,
error_value,
);
return complete_prepared_script_execution_failure_report_with_activity(
script,
@@ -2563,7 +2556,7 @@ impl PageVm {
PageOwnedScriptFailureClassification::Typed {
dynamic_kind,
module_failure_policy,
error_constructor,
error_value,
},
) if dynamic_kind.is_deferrable_module() => {
self.vm_mut()
@@ -2573,7 +2566,7 @@ impl PageVm {
message.clone(),
dynamic_kind,
module_failure_policy,
error_constructor,
error_value,
);
(
complete_prepared_script_execution_failure_report(
@@ -112,7 +112,7 @@ impl PageOwnedDocumentScriptHooks for MainPageOwnedDocumentScriptHooks<'_, '_> {
failure: PageOwnedDocumentScriptSourceFailure,
runtime_script_claim: Option<DynamicScriptPageTaskClaim>,
) -> PageOwnedDocumentScriptBodyExecution {
let (error, module_failure_policy, error_constructor) = failure.into_parts();
let (error, module_failure_policy, error_value) = failure.into_parts();
if let Some(claim) = runtime_script_claim {
let terminal_activity = self
.page_vm
@@ -122,7 +122,7 @@ impl PageOwnedDocumentScriptHooks for MainPageOwnedDocumentScriptHooks<'_, '_> {
&script,
&error,
module_failure_policy,
error_constructor,
error_value,
);
return complete_prepared_script_execution_failure_report_with_activity(
script,
@@ -415,7 +415,7 @@ impl PageVm {
}
ModuleScriptEvaluationReactionState::Rejected {
reason,
error_constructor,
error_value,
} => {
let message = format!(
"NativeEsmEvaluateFailed: native module graph evaluation rejected: {reason}"
@@ -441,7 +441,7 @@ impl PageVm {
self.vm_mut().report_window_error_body_best_effort(
&message,
Some(evaluation.script_continuation.script.url.as_str()),
error_constructor,
error_value,
);
MainParserContinuationTaskEffect::applied(
owner,
@@ -452,7 +452,7 @@ impl PageVm {
.report_module_tla_rejection_and_finish_reaction_best_effort(
&message,
Some(evaluation.script_continuation.script.url.as_str()),
error_constructor,
error_value,
);
MainParserContinuationTaskEffect::NotApplied
};
@@ -487,7 +487,7 @@ impl PageVm {
}
ModuleScriptEvaluationReactionState::Rejected {
reason,
error_constructor,
error_value,
} => Some(
self.complete_module_script_failure_for_terminal_disposition(
evaluation.script_continuation,
@@ -500,7 +500,7 @@ impl PageVm {
module_failure_policy: Some(
crate::host::ModuleFailurePolicy::EvaluationFailure,
),
error_constructor,
error_value,
},
PreparedScriptBodyActivity::NotEntered,
terminal_disposition,
@@ -148,6 +148,7 @@ mod main_runtime_script_completion;
mod media_element_event;
mod message_port_delivery;
mod misc_platform_api;
mod module_error_reporting;
mod module_reaction;
mod modulepreload_start_completion;
mod navigation_api_task;
@@ -0,0 +1,197 @@
use super::*;
fn bound_parser_module(page_vm: &mut PageVm, position: u32, url: Url) -> PreparedScript {
let mut script = prepared_external_module_for_page_vm_test_with_node(page_vm, position, url);
let runtime = &mut page_vm.vm_mut().document_runtime;
let body = runtime.snapshot_document().document_body_handle().unwrap();
let node = runtime
.dom_host_mut()
.create_parser_element_without_attributes(
"script".to_owned(),
"http://www.w3.org/1999/xhtml".to_owned(),
None,
);
assert!(runtime.dom_host_mut().append_child(body, node));
script.node_id = node;
script.host_script_handle = Some(runtime.bind_parser_owned_script_handle_for_node(node));
script
}
async fn assert_parser_module_reports_original_value(value: &str, reject_later: bool) {
let loader =
crate::network::ResourceRequestClient::new(&FetchConfig::default()).expect("loader");
let mut page_vm = test_page_vm_with_loader_and_document_url(
&loader,
Vec::new(),
Url::parse("https://example.com/module-reporting.html").unwrap(),
);
page_vm
.vm_mut()
.eval(&format!(
r#"
globalThis.__thrown = {value};
globalThis.__hadFileName = Object.hasOwn(Object(__thrown), 'fileName');
globalThis.__errors = [];
globalThis.__onerrors = [];
globalThis.__coercions = 0;
globalThis.__unhandled = 0;
globalThis.__loads = 0;
addEventListener('unhandledrejection', event => {{
++__unhandled;
event.preventDefault();
}});
addEventListener('error', event => {{
__errors.push(event.error);
event.preventDefault();
}});
onerror = (message, filename, line, column, error) => {{
__onerrors.push(error);
return true;
}};
"#,
))
.expect("install original exception and both observers");
let module_url = Url::parse("https://example.com/report-original.mjs").unwrap();
let script = bound_parser_module(&mut page_vm, 9101, module_url.clone());
page_vm
.vm_mut()
.eval("document.querySelector('script').addEventListener('load', () => ++__loads);")
.unwrap();
let work = install_parser_module_defer_work(&mut page_vm, script);
page_vm
.execute_post_parse_page_owned_task_on_named_owner_lane(&loader, work)
.await
.expect("module waits for its graph");
let source = if reject_later {
"await new Promise((_, reject) => { globalThis.__rejectModule = reject; });"
} else {
"throw globalThis.__thrown;"
};
enqueue_parser_owned_module_script_fetch_completion_for_test(
&mut page_vm,
0,
&module_url,
source,
);
assert!(
run_next_main_module_fetch_terminal_for_test(&mut page_vm)
.unwrap()
.is_some()
);
run_ready_parser_deferred_body_for_test(&mut page_vm, &loader, "original module exception")
.await;
if reject_later {
assert_eq!(page_vm.vm_mut().eval("__errors.length").unwrap(), "0");
page_vm
.vm_mut()
.eval("__rejectModule(__thrown); 'rejected'")
.unwrap();
}
run_parser_module_completion_turns_for_test(
&mut page_vm,
&loader,
usize::from(reject_later),
"original module exception",
)
.await;
assert_eq!(
page_vm.vm_mut().eval(
"JSON.stringify([__errors.length, __onerrors.length, Object.is(__errors[0], __thrown), Object.is(__onerrors[0], __thrown), __coercions, Object.hasOwn(Object(__thrown), 'fileName') === __hadFileName])"
).unwrap(),
"[1,1,true,true,0,true]",
"module error reporting must preserve {value}, reject_later={reject_later}",
);
assert_eq!(
page_vm
.vm_mut()
.eval("[__loads, __unhandled].join('|')")
.unwrap(),
"1|0",
"module scripts must load once without leaking their internal rejection"
);
assert_eq!(
page_vm
.report
.runs
.iter()
.filter(|run| run.url() == &module_url)
.count(),
1,
"reporting must not complete a module script twice",
);
}
const THROWN_VALUES: &[&str] = &[
"Object.freeze({ marker: 42 })",
"Object.freeze(new TypeError('original'))",
"undefined",
"null",
"false",
"-0",
"NaN",
"17n",
"Symbol('original')",
"'original string'",
"({ toString() { ++__coercions; throw new Error('must not coerce'); } })",
"({ get fileName() { ++__coercions; throw new Error('must not read'); }, set fileName(value) { ++__coercions; throw new Error('must not write'); } })",
];
#[tokio::test(flavor = "current_thread")]
async fn parser_module_error_reporting_preserves_synchronous_thrown_values() {
run_page_vm_async_test(async {
for value in THROWN_VALUES {
assert_parser_module_reports_original_value(value, false).await;
}
})
.await;
}
#[tokio::test(flavor = "current_thread")]
async fn parser_module_error_reporting_preserves_deferred_rejection_values() {
run_page_vm_async_test(async {
for value in THROWN_VALUES {
assert_parser_module_reports_original_value(value, true).await;
}
})
.await;
}
#[tokio::test(flavor = "current_thread")]
async fn parser_module_error_reporting_reuses_the_cached_parse_exception() {
run_page_vm_async_test(async {
let loader = crate::network::ResourceRequestClient::new(&FetchConfig::default()).unwrap();
let mut page_vm = test_page_vm_with_loader_and_document_url(
&loader, Vec::new(), Url::parse("https://example.com/parse-error.html").unwrap(),
);
page_vm.vm_mut().eval(r#"
globalThis.__errors = [];
addEventListener('error', event => {
__errors.push(event.error);
event.preventDefault();
});
"#).unwrap();
let url = Url::parse("https://example.com/shared-syntax-error.mjs").unwrap();
let owner = page_vm.vm().current_main_document_task_owner().unwrap();
for position in [9101, 9102] {
let script = bound_parser_module(&mut page_vm, position, url.clone());
assert!(page_vm.vm_mut().claim_main_parser_deferred_script(
owner, script, None, None, Default::default(),
).unwrap());
}
let work = page_vm.seal_main_parser_deferred_scripts(owner).unwrap();
page_vm.execute_post_parse_page_owned_task_on_named_owner_lane(&loader, work).await.unwrap();
enqueue_parser_owned_module_script_fetch_completion_for_test(
&mut page_vm, 0, &url, "export const broken = ;",
);
run_next_main_module_fetch_terminal_for_test(&mut page_vm).unwrap();
while page_vm.vm_mut().has_ready_native_module_owner_actions() {
run_next_native_module_owner_event_for_test(&mut page_vm, &loader, "shared syntax error").await;
}
for _ in 0..2 {
run_ready_parser_deferred_body_for_test(&mut page_vm, &loader, "shared syntax error").await;
}
assert_eq!(page_vm.vm_mut().eval(
"[__errors.length, __errors[0] instanceof SyntaxError, __errors[0] === __errors[1]].join('|')",
).unwrap(), "2|true|true");
}).await;
}
@@ -8,7 +8,7 @@ use crate::{
FrameDocumentModuleScriptGraphNotification, FrameDocumentModuleScriptTerminalFollowup,
},
module_runtime::ModuleEntryId,
types::ScriptErrorConstructorKind,
types::ScriptErrorValue,
};
pub(super) struct ChildDocumentScriptSchedulerOwner<'vm> {
@@ -231,7 +231,7 @@ impl<'vm> ChildDocumentScriptSchedulerOwner<'vm> {
&mut self,
reaction_id: u64,
reason: String,
error_constructor: Option<ScriptErrorConstructorKind>,
error_value: Option<ScriptErrorValue>,
) -> usize {
let mut host = self.vm._context_host.borrow_mut();
let queued_ready_action_count = host
@@ -239,7 +239,7 @@ impl<'vm> ChildDocumentScriptSchedulerOwner<'vm> {
.mark_parser_module_evaluation_rejected(
reaction_id,
reason,
error_constructor,
error_value,
|evaluation| evaluation,
)
.map(|update| update.queued_ready_action_count())
+12 -12
View File
@@ -1033,7 +1033,7 @@ impl ScriptVm {
)
},
);
let error_constructor = error.error_constructor();
let error_value = error.error_value();
let error = error.into_message();
if failure_kind
== crate::dynamic_script_owner::DynamicScriptFailureKind::Immediate
@@ -1051,7 +1051,7 @@ impl ScriptVm {
self.apply_runtime_script_failure_terminal(&script,
&error,
module_failure_policy,
error_constructor,
error_value,
lease,
);
}
@@ -1059,13 +1059,13 @@ impl ScriptVm {
self.document_runtime
.runtime_script_work_mut()
.dynamic_scripts
.note_script_failed_with_kind_and_error_constructor(
.note_script_failed_with_kind_and_error_value(
id,
&script,
error,
failure_kind,
module_failure_policy,
error_constructor,
error_value,
);
}
processed_runnable_this_turn = true;
@@ -1093,20 +1093,20 @@ impl ScriptVm {
kind,
module_failure_policy,
source_network_result,
error_constructor,
error_value,
} => {
if yield_after_one_runnable && processed_runnable_this_turn {
self.document_runtime
.runtime_script_work_mut()
.dynamic_scripts
.requeue_failed_script_front_with_error_constructor(
.requeue_failed_script_front_with_error_value(
id,
script,
message,
kind,
module_failure_policy,
source_network_result,
error_constructor,
error_value,
);
return Ok(RuntimePendingWorkFlushOutcome::Complete);
}
@@ -1121,14 +1121,14 @@ impl ScriptVm {
self.document_runtime
.runtime_script_work_mut()
.dynamic_scripts
.requeue_failed_script_front_with_error_constructor(
.requeue_failed_script_front_with_error_value(
id,
script,
message,
kind,
module_failure_policy,
source_network_result,
error_constructor,
error_value,
);
return Ok(RuntimePendingWorkFlushOutcome::Complete);
}
@@ -1156,7 +1156,7 @@ impl ScriptVm {
&script,
&message,
module_failure_policy,
error_constructor,
error_value,
lease,
);
}
@@ -1224,7 +1224,7 @@ impl ScriptVm {
self.document_runtime
.runtime_script_work_mut()
.dynamic_scripts
.requeue_failed_script_front_with_error_constructor(
.requeue_failed_script_front_with_error_value(
owner_id,
continuation.script,
error.message().to_owned(),
@@ -1233,7 +1233,7 @@ impl ScriptVm {
&error,
)),
None,
error.error_constructor(),
error.error_value(),
);
}
}
@@ -2,7 +2,7 @@ use super::ScriptVm;
use crate::document_runtime::DomHandle;
use crate::host::ModuleFailurePolicy;
use crate::module_runtime::{ModuleGraphHandle, ModuleLoadError, ModuleLoadStage};
use crate::types::ScriptErrorConstructorKind;
use crate::types::ScriptErrorValue;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) struct PendingMousePress {
@@ -85,7 +85,7 @@ pub(crate) struct PreparedScriptExecutionError {
message: String,
module_load_stage: Option<ModuleLoadStage>,
module_failure_policy: Option<ModuleFailurePolicy>,
error_constructor: Option<ScriptErrorConstructorKind>,
error_value: Option<ScriptErrorValue>,
body_activity: PreparedScriptBodyActivity,
}
@@ -95,7 +95,7 @@ impl PreparedScriptExecutionError {
message: message.into(),
module_load_stage: None,
module_failure_policy: None,
error_constructor: None,
error_value: None,
body_activity: PreparedScriptBodyActivity::NotEntered,
}
}
@@ -105,7 +105,7 @@ impl PreparedScriptExecutionError {
message: message.into(),
module_load_stage: None,
module_failure_policy: None,
error_constructor: None,
error_value: None,
body_activity: PreparedScriptBodyActivity::Entered,
}
}
@@ -121,7 +121,7 @@ impl PreparedScriptExecutionError {
message: error.message().to_owned(),
module_load_stage: Some(error.stage()),
module_failure_policy: Some(module_failure_policy),
error_constructor: error.error_constructor(),
error_value: error.error_value(),
body_activity: PreparedScriptBodyActivity::NotEntered,
}
}
@@ -131,7 +131,7 @@ impl PreparedScriptExecutionError {
message: message.into(),
module_load_stage: Some(ModuleLoadStage::Fetch),
module_failure_policy: Some(ModuleFailurePolicy::TopLevelLoadFailure),
error_constructor: None,
error_value: None,
body_activity: PreparedScriptBodyActivity::NotEntered,
}
}
@@ -148,8 +148,8 @@ impl PreparedScriptExecutionError {
self.module_failure_policy
}
pub(crate) fn error_constructor(&self) -> Option<ScriptErrorConstructorKind> {
self.error_constructor
pub(crate) fn error_value(&self) -> Option<ScriptErrorValue> {
self.error_value
}
pub(crate) fn body_activity(&self) -> PreparedScriptBodyActivity {
@@ -128,7 +128,7 @@ impl ScriptVm {
let settlement = match self.report_window_error_body(
&task.message,
task.filename.as_deref(),
task.error_constructor,
task.error_value,
) {
Ok(()) => MainDocumentPostParseCallbackSettlement::Completed,
Err(error) => {
+45 -36
View File
@@ -68,8 +68,8 @@ use crate::page_task_queue::{
};
use crate::planning::PreparedScript;
use crate::types::{
ChildDynamicImportFetchCompletion, ScriptErrorConstructorKind, SubresourceRequestInitiatorType,
SubresourceResourceType,
ChildDynamicImportFetchCompletion, ScriptErrorConstructorKind, ScriptErrorValue,
SubresourceRequestInitiatorType, SubresourceResourceType,
};
#[cfg(test)]
use crate::types::{
@@ -86,6 +86,7 @@ mod child_ready_document_script;
mod dynamic_import_selected_task_body;
mod load_error;
mod main_selected_task;
pub(super) use load_error::retained_module_exception;
use load_error::{module_load_error_value, retain_module_exception};
pub(crate) use main_selected_task::{
MainDynamicImportGraphFetchBodySettlement, MainNativeModuleSelectedTaskApplication,
@@ -2634,7 +2635,7 @@ impl ScriptVm {
&mut self,
reaction_id: u64,
reason: String,
error_constructor: Option<ScriptErrorConstructorKind>,
error_value: Option<ScriptErrorValue>,
) -> Option<DocumentModuleReactionUpdate> {
let parser_update = self
.document_runtime
@@ -2642,7 +2643,7 @@ impl ScriptVm {
.mark_parser_module_evaluation_rejected(
reaction_id,
reason.clone(),
error_constructor,
error_value,
parser_module_evaluation_continuation_into_ready_action,
)
.map(DocumentModuleReactionUpdate::ParserOwned);
@@ -2650,7 +2651,7 @@ impl ScriptVm {
self.mark_runtime_owned_module_script_evaluation_rejected(
reaction_id,
reason,
error_constructor,
error_value,
)
.map(DocumentModuleReactionUpdate::RuntimeOwned)
})
@@ -3775,6 +3776,13 @@ impl ScriptVm {
}
}
}
// Script-element evaluation consumes rejection itself, either
// below or through its retained TLA continuation. Claim that
// responsibility before cleanup can notify rejected promises.
// Dynamic import returned above and owns its own reaction.
if let Some(promise) = promise {
promise.mark_as_handled();
}
if let Err(error) = Self::perform_microtask_checkpoints(&mut scope, None) {
return Ok(Err(ModuleLoadError::new(
ModuleLoadStage::Evaluate,
@@ -4185,14 +4193,10 @@ impl ScriptVm {
RendererPageModuleReactionEvent::DocumentModuleScriptEvaluationRejected {
reaction_id,
reason,
error_constructor,
error_value,
..
} => self
.apply_native_module_script_evaluation_rejected(
reaction_id,
reason,
error_constructor,
)
.apply_native_module_script_evaluation_rejected(reaction_id, reason, error_value)
.map(PageModuleReactionApplication::module_state_updated),
RendererPageModuleReactionEvent::ChildParserModuleEvaluationFulfilled {
reaction_id,
@@ -4205,12 +4209,12 @@ impl ScriptVm {
RendererPageModuleReactionEvent::ChildParserModuleEvaluationRejected {
reaction_id,
reason,
error_constructor,
error_value,
..
} => (self.apply_child_parser_module_evaluation_rejected(
reaction_id,
reason,
error_constructor,
error_value,
) > 0)
.then_some(PageModuleReactionApplication::module_state_updated(
PageModuleReactionFollowup::None,
@@ -4351,12 +4355,12 @@ impl ScriptVm {
&mut self,
reaction_id: u64,
reason: String,
error_constructor: Option<ScriptErrorConstructorKind>,
error_value: Option<ScriptErrorValue>,
) -> Option<PageModuleReactionFollowup> {
let update = self.mark_module_evaluation_reaction_rejected_for_owner(
reaction_id,
reason,
error_constructor,
error_value,
)?;
Some(match update {
DocumentModuleReactionUpdate::ParserOwned(update) => {
@@ -4498,17 +4502,12 @@ fn native_module_script_reaction_rejected_callback<'s>(
let Some(host_ptr) = context_host_ptr_from_global_bridge(scope) else {
return;
};
let reason = args.get(0);
let error_constructor = script_error_constructor_kind_from_value(scope, reason);
let reason = reason
.to_string(scope)
.map(|value| value.to_rust_string_lossy(scope))
.unwrap_or_else(|| "unknown promise rejection".to_owned());
let error = native_module_evaluation_exception_error(scope, args.get(0), "");
unsafe { &mut *host_ptr }.queue_document_module_script_evaluation_rejected(
document_owner,
reaction_id,
reason,
error_constructor,
error.message().to_owned(),
error.error_value(),
);
}
@@ -4545,18 +4544,13 @@ fn child_parser_module_reaction_rejected_callback<'s>(
let Some(host_ptr) = context_host_ptr_from_global_bridge(scope) else {
return;
};
let reason = args.get(0);
let error_constructor = script_error_constructor_kind_from_value(scope, reason);
let reason = reason
.to_string(scope)
.map(|value| value.to_rust_string_lossy(scope))
.unwrap_or_else(|| "unknown promise rejection".to_owned());
let error = native_module_evaluation_exception_error(scope, args.get(0), "");
unsafe { &mut *host_ptr }.queue_child_parser_module_script_evaluation_rejected(
document_owner,
realm_id,
reaction_id,
reason,
error_constructor,
error.message().to_owned(),
error.error_value(),
);
}
@@ -4866,11 +4860,26 @@ fn native_module_evaluation_exception_error(
exception: v8::Local<'_, v8::Value>,
prefix: &str,
) -> ModuleLoadError {
let message = exception
.to_string(scope)
.map(|message| message.to_rust_string_lossy(scope))
.unwrap_or_else(|| "unknown module evaluation exception".to_owned());
let error = ModuleLoadError::new(ModuleLoadStage::Evaluate, format!("{prefix}: {message}"));
let id = match retain_module_exception(scope, exception) {
Ok(id) => id,
Err(error) => {
return ModuleLoadError::new(
ModuleLoadStage::Evaluate,
format!("failed to retain module evaluation exception: {error}"),
);
}
};
// V8's internal diagnostic does not invoke author-defined toString or
// location getters. The diagnostic text never substitutes for the value.
let message = v8::Exception::create_message(scope, exception)
.get(scope)
.to_rust_string_lossy(scope);
let message = if prefix.is_empty() {
message
} else {
format!("{prefix}: {message}")
};
let error = ModuleLoadError::new(ModuleLoadStage::Evaluate, message).with_exception_id(id);
match script_error_constructor_kind_from_value(scope, exception) {
Some(error_constructor) => error.with_error_constructor(error_constructor),
None => error,
@@ -1084,6 +1084,12 @@ impl ScriptVm {
}
}
}
// Child script elements own their evaluation rejection, just
// like main-document scripts. Do not publish the internal
// evaluation promise as an unhandled author promise.
if let Some(promise) = promise {
promise.mark_as_handled();
}
if let Err(error) = Self::perform_microtask_checkpoints(&mut scope, None) {
return Ok(Err(ModuleLoadError::new(
ModuleLoadStage::Evaluate,
@@ -183,9 +183,9 @@ impl ScriptVm {
&mut self,
reaction_id: u64,
reason: String,
error_constructor: Option<ScriptErrorConstructorKind>,
error_value: Option<ScriptErrorValue>,
) -> usize {
super::super::child_document_script_scheduler::ChildDocumentScriptSchedulerOwner::new(self)
.mark_parser_module_evaluation_rejected(reaction_id, reason, error_constructor)
.mark_parser_module_evaluation_rejected(reaction_id, reason, error_value)
}
}
@@ -46,20 +46,7 @@ pub(super) fn module_load_error_value<'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"));
return retained_module_exception(scope, id);
}
let message = v8_string(scope, error.message())
.ok_or_else(|| anyhow::anyhow!("failed to allocate module error message"))?;
@@ -73,6 +60,23 @@ pub(super) fn module_load_error_value<'s>(
.ok_or_else(|| anyhow::anyhow!("failed to create module error"))
}
pub(in crate::script_vm) fn retained_module_exception<'s>(
scope: &mut v8::PinScope<'s, '_>,
id: ModuleExceptionId,
) -> Result<v8::Local<'s, v8::Value>> {
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"
);
map.get(scope, key.into())
.ok_or_else(|| anyhow::anyhow!("failed to read retained module exception"))
}
impl ScriptVm {
pub(crate) fn preserve_native_module_load_error(
&mut self,
@@ -640,13 +640,13 @@ impl ScriptVm {
script: &PreparedScript,
message: &str,
module_failure_policy: Option<crate::host::ModuleFailurePolicy>,
error_constructor: Option<crate::types::ScriptErrorConstructorKind>,
error_value: Option<crate::types::ScriptErrorValue>,
) -> Result<FollowupPageTaskDisposition> {
self.enqueue_script_failure_lifecycle_work_with_load_delay_binding(
script,
message,
module_failure_policy,
error_constructor,
error_value,
None,
)
}
@@ -656,14 +656,14 @@ impl ScriptVm {
script: &PreparedScript,
message: &str,
module_failure_policy: Option<crate::host::ModuleFailurePolicy>,
error_constructor: Option<crate::types::ScriptErrorConstructorKind>,
error_value: Option<crate::types::ScriptErrorValue>,
load_delay_binding: Option<MainDocumentScriptLoadDelayLease>,
) -> Result<FollowupPageTaskDisposition> {
let mut planned_failure_work = self.document_runtime.plan_script_failure_lifecycle_work(
script,
message,
module_failure_policy,
error_constructor,
error_value,
);
if let Some(binding) = load_delay_binding {
planned_failure_work.push(PostParseLifecycleWork::SettleMainDocumentScriptLoadDelay(
@@ -681,13 +681,13 @@ impl ScriptVm {
script: &PreparedScript,
message: &str,
module_failure_policy: Option<crate::host::ModuleFailurePolicy>,
error_constructor: Option<crate::types::ScriptErrorConstructorKind>,
error_value: Option<crate::types::ScriptErrorValue>,
) {
match self.enqueue_script_failure_lifecycle_work_for_prepared_script(
script,
message,
module_failure_policy,
error_constructor,
error_value,
) {
Ok(FollowupPageTaskDisposition::Skipped) => {}
Ok(FollowupPageTaskDisposition::Deferred | FollowupPageTaskDisposition::Enqueued) => {}
@@ -367,7 +367,7 @@ impl ScriptVm {
message,
module_failure_policy,
source_network_result,
error_constructor,
error_value,
..
}) if self.prepared_script_uses_runtime_owned_page_task_execution(&script) => {
self.record_runtime_warning(format_args!(
@@ -389,7 +389,7 @@ impl ScriptVm {
failure: crate::document_script_scheduler::PageOwnedDocumentScriptSourceFailure::runtime_terminal(
message,
module_failure_policy,
error_constructor,
error_value,
),
source_network_result,
runtime_script_claim: Some(runtime_script_claim),
@@ -405,7 +405,7 @@ impl ScriptVm {
kind,
module_failure_policy,
source_network_result,
error_constructor,
error_value,
}) => {
let _ = (
id,
@@ -413,7 +413,7 @@ impl ScriptVm {
kind,
module_failure_policy,
source_network_result,
error_constructor,
error_value,
);
panic!(
"production DynamicScriptOwner failures must carry runtime-owned Page execution identity: {}",
@@ -552,12 +552,12 @@ impl ScriptVm {
&mut self,
reaction_id: u64,
reason: String,
error_constructor: Option<crate::types::ScriptErrorConstructorKind>,
error_value: Option<crate::types::ScriptErrorValue>,
) -> Option<ModuleScriptEvaluationUpdate> {
self.document_runtime
.runtime_script_work_mut()
.dynamic_scripts
.mark_module_script_evaluation_rejected(reaction_id, reason, error_constructor)
.mark_module_script_evaluation_rejected(reaction_id, reason, error_value)
}
#[cfg(test)]
@@ -1181,14 +1181,14 @@ impl ScriptVm {
script: &PreparedScript,
message: &str,
module_failure_policy: Option<crate::host::ModuleFailurePolicy>,
error_constructor: Option<crate::types::ScriptErrorConstructorKind>,
error_value: Option<crate::types::ScriptErrorValue>,
) -> crate::script_vm::ScriptTerminalBodyActivity {
let (dynamic_script_owner_id, lease) = claim.into_parts();
let settlement = self.apply_runtime_script_failure_terminal_body(
script,
message,
module_failure_policy,
error_constructor,
error_value,
lease,
);
debug!(
@@ -1232,14 +1232,14 @@ impl ScriptVm {
script: &PreparedScript,
message: &str,
module_failure_policy: Option<crate::host::ModuleFailurePolicy>,
error_constructor: Option<crate::types::ScriptErrorConstructorKind>,
error_value: Option<crate::types::ScriptErrorValue>,
lease: MainDocumentScriptLoadDelayLease,
) {
let work = self.document_runtime.plan_script_failure_lifecycle_work(
script,
message,
module_failure_policy,
error_constructor,
error_value,
);
for terminal in work {
match terminal {
@@ -1283,14 +1283,14 @@ impl ScriptVm {
script: &PreparedScript,
message: &str,
module_failure_policy: Option<crate::host::ModuleFailurePolicy>,
error_constructor: Option<crate::types::ScriptErrorConstructorKind>,
error_value: Option<crate::types::ScriptErrorValue>,
lease: MainDocumentScriptLoadDelayLease,
) -> RuntimeScriptFailureTerminalBodySettlement {
let work = self.document_runtime.plan_script_failure_lifecycle_work(
script,
message,
module_failure_policy,
error_constructor,
error_value,
);
let mut activity = crate::script_vm::ScriptTerminalBodyActivity::NoEventDispatch;
for terminal in work {
@@ -1303,7 +1303,7 @@ impl ScriptVm {
self.report_window_error_body_best_effort(
&task.message,
task.filename.as_deref(),
task.error_constructor,
task.error_value,
);
activity = crate::script_vm::ScriptTerminalBodyActivity::EventDispatchAttempted;
}
@@ -1435,7 +1435,7 @@ impl ScriptVm {
message: &str,
kind: crate::dynamic_script_owner::DynamicScriptFailureKind,
module_failure_policy: Option<crate::host::ModuleFailurePolicy>,
error_constructor: Option<crate::types::ScriptErrorConstructorKind>,
error_value: Option<crate::types::ScriptErrorValue>,
) -> crate::script_vm::ScriptTerminalBodyActivity {
if let Some(id) = dynamic_script_owner_id {
if kind == crate::dynamic_script_owner::DynamicScriptFailureKind::Immediate {
@@ -1454,7 +1454,7 @@ impl ScriptVm {
script,
message,
module_failure_policy,
error_constructor,
error_value,
lease,
)
.activity;
@@ -1462,13 +1462,13 @@ impl ScriptVm {
self.document_runtime
.runtime_script_work_mut()
.dynamic_scripts
.note_script_failed_with_kind_and_error_constructor(
.note_script_failed_with_kind_and_error_value(
id,
script,
message.to_owned(),
kind,
module_failure_policy,
error_constructor,
error_value,
);
self.enqueue_immediate_runtime_script_work_if_needed();
return crate::script_vm::ScriptTerminalBodyActivity::NoEventDispatch;
@@ -1483,7 +1483,7 @@ impl ScriptVm {
script,
message,
module_failure_policy,
error_constructor,
error_value,
);
self.enqueue_immediate_runtime_script_work_if_needed();
return activity;
@@ -1492,7 +1492,7 @@ impl ScriptVm {
script,
message,
module_failure_policy,
error_constructor,
error_value,
) {
Ok(FollowupPageTaskDisposition::Skipped) => {}
Ok(
@@ -1531,7 +1531,7 @@ impl ScriptVm {
message: String,
kind: crate::dynamic_script_owner::DynamicScriptFailureKind,
module_failure_policy: Option<crate::host::ModuleFailurePolicy>,
error_constructor: Option<crate::types::ScriptErrorConstructorKind>,
error_value: Option<crate::types::ScriptErrorValue>,
) {
debug_assert!(
kind.is_deferrable_module(),
@@ -1540,13 +1540,13 @@ impl ScriptVm {
self.document_runtime
.runtime_script_work_mut()
.dynamic_scripts
.note_script_failed_with_kind_and_error_constructor(
.note_script_failed_with_kind_and_error_value(
dynamic_script_owner_id,
script,
message,
kind,
module_failure_policy,
error_constructor,
error_value,
);
}
@@ -1604,14 +1604,14 @@ impl ScriptVm {
kind,
module_failure_policy,
source_network_result: _,
error_constructor,
error_value,
} = terminal;
if let Some(lease) = lease {
self.apply_runtime_script_failure_terminal(
&script,
&message,
module_failure_policy,
error_constructor,
error_value,
lease,
);
}
@@ -1648,7 +1648,7 @@ impl ScriptVm {
kind,
module_failure_policy,
source_network_result: _,
error_constructor,
error_value,
} = terminal;
if let Some(lease) = lease {
let lease_owner = lease.owner();
@@ -1656,7 +1656,7 @@ impl ScriptVm {
&script,
&message,
module_failure_policy,
error_constructor,
error_value,
lease,
);
if terminal.activity
@@ -1695,7 +1695,7 @@ impl ScriptVm {
message: &str,
kind: crate::dynamic_script_owner::DynamicScriptFailureKind,
module_failure_policy: Option<crate::host::ModuleFailurePolicy>,
error_constructor: Option<crate::types::ScriptErrorConstructorKind>,
error_value: Option<crate::types::ScriptErrorValue>,
) {
if let Some(id) = dynamic_script_owner_id {
if kind == crate::dynamic_script_owner::DynamicScriptFailureKind::Immediate {
@@ -1711,7 +1711,7 @@ impl ScriptVm {
script,
message,
module_failure_policy,
error_constructor,
error_value,
lease,
);
}
@@ -1720,13 +1720,13 @@ impl ScriptVm {
self.document_runtime
.runtime_script_work_mut()
.dynamic_scripts
.note_script_failed_with_kind_and_error_constructor(
.note_script_failed_with_kind_and_error_value(
id,
script,
message.to_owned(),
kind,
module_failure_policy,
error_constructor,
error_value,
);
debug!(
dynamic_script_owner_id = ?id,
@@ -1746,7 +1746,7 @@ impl ScriptVm {
script,
message,
module_failure_policy,
error_constructor,
error_value,
);
self.enqueue_immediate_runtime_script_work_if_needed();
return;
@@ -1755,7 +1755,7 @@ impl ScriptVm {
script,
message,
module_failure_policy,
error_constructor,
error_value,
) {
Ok(FollowupPageTaskDisposition::Skipped) => {}
Ok(
@@ -117,6 +117,12 @@ pub(super) fn flush_pending_promise_rejections(scope: &mut v8::PinScope<'_, '_>)
.realm
.with_current_scope(scope, host_ptr, |scope, dispatch_scope| {
let promise = v8::Local::new(scope, &rejection.promise);
// HTML rechecks PromiseIsHandled immediately before notifying.
// The host, or an earlier notification in this detached batch,
// may have handled it since it was first queued.
if promise.has_handler() {
return;
}
let reason = rejection
.reason
.as_ref()
@@ -16,6 +16,7 @@ use crate::context_bootstrap::{
};
use crate::host::ScriptEventTask;
use crate::native_bridge::JsContextHost;
use crate::types::ScriptErrorValue;
use crate::util::{get_private_value, v8_string, v8str};
impl ScriptVm {
@@ -51,9 +52,9 @@ impl ScriptVm {
&mut self,
message: &str,
filename: Option<&str>,
error_constructor: Option<crate::types::ScriptErrorConstructorKind>,
error_value: Option<ScriptErrorValue>,
) {
if let Err(error) = self.report_window_error_body(message, filename, error_constructor) {
if let Err(error) = self.report_window_error_body(message, filename, error_value) {
self.record_runtime_warning(format_args!(
"window script failure body dispatch failed for `{}`: {error}",
filename.unwrap_or("")
@@ -65,7 +66,7 @@ impl ScriptVm {
&mut self,
message: &str,
filename: Option<&str>,
error_constructor: Option<crate::types::ScriptErrorConstructorKind>,
error_value: Option<ScriptErrorValue>,
) -> Result<()> {
let context_ptr: *const v8::Global<v8::Context> = &self.page_default_context;
let context_host = self._context_host.clone();
@@ -78,13 +79,20 @@ impl ScriptVm {
let global = scope.get_current_context().global(scope);
let message_value = v8_string(scope, message)
.ok_or_else(|| anyhow!("failed to allocate reportError message"))?;
let error_value = window_script_failure_error_value(
scope,
global,
error_constructor,
message_value,
);
if let Some(filename) = filename
let retained = matches!(error_value, Some(ScriptErrorValue::Retained(_)));
let error_value = match error_value {
Some(ScriptErrorValue::Retained(id)) => {
super::native_module::retained_module_exception(scope, id)?
}
Some(ScriptErrorValue::Constructor(kind)) => {
window_script_failure_error_value(scope, global, Some(kind), message_value)
}
None => window_script_failure_error_value(scope, global, None, message_value),
};
// Location metadata belongs to the ErrorEvent. Never mutate
// the original exception (or invoke an author's setter).
if !retained
&& let Some(filename) = filename
&& let Some(filename_value) = v8_string(scope, filename)
&& let Ok(error_object) = v8::Local::<v8::Object>::try_from(error_value)
{
@@ -29,10 +29,10 @@ impl ScriptVm {
&mut self,
message: &str,
filename: Option<&str>,
error_constructor: Option<crate::types::ScriptErrorConstructorKind>,
error_value: Option<crate::types::ScriptErrorValue>,
boundary: &'static str,
) -> Result<()> {
self.report_window_error_body(message, filename, error_constructor)?;
self.report_window_error_body(message, filename, error_value)?;
self.perform_owner_lane_task_microtask_checkpoints()
.with_context(|| format!("{boundary} checkpoint failed"))
}
@@ -42,14 +42,14 @@ impl ScriptVm {
script: &PreparedScript,
message: &str,
module_failure_policy: Option<crate::host::ModuleFailurePolicy>,
error_constructor: Option<crate::types::ScriptErrorConstructorKind>,
error_value: Option<crate::types::ScriptErrorValue>,
boundary: &'static str,
) {
let planned_failure_work = self.document_runtime.plan_script_failure_lifecycle_work(
script,
message,
module_failure_policy,
error_constructor,
error_value,
);
for work in planned_failure_work {
let result = match work {
@@ -60,7 +60,7 @@ impl ScriptVm {
.report_window_error_and_finish_synchronous_checkpoint(
&task.message,
task.filename.as_deref(),
task.error_constructor,
task.error_value,
boundary,
),
_ => continue,
@@ -101,13 +101,13 @@ impl ScriptVm {
script: &PreparedScript,
message: &str,
module_failure_policy: Option<crate::host::ModuleFailurePolicy>,
error_constructor: Option<crate::types::ScriptErrorConstructorKind>,
error_value: Option<crate::types::ScriptErrorValue>,
) {
self.dispatch_planned_script_failure_and_finish_each_synchronous_checkpoint(
script,
message,
module_failure_policy,
error_constructor,
error_value,
"parser-owned module failure settlement",
);
}
@@ -120,13 +120,13 @@ impl ScriptVm {
script: &PreparedScript,
message: &str,
module_failure_policy: Option<crate::host::ModuleFailurePolicy>,
error_constructor: Option<crate::types::ScriptErrorConstructorKind>,
error_value: Option<crate::types::ScriptErrorValue>,
) {
self.dispatch_planned_script_failure_and_finish_each_synchronous_checkpoint(
script,
message,
module_failure_policy,
error_constructor,
error_value,
"unclaimed runtime-script terminal",
);
}
@@ -137,12 +137,12 @@ impl ScriptVm {
&mut self,
message: &str,
filename: Option<&str>,
error_constructor: Option<crate::types::ScriptErrorConstructorKind>,
error_value: Option<crate::types::ScriptErrorValue>,
) {
if let Err(error) = self.report_window_error_and_finish_synchronous_checkpoint(
message,
filename,
error_constructor,
error_value,
"module TLA rejection reaction",
) {
self.record_runtime_warning(format_args!(
@@ -179,7 +179,7 @@ impl ScriptVm {
if let Err(error) = self.report_window_error_and_finish_synchronous_checkpoint(
&task.message,
task.filename.as_deref(),
task.error_constructor,
task.error_value,
"runtime script failure terminal",
) {
self.record_runtime_warning(format_args!(
@@ -195,13 +195,13 @@ impl ScriptVm {
script: &PreparedScript,
message: &str,
module_failure_policy: Option<crate::host::ModuleFailurePolicy>,
error_constructor: Option<crate::types::ScriptErrorConstructorKind>,
error_value: Option<crate::types::ScriptErrorValue>,
) -> ScriptTerminalBodyActivity {
let planned_failure_work = self.document_runtime.plan_script_failure_lifecycle_work(
script,
message,
module_failure_policy,
error_constructor,
error_value,
);
let mut activity = ScriptTerminalBodyActivity::NoEventDispatch;
for work in planned_failure_work {
@@ -214,7 +214,7 @@ impl ScriptVm {
self.report_window_error_body_best_effort(
&task.message,
task.filename.as_deref(),
task.error_constructor,
task.error_value,
);
activity = ScriptTerminalBodyActivity::EventDispatchAttempted;
}
@@ -245,7 +245,7 @@ impl ScriptVm {
self.report_window_script_failure_and_checkpoint_for_test(
&task.message,
task.filename.as_deref(),
task.error_constructor,
task.error_value,
);
}
@@ -254,12 +254,12 @@ impl ScriptVm {
&mut self,
message: &str,
filename: Option<&str>,
error_constructor: Option<crate::types::ScriptErrorConstructorKind>,
error_value: Option<crate::types::ScriptErrorValue>,
) {
if let Err(error) = self.report_window_error_and_finish_synchronous_checkpoint(
message,
filename,
error_constructor,
error_value,
"test-only Window error",
) {
self.record_runtime_warning(format_args!(
@@ -1,5 +1,29 @@
use super::*;
#[test]
fn rejection_notification_rechecks_handlers_added_by_an_earlier_notification() {
let mut vm = new_storage_test_vm("https://promise-notification-batch.test/");
vm.eval(
r#"
globalThis.__notifications = [];
const first = Promise.reject('first');
const second = Promise.reject('second');
onunhandledrejection = event => {
__notifications.push(event.reason);
event.preventDefault();
if (event.promise === first) second.catch(() => {});
};
onrejectionhandled = () => __notifications.push('rejectionhandled');
"#,
)
.unwrap();
vm.eval("0").unwrap();
assert_eq!(
vm.eval("JSON.stringify(__notifications)").unwrap(),
r#"["first"]"#
);
}
#[test]
fn main_window_unhandled_rejection_dispatches_to_main_window() {
let mut vm = new_storage_test_vm("https://main-promise-rejection.test/");
@@ -1,6 +1,47 @@
use super::*;
use crate::parser::{PreparedImportMap, PreparedImportMapSource};
#[test]
fn retained_window_error_body_preserves_identity_without_owning_the_checkpoint() {
let mut vm = new_storage_test_vm("https://script-terminal.test/retained.html");
vm.eval(
r#"
globalThis.__errors = [];
globalThis.__order = [];
addEventListener('error', event => {
__errors.push(event.error);
__order.push('error');
queueMicrotask(() => __order.push('microtask'));
event.preventDefault();
});
"#,
)
.unwrap();
let error = vm
.preserve_native_module_load_error(
None,
crate::module_runtime::ModuleLoadError::new(
crate::module_runtime::ModuleLoadStage::Compile,
"remembered syntax error",
)
.with_error_constructor(crate::types::ScriptErrorConstructorKind::SyntaxError),
)
.unwrap();
for _ in 0..2 {
vm.report_window_error_body(error.message(), Some("module.js"), error.error_value())
.unwrap();
}
assert_eq!(vm.eval_without_microtask_checkpoint_for_test(
"[__order.join('|'), __errors[0] === __errors[1], __errors[0] instanceof SyntaxError, Object.hasOwn(__errors[0], 'fileName')].join(',')",
).unwrap(), "error|error,true,true,false");
vm.perform_script_task_checkpoint(None).unwrap();
assert_eq!(
vm.eval_without_microtask_checkpoint_for_test("__order.join('|')")
.unwrap(),
"error|error|microtask|microtask"
);
}
#[test]
fn window_error_body_leaves_checkpoint_to_its_carrier() {
let mut vm = new_storage_test_vm("https://script-terminal.test/body.html");
+15
View File
@@ -37,6 +37,21 @@ pub(crate) enum ScriptErrorConstructorKind {
WebAssemblyLinkError,
}
/// The value carried by script-failure reporting tasks. Constructor metadata
/// remains a fallback for failures originating in the host; a JavaScript
/// exception must instead retain its original value in the reporting realm.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ScriptErrorValue {
Constructor(ScriptErrorConstructorKind),
Retained(moli_module_script_tree::ModuleExceptionId),
}
impl From<ScriptErrorConstructorKind> for ScriptErrorValue {
fn from(constructor: ScriptErrorConstructorKind) -> Self {
Self::Constructor(constructor)
}
}
pub use moli_script::{
ScriptElementClassificationInput, ScriptPreparationClassificationInput,
classify_script_preparation,