mirror of
https://github.com/lexmount/moli.git
synced 2026-10-09 00:01:06 +00:00
fix(events): preserve window.event during exception reporting
This commit is contained in:
@@ -192,6 +192,60 @@ impl CallbackInvoker {
|
||||
callback_name: &str,
|
||||
invocation: CallbackInvocation<'s, '_>,
|
||||
) -> CallbackInvocationOutcome {
|
||||
Self::invoke_with_completion(
|
||||
scope,
|
||||
callback_kind,
|
||||
log_label,
|
||||
log_level,
|
||||
callback_name,
|
||||
invocation,
|
||||
false,
|
||||
|_scope, outcome| outcome,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn invoke_event_and_then<'s, R>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
callback_kind: &str,
|
||||
log_label: &str,
|
||||
log_level: CallbackExceptionLogLevel,
|
||||
callback_name: &str,
|
||||
invocation: CallbackInvocation<'s, '_>,
|
||||
complete: impl FnOnce(&mut v8::PinScope<'s, '_>, CallbackInvocationOutcome) -> R,
|
||||
) -> R {
|
||||
let host_ptr = invocation.host_ptr;
|
||||
let event_callback_scope = host_ptr
|
||||
.map(|host_ptr| unsafe { &mut *host_ptr }.enter_event_callback_invocation_scope());
|
||||
let defer_window_event_restore = event_callback_scope
|
||||
.as_ref()
|
||||
.is_some_and(|event_scope| event_scope.is_outermost())
|
||||
&& host_ptr
|
||||
.is_some_and(|host_ptr| !unsafe { &*host_ptr }.explicit_event_dispatch_is_active());
|
||||
let result = Self::invoke_with_completion(
|
||||
scope,
|
||||
callback_kind,
|
||||
log_label,
|
||||
log_level,
|
||||
callback_name,
|
||||
invocation,
|
||||
defer_window_event_restore,
|
||||
complete,
|
||||
);
|
||||
drop(event_callback_scope);
|
||||
result
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn invoke_with_completion<'s, R>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
callback_kind: &str,
|
||||
log_label: &str,
|
||||
log_level: CallbackExceptionLogLevel,
|
||||
callback_name: &str,
|
||||
invocation: CallbackInvocation<'s, '_>,
|
||||
defer_window_event_restore: bool,
|
||||
complete: impl FnOnce(&mut v8::PinScope<'s, '_>, CallbackInvocationOutcome) -> R,
|
||||
) -> R {
|
||||
if let Some(host_ptr) = invocation.host_ptr {
|
||||
unsafe { &*host_ptr }.debug_assert_not_in_structural_mutation("callback invocation");
|
||||
}
|
||||
@@ -199,10 +253,10 @@ impl CallbackInvoker {
|
||||
(invocation.host_ptr, invocation.relevant_identity)
|
||||
&& !unsafe { &*host_ptr }.window_execution_context_identity_is_current(identity)
|
||||
{
|
||||
return CallbackInvocationOutcome::Retired;
|
||||
return complete(scope, CallbackInvocationOutcome::Retired);
|
||||
}
|
||||
|
||||
let result = with_webidl_callback_contexts(
|
||||
with_webidl_callback_contexts(
|
||||
scope,
|
||||
invocation.relevant_context,
|
||||
invocation.incumbent_context,
|
||||
@@ -255,18 +309,29 @@ impl CallbackInvoker {
|
||||
},
|
||||
);
|
||||
|
||||
if let Some(previous) = previous_window_event {
|
||||
let global = relevant_context.global(scope);
|
||||
let _ = global.set(scope, v8str(scope, WINDOW_EVENT_SLOT).into(), previous);
|
||||
}
|
||||
result
|
||||
},
|
||||
);
|
||||
let outcome = match result {
|
||||
Ok(value) => CallbackInvocationOutcome::Returned(value),
|
||||
Err(report) => CallbackInvocationOutcome::Threw(report),
|
||||
};
|
||||
let completed = complete(scope, outcome);
|
||||
|
||||
match result {
|
||||
Ok(value) => CallbackInvocationOutcome::Returned(value),
|
||||
Err(report) => CallbackInvocationOutcome::Threw(report),
|
||||
}
|
||||
if let Some(previous) = previous_window_event {
|
||||
if defer_window_event_restore && let Some(host_ptr) = invocation.host_ptr {
|
||||
crate::context_bootstrap::enqueue_window_event_restore_after_microtask_checkpoint(
|
||||
scope,
|
||||
host_ptr,
|
||||
invocation.relevant_identity,
|
||||
relevant_context,
|
||||
previous,
|
||||
);
|
||||
} else {
|
||||
let global = relevant_context.global(scope);
|
||||
let _ = global.set(scope, v8str(scope, WINDOW_EVENT_SLOT).into(), previous);
|
||||
}
|
||||
}
|
||||
completed
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -328,6 +328,7 @@ pub(crate) use self::message_ports::{
|
||||
};
|
||||
use self::message_ports::{schedule_host_callback, schedule_scope_callback};
|
||||
pub(crate) use self::microtask_checkpoint::{
|
||||
enqueue_window_event_restore_after_microtask_checkpoint,
|
||||
install_agent_microtask_checkpoint_tasks, run_end_of_microtask_checkpoint_tasks,
|
||||
};
|
||||
pub(crate) use self::navigation_bootstrap::{
|
||||
|
||||
+3
@@ -1,4 +1,5 @@
|
||||
use super::*;
|
||||
use crate::util::context_host_ptr_from_global_bridge;
|
||||
|
||||
pub(crate) fn simple_event_target_add_event_listener_callback<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
@@ -37,5 +38,7 @@ pub(crate) fn simple_event_target_dispatch_event_callback<'s>(
|
||||
rv.set(v8::Boolean::new(scope, true).into());
|
||||
return;
|
||||
};
|
||||
let _explicit_dispatch_scope = context_host_ptr_from_global_bridge(scope)
|
||||
.map(|host_ptr| unsafe { &mut *host_ptr }.enter_explicit_event_dispatch_scope());
|
||||
simple_object_event_target_dispatch(scope, &args, slot_name, &mut rv);
|
||||
}
|
||||
|
||||
+18
-17
@@ -257,28 +257,29 @@ fn invoke_simple_event_callback_with_invocation<'s>(
|
||||
let target_name = simple_event_target_interface_name(scope, callback_target);
|
||||
host.schedule_dom_debugger_event_listener_pause_for_interface(event_type, &target_name)
|
||||
});
|
||||
match CallbackInvoker::invoke(
|
||||
CallbackInvoker::invoke_event_and_then(
|
||||
scope,
|
||||
"event listener",
|
||||
"simple event listener threw",
|
||||
CallbackExceptionLogLevel::Debug,
|
||||
callback_name,
|
||||
invocation,
|
||||
) {
|
||||
CallbackInvocationOutcome::Returned(value) => Some(value),
|
||||
CallbackInvocationOutcome::Threw(report) => {
|
||||
if let Some(host_ptr) = host_ptr {
|
||||
report_event_callback_exception(
|
||||
scope,
|
||||
host_ptr,
|
||||
event_type,
|
||||
relevant_identity,
|
||||
None,
|
||||
&report,
|
||||
);
|
||||
|scope, outcome| match outcome {
|
||||
CallbackInvocationOutcome::Returned(value) => Some(value),
|
||||
CallbackInvocationOutcome::Threw(report) => {
|
||||
if let Some(host_ptr) = host_ptr {
|
||||
report_event_callback_exception(
|
||||
scope,
|
||||
host_ptr,
|
||||
event_type,
|
||||
relevant_identity,
|
||||
None,
|
||||
&report,
|
||||
);
|
||||
}
|
||||
None
|
||||
}
|
||||
None
|
||||
}
|
||||
CallbackInvocationOutcome::Retired => None,
|
||||
}
|
||||
CallbackInvocationOutcome::Retired => None,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -10,6 +10,38 @@ enum AgentMicrotaskCheckpointTask {
|
||||
context: v8::Global<v8::Context>,
|
||||
transaction: v8::Global<v8::Object>,
|
||||
},
|
||||
RestoreWindowEvent {
|
||||
host_ptr: *mut crate::native_bridge::JsContextHost,
|
||||
relevant_identity: Option<crate::native_bridge::WindowExecutionContextIdentity>,
|
||||
context: v8::Global<v8::Context>,
|
||||
previous: v8::Global<v8::Value>,
|
||||
},
|
||||
}
|
||||
|
||||
pub(crate) fn enqueue_window_event_restore_after_microtask_checkpoint(
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
host_ptr: *mut crate::native_bridge::JsContextHost,
|
||||
relevant_identity: Option<crate::native_bridge::WindowExecutionContextIdentity>,
|
||||
context: v8::Local<'_, v8::Context>,
|
||||
previous: v8::Local<'_, v8::Value>,
|
||||
) {
|
||||
if scope.get_slot::<AgentMicrotaskCheckpointTasks>().is_none() {
|
||||
assert!(
|
||||
scope.set_slot(AgentMicrotaskCheckpointTasks::default()),
|
||||
"agent checkpoint state should be installed once on first use"
|
||||
);
|
||||
}
|
||||
let task = AgentMicrotaskCheckpointTask::RestoreWindowEvent {
|
||||
host_ptr,
|
||||
relevant_identity,
|
||||
context: v8::Global::new(scope, context),
|
||||
previous: v8::Global::new(scope, previous),
|
||||
};
|
||||
scope
|
||||
.get_slot_mut::<AgentMicrotaskCheckpointTasks>()
|
||||
.expect("agent checkpoint state should exist after installation")
|
||||
.tasks
|
||||
.push(task);
|
||||
}
|
||||
|
||||
pub(crate) fn install_agent_microtask_checkpoint_tasks(isolate: &mut v8::Isolate) {
|
||||
@@ -50,14 +82,27 @@ pub(crate) fn run_end_of_microtask_checkpoint_tasks(scope: &mut v8::PinScope<'_,
|
||||
return;
|
||||
};
|
||||
|
||||
let mut window_event_restores = Vec::new();
|
||||
for task in tasks {
|
||||
match task {
|
||||
AgentMicrotaskCheckpointTask::DeactivateIndexedDbTransaction {
|
||||
context,
|
||||
transaction,
|
||||
} => run_indexed_db_transaction_deactivation(scope, context, transaction),
|
||||
AgentMicrotaskCheckpointTask::RestoreWindowEvent {
|
||||
host_ptr,
|
||||
relevant_identity,
|
||||
context,
|
||||
previous,
|
||||
} => {
|
||||
window_event_restores.push((host_ptr, relevant_identity, context, previous));
|
||||
}
|
||||
}
|
||||
}
|
||||
for (host_ptr, relevant_identity, context, previous) in window_event_restores.into_iter().rev()
|
||||
{
|
||||
restore_window_event(scope, host_ptr, relevant_identity, context, previous);
|
||||
}
|
||||
}
|
||||
|
||||
fn run_indexed_db_transaction_deactivation(
|
||||
@@ -73,3 +118,26 @@ fn run_indexed_db_transaction_deactivation(
|
||||
transaction,
|
||||
);
|
||||
}
|
||||
|
||||
fn restore_window_event(
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
host_ptr: *mut crate::native_bridge::JsContextHost,
|
||||
relevant_identity: Option<crate::native_bridge::WindowExecutionContextIdentity>,
|
||||
context: v8::Global<v8::Context>,
|
||||
previous: v8::Global<v8::Value>,
|
||||
) {
|
||||
if relevant_identity.is_some_and(|identity| {
|
||||
!unsafe { &*host_ptr }.window_execution_context_identity_is_current(identity)
|
||||
}) {
|
||||
return;
|
||||
}
|
||||
let context = v8::Local::new(scope, &context);
|
||||
let scope = &mut v8::ContextScope::new(scope, context);
|
||||
let global = context.global(scope);
|
||||
let previous = v8::Local::new(scope, &previous);
|
||||
let _ = global.set(
|
||||
scope,
|
||||
crate::util::v8str(scope, crate::host::WINDOW_EVENT_SLOT).into(),
|
||||
previous,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -630,11 +630,11 @@ fn window_length_replaceable_getter<'s>(
|
||||
|
||||
fn window_event_replaceable_getter<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
_args: v8::FunctionCallbackArguments<'s>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'s, v8::Value>,
|
||||
) {
|
||||
rv.set(
|
||||
global_hidden_value(scope, WINDOW_EVENT_SLOT)
|
||||
window_event_value_for_receiver(scope, args.this())
|
||||
.unwrap_or_else(|| v8::undefined(scope).into()),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -158,9 +158,10 @@ pub(crate) const BODY_OR_FRAMESET_WINDOW_EVENT_HANDLER_PROPERTIES: &[&str] = &[
|
||||
|
||||
pub(super) use accessors::{
|
||||
window_console_getter, window_event_getter, window_event_setter,
|
||||
window_onerror_getter_function, window_onerror_setter_function,
|
||||
window_onrejectionhandled_getter_function, window_onrejectionhandled_setter_function,
|
||||
window_onunhandledrejection_getter_function, window_onunhandledrejection_setter_function,
|
||||
window_event_value_for_receiver, window_onerror_getter_function,
|
||||
window_onerror_setter_function, window_onrejectionhandled_getter_function,
|
||||
window_onrejectionhandled_setter_function, window_onunhandledrejection_getter_function,
|
||||
window_onunhandledrejection_setter_function,
|
||||
};
|
||||
pub(super) use console::{
|
||||
console_assert_callback, console_debug_callback, console_error_callback,
|
||||
|
||||
@@ -142,12 +142,37 @@ pub(in crate::context_bootstrap) fn window_event_getter<'s>(
|
||||
if !require_window_receiver(scope, &args) {
|
||||
return;
|
||||
}
|
||||
match global_hidden_value(scope, WINDOW_EVENT_SLOT) {
|
||||
match window_event_value_for_receiver(scope, args.this()) {
|
||||
Some(value) => rv.set(value),
|
||||
None => rv.set(v8::undefined(scope).into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(in crate::context_bootstrap) fn window_event_value_for_receiver<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
receiver: v8::Local<'s, v8::Object>,
|
||||
) -> Option<v8::Local<'s, v8::Value>> {
|
||||
let target_event = context_host_ptr_from_window_object(scope, receiver)
|
||||
.or_else(|| context_host_ptr_from_global_bridge(scope))
|
||||
.and_then(|host_ptr| {
|
||||
let dispatch_scope = if let Some(popup_id) =
|
||||
crate::native_bridge::lightweight_popup_id_from_window(scope, receiver)
|
||||
{
|
||||
crate::native_bridge::OwnerDispatchScope::LightweightPopup(popup_id)
|
||||
} else if let Some(handle) = window_child_context_handle(scope, receiver) {
|
||||
crate::native_bridge::OwnerDispatchScope::Child(handle)
|
||||
} else {
|
||||
crate::native_bridge::OwnerDispatchScope::Top
|
||||
};
|
||||
let host = unsafe { &*host_ptr };
|
||||
let owner = host.current_window_execution_context_owner(dispatch_scope)?;
|
||||
let (_, context) = host.window_execution_context(scope, owner, dispatch_scope)?;
|
||||
let global = context.global(scope);
|
||||
object_own_hidden_value(scope, global, WINDOW_EVENT_SLOT)
|
||||
});
|
||||
target_event.or_else(|| global_hidden_value(scope, WINDOW_EVENT_SLOT))
|
||||
}
|
||||
|
||||
pub(in crate::context_bootstrap) fn window_event_setter<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
|
||||
@@ -132,6 +132,21 @@ pub(crate) fn dispatch_window_error_event_with_details<'s>(
|
||||
colno: u32,
|
||||
error_value: Option<v8::Local<'s, v8::Value>>,
|
||||
) -> std::result::Result<(), String> {
|
||||
let current_context = scope.get_current_context();
|
||||
let reporting_owner = unsafe { &*host_ptr }
|
||||
.window_execution_context_identity_for_v8_context(scope, current_context)
|
||||
.map(|identity| identity.owner());
|
||||
let _error_reporting_scope = if let Some(owner) = reporting_owner {
|
||||
let Some(reporting_scope) =
|
||||
(unsafe { &mut *host_ptr }).enter_window_error_reporting_scope(owner)
|
||||
else {
|
||||
return Ok(());
|
||||
};
|
||||
Some(reporting_scope)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let global = scope.get_current_context().global(scope);
|
||||
ensure_window_reflecting_body_onerror_handler(scope);
|
||||
let error_value = error_value.unwrap_or_else(|| v8::null(scope).into());
|
||||
|
||||
@@ -302,28 +302,29 @@ fn invoke_prepared_event_callback_with_receiver<'s>(
|
||||
current_event,
|
||||
)
|
||||
.with_execution_context_currentness(host_ptr, relevant_identity);
|
||||
match CallbackInvoker::invoke(
|
||||
CallbackInvoker::invoke_event_and_then(
|
||||
scope,
|
||||
"event listener",
|
||||
"host event listener threw",
|
||||
crate::exception_reporting::CallbackExceptionLogLevel::Debug,
|
||||
callback_name,
|
||||
invocation,
|
||||
) {
|
||||
CallbackInvocationOutcome::Returned(value) => Some(value),
|
||||
CallbackInvocationOutcome::Threw(report) => {
|
||||
report_event_callback_exception(
|
||||
scope,
|
||||
host_ptr,
|
||||
event_type,
|
||||
relevant_identity,
|
||||
None,
|
||||
&report,
|
||||
);
|
||||
None
|
||||
}
|
||||
CallbackInvocationOutcome::Retired => None,
|
||||
}
|
||||
|scope, outcome| match outcome {
|
||||
CallbackInvocationOutcome::Returned(value) => Some(value),
|
||||
CallbackInvocationOutcome::Threw(report) => {
|
||||
report_event_callback_exception(
|
||||
scope,
|
||||
host_ptr,
|
||||
event_type,
|
||||
relevant_identity,
|
||||
None,
|
||||
&report,
|
||||
);
|
||||
None
|
||||
}
|
||||
CallbackInvocationOutcome::Retired => None,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn invoke_event_handler_property<'s>(
|
||||
@@ -573,10 +574,6 @@ pub(crate) fn report_event_callback_exception<'s>(
|
||||
child_handle: Option<DomHandle>,
|
||||
report: &V8ExceptionReport,
|
||||
) {
|
||||
if event_type == "error" {
|
||||
return;
|
||||
}
|
||||
|
||||
let child_handle = child_handle.or_else(|| {
|
||||
relevant_identity.and_then(|identity| match identity.dispatch_scope() {
|
||||
crate::native_bridge::OwnerDispatchScope::Child(handle) => Some(handle),
|
||||
@@ -585,6 +582,25 @@ pub(crate) fn report_event_callback_exception<'s>(
|
||||
})
|
||||
});
|
||||
if let Some(handle) = child_handle {
|
||||
let reporting_owner = relevant_identity
|
||||
.map(|identity| identity.owner())
|
||||
.or_else(|| {
|
||||
unsafe { &*host_ptr }.current_window_execution_context_owner(
|
||||
crate::native_bridge::OwnerDispatchScope::Child(handle),
|
||||
)
|
||||
});
|
||||
let _error_reporting_scope = if let Some(owner) = reporting_owner {
|
||||
let Some(reporting_scope) =
|
||||
(unsafe { &mut *host_ptr }).enter_window_error_reporting_scope(owner)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
Some(reporting_scope)
|
||||
} else if event_type == "error" {
|
||||
return;
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let event = {
|
||||
let host = unsafe { &mut *host_ptr };
|
||||
host.child_browsing_context_window_wrapper(scope, handle)
|
||||
|
||||
@@ -370,6 +370,9 @@ impl JsContextHost {
|
||||
child_window_event_listeners: HashMap::new(),
|
||||
next_child_window_event_registration_id: 0,
|
||||
event_callbacks: Default::default(),
|
||||
explicit_event_dispatch_depth: 0,
|
||||
event_callback_invocation_depth: 0,
|
||||
active_window_error_report_owners: HashSet::new(),
|
||||
browser_context_runtime,
|
||||
top_level_navigation_handoff_tx,
|
||||
service_worker_task_tx,
|
||||
|
||||
@@ -17,6 +17,55 @@ pub(crate) struct PreparedEventCallback {
|
||||
relevant_identity: Option<WindowExecutionContextIdentity>,
|
||||
}
|
||||
|
||||
pub(crate) struct WindowErrorReportingScope {
|
||||
host_ptr: *mut JsContextHost,
|
||||
owner: WindowExecutionContextOwner,
|
||||
}
|
||||
|
||||
pub(crate) struct ExplicitEventDispatchScope {
|
||||
host_ptr: *mut JsContextHost,
|
||||
}
|
||||
|
||||
pub(crate) struct EventCallbackInvocationScope {
|
||||
host_ptr: *mut JsContextHost,
|
||||
outermost: bool,
|
||||
}
|
||||
|
||||
impl EventCallbackInvocationScope {
|
||||
pub(crate) fn is_outermost(&self) -> bool {
|
||||
self.outermost
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for EventCallbackInvocationScope {
|
||||
fn drop(&mut self) {
|
||||
let host = unsafe { &mut *self.host_ptr };
|
||||
host.event_callback_invocation_depth = host
|
||||
.event_callback_invocation_depth
|
||||
.checked_sub(1)
|
||||
.expect("event callback invocation scope must be active");
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ExplicitEventDispatchScope {
|
||||
fn drop(&mut self) {
|
||||
let host = unsafe { &mut *self.host_ptr };
|
||||
host.explicit_event_dispatch_depth = host
|
||||
.explicit_event_dispatch_depth
|
||||
.checked_sub(1)
|
||||
.expect("explicit EventTarget dispatch scope must be active");
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for WindowErrorReportingScope {
|
||||
fn drop(&mut self) {
|
||||
let removed = unsafe { &mut *self.host_ptr }
|
||||
.active_window_error_report_owners
|
||||
.remove(&self.owner);
|
||||
debug_assert!(removed, "Window error reporting scope must be active");
|
||||
}
|
||||
}
|
||||
|
||||
impl PreparedEventCallback {
|
||||
pub(crate) fn callback<'s>(
|
||||
&self,
|
||||
@@ -80,6 +129,36 @@ impl EventCallbackRegistry {
|
||||
}
|
||||
|
||||
impl JsContextHost {
|
||||
pub(crate) fn enter_event_callback_invocation_scope(&mut self) -> EventCallbackInvocationScope {
|
||||
let outermost = self.event_callback_invocation_depth == 0;
|
||||
self.event_callback_invocation_depth += 1;
|
||||
EventCallbackInvocationScope {
|
||||
host_ptr: self,
|
||||
outermost,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn enter_explicit_event_dispatch_scope(&mut self) -> ExplicitEventDispatchScope {
|
||||
self.explicit_event_dispatch_depth += 1;
|
||||
ExplicitEventDispatchScope { host_ptr: self }
|
||||
}
|
||||
|
||||
pub(crate) fn explicit_event_dispatch_is_active(&self) -> bool {
|
||||
self.explicit_event_dispatch_depth != 0
|
||||
}
|
||||
|
||||
pub(crate) fn enter_window_error_reporting_scope(
|
||||
&mut self,
|
||||
owner: WindowExecutionContextOwner,
|
||||
) -> Option<WindowErrorReportingScope> {
|
||||
self.active_window_error_report_owners
|
||||
.insert(owner)
|
||||
.then(|| WindowErrorReportingScope {
|
||||
host_ptr: self,
|
||||
owner,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn register_target_event_listener<'s>(
|
||||
&mut self,
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
|
||||
@@ -982,6 +982,9 @@ pub(crate) struct JsContextHost {
|
||||
HashMap<DomHandle, IndexMap<String, Vec<ChildWindowEventListenerEntry>>>,
|
||||
next_child_window_event_registration_id: u64,
|
||||
event_callbacks: event_callbacks::EventCallbackRegistry,
|
||||
explicit_event_dispatch_depth: usize,
|
||||
event_callback_invocation_depth: usize,
|
||||
active_window_error_report_owners: HashSet<WindowExecutionContextOwner>,
|
||||
browser_context_runtime: crate::runtime::RendererBrowserContextRuntime,
|
||||
top_level_navigation_handoff_tx:
|
||||
crate::page_task_queue::RendererTopLevelNavigationHandoffSender,
|
||||
|
||||
@@ -2175,6 +2175,192 @@ fn cross_realm_listener_throw_reports_listener_global_not_target_global() {
|
||||
r#"[["function",true,true],["function-window-event-restored",true],["object",true,true],["object-window-event-restored",true]]"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn listener_exception_reporting_preserves_outer_realm_window_event() {
|
||||
let mut vm = new_storage_test_vm("https://event-listener-nested-window-event.test/");
|
||||
|
||||
vm.eval(
|
||||
r#"
|
||||
(() => {
|
||||
const host = document.body || document.documentElement || document;
|
||||
const listenerFrame = document.createElement("iframe");
|
||||
listenerFrame.srcdoc = `<script>
|
||||
function listener() { throw new Error("listener failure"); }
|
||||
function currentEventType() { return window.event?.type; }
|
||||
<\/script>`;
|
||||
const handlerFrame = document.createElement("iframe");
|
||||
host.appendChild(listenerFrame);
|
||||
host.appendChild(handlerFrame);
|
||||
globalThis.__nestedEventFrames = [listenerFrame, handlerFrame];
|
||||
return "ready";
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.expect("nested window.event frame setup should evaluate");
|
||||
vm.drain_pending_child_frame_work_for_test();
|
||||
|
||||
let result = vm
|
||||
.eval(
|
||||
r#"
|
||||
(() => {
|
||||
const [listenerFrame, handlerFrame] = globalThis.__nestedEventFrames;
|
||||
const listenerWindow = listenerFrame.contentWindow;
|
||||
const handlerWindow = handlerFrame.contentWindow;
|
||||
listenerWindow.onerror = new handlerWindow.Function(`
|
||||
parent.__nestedWindowEventState = [
|
||||
parent.__nestedEventFrames[0].contentWindow.currentEventType(),
|
||||
window.event?.type
|
||||
];
|
||||
return true;
|
||||
`);
|
||||
|
||||
const target = new EventTarget();
|
||||
target.addEventListener("party", listenerWindow.listener);
|
||||
target.dispatchEvent(new Event("party"));
|
||||
|
||||
return JSON.stringify({
|
||||
during: globalThis.__nestedWindowEventState,
|
||||
listenerAfter: listenerWindow.event,
|
||||
handlerAfter: handlerWindow.event
|
||||
});
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.expect("nested window.event exception reporting should evaluate");
|
||||
|
||||
assert_eq!(result, r#"{"during":["party","error"]}"#,);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cross_realm_window_event_getters_preserve_nested_handler_events() {
|
||||
let mut vm = new_storage_test_vm("https://nested-handler-window-event.test/");
|
||||
|
||||
vm.eval(
|
||||
r#"
|
||||
(() => {
|
||||
const host = document.body || document.documentElement || document;
|
||||
const firstFrame = document.createElement("iframe");
|
||||
const secondFrame = document.createElement("iframe");
|
||||
host.appendChild(firstFrame);
|
||||
host.appendChild(secondFrame);
|
||||
globalThis.__handlerEventFrames = [firstFrame, secondFrame];
|
||||
return "ready";
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.expect("nested handler frame setup should evaluate");
|
||||
vm.drain_pending_child_frame_work_for_test();
|
||||
|
||||
let result = vm
|
||||
.eval(
|
||||
r#"
|
||||
(() => {
|
||||
const [firstFrame, secondFrame] = globalThis.__handlerEventFrames;
|
||||
firstFrame.contentWindow.onerror = new secondFrame.contentWindow.Function(`
|
||||
top.__secondHandlerEvents = [
|
||||
top.event?.type,
|
||||
top.frames[0].event?.type,
|
||||
top.frames[1].event?.type,
|
||||
top.frames[1].event?.error?.name
|
||||
];
|
||||
return true;
|
||||
`);
|
||||
window.onerror = new firstFrame.contentWindow.Function(`
|
||||
top.__firstHandlerEvents = [
|
||||
top.event?.type,
|
||||
top.frames[0].event?.type,
|
||||
top.frames[1].event?.type ?? null
|
||||
];
|
||||
missingNestedHandlerName;
|
||||
`);
|
||||
window.onload = loadEvent => {
|
||||
const errorEvent = new ErrorEvent("error", {
|
||||
error: new Error("outer error")
|
||||
});
|
||||
window.dispatchEvent(errorEvent);
|
||||
};
|
||||
|
||||
window.dispatchEvent(new Event("load"));
|
||||
return JSON.stringify({
|
||||
first: globalThis.__firstHandlerEvents,
|
||||
second: globalThis.__secondHandlerEvents,
|
||||
after: [window.event, frames[0].event, frames[1].event]
|
||||
});
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.expect("nested cross-realm Window event handlers should evaluate");
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
r#"{"first":["load","error",null],"second":["load","error","error","ReferenceError"],"after":[null,null,null]}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn same_realm_onerror_throw_does_not_reenter_error_reporting() {
|
||||
let mut vm = new_storage_test_vm("https://onerror-recursion.test/");
|
||||
|
||||
let result = vm
|
||||
.eval(
|
||||
r#"
|
||||
(() => {
|
||||
let calls = 0;
|
||||
window.onerror = () => {
|
||||
calls++;
|
||||
throw new Error("nested onerror failure");
|
||||
};
|
||||
window.reportError(new Error("initial failure"));
|
||||
return String(calls);
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.expect("same-realm onerror recursion probe should evaluate");
|
||||
|
||||
assert_eq!(result, "1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn host_event_microtasks_observe_current_window_event_until_checkpoint_cleanup() {
|
||||
let mut vm = new_storage_test_vm("https://host-event-microtasks.test/");
|
||||
|
||||
vm.eval(
|
||||
r#"
|
||||
(() => {
|
||||
const frame = document.createElement("iframe");
|
||||
frame.srcdoc = `<script>window.__finalSrcdocRealm = true;<\/script>`;
|
||||
globalThis.__hostEventMicrotaskState = null;
|
||||
frame.addEventListener("load", event => {
|
||||
if (!frame.contentWindow.__finalSrcdocRealm) return;
|
||||
Promise.resolve().then(() => {
|
||||
globalThis.__hostEventMicrotaskState = [
|
||||
window.event === event,
|
||||
window.event?.type
|
||||
];
|
||||
});
|
||||
});
|
||||
(document.body || document.documentElement || document).appendChild(frame);
|
||||
return "ready";
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.expect("host-entered event microtask setup should evaluate");
|
||||
vm.drain_pending_child_frame_work_for_test();
|
||||
vm.perform_owner_lane_task_microtask_checkpoints()
|
||||
.expect("host event task checkpoint should complete");
|
||||
|
||||
let result = vm
|
||||
.eval(
|
||||
r#"JSON.stringify({
|
||||
during: globalThis.__hostEventMicrotaskState,
|
||||
after: window.event ?? null
|
||||
})"#,
|
||||
)
|
||||
.expect("host-entered event microtask state should evaluate");
|
||||
assert_eq!(result, r#"{"during":[true,"load"],"after":null}"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn event_timestamp_uses_performance_origin_and_safe_resolution() {
|
||||
let mut vm = new_storage_test_vm("https://event-timestamp.test/");
|
||||
|
||||
@@ -497,6 +497,8 @@ pub(super) fn event_target_dispatch_event_callback<'s>(
|
||||
return;
|
||||
}
|
||||
|
||||
let _explicit_dispatch_scope = host.enter_explicit_event_dispatch_scope();
|
||||
|
||||
let event_type = event_type_string(scope, event);
|
||||
if let Some(handle) = child_window_target {
|
||||
let event_type = event_type.as_deref().unwrap_or_default();
|
||||
|
||||
Reference in New Issue
Block a user