fix(dom): preserve event listener type identity

This commit is contained in:
ldm0
2026-10-11 18:06:46 +08:00
parent a6c879ad86
commit 5582c9e525
28 changed files with 573 additions and 194 deletions
@@ -4,12 +4,13 @@ use super::{
clear_event_composed_path, event_initialized, event_internal_bool_flag, event_is_dispatching,
new_dom_exception_value, set_event_composed_path, set_event_internal_flag, set_event_trusted,
};
use crate::event_type::EventType;
use crate::util::{throw_type_error, v8str};
pub(crate) fn prepare_script_dispatch<'s>(
scope: &mut v8::PinScope<'s, '_>,
value: v8::Local<'s, v8::Value>,
) -> Option<(v8::Local<'s, v8::Object>, String)> {
) -> Option<(v8::Local<'s, v8::Object>, EventType)> {
let event = v8::Local::<v8::Object>::try_from(value).ok();
let Some((event, initialized)) =
event.and_then(|event| event_initialized(scope, event).map(|flag| (event, flag)))
@@ -31,8 +32,8 @@ pub(crate) fn prepare_script_dispatch<'s>(
set_event_trusted(scope, event, false);
let event_type = crate::context_bootstrap::event_backing(scope, event)
.get(scope, v8str(scope, "type").into())?
.to_string(scope)?
.to_rust_string_lossy(scope);
.to_string(scope)?;
let event_type = EventType::from_v8(scope, event_type);
Some((event, event_type))
}
@@ -2,6 +2,7 @@ use super::*;
use crate::context_bootstrap::{
EventHandlerType, event_target_dispatch, events, invoke_simple_event_target_listeners,
};
use crate::event_type::EventTypeKey;
use crate::util::get_private_object;
#[derive(Clone, Copy)]
@@ -61,7 +62,7 @@ pub(in crate::context_bootstrap::indexed_db) fn dispatch_idb_event_object<'s>(
scope: &mut v8::PinScope<'s, '_>,
target: v8::Local<'s, v8::Object>,
event: v8::Local<'s, v8::Object>,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
) -> IdbEventDispatchResult {
let mut result = IdbEventDispatchResult::default();
let mut path = vec![target];
@@ -29,6 +29,5 @@ pub(super) use self::events::{
use super::{
MEDIA_QUERY_LIST_LISTENERS_SLOT, SIMPLE_EVENT_TARGET_ORDERED_HANDLERS_SLOT,
SIMPLE_EVENT_TARGET_SLOT, object_property_as_array, object_string_property_defined, v8_string,
v8str,
SIMPLE_EVENT_TARGET_SLOT, object_string_property_defined, v8str,
};
@@ -1,7 +1,6 @@
use super::{
MEDIA_QUERY_LIST_LISTENERS_SLOT, SIMPLE_EVENT_TARGET_ORDERED_HANDLERS_SLOT,
SIMPLE_EVENT_TARGET_SLOT, object_property_as_array, object_string_property_defined, v8_string,
v8str,
SIMPLE_EVENT_TARGET_SLOT, object_string_property_defined, v8str,
};
const SIMPLE_EVENT_TARGET_HANDLER_SLOT_FIELD: &str = "handlerSlot";
@@ -23,7 +23,7 @@ pub(in crate::context_bootstrap) fn media_query_list_add_listener_callback<'s>(
scope,
args.this(),
MEDIA_QUERY_LIST_LISTENERS_SLOT,
"change".to_owned(),
"change".into(),
listener,
webidl::EventListenerOptions::default(),
None,
@@ -1,4 +1,5 @@
use super::*;
use crate::event_type::EventTypeKey;
use crate::{
callback_invocation::{CallbackInvocation, CallbackInvocationOutcome, CallbackInvoker},
context_bootstrap::events::{
@@ -46,7 +47,7 @@ pub(crate) fn dispatch_simple_event_target_event<'s>(
scope: &mut v8::PinScope<'s, '_>,
target: v8::Local<'s, v8::Object>,
slot_name: &str,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
event: v8::Local<'s, v8::Object>,
) -> bool {
dispatch_simple_event_target_event_collecting_errors(
@@ -64,7 +65,7 @@ pub(crate) fn dispatch_simple_event_target_event_collecting_errors<'s>(
scope: &mut v8::PinScope<'s, '_>,
target: v8::Local<'s, v8::Object>,
slot_name: &str,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
event: v8::Local<'s, v8::Object>,
callback_errors: Option<&mut Vec<crate::exception_reporting::V8ExceptionReport>>,
) -> SimpleEventDispatchResult {
@@ -84,7 +85,7 @@ pub(crate) fn dispatch_simple_event_target_event_with_original_target<'s>(
target: v8::Local<'s, v8::Object>,
original_target: v8::Local<'s, v8::Object>,
slot_name: &str,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
event: v8::Local<'s, v8::Object>,
) -> bool {
dispatch_simple_event_target_event_with_original_target_collecting_errors(
@@ -104,7 +105,7 @@ fn dispatch_simple_event_target_event_with_original_target_collecting_errors<'s>
target: v8::Local<'s, v8::Object>,
original_target: v8::Local<'s, v8::Object>,
slot_name: &str,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
event: v8::Local<'s, v8::Object>,
mut callback_errors: Option<&mut Vec<crate::exception_reporting::V8ExceptionReport>>,
) -> SimpleEventDispatchResult {
@@ -117,7 +118,7 @@ fn dispatch_simple_event_target_event_with_original_target_collecting_errors<'s>
event,
);
let error_arguments = if event_type == "error"
let error_arguments = if event_type.is_type("error")
&& (crate::web_api_interfaces::WorkerGlobalScope::is_instance(scope, target)
|| crate::web_api_interfaces::Window::is_instance(scope, target))
{
@@ -136,7 +137,8 @@ fn dispatch_simple_event_target_event_with_original_target_collecting_errors<'s>
};
if can_invoke && !simple_event_target_uses_ordered_handlers(scope, target) {
let handler_name = format!("on{event_type}");
if let Some(handler_key) = v8_string(scope, &handler_name)
if let Some(event_key) = event_type.to_v8(scope)
&& let Some(handler_key) = v8::String::concat(scope, v8str(scope, "on"), event_key)
&& let Some(handler_value) = target.get(scope, handler_key.into())
&& let Ok(handler) = v8::Local::<v8::Object>::try_from(handler_value)
&& handler.is_callable()
@@ -223,7 +225,7 @@ pub(crate) fn invoke_simple_event_target_listeners<'s>(
scope: &mut v8::PinScope<'s, '_>,
target: v8::Local<'s, v8::Object>,
slot_name: &str,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
event: v8::Local<'s, v8::Object>,
capture_phase: bool,
handler_arguments: &[v8::Local<'s, v8::Value>],
@@ -303,7 +305,7 @@ pub(crate) fn invoke_simple_event_target_listeners<'s>(
pub(crate) fn invoke_simple_event_listener<'s>(
scope: &mut v8::PinScope<'s, '_>,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
callback_name: &str,
listener: &SimpleObjectEventListenerSnapshot<'s>,
callback_this: v8::Local<'s, v8::Value>,
@@ -332,7 +334,7 @@ struct SimpleEventCallbackResult {
#[allow(clippy::too_many_arguments)]
fn invoke_simple_event_listener_collecting_errors<'s>(
scope: &mut v8::PinScope<'s, '_>,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
callback_name: &str,
listener: &SimpleObjectEventListenerSnapshot<'s>,
callback_this: v8::Local<'s, v8::Value>,
@@ -365,7 +367,7 @@ fn invoke_simple_event_listener_collecting_errors<'s>(
#[allow(clippy::too_many_arguments)]
fn invoke_simple_event_callback<'s>(
scope: &mut v8::PinScope<'s, '_>,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
callback_name: &str,
callback: v8::Local<'s, v8::Object>,
relevant_context: v8::Local<'s, v8::Context>,
@@ -417,7 +419,7 @@ fn simple_event_target_interface_name<'s>(
#[allow(clippy::too_many_arguments)]
fn invoke_simple_event_callback_with_invocation<'s>(
scope: &mut v8::PinScope<'s, '_>,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
callback_name: &str,
callback_target: v8::Local<'s, v8::Value>,
relevant_context: v8::Local<'s, v8::Context>,
@@ -4,6 +4,7 @@ use crate::callback_invocation::CallbackInvocation;
use crate::event_listener_args::{
AddEventListenerArgs, RemoveEventListenerArgs, parse_listener_args,
};
use crate::event_type::{EventType, EventTypeKey};
use crate::native_bridge::{WindowExecutionContextIdentity, lightweight_popup_id_from_window};
use crate::util::{
context_host_ptr_from_global_bridge, get_private_object, get_private_value,
@@ -128,7 +129,7 @@ impl<'s> SimpleObjectEventListenerSnapshot<'s> {
scope: &mut v8::PinScope<'s, '_>,
target: v8::Local<'s, v8::Object>,
slot_name: &str,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
) -> Option<Self> {
let listener = self.resolve_callback(scope, target, slot_name, event_type)?;
if listener.once {
@@ -149,7 +150,7 @@ impl<'s> SimpleObjectEventListenerSnapshot<'s> {
scope: &mut v8::PinScope<'s, '_>,
target: v8::Local<'s, v8::Object>,
slot_name: &str,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
) -> Option<Self> {
// Removing and re-adding the same callback creates a different entry.
// The removed entry must stay inactive in an existing dispatch snapshot.
@@ -262,7 +263,7 @@ pub(crate) fn simple_object_event_target_register_webidl_listener<'s>(
scope: &mut v8::PinScope<'s, '_>,
target: v8::Local<'s, v8::Object>,
slot_name: &str,
event_type: String,
event_type: EventType,
listener: webidl::WebIdlCallbackInterface,
options: webidl::EventListenerOptions,
signal: Option<ResolvedAbortSignal<'s>>,
@@ -278,7 +279,7 @@ fn simple_object_event_target_register_resolved_listener<'s>(
scope: &mut v8::PinScope<'s, '_>,
target: v8::Local<'s, v8::Object>,
slot_name: &str,
event_type: String,
event_type: EventType,
listener: SimpleObjectResolvedEventListener<'s>,
options: webidl::EventListenerOptions,
signal: Option<ResolvedAbortSignal<'s>>,
@@ -339,7 +340,7 @@ fn simple_object_event_target_register_resolved_listener<'s>(
options.capture,
);
}
if event_type == "message" {
if event_type.is_type("message") {
crate::context_bootstrap::flush_pending_worker_messages_for_listener(scope, target);
}
}
@@ -393,7 +394,7 @@ fn simple_object_abort_remove_listener_callback<'s>(
let Some(event_type) = data
.get(scope, v8str(scope, "type").into())
.and_then(|value| value.to_string(scope))
.map(|value| value.to_rust_string_lossy(scope))
.map(|value| EventType::from_v8(scope, value))
else {
return;
};
@@ -429,14 +430,14 @@ fn register_simple_object_abort_listener<'s>(
target: v8::Local<'s, v8::Object>,
entry: v8::Local<'s, v8::Object>,
slot_name: &str,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
listener: v8::Local<'s, v8::Value>,
capture: bool,
) -> bool {
let Some(slot_value) = v8_string(scope, slot_name) else {
return false;
};
let Some(event_type_value) = v8_string(scope, event_type) else {
let Some(event_type_value) = event_type.to_v8(scope) else {
return false;
};
let data = SimpleObjectAbortListenerDataDeclaration::new(
@@ -499,22 +500,21 @@ pub(in crate::context_bootstrap::media_queries::events::simple_event_target) fn
scope: &mut v8::PinScope<'s, '_>,
target: v8::Local<'s, v8::Object>,
slot_name: &str,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
create: bool,
) -> Option<v8::Local<'s, v8::Array>> {
let registry = simple_object_event_listener_registry(scope, target, slot_name, create)?;
if let Some(listeners) = object_property_as_array(scope, registry, event_type) {
if let Some(listeners) = registry
.get(scope, event_type.to_v8(scope)?.into())
.and_then(|value| v8::Local::<v8::Array>::try_from(value).ok())
{
return Some(listeners);
}
if !create {
return None;
}
let listeners = v8::Array::new(scope, 0);
let _ = registry.set(
scope,
v8_string(scope, event_type)?.into(),
listeners.into(),
);
let _ = registry.set(scope, event_type.to_v8(scope)?.into(), listeners.into());
Some(listeners)
}
@@ -522,7 +522,7 @@ pub(crate) fn simple_object_event_listeners_snapshot<'s>(
scope: &mut v8::PinScope<'s, '_>,
target: v8::Local<'s, v8::Object>,
slot_name: &str,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
) -> Vec<SimpleObjectEventListenerSnapshot<'s>> {
let Some(listeners) =
simple_object_event_listener_array(scope, target, slot_name, event_type, false)
@@ -558,7 +558,7 @@ pub(crate) fn simple_object_has_event_listeners<'s>(
let Some(event_type) = event_types
.get_index(scope, index)
.and_then(|value| value.to_string(scope))
.map(|value| value.to_rust_string_lossy(scope))
.map(|value| EventType::from_v8(scope, value))
else {
continue;
};
@@ -589,7 +589,7 @@ pub(crate) fn simple_event_target_inspector_listener_snapshots<'s>(
let Some(event_type) = event_types
.get_index(scope, index)
.and_then(|value| value.to_string(scope))
.map(|value| value.to_rust_string_lossy(scope))
.map(|value| EventType::from_v8(scope, value))
else {
continue;
};
@@ -609,7 +609,7 @@ pub(crate) fn simple_event_target_inspector_listener_snapshots<'s>(
listener.original
};
snapshots.push(SimpleObjectEventListenerInspectorSnapshot {
event_type: event_type.clone(),
event_type: event_type.as_str_lossy().to_owned(),
original,
callback,
relevant_context: listener.relevant_context,
@@ -627,7 +627,7 @@ pub(crate) fn simple_object_event_set_ordered_handler<'s>(
scope: &mut v8::PinScope<'s, '_>,
target: v8::Local<'s, v8::Object>,
slot_name: &str,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
handler_slot_name: &'static str,
active: bool,
) {
@@ -670,7 +670,7 @@ pub(crate) fn simple_object_event_set_ordered_handler<'s>(
let _ = next.set_index(scope, next.length(), candidate);
}
if let Some(registry) = simple_object_event_listener_registry(scope, target, slot_name, false)
&& let Some(key) = v8_string(scope, event_type)
&& let Some(key) = event_type.to_v8(scope)
{
let _ = registry.set(scope, key.into(), next.into());
if next.length() == 0 {
@@ -696,7 +696,7 @@ pub(crate) fn simple_object_event_activate_uncompiled_handler<'s>(
scope: &mut v8::PinScope<'s, '_>,
target: v8::Local<'s, v8::Object>,
slot_name: &str,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
handler_slot_name: &'static str,
) {
set_simple_object_event_ordered_handler_callback(
@@ -713,7 +713,7 @@ fn set_simple_object_event_ordered_handler_callback<'s>(
scope: &mut v8::PinScope<'s, '_>,
target: v8::Local<'s, v8::Object>,
slot_name: &str,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
handler_slot_name: &'static str,
callback: Option<v8::Local<'s, v8::Object>>,
) {
@@ -800,7 +800,7 @@ pub(crate) fn simple_object_event_remove_listener_value_for_type<'s>(
scope: &mut v8::PinScope<'s, '_>,
target: v8::Local<'s, v8::Object>,
slot_name: &str,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
listener: v8::Local<'s, v8::Value>,
capture: bool,
) {
@@ -808,7 +808,11 @@ pub(crate) fn simple_object_event_remove_listener_value_for_type<'s>(
else {
return;
};
let Some(current) = object_property_as_array(scope, registry, event_type) else {
let Some(current) = event_type
.to_v8(scope)
.and_then(|key| registry.get(scope, key.into()))
.and_then(|value| v8::Local::<v8::Array>::try_from(value).ok())
else {
return;
};
let next = v8::Array::new(scope, 0);
@@ -825,7 +829,7 @@ pub(crate) fn simple_object_event_remove_listener_value_for_type<'s>(
}
let _ = next.set_index(scope, next.length(), candidate);
}
if let Some(key) = v8_string(scope, event_type) {
if let Some(key) = event_type.to_v8(scope) {
let _ = registry.set(scope, key.into(), next.into());
if next.length() == 0 {
remove_simple_object_event_type_order(scope, registry, event_type);
@@ -899,7 +903,7 @@ fn ensure_simple_object_event_type_order<'s>(
scope: &mut v8::PinScope<'s, '_>,
target: v8::Local<'s, v8::Object>,
slot_name: &str,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
) {
let Some(registry) = simple_object_event_listener_registry(scope, target, slot_name, false)
else {
@@ -908,36 +912,40 @@ fn ensure_simple_object_event_type_order<'s>(
let Some(order) = simple_object_event_type_order(scope, registry, true) else {
return;
};
let Some(key) = event_type.to_v8(scope) else {
return;
};
for index in 0..order.length() {
if order
.get_index(scope, index)
.and_then(|value| value.to_string(scope))
.is_some_and(|value| value.to_rust_string_lossy(scope) == event_type)
.is_some_and(|value| value.strict_equals(key.into()))
{
return;
}
}
if let Some(event_type) = v8_string(scope, event_type) {
let _ = order.set_index(scope, order.length(), event_type.into());
}
let _ = order.set_index(scope, order.length(), key.into());
}
fn remove_simple_object_event_type_order<'s>(
scope: &mut v8::PinScope<'s, '_>,
registry: v8::Local<'s, v8::Object>,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
) {
let Some(order) = simple_object_event_type_order(scope, registry, false) else {
return;
};
let next = v8::Array::new(scope, 0);
let Some(key) = event_type.to_v8(scope) else {
return;
};
for index in 0..order.length() {
let Some(candidate) = order.get_index(scope, index) else {
continue;
};
if candidate
.to_string(scope)
.is_some_and(|value| value.to_rust_string_lossy(scope) == event_type)
.is_some_and(|value| value.strict_equals(key.into()))
{
continue;
}
@@ -1129,7 +1137,7 @@ fn simple_object_event_listener_entry_registered<'s>(
scope: &mut v8::PinScope<'s, '_>,
target: v8::Local<'s, v8::Object>,
slot_name: &str,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
entry: v8::Local<'s, v8::Object>,
) -> bool {
let Some(listeners) =
@@ -3,8 +3,8 @@ pub(in crate::context_bootstrap) use crate::util::{
array_contains_strict, array_push_value, get_own_static_property, get_private_value,
global_constructor_object, global_constructor_prototype, object_bool_property,
object_defined_string_property as object_string_property_defined,
object_own_static_bool_property, object_property_as_array, object_property_as_object,
object_string_property, set_private_value,
object_own_static_bool_property, object_property_as_object, object_string_property,
set_private_value,
};
pub(in crate::context_bootstrap) fn global_queue_array<'s>(
@@ -152,6 +152,11 @@ fn handler_setter<'s>(
return;
};
media_queries::simple_object_event_set_ordered_handler(
scope, target, &listeners, event_type, slot, active,
scope,
target,
&listeners,
*event_type,
slot,
active,
);
}
@@ -1,4 +1,5 @@
use super::*;
use crate::event_type::EventTypeKey;
use crate::host::HostTimerOwner;
use crate::host::PublicEventDispatchResult;
use crate::script_provenance::CompiledStringProvenance;
@@ -388,7 +389,7 @@ impl DocumentRuntime {
pub(crate) fn event_listener_callback_ids(
&self,
target: EventTargetHandle,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
capture: bool,
) -> Vec<crate::native_bridge::EventCallbackId> {
self.events
@@ -405,7 +406,7 @@ impl DocumentRuntime {
pub(crate) fn insert_event_listener(
&mut self,
target: EventTargetHandle,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
registration: crate::host::EventListenerRegistration,
) {
if !self
@@ -427,7 +428,7 @@ impl DocumentRuntime {
pub(crate) fn remove_event_listener_by_id(
&mut self,
target: EventTargetHandle,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
callback_id: crate::native_bridge::EventCallbackId,
capture: bool,
) -> bool {
@@ -445,7 +446,7 @@ impl DocumentRuntime {
pub(crate) fn set_event_handler_property(
&mut self,
target: EventTargetHandle,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
callback_id: Option<crate::native_bridge::EventCallbackId>,
) -> Option<crate::native_bridge::EventCallbackId> {
self.events
@@ -455,7 +456,7 @@ impl DocumentRuntime {
pub(crate) fn set_compiled_event_handler_property(
&mut self,
target: EventTargetHandle,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
callback_id: Option<crate::native_bridge::EventCallbackId>,
) -> Option<crate::native_bridge::EventCallbackId> {
self.events
@@ -465,7 +466,7 @@ impl DocumentRuntime {
pub(crate) fn set_event_handler_content_attribute(
&mut self,
target: EventTargetHandle,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
owner: Option<DomHandle>,
) -> Option<crate::native_bridge::EventCallbackId> {
self.events
@@ -527,7 +528,7 @@ impl DocumentRuntime {
pub(crate) fn uncompiled_event_handler_content_attribute_owner(
&self,
target: EventTargetHandle,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
) -> Option<DomHandle> {
self.events
.uncompiled_event_handler_content_attribute_owner(target, event_type)
@@ -536,13 +537,17 @@ impl DocumentRuntime {
pub(crate) fn event_handler_property_callback_id(
&self,
target: EventTargetHandle,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
) -> Option<Option<crate::native_bridge::EventCallbackId>> {
self.events
.event_handler_property_callback_id(target, event_type)
}
pub(crate) fn has_event_listener(&self, target: EventTargetHandle, event_type: &str) -> bool {
pub(crate) fn has_event_listener(
&self,
target: EventTargetHandle,
event_type: &(impl EventTypeKey + ?Sized),
) -> bool {
self.events.has_listener(target, event_type)
}
+5 -4
View File
@@ -1,6 +1,7 @@
//! The shared EventTarget argument boundary, before target-specific mutation.
use crate::abort_signal_route::{ResolvedAbortSignal, event_listener_signal_from_options_value};
use crate::event_type::EventType;
use crate::webidl;
pub(crate) fn parse_listener_args<'s, T: webidl::WebIdlArguments<'s>>(
@@ -26,8 +27,8 @@ pub(crate) fn parse_listener_args<'s, T: webidl::WebIdlArguments<'s>>(
#[derive(webidl::WebIdlArgs)]
#[webidl(prefix = "EventTarget.addEventListener")]
pub(crate) struct AddEventListenerArgs<'s> {
#[webidl(required, name = "type")]
pub(crate) event_type: String,
#[webidl(required, name = "type", converter = "raw")]
pub(crate) event_type: EventType,
#[webidl(required, converter = "callback_interface", nullable)]
pub(crate) listener: Option<webidl::WebIdlCallbackInterface>,
#[webidl(with = add_event_listener_options)]
@@ -37,8 +38,8 @@ pub(crate) struct AddEventListenerArgs<'s> {
#[derive(webidl::WebIdlArgs)]
#[webidl(prefix = "EventTarget.removeEventListener")]
pub(crate) struct RemoveEventListenerArgs {
#[webidl(required, name = "type")]
pub(crate) event_type: String,
#[webidl(required, name = "type", converter = "raw")]
pub(crate) event_type: EventType,
#[webidl(required, converter = "callback_interface", nullable)]
pub(crate) listener: Option<webidl::WebIdlCallbackInterface>,
#[webidl(with = webidl::event_listener_options)]
+139
View File
@@ -0,0 +1,139 @@
//! Event type identity is a DOMString, including unpaired UTF-16 surrogates.
use std::{
fmt,
hash::{Hash, Hasher},
};
use crate::{dom::native::DomStringValue, util, webidl};
use indexmap::Equivalent;
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub(crate) struct EventType(DomStringValue);
impl EventType {
pub(crate) fn from_v8(scope: &v8::PinScope<'_, '_>, value: v8::Local<'_, v8::String>) -> Self {
let mut units = vec![0; value.length()];
value.write_v2(scope, 0, &mut units, v8::WriteFlags::empty());
Self(DomStringValue::from_utf16(&units))
}
}
impl Hash for EventType {
fn hash<H: Hasher>(&self, state: &mut H) {
// Scalar strings share str's hash so native names can be queried without
// allocating. Surrogate-bearing keys retain their original identity.
self.0.as_str_lossy().hash(state);
if self.0.as_str().is_none() {
self.0.utf16_units().hash(state);
}
}
}
impl fmt::Display for EventType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.0.as_str_lossy())
}
}
impl From<&str> for EventType {
fn from(value: &str) -> Self {
Self(value.into())
}
}
impl<'s> webidl::WebIdlConverter<'s> for EventType {
type Options = webidl::StringOptions;
fn convert(
scope: &mut v8::PinScope<'s, '_>,
value: v8::Local<'s, v8::Value>,
context: webidl::Context,
options: &Self::Options,
) -> Result<Self, webidl::WebIdlError> {
webidl::DomString16::convert(scope, value, context, options)
.map(|value| Self(DomStringValue::from_utf16(&value.0)))
}
}
/// A borrowed native name or an already-converted JS event type.
pub(crate) trait EventTypeKey: Hash + Equivalent<EventType> + fmt::Display {
fn to_event_type(&self) -> EventType;
fn as_str_lossy(&self) -> &str;
fn as_str(&self) -> Option<&str>;
fn is_type(&self, name: &str) -> bool;
fn to_v8<'s>(&self, scope: &v8::PinScope<'s, '_>) -> Option<v8::Local<'s, v8::String>>;
}
impl EventTypeKey for EventType {
fn to_event_type(&self) -> EventType {
self.clone()
}
fn as_str_lossy(&self) -> &str {
self.0.as_str_lossy()
}
fn as_str(&self) -> Option<&str> {
self.0.as_str()
}
fn is_type(&self, name: &str) -> bool {
self.0.as_str() == Some(name)
}
fn to_v8<'s>(&self, scope: &v8::PinScope<'s, '_>) -> Option<v8::Local<'s, v8::String>> {
match self.0.as_str() {
Some(value) => util::v8_string(scope, value),
None => v8::String::new_from_two_byte(
scope,
&self.0.utf16_units(),
v8::NewStringType::Normal,
),
}
}
}
impl Equivalent<EventType> for str {
fn equivalent(&self, other: &EventType) -> bool {
other.is_type(self)
}
}
impl EventTypeKey for str {
fn to_event_type(&self) -> EventType {
self.into()
}
fn as_str_lossy(&self) -> &str {
self
}
fn as_str(&self) -> Option<&str> {
Some(self)
}
fn is_type(&self, name: &str) -> bool {
self == name
}
fn to_v8<'s>(&self, scope: &v8::PinScope<'s, '_>) -> Option<v8::Local<'s, v8::String>> {
util::v8_string(scope, self)
}
}
impl Equivalent<EventType> for String {
fn equivalent(&self, other: &EventType) -> bool {
self.as_str().equivalent(other)
}
}
impl EventTypeKey for String {
fn to_event_type(&self) -> EventType {
self.as_str().into()
}
fn as_str_lossy(&self) -> &str {
self
}
fn as_str(&self) -> Option<&str> {
Some(self)
}
fn is_type(&self, name: &str) -> bool {
self == name
}
fn to_v8<'s>(&self, scope: &v8::PinScope<'s, '_>) -> Option<v8::Local<'s, v8::String>> {
util::v8_string(scope, self)
}
}
+68 -59
View File
@@ -1,4 +1,5 @@
use super::*;
use crate::event_type::{EventType, EventTypeKey};
use crate::{
context_bootstrap::{
CHILD_BROWSING_CONTEXT_HANDLE_SLOT, EventHandlerType,
@@ -158,8 +159,9 @@ impl EventDispatchEntry {
pub(crate) fn event_handler_content_attribute_owner(
runtime: &DocumentRuntime,
target: EventTargetHandle,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
) -> Option<DomHandle> {
let event_type = event_type.as_str()?;
let attribute_name =
crate::native_bridge::element::event_handler_content_attribute_name(event_type)?;
let handle = match target {
@@ -195,9 +197,10 @@ pub(crate) fn event_handler_content_attribute_owner(
}
pub(crate) struct HostEventTargetRegistry {
listeners: HashMap<EventTargetHandle, HashMap<String, Vec<EventListenerEntry>>>,
handler_properties: HashMap<EventTargetHandle, HashMap<String, EventHandlerPropertyEntry>>,
event_type_registration_ids: HashMap<EventTargetHandle, HashMap<String, u64>>,
listeners: HashMap<EventTargetHandle, indexmap::IndexMap<EventType, Vec<EventListenerEntry>>>,
handler_properties:
HashMap<EventTargetHandle, indexmap::IndexMap<EventType, EventHandlerPropertyEntry>>,
event_type_registration_ids: HashMap<EventTargetHandle, indexmap::IndexMap<EventType, u64>>,
next_listener_id: u64,
}
@@ -235,7 +238,7 @@ pub(crate) fn invoke_prepared_event_callback<'s>(
scope: &mut v8::PinScope<'s, '_>,
host_ptr: *mut JsContextHost,
invocation_target_in_shadow_tree: bool,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
callback_name: &str,
callback: crate::native_bridge::PreparedEventCallback,
target: EventTargetHandle,
@@ -261,7 +264,7 @@ pub(crate) fn invoke_prepared_event_callback<'s>(
fn invoke_prepared_event_callback_with_receiver<'s>(
scope: &mut v8::PinScope<'s, '_>,
host_ptr: *mut JsContextHost,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
callback_name: &str,
callback: crate::native_bridge::PreparedEventCallback,
receiver: v8::Local<'s, v8::Value>,
@@ -303,7 +306,7 @@ pub(crate) fn invoke_prepared_before_unload_event_handler<'s>(
host_ptr: *mut JsContextHost,
target: EventTargetHandle,
invocation_target_in_shadow_tree: bool,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
callback_name: &str,
callback: crate::native_bridge::PreparedEventCallback,
event: v8::Local<'s, v8::Object>,
@@ -393,11 +396,12 @@ fn invoke_event_handler_property<'s>(
target: EventTargetHandle,
invocation_target_in_shadow_tree: bool,
target_object: v8::Local<'s, v8::Object>,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
event: v8::Local<'s, v8::Object>,
) {
let Some(handler_name) =
crate::native_bridge::element::event_handler_content_attribute_name(event_type)
let Some(handler_name) = event_type
.as_str()
.and_then(crate::native_bridge::element::event_handler_content_attribute_name)
else {
return;
};
@@ -481,7 +485,7 @@ fn invoke_registered_event_handler<'s>(
host_ptr: *mut JsContextHost,
target: EventTargetHandle,
invocation_target_in_shadow_tree: bool,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
event: v8::Local<'s, v8::Object>,
callback_id: crate::native_bridge::EventCallbackId,
) {
@@ -492,7 +496,7 @@ fn invoke_registered_event_handler<'s>(
let timing_started = moli_trace::cdp_nav_timing_enabled().then(Instant::now);
if target == EventTargetHandle::Window
&& event_type == "error"
&& event_type.is_type("error")
&& let Some(arguments) = error_event_handler_arguments(scope, event)
{
let returned = invoke_prepared_event_callback(
@@ -519,7 +523,7 @@ fn invoke_registered_event_handler<'s>(
tracing::info!(
target: "moli_cdp_nav_timing",
stage = "event_handler_property_invoked",
event_type,
event_type = %event_type,
handler_name,
?target,
elapsed_ms = timing_started.elapsed().as_millis(),
@@ -528,7 +532,7 @@ fn invoke_registered_event_handler<'s>(
return;
}
if event_type == "beforeunload" {
if event_type.is_type("beforeunload") {
invoke_prepared_before_unload_event_handler(
scope,
host_ptr,
@@ -543,7 +547,7 @@ fn invoke_registered_event_handler<'s>(
tracing::info!(
target: "moli_cdp_nav_timing",
stage = "event_handler_property_invoked",
event_type,
event_type = %event_type,
handler_name,
?target,
elapsed_ms = timing_started.elapsed().as_millis(),
@@ -571,7 +575,7 @@ fn invoke_registered_event_handler<'s>(
tracing::info!(
target: "moli_cdp_nav_timing",
stage = "event_handler_property_invoked",
event_type,
event_type = %event_type,
handler_name,
?target,
elapsed_ms = timing_started.elapsed().as_millis(),
@@ -585,7 +589,7 @@ fn call_event_target_listeners_filtered<'s>(
host_ptr: *mut JsContextHost,
target: EventTargetHandle,
invocation_target_in_shadow_tree: bool,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
event: v8::Local<'s, v8::Object>,
capture_only: bool,
at_target: bool,
@@ -641,7 +645,7 @@ fn event_target_is_current(host_ptr: *mut JsContextHost, target: EventTargetHand
pub(crate) fn report_event_listener_exception<'s>(
scope: &mut v8::PinScope<'s, '_>,
host_ptr: *mut JsContextHost,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
handler: v8::Local<'s, v8::Function>,
report: &V8ExceptionReport,
) {
@@ -662,7 +666,7 @@ pub(crate) fn report_event_listener_exception<'s>(
pub(crate) fn report_event_callback_exception<'s>(
scope: &mut v8::PinScope<'s, '_>,
host_ptr: *mut JsContextHost,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
relevant_identity: Option<crate::native_bridge::WindowExecutionContextIdentity>,
child_handle: Option<DomHandle>,
report: &V8ExceptionReport,
@@ -689,7 +693,7 @@ pub(crate) fn report_event_callback_exception<'s>(
return;
};
Some(reporting_scope)
} else if event_type == "error" {
} else if event_type.is_type("error") {
return;
} else {
None
@@ -869,7 +873,7 @@ impl HostEventTargetRegistry {
pub(crate) fn set_event_handler_property(
&mut self,
target: EventTargetHandle,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
callback_id: Option<crate::native_bridge::EventCallbackId>,
) -> Option<crate::native_bridge::EventCallbackId> {
let existing_listener_id = self
@@ -886,7 +890,7 @@ impl HostEventTargetRegistry {
.map(EventHandlerPropertyState::Callback)
.unwrap_or(EventHandlerPropertyState::Null);
let previous = self.handler_properties.entry(target).or_default().insert(
event_type.to_owned(),
event_type.to_event_type(),
EventHandlerPropertyEntry { listener_id, state },
);
let previous_callback = previous.and_then(event_handler_callback_id);
@@ -899,7 +903,7 @@ impl HostEventTargetRegistry {
pub(crate) fn set_compiled_event_handler_property(
&mut self,
target: EventTargetHandle,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
callback_id: Option<crate::native_bridge::EventCallbackId>,
) -> Option<crate::native_bridge::EventCallbackId> {
let listener_id = self
@@ -913,7 +917,7 @@ impl HostEventTargetRegistry {
.map(EventHandlerPropertyState::Callback)
.unwrap_or(EventHandlerPropertyState::Null);
let previous = self.handler_properties.entry(target).or_default().insert(
event_type.to_owned(),
event_type.to_event_type(),
EventHandlerPropertyEntry {
listener_id: Some(listener_id),
state,
@@ -925,18 +929,18 @@ impl HostEventTargetRegistry {
pub(crate) fn set_event_handler_content_attribute(
&mut self,
target: EventTargetHandle,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
owner: Option<DomHandle>,
) -> Option<crate::native_bridge::EventCallbackId> {
let Some(owner) = owner else {
let removed = self
.handler_properties
.get_mut(&target)
.and_then(|handlers| handlers.remove(event_type));
.and_then(|handlers| handlers.shift_remove(event_type));
let remove_target = self
.handler_properties
.get(&target)
.is_some_and(HashMap::is_empty);
.is_some_and(indexmap::IndexMap::is_empty);
if remove_target {
self.handler_properties.remove(&target);
}
@@ -952,7 +956,7 @@ impl HostEventTargetRegistry {
.unwrap_or_else(|| self.allocate_listener_id());
self.ensure_event_type_registration_id(target, event_type, listener_id);
let previous = self.handler_properties.entry(target).or_default().insert(
event_type.to_owned(),
event_type.to_event_type(),
EventHandlerPropertyEntry {
listener_id: Some(listener_id),
state: EventHandlerPropertyState::Uncompiled(owner),
@@ -964,7 +968,7 @@ impl HostEventTargetRegistry {
pub(crate) fn has_event_handler_property_entry(
&self,
target: EventTargetHandle,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
) -> bool {
self.handler_properties
.get(&target)
@@ -974,7 +978,7 @@ impl HostEventTargetRegistry {
pub(crate) fn ensure_event_handler_content_attribute(
&mut self,
target: EventTargetHandle,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
owner: Option<DomHandle>,
) {
if let Some(owner) = owner
@@ -991,7 +995,7 @@ impl HostEventTargetRegistry {
pub(crate) fn uncompiled_event_handler_content_attribute_owner(
&self,
target: EventTargetHandle,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
) -> Option<DomHandle> {
match &self
.handler_properties
@@ -1007,7 +1011,7 @@ impl HostEventTargetRegistry {
fn event_handler_property_value(
&self,
target: EventTargetHandle,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
) -> Option<EventHandlerPropertyValue> {
match &self
.handler_properties
@@ -1026,7 +1030,7 @@ impl HostEventTargetRegistry {
pub(crate) fn event_handler_property_callback_id(
&self,
target: EventTargetHandle,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
) -> Option<Option<crate::native_bridge::EventCallbackId>> {
match &self
.handler_properties
@@ -1043,7 +1047,7 @@ impl HostEventTargetRegistry {
pub(crate) fn listener_callback_ids(
&self,
target: EventTargetHandle,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
capture: bool,
) -> Vec<crate::native_bridge::EventCallbackId> {
self.listeners
@@ -1071,7 +1075,7 @@ impl HostEventTargetRegistry {
event_type_registration_id,
EventListenerInspectorSnapshot {
registration_id: entry.id,
event_type: event_type.clone(),
event_type: event_type.as_str_lossy().to_owned(),
callback_id: entry.callback_id,
capture: entry.capture,
once: entry.once,
@@ -1094,7 +1098,7 @@ impl HostEventTargetRegistry {
.unwrap_or(registration_id),
EventListenerInspectorSnapshot {
registration_id,
event_type: event_type.clone(),
event_type: event_type.as_str_lossy().to_owned(),
callback_id,
capture: false,
once: false,
@@ -1115,7 +1119,7 @@ impl HostEventTargetRegistry {
pub(crate) fn insert_listener(
&mut self,
target: EventTargetHandle,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
registration: EventListenerRegistration,
) {
let id = self.allocate_listener_id();
@@ -1124,14 +1128,14 @@ impl HostEventTargetRegistry {
.listeners
.entry(target)
.or_default()
.entry(event_type.to_owned())
.entry(event_type.to_event_type())
.or_default();
if moli_trace::cdp_nav_timing_enabled() {
tracing::info!(
target: "moli_cdp_nav_timing",
stage = "event_listener_registered",
id,
event_type,
event_type = %event_type,
?target,
capture = registration.capture,
passive = registration.passive,
@@ -1160,7 +1164,7 @@ impl HostEventTargetRegistry {
pub(crate) fn remove_listener_by_id(
&mut self,
target: EventTargetHandle,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
callback_id: crate::native_bridge::EventCallbackId,
capture: bool,
) -> bool {
@@ -1174,7 +1178,7 @@ impl HostEventTargetRegistry {
entries.retain(|entry| entry.callback_id != callback_id || entry.capture != capture);
let removed = entries.len() != before;
if entries.is_empty() {
target_listeners.remove(event_type);
target_listeners.shift_remove(event_type);
}
if target_listeners.is_empty() {
self.listeners.remove(&target);
@@ -1212,7 +1216,11 @@ impl HostEventTargetRegistry {
self.retain_active_event_type_registration_ids();
}
pub(crate) fn has_listener(&self, target: EventTargetHandle, event_type: &str) -> bool {
pub(crate) fn has_listener(
&self,
target: EventTargetHandle,
event_type: &(impl EventTypeKey + ?Sized),
) -> bool {
self.handler_properties
.get(&target)
.and_then(|handlers| handlers.get(event_type))
@@ -1236,20 +1244,20 @@ impl HostEventTargetRegistry {
fn ensure_event_type_registration_id(
&mut self,
target: EventTargetHandle,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
registration_id: u64,
) {
self.event_type_registration_ids
.entry(target)
.or_default()
.entry(event_type.to_owned())
.entry(event_type.to_event_type())
.or_insert(registration_id);
}
fn event_type_registration_id(
&self,
target: EventTargetHandle,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
) -> Option<u64> {
self.event_type_registration_ids
.get(&target)
@@ -1260,7 +1268,7 @@ impl HostEventTargetRegistry {
fn event_type_has_live_registration(
&self,
target: EventTargetHandle,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
) -> bool {
self.listeners
.get(&target)
@@ -1276,7 +1284,7 @@ impl HostEventTargetRegistry {
fn remove_event_type_registration_id_if_unused(
&mut self,
target: EventTargetHandle,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
) {
if self.event_type_has_live_registration(target, event_type) {
return;
@@ -1284,7 +1292,7 @@ impl HostEventTargetRegistry {
let Some(event_types) = self.event_type_registration_ids.get_mut(&target) else {
return;
};
event_types.remove(event_type);
event_types.shift_remove(event_type);
if event_types.is_empty() {
self.event_type_registration_ids.remove(&target);
}
@@ -1315,7 +1323,7 @@ impl HostEventTargetRegistry {
host_ptr: *mut JsContextHost,
target: EventTargetHandle,
invocation_target_in_shadow_tree: bool,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
event: v8::Local<'s, v8::Object>,
capture_only: bool,
at_target: bool,
@@ -1381,7 +1389,7 @@ impl HostEventTargetRegistry {
tracing::info!(
target: "moli_cdp_nav_timing",
stage = "event_listener_batch_start",
event_type,
event_type = %event_type,
?target,
capture_only,
at_target,
@@ -1443,7 +1451,7 @@ impl HostEventTargetRegistry {
tracing::debug!(
listener_id = listener.id,
?target,
event_type,
event_type = %event_type,
"skipped event listener owned by a retired execution context"
);
continue;
@@ -1490,7 +1498,7 @@ impl HostEventTargetRegistry {
target: "moli_cdp_nav_timing",
stage = "event_listener_invoked",
listener_id = listener.id,
event_type,
event_type = %event_type,
?target,
capture_only,
at_target,
@@ -1878,7 +1886,7 @@ pub(crate) fn dispatch_public_event_with_original_target<'s, 'i>(
post_dispatch_related_target,
)?;
set_event_post_dispatch_source(scope, host_ptr, event, post_dispatch_source_target)?;
if event_type == "mouseover"
if event_type.is_type("mouseover")
&& !event_default_prevented(scope, event)
&& let EventTargetHandle::Node(handle) = dispatch_target
{
@@ -1886,7 +1894,7 @@ pub(crate) fn dispatch_public_event_with_original_target<'s, 'i>(
scope, host_ptr, handle,
);
}
if event_type == "keydown"
if event_type.is_type("keydown")
&& event_bool_property(scope, event, "isTrusted")
&& !event_default_prevented(scope, event)
{
@@ -1918,9 +1926,10 @@ fn stops_submit_or_reset_at_ancestor_form(
host_ptr: *mut JsContextHost,
original_target: EventTargetHandle,
current_target: EventTargetHandle,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
) -> bool {
if !matches!(event_type, "submit" | "reset") || current_target == original_target {
if !matches!(event_type.as_str(), Some("submit" | "reset")) || current_target == original_target
{
return false;
}
let EventTargetHandle::Node(handle) = current_target else {
@@ -1979,7 +1988,7 @@ fn run_host_event_dispatch<'s>(
host_ptr: *mut JsContextHost,
dispatch_target: EventTargetHandle,
invocation_target_in_shadow_tree: bool,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
event: v8::Local<'s, v8::Object>,
) -> std::result::Result<(), String> {
let status = call_event_target_listeners_filtered(
@@ -2139,7 +2148,7 @@ fn normalize_public_event_record<'s>(
scope: &mut v8::PinScope<'s, '_>,
event: v8::Local<'s, v8::Object>,
default_target: v8::Local<'s, v8::Value>,
) -> std::result::Result<String, String> {
) -> std::result::Result<EventType, String> {
let type_key = v8str(scope, "type");
let target_key = v8str(scope, "target");
let current_target_key = v8str(scope, "currentTarget");
@@ -2152,7 +2161,7 @@ fn normalize_public_event_record<'s>(
}
let event_type = event_type_value
.to_string(scope)
.map(|value| value.to_rust_string_lossy(scope))
.map(|value| EventType::from_v8(scope, value))
.unwrap_or_default();
let target_value =
+1
View File
@@ -57,6 +57,7 @@ mod document_task_lane;
mod dom_parser;
mod dynamic_script_owner;
mod event_listener_args;
mod event_type;
mod exception_reporting;
mod frame_owner_model;
mod host;
+4 -3
View File
@@ -1,3 +1,4 @@
use crate::event_type::{EventType, EventTypeKey};
use std::collections::{HashMap, HashSet};
use super::super::document_runtime::EventTargetHandle;
@@ -68,7 +69,7 @@ struct AbortSignalState {
struct AbortLinkedTargetListener {
target: EventTargetHandle,
event_type: String,
event_type: EventType,
callback_id: super::EventCallbackId,
capture: bool,
}
@@ -345,7 +346,7 @@ impl AbortStore {
scope: &mut v8::PinScope<'s, '_>,
signal: v8::Local<'s, v8::Object>,
target: EventTargetHandle,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
callback_id: super::EventCallbackId,
capture: bool,
) {
@@ -359,7 +360,7 @@ impl AbortStore {
.linked_target_listeners
.push(AbortLinkedTargetListener {
target,
event_type: event_type.to_owned(),
event_type: event_type.to_event_type(),
callback_id,
capture,
});
@@ -1,4 +1,5 @@
use super::JsContextHost;
use crate::event_type::EventTypeKey;
use crate::{
context_bootstrap::{
EVENT_DISPATCHING_SLOT, EVENT_STOP_IMMEDIATE_PROPAGATION_SLOT, EVENT_STOP_PROPAGATION_SLOT,
@@ -63,7 +64,7 @@ impl JsContextHost {
fn child_window_event_requires_runtime_dispatch(
&self,
handle: DomHandle,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
) -> bool {
self.child_window_proxy_records.has_live_window(handle)
|| self
@@ -84,7 +85,7 @@ impl JsContextHost {
pub(crate) fn child_window_event_listener_callback_ids(
&self,
handle: DomHandle,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
capture: bool,
) -> Vec<EventCallbackId> {
self.child_window_event_listeners
@@ -118,7 +119,7 @@ impl JsContextHost {
.filter_map(|entry| {
Some(crate::host::EventListenerInspectorSnapshot {
registration_id: entry.registration_id.0,
event_type: event_type.clone(),
event_type: event_type.as_str_lossy().to_owned(),
callback_id: entry.callback_state.callback_id()?,
capture: entry.capture,
once: entry.once,
@@ -132,7 +133,7 @@ impl JsContextHost {
pub(crate) fn insert_child_window_event_listener(
&mut self,
target: ChildWindowEventTarget,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
callback_id: EventCallbackId,
capture: bool,
once: bool,
@@ -141,7 +142,7 @@ impl JsContextHost {
self.child_window_event_listeners
.entry(target.child_handle())
.or_default()
.entry(event_type.to_owned())
.entry(event_type.to_event_type())
.or_default()
.push(ChildWindowEventListenerEntry {
registration_id,
@@ -193,7 +194,7 @@ impl JsContextHost {
pub(crate) fn set_child_window_event_handler_content_attribute(
&mut self,
handle: DomHandle,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
owner: Option<DomHandle>,
) -> Option<EventCallbackId> {
match owner {
@@ -209,7 +210,7 @@ impl JsContextHost {
fn set_child_window_event_handler_state(
&mut self,
handle: DomHandle,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
state: EventHandlerPropertyState,
) -> Option<EventCallbackId> {
let local_window_id = self
@@ -235,7 +236,7 @@ impl JsContextHost {
self.child_window_event_listeners
.entry(handle)
.or_default()
.entry(event_type.to_owned())
.entry(event_type.to_event_type())
.or_default()
.push(ChildWindowEventListenerEntry {
registration_id,
@@ -332,7 +333,7 @@ impl JsContextHost {
fn remove_child_window_event_handler_property(
&mut self,
handle: DomHandle,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
) -> Option<EventCallbackId> {
let target_map = self.child_window_event_listeners.get_mut(&handle)?;
let entries = target_map.get_mut(event_type)?;
@@ -449,7 +450,7 @@ impl JsContextHost {
&mut self,
scope: &mut v8::PinScope<'s, '_>,
target: ChildWindowEventTarget,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
event: v8::Local<'s, v8::Object>,
capture_only: bool,
at_target: bool,
@@ -527,7 +528,7 @@ impl JsContextHost {
&mut self,
scope: &mut v8::PinScope<'s, '_>,
handle: DomHandle,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
event: v8::Local<'s, v8::Object>,
) {
self.dispatch_child_window_event_with_target_override(
@@ -539,7 +540,7 @@ impl JsContextHost {
&mut self,
scope: &mut v8::PinScope<'s, '_>,
handle: DomHandle,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
event: v8::Local<'s, v8::Object>,
legacy_target_override: bool,
) {
@@ -634,7 +635,7 @@ impl JsContextHost {
fn child_window_event_dispatch_slots(
&self,
handle: DomHandle,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
registration_kind: Option<ChildWindowEventRegistrationKind>,
capture: Option<bool>,
) -> Vec<ChildWindowEventDispatchSlot> {
@@ -661,7 +662,7 @@ impl JsContextHost {
fn child_window_event_listener_snapshot(
&self,
handle: DomHandle,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
slot: ChildWindowEventDispatchSlot,
) -> Option<ChildWindowEventListenerSnapshot> {
let entry = self
@@ -681,7 +682,7 @@ impl JsContextHost {
fn retire_child_window_event_registration(
&mut self,
handle: DomHandle,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
slot: ChildWindowEventDispatchSlot,
) {
if let Some(callback_id) = self.remove_child_window_event_registration_by_id(
@@ -698,7 +699,7 @@ impl JsContextHost {
&mut self,
scope: &mut v8::PinScope<'_, '_>,
handle: DomHandle,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
slot: ChildWindowEventDispatchSlot,
) -> Option<ReadyChildWindowEventListenerInvocation> {
let mut snapshot = self.child_window_event_listener_snapshot(handle, event_type, slot)?;
@@ -742,14 +743,14 @@ impl JsContextHost {
&mut self,
scope: &mut v8::PinScope<'s, '_>,
ready: ReadyChildWindowEventListenerInvocation,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
event: v8::Local<'s, v8::Object>,
) -> (bool, Option<v8::Global<v8::Value>>) {
if !self.child_window_event_target_is_current(ready.target) {
return (false, None);
}
if ready.registration_kind == ChildWindowEventRegistrationKind::EventHandlerProperty
&& event_type == "beforeunload"
&& event_type.is_type("beforeunload")
{
invoke_prepared_before_unload_event_handler(
scope,
@@ -786,7 +787,7 @@ impl JsContextHost {
fn remove_child_window_event_registration_by_id(
&mut self,
handle: DomHandle,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
registration_id: ChildWindowEventRegistrationId,
) -> Option<EventCallbackId> {
let target_map = self.child_window_event_listeners.get_mut(&handle)?;
@@ -807,7 +808,7 @@ impl JsContextHost {
pub(crate) fn remove_child_window_event_listener_by_id(
&mut self,
handle: DomHandle,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
callback_id: EventCallbackId,
capture: bool,
) -> bool {
@@ -869,7 +870,7 @@ impl JsContextHost {
pub(crate) fn child_window_event_callback_identities_for_test(
&self,
handle: DomHandle,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
) -> Vec<(
Option<crate::native_bridge::WindowExecutionContextIdentity>,
Option<crate::native_bridge::WindowExecutionContextIdentity>,
@@ -893,11 +894,11 @@ fn child_window_event_type_from_handler_name(name: &str) -> Option<&str> {
fn child_window_event_callback_arguments<'s>(
scope: &mut v8::PinScope<'s, '_>,
registration_kind: ChildWindowEventRegistrationKind,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
event: v8::Local<'s, v8::Object>,
) -> Vec<v8::Local<'s, v8::Value>> {
if registration_kind == ChildWindowEventRegistrationKind::EventHandlerProperty
&& event_type == "error"
&& event_type.is_type("error")
&& let Some(arguments) = error_event_handler_arguments(scope, event)
{
arguments.to_vec()
@@ -908,7 +909,7 @@ fn child_window_event_callback_arguments<'s>(
fn apply_child_window_event_handler_return<'s>(
scope: &mut v8::PinScope<'s, '_>,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
event: v8::Local<'s, v8::Object>,
returned: Option<v8::Global<v8::Value>>,
) {
@@ -916,7 +917,7 @@ fn apply_child_window_event_handler_return<'s>(
return;
};
let returned = v8::Local::new(scope, returned);
let handler_type = if event_type == "error" && event_is_error_event(scope, event) {
let handler_type = if event_type.is_type("error") && event_is_error_event(scope, event) {
EventHandlerType::OnErrorEventHandler
} else {
EventHandlerType::EventHandler
@@ -1,3 +1,4 @@
use crate::event_type::EventTypeKey;
use std::collections::{BTreeMap, BTreeSet, HashMap};
use moli_page_types::{
@@ -307,7 +308,7 @@ impl JsContextHost {
pub(crate) fn schedule_dom_debugger_event_listener_pause_for_target(
&self,
event_name: &str,
event_name: &(impl EventTypeKey + ?Sized),
target: EventTargetHandle,
) -> Option<RendererDomDebuggerScheduledPause> {
if self
@@ -332,9 +333,10 @@ impl JsContextHost {
pub(crate) fn schedule_dom_debugger_event_listener_pause_for_interface(
&self,
event_name: &str,
event_name: &(impl EventTypeKey + ?Sized),
target_name: &str,
) -> Option<RendererDomDebuggerScheduledPause> {
let event_name = event_name.as_str()?;
self.dom_debugger_state
.schedule_event_listener_pause(event_name, target_name)
}
@@ -1,4 +1,5 @@
use super::{JsContextHost, WindowExecutionContextIdentity, WindowExecutionContextOwner};
use crate::event_type::EventTypeKey;
use moli_webidl_callback::{PreparedWebIdlCallbackInterface, WebIdlCallbackInterface};
use std::collections::{HashMap, HashSet};
@@ -142,7 +143,7 @@ impl JsContextHost {
&mut self,
scope: &mut v8::PinScope<'s, '_>,
target: crate::document_runtime::EventTargetHandle,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
callback: v8::Local<'s, v8::Object>,
relevant_context: v8::Local<'s, v8::Context>,
incumbent_context: v8::Local<'s, v8::Context>,
@@ -207,7 +208,7 @@ impl JsContextHost {
&mut self,
scope: &mut v8::PinScope<'s, '_>,
target: crate::document_runtime::EventTargetHandle,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
callback: v8::Local<'s, v8::Object>,
capture: bool,
) -> bool {
@@ -255,7 +256,7 @@ impl JsContextHost {
&mut self,
scope: &mut v8::PinScope<'s, '_>,
target: crate::document_runtime::EventTargetHandle,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
handler: Option<v8::Local<'s, v8::Object>>,
) {
let relevant_context = handler
@@ -278,7 +279,7 @@ impl JsContextHost {
&mut self,
scope: &mut v8::PinScope<'s, '_>,
target: crate::document_runtime::EventTargetHandle,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
handler: Option<v8::Local<'s, v8::Function>>,
target_context: v8::Local<'s, v8::Context>,
) {
@@ -301,7 +302,7 @@ impl JsContextHost {
&mut self,
scope: &mut v8::PinScope<'s, '_>,
target: crate::document_runtime::EventTargetHandle,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
handler: Option<v8::Local<'s, v8::Object>>,
relevant_context: v8::Local<'s, v8::Context>,
incumbent_context: v8::Local<'s, v8::Context>,
@@ -323,7 +324,7 @@ impl JsContextHost {
&self,
scope: &mut v8::PinScope<'s, '_>,
target: crate::document_runtime::EventTargetHandle,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
) -> Option<v8::Local<'s, v8::Value>> {
match self.event_handler_property_callback_id(target, event_type)? {
Some(callback_id) => self.event_callback_value(scope, callback_id),
@@ -1037,8 +1037,10 @@ pub(crate) struct JsContextHost {
completed_child_document_networks:
Vec<crate::protocol_types::ChildFrameDocumentNetworkActivitySnapshot>,
active_child_subresource_request_scopes: Vec<DomHandle>,
child_window_event_listeners:
HashMap<DomHandle, IndexMap<String, Vec<ChildWindowEventListenerEntry>>>,
child_window_event_listeners: HashMap<
DomHandle,
IndexMap<crate::event_type::EventType, Vec<ChildWindowEventListenerEntry>>,
>,
next_child_window_event_registration_id: u64,
event_callbacks: event_callbacks::EventCallbackRegistry,
active_window_error_report_owners: HashSet<WindowExecutionContextOwner>,
@@ -4,6 +4,7 @@ use super::{
JsContextHost, NavigationHistoryEntrySeed, child_frame_runtime::WINDOW_EVENT_HANDLER_PROPERTIES,
};
use crate::document_response_decoder::decode_document_response;
use crate::event_type::EventTypeKey;
use crate::web_api_interfaces;
use crate::{
content_security_policy::content_security_policy_forces_opaque_origin,
@@ -3626,7 +3627,7 @@ impl JsContextHost {
&mut self,
scope: &mut v8::PinScope<'s, '_>,
target: PopupWindowEventTarget,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
event: v8::Local<'s, v8::Object>,
capture_only: bool,
at_target: bool,
@@ -3649,7 +3650,7 @@ impl JsContextHost {
let previous_message_source = self.enter_window_message_source_scope(
super::PendingWindowMessageEndpoint::LightweightPopup(popup_id),
);
let error_arguments = (event_type == "error")
let error_arguments = (event_type.is_type("error"))
.then(|| crate::context_bootstrap::error_event_handler_arguments(scope, event))
.flatten();
let ordinary_arguments = [event.into()];
@@ -1,4 +1,5 @@
use super::*;
use crate::event_type::EventTypeKey;
impl JsContextHost {
pub(crate) fn is_abort_signal<'s>(
@@ -30,7 +31,7 @@ impl JsContextHost {
scope: &mut v8::PinScope<'s, '_>,
signal: v8::Local<'s, v8::Object>,
target: crate::document_runtime::EventTargetHandle,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
callback_id: EventCallbackId,
capture: bool,
) {
@@ -73,7 +74,7 @@ impl JsContextHost {
pub(crate) fn remove_registered_event_listener_by_id(
&mut self,
target: crate::document_runtime::EventTargetHandle,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
callback_id: EventCallbackId,
capture: bool,
) {
@@ -1,3 +1,4 @@
use crate::event_type::EventType;
use crate::{
abort_signal_route::ResolvedAbortSignal,
event_listener_args::{AddEventListenerArgs, AddEventListenerOptions},
@@ -20,8 +21,8 @@ struct ObservableEventListenerOptions {
#[derive(webidl::WebIdlArgs)]
#[webidl(prefix = "EventTarget.when")]
struct WhenArgs {
#[webidl(required, name = "type")]
event_type: String,
#[webidl(required, name = "type", converter = "raw")]
event_type: EventType,
#[webidl(with = options_arg)]
options: ObservableEventListenerOptions,
}
@@ -43,7 +44,7 @@ fn options_arg<'s>(
/// Observable must not keep an otherwise unreachable standalone target alive.
pub(super) struct EventSource {
target: v8::Weak<v8::Object>,
event_type: String,
event_type: EventType,
options: webidl::EventListenerOptions,
// A WindowProxy can survive navigation; retain only the exact identity,
// never a strong context or a lookup that follows the replacement Window.
@@ -52,7 +53,7 @@ pub(super) struct EventSource {
pub(super) struct PreparedEventSource<'s> {
target: v8::Local<'s, v8::Object>,
event_type: String,
event_type: EventType,
options: webidl::EventListenerOptions,
window_identity: Option<WindowExecutionContextIdentity>,
}
@@ -0,0 +1,92 @@
globalThis.__probeEventListenerTypes = function eventListenerTypeRows(targets, EventCtor, OtherEventCtor = EventCtor) {
const rows = [];
const check = (name, passed) => rows.push({name, passed: !!passed});
const pairs = [['\ud800', '\ufffd'], ['\udc00', '\ufffd'], ['\ud800', '\ud801'],
['prefix\ud800\0suffix', 'prefix\ufffd\0suffix'], ['\ud800\ud800', '\ufffd\ufffd'],
['\udc00\ud800', '\ufffd\ufffd'], ['load', 'LOAD'], ['é', 'e\u0301'],
['type\0', 'type'], ['\ud83d\ude80', '\ufffd\ufffd']];
for (const [name, target] of targets) {
for (const [raw, other] of pairs) {
const prefix = name + ' ' + JSON.stringify([raw, other]);
const calls = [];
const first = event => calls.push('first:' + (event.type === raw));
const second = () => calls.push('second');
target.addEventListener(raw, first);
target.addEventListener(other, second);
const event = new EventCtor(raw);
target.dispatchEvent(event);
check(prefix + ' distinct dispatch', calls.join(',') === 'first:true');
calls.length = 0;
target.removeEventListener(other, first);
target.dispatchEvent(new EventCtor(raw));
check(prefix + ' removal preserves other key', calls.join(',') === 'first:true');
target.removeEventListener(raw, first);
target.removeEventListener(other, second);
calls.length = 0;
const both = () => calls.push('both');
target.addEventListener(raw, both, {once:true});
target.addEventListener(other, both, {once:true});
target.addEventListener(raw, both, {once:false});
target.dispatchEvent(new EventCtor(raw));
target.dispatchEvent(new EventCtor(raw));
target.dispatchEvent(new EventCtor(other));
target.dispatchEvent(new EventCtor(other));
check(prefix + ' duplicate and once identity', calls.join(',') === 'both,both');
target.removeEventListener(raw, both);
target.removeEventListener(other, both);
calls.length = 0;
const controller = new AbortController();
target.addEventListener(raw, both, {signal:controller.signal});
target.addEventListener(other, both);
controller.abort();
target.dispatchEvent(new EventCtor(raw));
target.dispatchEvent(new EventCtor(other));
check(prefix + ' abort removes exact key', calls.join(',') === 'both');
target.removeEventListener(raw, both);
target.removeEventListener(other, both);
calls.length = 0;
let conversions = 0;
target.addEventListener({[Symbol.toPrimitive](hint) {
conversions++; check(prefix + ' string hint', hint === 'string'); return raw;
}}, both);
target.dispatchEvent(new OtherEventCtor(raw));
check(prefix + ' one conversion and cross realm event', conversions === 1 && calls.join(',') === 'both');
target.removeEventListener({toString() { conversions++; return raw; }}, both);
target.dispatchEvent(new EventCtor(raw));
check(prefix + ' converted removal', conversions === 2 && calls.join(',') === 'both');
}
}
return rows;
};
(() => {
const child = document.querySelector('#child').contentWindow;
const channel = new MessageChannel();
const popup = open('about:blank');
if (!popup) throw new Error('listener identity popup did not open');
const detached = document.implementation.createHTMLDocument('listener types');
const element = document.createElement('button');
document.body.appendChild(element);
const targets = [
['EventTarget', new EventTarget()], ['Window', window], ['Document', document],
['connected Element', element], ['windowless Document', detached],
['detached select', detached.createElement('select')], ['child Window', child],
['popup Window', popup],
['MessagePort', channel.port1], ['AbortSignal', new AbortController().signal],
['FileReader', new FileReader()], ['XMLHttpRequest', new XMLHttpRequest()],
['XMLHttpRequestUpload', new XMLHttpRequest().upload],
['MediaQueryList', matchMedia('all')], ['Performance', performance],
['FontFaceSet', document.fonts]
];
let rows;
try {
rows = __probeEventListenerTypes(targets, Event, child.Event);
} finally {
channel.port1.close();
channel.port2.close();
popup.close();
element.remove();
}
globalThis.__eventListenerTypeResults = {rows, passed: rows.filter(row => row.passed).length,
total: rows.length, targets: targets.map(([name]) => name)};
return rows.every(row => row.passed) || JSON.stringify(rows.filter(row => !row.passed));
})()
@@ -0,0 +1,45 @@
use super::service_worker_drain::drain_service_worker_test_turn;
use super::*;
#[test]
fn event_listener_types_preserve_utf16_identity_for_native_and_wrapper_local_targets() {
let mut vm = new_storage_html_test_vm("https://event-listener-type.test/");
vm.eval("document.body.innerHTML = '<iframe id=child></iframe>';")
.unwrap();
assert_eq!(
vm.eval(include_str!("event_listener_type.js")).unwrap(),
"true"
);
}
#[tokio::test]
async fn event_listener_types_preserve_utf16_identity_in_workers_indexed_db_and_observable() {
let loader = ResourceRequestClient::new(&moli_fetch::FetchConfig::default()).unwrap();
let (mut vm, browser_context_runtime) =
new_service_worker_page_test_vm_with_loader_and_browser_context_runtime(
"https://event-listener-type-runtime.test/",
&loader,
);
vm.eval("document.body.innerHTML = '<iframe id=child></iframe>';")
.unwrap();
assert_eq!(
vm.eval(include_str!("event_listener_type.js")).unwrap(),
"true"
);
vm.eval(&format!(
"({}).then(result => globalThis.__listenerTypeDone = result, error => globalThis.__listenerTypeDone = String(error));",
include_str!("event_listener_type_runtime.js")
)).unwrap();
tokio::time::timeout(std::time::Duration::from_secs(10), async {
while vm
.eval("globalThis.__listenerTypeDone !== undefined")
.unwrap()
!= "true"
{
drain_service_worker_test_turn(&mut vm, &browser_context_runtime, &loader).await;
}
})
.await
.expect("worker and IndexedDB listener type fixture should settle");
assert_eq!(vm.eval("globalThis.__listenerTypeDone").unwrap(), "true");
}
@@ -0,0 +1,61 @@
(async () => {
const rows = [];
const source = `const probe = ${__probeEventListenerTypes.toString()};
const channel = new MessageChannel();
try {
postMessage(probe([['WorkerGlobalScope', self], ['worker EventTarget', new EventTarget()],
['worker AbortSignal', new AbortController().signal], ['worker MessagePort', channel.port1]], Event));
} finally { channel.port1.close(); channel.port2.close(); }`;
const url = URL.createObjectURL(new Blob([source], {type:'text/javascript'}));
const worker = new Worker(url);
try {
rows.push(...await new Promise((resolve, reject) => {
const timer = setTimeout(() => reject(new Error('worker listener identity timeout')), 5000);
worker.onmessage = event => { clearTimeout(timer); resolve(event.data); };
worker.onerror = event => { clearTimeout(timer); event.preventDefault(); reject(new Error(event.message)); };
}));
} finally { worker.terminate(); URL.revokeObjectURL(url); }
const name = 'event-listener-type-' + crypto.randomUUID();
let database;
try {
await new Promise((resolve, reject) => {
const request = indexedDB.open(name, 1);
request.onupgradeneeded = () => {
database = request.result;
const transaction = request.transaction;
const store = database.createObjectStore('store');
rows.push(...__probeEventListenerTypes([
['IDBDatabase', database], ['IDBOpenDBRequest', request],
['IDBTransaction', transaction], ['IDBRequest', store.get('key')]], Event));
};
request.onsuccess = () => { database = request.result; resolve(); };
request.onerror = () => reject(request.error);
});
} finally {
database?.close();
await new Promise((resolve, reject) => {
const request = indexedDB.deleteDatabase(name);
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
request.onblocked = () => reject(new Error('listener identity database remained open'));
});
}
const channel = new MessageChannel();
try {
for (const target of [new EventTarget(), channel.port1]) {
const raw = '\ud800', replacement = '\ufffd', calls = [];
const controller = new AbortController();
target.when(raw).subscribe(() => calls.push('raw'), {signal:controller.signal});
target.when(replacement).subscribe(() => calls.push('replacement'), {signal:controller.signal});
target.dispatchEvent(new Event(raw));
rows.push({name:'Observable exact event type', passed:calls.join(',') === 'raw'});
controller.abort();
target.dispatchEvent(new Event(raw));
rows.push({name:'Observable abort removes exact type', passed:calls.join(',') === 'raw'});
}
} finally { channel.port1.close(); channel.port2.close(); }
globalThis.__eventListenerRuntimeResults = {rows, passed:rows.filter(row => row.passed).length, total:rows.length};
const combined = [...__eventListenerTypeResults.rows, ...rows];
globalThis.__eventListenerTypeResults = {rows:combined, passed:combined.filter(row => row.passed).length, total:combined.length};
return combined.every(row => row.passed) || JSON.stringify(combined.filter(row => !row.passed));
})()
@@ -28,6 +28,7 @@ mod document_open_origin;
mod event_constructor_type;
mod event_handlers;
mod event_listener_options;
mod event_listener_type;
mod events_selection_storage;
mod fontface_descriptors;
mod fullscreen;
+1 -1
View File
@@ -10,7 +10,7 @@ pub use moli_v8_util::{
initialize_ecmascript_intrinsic_registry, new_null_prototype_object, object_bool_property,
object_chain_contains, object_defined_string_property, object_non_empty_string_property,
object_number_property, object_own_static_bool_property,
object_own_static_string_property, object_property_as_array, object_property_as_object,
object_own_static_string_property, object_property_as_object,
object_string_property, private_key, register_ecmascript_intrinsic,
registered_ecmascript_constructor, registered_ecmascript_prototype, set_null_prototype,
set_private_value, throw_range_error, throw_type_error, v8_json_parse, v8_string, v8str,
+18 -18
View File
@@ -50,6 +50,7 @@ use super::{
use crate::event_listener_args::{
AddEventListenerArgs, RemoveEventListenerArgs, parse_listener_args,
};
use crate::event_type::{EventType, EventTypeKey};
use crate::web_api_interfaces;
use moli_webapi_declare::{WebApiFunctionTemplate, WebApiObject};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
@@ -280,7 +281,7 @@ fn register_dom_event_target_listener<'s>(
) else {
return;
};
if call.event_type == "selectionchange"
if call.event_type.is_type("selectionchange")
&& target == EventTargetHandle::Node(host.document_handle())
{
define_non_enumerable_static_bool_property(
@@ -300,7 +301,9 @@ fn register_dom_event_target_listener<'s>(
capture,
);
}
if !matches!(target, EventTargetHandle::ChildWindow(_)) && call.event_type == "animationstart" {
if !matches!(target, EventTargetHandle::ChildWindow(_))
&& call.event_type.is_type("animationstart")
{
queue_animation_start_for_listener_target(scope, host_ptr, target);
}
}
@@ -308,11 +311,11 @@ fn register_dom_event_target_listener<'s>(
fn default_passive_value(
host: &JsContextHost,
target: EventTargetHandle,
event_type: &str,
event_type: &(impl EventTypeKey + ?Sized),
) -> bool {
if !matches!(
event_type,
"touchstart" | "touchmove" | "wheel" | "mousewheel"
event_type.as_str(),
Some("touchstart" | "touchmove" | "wheel" | "mousewheel")
) {
return false;
}
@@ -459,7 +462,9 @@ pub(super) fn event_target_dispatch_event_callback<'s>(
}
set_event_trusted(scope, event, false);
let event_type = event_type_string(scope, event);
let Some(event_type) = event_type_value(scope, event) else {
return;
};
if let Some(receiver) = window_receiver {
let Some(binding) = receiver.resolve_live_binding(host) else {
rv.set_bool(true);
@@ -470,12 +475,7 @@ pub(super) fn event_target_dispatch_event_callback<'s>(
let event = v8::Local::new(scope, &event);
let host = unsafe { &mut *host_ptr };
if let OwnerDispatchScope::Child(handle) = dispatch_scope {
host.dispatch_child_window_event(
scope,
handle,
event_type.as_deref().unwrap_or_default(),
event,
);
host.dispatch_child_window_event(scope, handle, &event_type, event);
Ok(!crate::context_bootstrap::event_bool_attribute(
scope,
event,
@@ -484,7 +484,7 @@ pub(super) fn event_target_dispatch_event_callback<'s>(
} else {
host.dispatch_public_event(scope, host_ptr, EventTargetHandle::Window, event)
.map(|dispatch| {
if let Some(event_type) = event_type.as_deref() {
if let Some(event_type) = event_type.as_str() {
increment_performance_event_count(scope, event_type);
}
dispatch.dispatch_event_return_value()
@@ -502,7 +502,7 @@ pub(super) fn event_target_dispatch_event_callback<'s>(
rv.set_bool(false);
return;
};
let click_activation_target = if event_type.as_deref() == Some("click")
let click_activation_target = if event_type.is_type("click")
&& event_is_mouse_event(scope, event)
&& let EventTargetHandle::Node(handle) = target
{
@@ -520,7 +520,7 @@ pub(super) fn event_target_dispatch_event_callback<'s>(
match host.dispatch_public_event(scope, host_ptr, target, event) {
Ok(dispatch) => {
let return_value = dispatch.dispatch_event_return_value();
if let Some(event_type) = event_type.as_deref() {
if let Some(event_type) = event_type.as_str() {
increment_performance_event_count(scope, event_type);
}
if let Some(activation) = legacy_click_activation {
@@ -540,14 +540,14 @@ pub(super) fn event_target_dispatch_event_callback<'s>(
}
}
fn event_type_string(
fn event_type_value(
scope: &mut v8::PinScope<'_, '_>,
event: v8::Local<'_, v8::Object>,
) -> Option<String> {
) -> Option<EventType> {
crate::context_bootstrap::event_backing(scope, event)
.get(scope, v8str(scope, "type").into())
.and_then(|value| value.to_string(scope))
.map(|value| value.to_rust_string_lossy(scope))
.map(|value| EventType::from_v8(scope, value))
}
pub(super) fn window_set_timeout_callback<'s>(