diff --git a/moli-renderer-v8/src/context_bootstrap/event_target_dispatch.rs b/moli-renderer-v8/src/context_bootstrap/event_target_dispatch.rs index 749c500c21..f3ec578dae 100644 --- a/moli-renderer-v8/src/context_bootstrap/event_target_dispatch.rs +++ b/moli-renderer-v8/src/context_bootstrap/event_target_dispatch.rs @@ -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::::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)) } diff --git a/moli-renderer-v8/src/context_bootstrap/indexed_db/event_target/dispatch.rs b/moli-renderer-v8/src/context_bootstrap/indexed_db/event_target/dispatch.rs index 6202b2df1c..93dc252cba 100644 --- a/moli-renderer-v8/src/context_bootstrap/indexed_db/event_target/dispatch.rs +++ b/moli-renderer-v8/src/context_bootstrap/indexed_db/event_target/dispatch.rs @@ -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]; diff --git a/moli-renderer-v8/src/context_bootstrap/media_queries.rs b/moli-renderer-v8/src/context_bootstrap/media_queries.rs index ccb2818e43..83c1cdd456 100644 --- a/moli-renderer-v8/src/context_bootstrap/media_queries.rs +++ b/moli-renderer-v8/src/context_bootstrap/media_queries.rs @@ -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, }; diff --git a/moli-renderer-v8/src/context_bootstrap/media_queries/events.rs b/moli-renderer-v8/src/context_bootstrap/media_queries/events.rs index 393ed1d679..3983e6ee61 100644 --- a/moli-renderer-v8/src/context_bootstrap/media_queries/events.rs +++ b/moli-renderer-v8/src/context_bootstrap/media_queries/events.rs @@ -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"; diff --git a/moli-renderer-v8/src/context_bootstrap/media_queries/events/media_query_list/callbacks.rs b/moli-renderer-v8/src/context_bootstrap/media_queries/events/media_query_list/callbacks.rs index e4eec173bc..28626b5da5 100644 --- a/moli-renderer-v8/src/context_bootstrap/media_queries/events/media_query_list/callbacks.rs +++ b/moli-renderer-v8/src/context_bootstrap/media_queries/events/media_query_list/callbacks.rs @@ -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, diff --git a/moli-renderer-v8/src/context_bootstrap/media_queries/events/simple_event_target/dispatch.rs b/moli-renderer-v8/src/context_bootstrap/media_queries/events/simple_event_target/dispatch.rs index 4bb62278d2..123bfd02f7 100644 --- a/moli-renderer-v8/src/context_bootstrap/media_queries/events/simple_event_target/dispatch.rs +++ b/moli-renderer-v8/src/context_bootstrap/media_queries/events/simple_event_target/dispatch.rs @@ -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>, ) -> 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>, ) -> 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::::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>, diff --git a/moli-renderer-v8/src/context_bootstrap/media_queries/events/simple_event_target/listeners.rs b/moli-renderer-v8/src/context_bootstrap/media_queries/events/simple_event_target/listeners.rs index f06c228723..c26f8c6072 100644 --- a/moli-renderer-v8/src/context_bootstrap/media_queries/events/simple_event_target/listeners.rs +++ b/moli-renderer-v8/src/context_bootstrap/media_queries/events/simple_event_target/listeners.rs @@ -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 { 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 { // 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>, @@ -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>, @@ -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> { 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::::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> { 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>, ) { @@ -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::::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) = diff --git a/moli-renderer-v8/src/context_bootstrap/shared/object.rs b/moli-renderer-v8/src/context_bootstrap/shared/object.rs index 81d5adb776..a2b5d8dd77 100644 --- a/moli-renderer-v8/src/context_bootstrap/shared/object.rs +++ b/moli-renderer-v8/src/context_bootstrap/shared/object.rs @@ -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>( diff --git a/moli-renderer-v8/src/context_bootstrap/shared_event_targets.rs b/moli-renderer-v8/src/context_bootstrap/shared_event_targets.rs index 9deee49511..da4480a296 100644 --- a/moli-renderer-v8/src/context_bootstrap/shared_event_targets.rs +++ b/moli-renderer-v8/src/context_bootstrap/shared_event_targets.rs @@ -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, ); } diff --git a/moli-renderer-v8/src/document_runtime/events.rs b/moli-renderer-v8/src/document_runtime/events.rs index 32bacc89aa..d13ff6a10a 100644 --- a/moli-renderer-v8/src/document_runtime/events.rs +++ b/moli-renderer-v8/src/document_runtime/events.rs @@ -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 { 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, ) -> Option { 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, ) -> Option { 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, ) -> Option { 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 { 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> { 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) } diff --git a/moli-renderer-v8/src/event_listener_args.rs b/moli-renderer-v8/src/event_listener_args.rs index 5a801a4ee4..3552f66048 100644 --- a/moli-renderer-v8/src/event_listener_args.rs +++ b/moli-renderer-v8/src/event_listener_args.rs @@ -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(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(with = webidl::event_listener_options)] diff --git a/moli-renderer-v8/src/event_type.rs b/moli-renderer-v8/src/event_type.rs new file mode 100644 index 0000000000..70d86e9b0e --- /dev/null +++ b/moli-renderer-v8/src/event_type.rs @@ -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(&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 { + 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 + 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>; +} + +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> { + 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 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> { + util::v8_string(scope, self) + } +} + +impl Equivalent 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> { + util::v8_string(scope, self) + } +} diff --git a/moli-renderer-v8/src/host/events.rs b/moli-renderer-v8/src/host/events.rs index a542cc23a6..602e20929a 100644 --- a/moli-renderer-v8/src/host/events.rs +++ b/moli-renderer-v8/src/host/events.rs @@ -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 { + 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>>, - handler_properties: HashMap>, - event_type_registration_ids: HashMap>, + listeners: HashMap>>, + handler_properties: + HashMap>, + event_type_registration_ids: HashMap>, 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, child_handle: Option, 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, ) -> Option { 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, ) -> Option { 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, ) -> Option { 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, ) { 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 { 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 { 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> { 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 { 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 { 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 { +) -> std::result::Result { 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 = diff --git a/moli-renderer-v8/src/lib.rs b/moli-renderer-v8/src/lib.rs index b6a38da834..ce8e8395b1 100644 --- a/moli-renderer-v8/src/lib.rs +++ b/moli-renderer-v8/src/lib.rs @@ -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; diff --git a/moli-renderer-v8/src/native_bridge/abort.rs b/moli-renderer-v8/src/native_bridge/abort.rs index e2e19a8696..6e77165414 100644 --- a/moli-renderer-v8/src/native_bridge/abort.rs +++ b/moli-renderer-v8/src/native_bridge/abort.rs @@ -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, }); diff --git a/moli-renderer-v8/src/native_bridge/context_host/child_events.rs b/moli-renderer-v8/src/native_bridge/context_host/child_events.rs index 8eafbab984..7bd9171e32 100644 --- a/moli-renderer-v8/src/native_bridge/context_host/child_events.rs +++ b/moli-renderer-v8/src/native_bridge/context_host/child_events.rs @@ -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 { 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, ) -> Option { 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 { 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 { 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, capture: Option, ) -> Vec { @@ -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 { 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 { 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>) { 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 { 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, Option, @@ -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> { 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>, ) { @@ -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 diff --git a/moli-renderer-v8/src/native_bridge/context_host/dom_debugger.rs b/moli-renderer-v8/src/native_bridge/context_host/dom_debugger.rs index c6bb9aafa8..f18c88a183 100644 --- a/moli-renderer-v8/src/native_bridge/context_host/dom_debugger.rs +++ b/moli-renderer-v8/src/native_bridge/context_host/dom_debugger.rs @@ -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 { 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 { + let event_name = event_name.as_str()?; self.dom_debugger_state .schedule_event_listener_pause(event_name, target_name) } diff --git a/moli-renderer-v8/src/native_bridge/context_host/event_callbacks.rs b/moli-renderer-v8/src/native_bridge/context_host/event_callbacks.rs index 527ce35be0..41cb17ad28 100644 --- a/moli-renderer-v8/src/native_bridge/context_host/event_callbacks.rs +++ b/moli-renderer-v8/src/native_bridge/context_host/event_callbacks.rs @@ -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>, ) { 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>, 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>, 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> { match self.event_handler_property_callback_id(target, event_type)? { Some(callback_id) => self.event_callback_value(scope, callback_id), diff --git a/moli-renderer-v8/src/native_bridge/context_host/mod.rs b/moli-renderer-v8/src/native_bridge/context_host/mod.rs index 6eaa324b5f..37abc34196 100644 --- a/moli-renderer-v8/src/native_bridge/context_host/mod.rs +++ b/moli-renderer-v8/src/native_bridge/context_host/mod.rs @@ -1037,8 +1037,10 @@ pub(crate) struct JsContextHost { completed_child_document_networks: Vec, active_child_subresource_request_scopes: Vec, - child_window_event_listeners: - HashMap>>, + child_window_event_listeners: HashMap< + DomHandle, + IndexMap>, + >, next_child_window_event_registration_id: u64, event_callbacks: event_callbacks::EventCallbackRegistry, active_window_error_report_owners: HashSet, diff --git a/moli-renderer-v8/src/native_bridge/context_host/popups.rs b/moli-renderer-v8/src/native_bridge/context_host/popups.rs index b421c19599..6b3a70cbf6 100644 --- a/moli-renderer-v8/src/native_bridge/context_host/popups.rs +++ b/moli-renderer-v8/src/native_bridge/context_host/popups.rs @@ -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()]; diff --git a/moli-renderer-v8/src/native_bridge/context_host/signal_bridge.rs b/moli-renderer-v8/src/native_bridge/context_host/signal_bridge.rs index 7c4a073382..117567c890 100644 --- a/moli-renderer-v8/src/native_bridge/context_host/signal_bridge.rs +++ b/moli-renderer-v8/src/native_bridge/context_host/signal_bridge.rs @@ -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, ) { diff --git a/moli-renderer-v8/src/observable/event_target.rs b/moli-renderer-v8/src/observable/event_target.rs index fc9b75f4f7..9230d22d9b 100644 --- a/moli-renderer-v8/src/observable/event_target.rs +++ b/moli-renderer-v8/src/observable/event_target.rs @@ -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, - 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, } diff --git a/moli-renderer-v8/src/script_vm/tests/browser_api/event_listener_type.js b/moli-renderer-v8/src/script_vm/tests/browser_api/event_listener_type.js new file mode 100644 index 0000000000..e6e07b4289 --- /dev/null +++ b/moli-renderer-v8/src/script_vm/tests/browser_api/event_listener_type.js @@ -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)); +})() diff --git a/moli-renderer-v8/src/script_vm/tests/browser_api/event_listener_type.rs b/moli-renderer-v8/src/script_vm/tests/browser_api/event_listener_type.rs new file mode 100644 index 0000000000..d56f8232c4 --- /dev/null +++ b/moli-renderer-v8/src/script_vm/tests/browser_api/event_listener_type.rs @@ -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 = '';") + .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 = '';") + .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"); +} diff --git a/moli-renderer-v8/src/script_vm/tests/browser_api/event_listener_type_runtime.js b/moli-renderer-v8/src/script_vm/tests/browser_api/event_listener_type_runtime.js new file mode 100644 index 0000000000..6b1a3f7442 --- /dev/null +++ b/moli-renderer-v8/src/script_vm/tests/browser_api/event_listener_type_runtime.js @@ -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)); +})() diff --git a/moli-renderer-v8/src/script_vm/tests/browser_api/mod.rs b/moli-renderer-v8/src/script_vm/tests/browser_api/mod.rs index 5df16bf6e5..1055774947 100644 --- a/moli-renderer-v8/src/script_vm/tests/browser_api/mod.rs +++ b/moli-renderer-v8/src/script_vm/tests/browser_api/mod.rs @@ -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; diff --git a/moli-renderer-v8/src/util.rs b/moli-renderer-v8/src/util.rs index 7e6b91c7bc..895c5a49c3 100644 --- a/moli-renderer-v8/src/util.rs +++ b/moli-renderer-v8/src/util.rs @@ -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, diff --git a/moli-renderer-v8/src/window_host.rs b/moli-renderer-v8/src/window_host.rs index 8bc4016570..65ccff5840 100644 --- a/moli-renderer-v8/src/window_host.rs +++ b/moli-renderer-v8/src/window_host.rs @@ -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 { +) -> Option { 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>(