diff --git a/moli-renderer-v8/src/context_bootstrap/event_template.rs b/moli-renderer-v8/src/context_bootstrap/event_template.rs index eea44ed7b0..84319de06b 100644 --- a/moli-renderer-v8/src/context_bootstrap/event_template.rs +++ b/moli-renderer-v8/src/context_bootstrap/event_template.rs @@ -436,6 +436,38 @@ struct KeyboardEventTemplateMethodsDeclaration { #[derive(WebApiFunctionTemplate)] #[webapi(interface = web_api_interfaces::MouseEvent, enumerable, receiver)] struct MouseEventTemplateMethodsDeclaration { + #[webapi(accessor_property = "screenX", getter = event_value_attribute_getter, data = crate::util::v8str(scope, "screenX"))] + screen_x: (), + #[webapi(accessor_property = "screenY", getter = event_value_attribute_getter, data = crate::util::v8str(scope, "screenY"))] + screen_y: (), + #[webapi(accessor_property = "clientX", getter = event_value_attribute_getter, data = crate::util::v8str(scope, "clientX"))] + client_x: (), + #[webapi(accessor_property = "clientY", getter = event_value_attribute_getter, data = crate::util::v8str(scope, "clientY"))] + client_y: (), + #[webapi(accessor_property = "button", getter = event_value_attribute_getter, data = crate::util::v8str(scope, "button"))] + button: (), + #[webapi(accessor_property = "buttons", getter = event_value_attribute_getter, data = crate::util::v8str(scope, "buttons"))] + buttons: (), + #[webapi(accessor_property = "ctrlKey", getter = event_value_attribute_getter, data = crate::util::v8str(scope, "ctrlKey"))] + ctrl_key: (), + #[webapi(accessor_property = "altKey", getter = event_value_attribute_getter, data = crate::util::v8str(scope, "altKey"))] + alt_key: (), + #[webapi(accessor_property = "shiftKey", getter = event_value_attribute_getter, data = crate::util::v8str(scope, "shiftKey"))] + shift_key: (), + #[webapi(accessor_property = "metaKey", getter = event_value_attribute_getter, data = crate::util::v8str(scope, "metaKey"))] + meta_key: (), + #[webapi(accessor_property = "movementX", getter = event_value_attribute_getter, data = crate::util::v8str(scope, "movementX"))] + movement_x: (), + #[webapi(accessor_property = "movementY", getter = event_value_attribute_getter, data = crate::util::v8str(scope, "movementY"))] + movement_y: (), + #[webapi(accessor_property = "pageX", getter = event_value_attribute_getter, data = crate::util::v8str(scope, "pageX"))] + page_x: (), + #[webapi(accessor_property = "pageY", getter = event_value_attribute_getter, data = crate::util::v8str(scope, "pageY"))] + page_y: (), + #[webapi(accessor_property = "x", getter = event_value_attribute_getter, data = crate::util::v8str(scope, "x"))] + x: (), + #[webapi(accessor_property = "y", getter = event_value_attribute_getter, data = crate::util::v8str(scope, "y"))] + y: (), #[webapi( method = "getModifierState", length = 1, @@ -460,7 +492,7 @@ struct MouseEventTemplateMethodsDeclaration { } #[derive(WebApiFunctionTemplate)] -#[webapi(interface = web_api_interfaces::WheelEvent, enumerable)] +#[webapi(interface = web_api_interfaces::WheelEvent, enumerable, receiver)] struct WheelEventTemplateConstantsDeclaration { #[webapi(constant = "DOM_DELTA_PIXEL", value = 0u32)] dom_delta_pixel: (), @@ -470,11 +502,53 @@ struct WheelEventTemplateConstantsDeclaration { #[webapi(constant = "DOM_DELTA_PAGE", value = 2u32)] dom_delta_page: (), + + #[webapi(accessor_property = "deltaMode", getter = event_value_attribute_getter, data = crate::util::v8str(scope, "deltaMode"))] + delta_mode: (), + #[webapi(accessor_property = "deltaX", getter = event_value_attribute_getter, data = crate::util::v8str(scope, "deltaX"))] + delta_x: (), + #[webapi(accessor_property = "deltaY", getter = event_value_attribute_getter, data = crate::util::v8str(scope, "deltaY"))] + delta_y: (), + #[webapi(accessor_property = "deltaZ", getter = event_value_attribute_getter, data = crate::util::v8str(scope, "deltaZ"))] + delta_z: (), +} + +#[derive(WebApiFunctionTemplate)] +#[webapi(interface = web_api_interfaces::DragEvent, enumerable, receiver)] +struct DragEventTemplateAccessorsDeclaration { + #[webapi(accessor_property = "dataTransfer", getter = event_platform_attribute_getter, data = crate::util::v8str(scope, "dataTransfer"))] + data_transfer: (), } #[derive(WebApiFunctionTemplate)] #[webapi(interface = web_api_interfaces::PointerEvent, enumerable, receiver)] struct PointerEventTemplateMethodsDeclaration { + #[webapi(accessor_property = "pointerId", getter = event_value_attribute_getter, data = crate::util::v8str(scope, "pointerId"))] + pointer_id: (), + #[webapi(accessor_property = "width", getter = event_value_attribute_getter, data = crate::util::v8str(scope, "width"))] + width: (), + #[webapi(accessor_property = "height", getter = event_value_attribute_getter, data = crate::util::v8str(scope, "height"))] + height: (), + #[webapi(accessor_property = "pressure", getter = event_value_attribute_getter, data = crate::util::v8str(scope, "pressure"))] + pressure: (), + #[webapi(accessor_property = "tangentialPressure", getter = event_value_attribute_getter, data = crate::util::v8str(scope, "tangentialPressure"))] + tangential_pressure: (), + #[webapi(accessor_property = "tiltX", getter = event_value_attribute_getter, data = crate::util::v8str(scope, "tiltX"))] + tilt_x: (), + #[webapi(accessor_property = "tiltY", getter = event_value_attribute_getter, data = crate::util::v8str(scope, "tiltY"))] + tilt_y: (), + #[webapi(accessor_property = "twist", getter = event_value_attribute_getter, data = crate::util::v8str(scope, "twist"))] + twist: (), + #[webapi(accessor_property = "altitudeAngle", getter = event_value_attribute_getter, data = crate::util::v8str(scope, "altitudeAngle"))] + altitude_angle: (), + #[webapi(accessor_property = "azimuthAngle", getter = event_value_attribute_getter, data = crate::util::v8str(scope, "azimuthAngle"))] + azimuth_angle: (), + #[webapi(accessor_property = "pointerType", getter = event_value_attribute_getter, data = crate::util::v8str(scope, "pointerType"))] + pointer_type: (), + #[webapi(accessor_property = "isPrimary", getter = event_value_attribute_getter, data = crate::util::v8str(scope, "isPrimary"))] + is_primary: (), + #[webapi(accessor_property = "persistentDeviceId", getter = event_value_attribute_getter, data = crate::util::v8str(scope, "persistentDeviceId"))] + persistent_device_id: (), #[webapi( method = "getPredictedEvents", length = 0, @@ -578,6 +652,7 @@ fn event_template_declaration(interface: &str) -> Option() } "MouseEvent" => EventTemplateDeclaration::new::(), + "DragEvent" => EventTemplateDeclaration::new::(), "WheelEvent" => EventTemplateDeclaration::new::(), "PointerEvent" => EventTemplateDeclaration::new::(), "CloseEvent" => EventTemplateDeclaration::new::(), diff --git a/moli-renderer-v8/src/context_bootstrap/events.rs b/moli-renderer-v8/src/context_bootstrap/events.rs index 33959c1623..34cb523d8b 100644 --- a/moli-renderer-v8/src/context_bootstrap/events.rs +++ b/moli-renderer-v8/src/context_bootstrap/events.rs @@ -87,7 +87,7 @@ struct StorageEventStateDeclaration<'scope> { } #[derive(Default, WebApiObject)] -#[webapi(fragment, prototype = "PointerEvent", enumerable)] +#[webapi(fragment, prototype = "PointerEvent", enumerable, receiver = crate::web_api_interfaces::PointerEvent::is_instance)] struct SecurePointerEventPrototypeRuntimeDeclaration { #[webapi( method = "getCoalescedEvents", diff --git a/moli-renderer-v8/src/context_bootstrap/events/modifiers.rs b/moli-renderer-v8/src/context_bootstrap/events/modifiers.rs index 117747ac70..ec5505d6cd 100644 --- a/moli-renderer-v8/src/context_bootstrap/events/modifiers.rs +++ b/moli-renderer-v8/src/context_bootstrap/events/modifiers.rs @@ -103,27 +103,6 @@ impl EventModifierInitMembers { } } -pub(super) fn initialize_event_modifiers<'s>( - scope: &mut v8::PinScope<'s, '_>, - event: v8::Local<'s, v8::Object>, - init: Option>, -) -> bool { - let modifiers = match init { - Some(init) => { - match webidl::parse_dictionary_object::(scope, init) { - Ok(modifiers) => modifiers, - Err(error) => { - webidl::throw_error(scope, &error); - return false; - } - } - } - None => EventModifierInitMembers::default(), - }; - modifiers.initialize(scope, event); - true -} - pub(in crate::context_bootstrap) fn initialize_legacy_event_modifiers<'s>( scope: &mut v8::PinScope<'s, '_>, event: v8::Local<'s, v8::Object>, diff --git a/moli-renderer-v8/src/context_bootstrap/events/subclasses/constructor.rs b/moli-renderer-v8/src/context_bootstrap/events/subclasses/constructor.rs index 8e7c71c994..25fc86ae28 100644 --- a/moli-renderer-v8/src/context_bootstrap/events/subclasses/constructor.rs +++ b/moli-renderer-v8/src/context_bootstrap/events/subclasses/constructor.rs @@ -17,6 +17,10 @@ enum EventSubclassInit<'s> { Clipboard(data::ClipboardEventInitMembers<'s>), ClipboardChange(data::ClipboardChangeEventInitMembers<'s>), Keyboard(keyboard::KeyboardEventInit<'s>), + Mouse(pointer::MouseEventInit<'s>), + Wheel(pointer::WheelEventInit<'s>), + Pointer(pointer::PointerEventInit<'s>), + Drag(pointer::DragEventInit<'s>), Message(message::MessageEventInit<'s>), Storage(data::StorageEventInitMembers<'s>), Error(error::ErrorEventInit<'s>), @@ -48,6 +52,18 @@ impl<'s> EventSubclassInit<'s> { EventSubclassKind::KeyboardEvent => { Self::Keyboard(keyboard::parse_keyboard_event_init(scope, args)?) } + EventSubclassKind::MouseEvent => { + Self::Mouse(ui::parse_ui_dictionary(scope, args, "MouseEvent")?) + } + EventSubclassKind::WheelEvent => { + Self::Wheel(ui::parse_ui_dictionary(scope, args, "WheelEvent")?) + } + EventSubclassKind::PointerEvent => { + Self::Pointer(ui::parse_ui_dictionary(scope, args, "PointerEvent")?) + } + EventSubclassKind::DragEvent => { + Self::Drag(ui::parse_ui_dictionary(scope, args, "DragEvent")?) + } EventSubclassKind::MessageEvent => { Self::Message(message::parse_message_event_init(scope, args)?) } @@ -87,6 +103,10 @@ impl<'s> EventSubclassInit<'s> { Self::Clipboard(init) => init.event_flags(), Self::ClipboardChange(init) => init.event_flags(), Self::Keyboard(init) => init.event_flags(), + Self::Mouse(init) => init.event_flags(), + Self::Wheel(init) => init.event_flags(), + Self::Pointer(init) => init.event_flags(), + Self::Drag(init) => init.event_flags(), Self::Message(init) => init.event_flags(), Self::Storage(init) => init.event_flags(), Self::Error(init) => init.event_flags(), @@ -114,6 +134,10 @@ impl<'s> EventSubclassInit<'s> { return data::initialize_clipboard_change_event(scope, event, init); } Self::Keyboard(init) => keyboard::initialize_keyboard_event(scope, event, init), + Self::Mouse(init) => init.initialize(scope, event), + Self::Wheel(init) => init.initialize(scope, event), + Self::Pointer(init) => init.initialize(scope, event), + Self::Drag(init) => init.initialize(scope, event), Self::Message(init) => message::initialize_message_event(scope, event, init), Self::Storage(init) => data::initialize_storage_event(scope, event, init), Self::Error(init) => init.initialize(scope, event), @@ -224,32 +248,12 @@ fn initialize_legacy_event<'s>( } } EventSubclassKind::CustomEvent => basic::initialize_custom_event(scope, event, init), - EventSubclassKind::MouseEvent => { - if !pointer::initialize_mouse_event(scope, event, init) { - return false; - } - } - EventSubclassKind::DragEvent => { - if !data::initialize_drag_event(scope, event, init) { - return false; - } - } EventSubclassKind::CapturedMouseEvent => { if !data::initialize_captured_mouse_event(scope, event, init) { return false; } } EventSubclassKind::InputEvent => data::initialize_input_event(scope, event, init), - EventSubclassKind::WheelEvent => { - if !pointer::initialize_wheel_event(scope, event, init) { - return false; - } - } - EventSubclassKind::PointerEvent => { - if !pointer::initialize_pointer_event(scope, event, init) { - return false; - } - } EventSubclassKind::TouchEvent => { crate::context_bootstrap::touch_runtime::initialize_touch_event(scope, event, init); } @@ -297,6 +301,10 @@ fn initialize_legacy_event<'s>( | EventSubclassKind::CompositionEvent | EventSubclassKind::ErrorEvent | EventSubclassKind::KeyboardEvent + | EventSubclassKind::MouseEvent + | EventSubclassKind::WheelEvent + | EventSubclassKind::PointerEvent + | EventSubclassKind::DragEvent | EventSubclassKind::MessageEvent | EventSubclassKind::NavigateEvent | EventSubclassKind::NavigationCurrentEntryChangeEvent diff --git a/moli-renderer-v8/src/context_bootstrap/events/subclasses/data.rs b/moli-renderer-v8/src/context_bootstrap/events/subclasses/data.rs index 46b815fc74..1bfee7a392 100644 --- a/moli-renderer-v8/src/context_bootstrap/events/subclasses/data.rs +++ b/moli-renderer-v8/src/context_bootstrap/events/subclasses/data.rs @@ -137,12 +137,6 @@ struct NavigateEventMethodsDeclaration { scroll: (), } -#[derive(WebApiObject)] -#[webapi(plain, data_properties, enumerable)] -struct DragEventInitDeclaration<'scope> { - data_transfer: v8::Local<'scope, v8::Value>, -} - /// Web IDL converts inherited `EventInit` members before the derived members, /// with each dictionary's members in lexicographic order. Keeping them in one /// conversion also ensures each init getter is read only once. @@ -469,53 +463,6 @@ pub(super) fn parse_clipboard_change_event_init<'s>( } } -pub(in crate::context_bootstrap::events::subclasses) fn initialize_drag_event<'s>( - scope: &mut v8::PinScope<'s, '_>, - event: v8::Local<'s, v8::Object>, - init: Option>, -) -> bool { - if !pointer::initialize_mouse_event(scope, event, init) { - return false; - } - let data_transfer = match init { - None => v8::null(scope).into(), - Some(init) => match webidl::property_result( - scope, - init, - "dataTransfer", - webidl::Context::member("DragEventInit", "dataTransfer"), - ) { - Err(error) => { - webidl::throw_error(scope, &error); - return false; - } - Ok(None) => v8::null(scope).into(), - Ok(Some(value)) if value.is_null_or_undefined() => v8::null(scope).into(), - Ok(Some(value)) => { - let Ok(object) = v8::Local::::try_from(value) else { - throw_type_error( - scope, - "Failed to construct 'DragEvent': member dataTransfer is not of type DataTransfer.", - ); - return false; - }; - if !is_branded_data_transfer_object(scope, object) { - throw_type_error( - scope, - "Failed to construct 'DragEvent': member dataTransfer is not of type DataTransfer.", - ); - return false; - } - value - } - }, - }; - DragEventInitDeclaration::new(data_transfer) - .initialize(scope, event) - .expect("DragEvent init declaration should initialize"); - true -} - pub(in crate::context_bootstrap::events::subclasses) fn initialize_clipboard_event<'s>( scope: &mut v8::PinScope<'s, '_>, event: v8::Local<'s, v8::Object>, diff --git a/moli-renderer-v8/src/context_bootstrap/events/subclasses/pointer.rs b/moli-renderer-v8/src/context_bootstrap/events/subclasses/pointer.rs index 06bd15fa52..d567ac9157 100644 --- a/moli-renderer-v8/src/context_bootstrap/events/subclasses/pointer.rs +++ b/moli-renderer-v8/src/context_bootstrap/events/subclasses/pointer.rs @@ -1,32 +1,366 @@ use super::*; use moli_webapi_declare::WebApiObject; +use crate::context_bootstrap::events::{modifiers::EventModifierInitMembers, ui::UiEventInit}; +use crate::context_bootstrap::file_api::is_branded_data_transfer_object; +use crate::web_api_interfaces; use crate::webidl; const POINTER_EVENT_COALESCED_EVENTS_SLOT: &str = "__moliPointerEventCoalescedEvents"; const POINTER_EVENT_PREDICTED_EVENTS_SLOT: &str = "__moliPointerEventPredictedEvents"; -#[derive(Default, webidl::WebIdlDictionary)] -#[webidl(prefix = "PointerEventInit")] -struct PointerEventSequences<'scope> { - #[webidl(sequence, interface = crate::web_api_interfaces::PointerEvent, brand_check = is_pointer_event_sequence_item, default = Vec::new())] - coalesced_events: Vec>, - #[webidl(sequence, interface = crate::web_api_interfaces::PointerEvent, brand_check = is_pointer_event_sequence_item, default = Vec::new())] - predicted_events: Vec>, +struct EventTargetReference<'s>(v8::Local<'s, v8::Object>); + +impl<'s> webidl::WebIdlConverter<'s> for EventTargetReference<'s> { + type Options = (); + + fn convert( + scope: &mut v8::PinScope<'s, '_>, + value: v8::Local<'s, v8::Value>, + context: webidl::Context, + _options: &Self::Options, + ) -> Result { + let target = webidl::convert::>(scope, value, context)?; + if !web_api_interfaces::EventTarget::is_instance(scope, target) { + return Err(webidl::WebIdlError::cannot_convert(context, "EventTarget")); + } + Ok(Self(target)) + } } -fn is_pointer_event_sequence_item<'s>( - scope: &mut v8::PinScope<'s, '_>, - event: v8::Local<'s, v8::Object>, -) -> bool { - super::super::event_subclass_kind(scope, event) == Some(EventSubclassKind::PointerEvent) +/// MouseEventInit includes the CSSOM View and Pointer Lock double members. +/// Each derived dictionary delegates to this base before reading its own members. +#[derive(Default, webidl::WebIdlDictionary)] +#[webidl(prefix = "MouseEventInit")] +struct MouseEventInitMembers<'s> { + #[webidl(default = 0)] + button: i16, + #[webidl(default = 0)] + buttons: u16, + #[webidl(default = 0.0, converter = "double")] + client_x: f64, + #[webidl(default = 0.0, converter = "double")] + client_y: f64, + #[webidl(default = 0.0, converter = "double")] + movement_x: f64, + #[webidl(default = 0.0, converter = "double")] + movement_y: f64, + #[webidl(nullable, converter = "raw")] + related_target: Option>, + #[webidl(default = 0.0, converter = "double")] + screen_x: f64, + #[webidl(default = 0.0, converter = "double")] + screen_y: f64, +} + +#[derive(Default)] +pub(super) struct MouseEventInit<'s> { + ui: UiEventInit<'s>, + modifiers: EventModifierInitMembers, + members: MouseEventInitMembers<'s>, +} + +impl<'s> webidl::WebIdlDictionary<'s> for MouseEventInit<'s> { + fn parse_dictionary( + scope: &mut v8::PinScope<'s, '_>, + object: v8::Local<'s, v8::Object>, + ) -> Result { + Ok(Self { + ui: webidl::parse_dictionary_object(scope, object)?, + modifiers: webidl::parse_dictionary_object(scope, object)?, + members: webidl::parse_dictionary_object(scope, object)?, + }) + } +} + +impl<'s> MouseEventInit<'s> { + pub(super) fn event_flags(&self) -> (bool, bool, bool) { + self.ui.event_flags() + } + + pub(super) fn initialize( + self, + scope: &mut v8::PinScope<'s, '_>, + event: v8::Local<'s, v8::Object>, + ) { + self.ui.initialize(scope, event); + self.modifiers.initialize(scope, event); + let members = self.members; + MouseEventBaseInitDeclaration::new( + members.screen_x, + members.screen_y, + members.client_x, + members.client_y, + members.client_x, + members.client_y, + members.client_x, + members.client_y, + members.button, + members.buttons, + members.movement_x, + members.movement_y, + ) + .initialize(scope, event) + .expect("MouseEvent state should initialize"); + let related_target = members + .related_target + .map(|target| target.0.into()) + .unwrap_or_else(|| v8::null(scope).into()); + MouseEventRelatedTargetDeclaration::new(related_target) + .initialize(scope, event) + .expect("MouseEvent relatedTarget should initialize"); + } +} + +#[derive(Default, webidl::WebIdlDictionary)] +#[webidl(prefix = "WheelEventInit")] +struct WheelEventInitMembers { + #[webidl(default = 0)] + delta_mode: u32, + #[webidl(default = 0.0, converter = "double")] + delta_x: f64, + #[webidl(default = 0.0, converter = "double")] + delta_y: f64, + #[webidl(default = 0.0, converter = "double")] + delta_z: f64, +} + +#[derive(Default)] +pub(super) struct WheelEventInit<'s> { + mouse: MouseEventInit<'s>, + members: WheelEventInitMembers, +} + +impl<'s> webidl::WebIdlDictionary<'s> for WheelEventInit<'s> { + fn parse_dictionary( + scope: &mut v8::PinScope<'s, '_>, + object: v8::Local<'s, v8::Object>, + ) -> Result { + Ok(Self { + mouse: webidl::parse_dictionary_object(scope, object)?, + members: webidl::parse_dictionary_object(scope, object)?, + }) + } +} + +impl<'s> WheelEventInit<'s> { + pub(super) fn event_flags(&self) -> (bool, bool, bool) { + self.mouse.event_flags() + } + + pub(super) fn initialize( + self, + scope: &mut v8::PinScope<'s, '_>, + event: v8::Local<'s, v8::Object>, + ) { + self.mouse.initialize(scope, event); + WheelEventDeltaInitDeclaration::new( + self.members.delta_x, + self.members.delta_y, + self.members.delta_z, + self.members.delta_mode, + ) + .initialize(scope, event) + .expect("WheelEvent state should initialize"); + } +} + +#[derive(webidl::WebIdlDictionary)] +#[webidl(prefix = "PointerEventInit")] +struct PointerEventInitMembers<'s> { + #[webidl(converter = "double")] + altitude_angle: Option, + #[webidl(converter = "double")] + azimuth_angle: Option, + #[webidl(sequence, interface = web_api_interfaces::PointerEvent, default = Vec::new())] + coalesced_events: Vec>, + #[webidl(default = 1.0, converter = "double")] + height: f64, + #[webidl(default = false)] + is_primary: bool, + #[webidl(default = 0)] + persistent_device_id: i32, + #[webidl(default = 0)] + pointer_id: i32, + #[webidl(default = webidl::DomString16(Vec::new()), converter = "raw")] + pointer_type: webidl::DomString16, + #[webidl(sequence, interface = web_api_interfaces::PointerEvent, default = Vec::new())] + predicted_events: Vec>, + #[webidl(default = 0.0)] + pressure: f32, + #[webidl(default = 0.0)] + tangential_pressure: f32, + tilt_x: Option, + tilt_y: Option, + #[webidl(default = 0)] + twist: i32, + #[webidl(default = 1.0, converter = "double")] + width: f64, +} + +impl Default for PointerEventInitMembers<'_> { + fn default() -> Self { + Self { + altitude_angle: None, + azimuth_angle: None, + coalesced_events: Vec::new(), + height: 1.0, + is_primary: false, + persistent_device_id: 0, + pointer_id: 0, + pointer_type: webidl::DomString16(Vec::new()), + predicted_events: Vec::new(), + pressure: 0.0, + tangential_pressure: 0.0, + tilt_x: None, + tilt_y: None, + twist: 0, + width: 1.0, + } + } +} + +#[derive(Default)] +pub(super) struct PointerEventInit<'s> { + mouse: MouseEventInit<'s>, + members: PointerEventInitMembers<'s>, +} + +impl<'s> webidl::WebIdlDictionary<'s> for PointerEventInit<'s> { + fn parse_dictionary( + scope: &mut v8::PinScope<'s, '_>, + object: v8::Local<'s, v8::Object>, + ) -> Result { + Ok(Self { + mouse: webidl::parse_dictionary_object(scope, object)?, + members: webidl::parse_dictionary_object(scope, object)?, + }) + } +} + +impl<'s> PointerEventInit<'s> { + pub(super) fn event_flags(&self) -> (bool, bool, bool) { + self.mouse.event_flags() + } + + pub(super) fn initialize( + self, + scope: &mut v8::PinScope<'s, '_>, + event: v8::Local<'s, v8::Object>, + ) { + self.mouse.initialize(scope, event); + let angles = pointer_event_angle_init(&self.members); + let members = self.members; + PointerEventNumberInitDeclaration::new( + members.pointer_id, + members.width, + members.height, + members.pressure, + members.tangential_pressure, + angles.tilt_x, + angles.tilt_y, + angles.azimuth_angle, + angles.altitude_angle, + members.twist, + members.persistent_device_id, + ) + .initialize(scope, event) + .expect("PointerEvent state should initialize"); + let pointer_type = crate::util::v8_string_from_utf16_units(scope, &members.pointer_type.0) + .expect("PointerEvent pointerType should fit in a V8 string"); + PointerEventTailInitDeclaration::new(members.is_primary, pointer_type) + .initialize(scope, event) + .expect("PointerEvent tail should initialize"); + store_pointer_event_sequence( + scope, + event, + POINTER_EVENT_COALESCED_EVENTS_SLOT, + &members.coalesced_events, + ); + store_pointer_event_sequence( + scope, + event, + POINTER_EVENT_PREDICTED_EVENTS_SLOT, + &members.predicted_events, + ); + } +} + +struct DataTransferReference<'s>(v8::Local<'s, v8::Object>); + +impl<'s> webidl::WebIdlConverter<'s> for DataTransferReference<'s> { + type Options = (); + + fn convert( + scope: &mut v8::PinScope<'s, '_>, + value: v8::Local<'s, v8::Value>, + context: webidl::Context, + _options: &Self::Options, + ) -> Result { + let object = webidl::convert::>(scope, value, context)?; + if !is_branded_data_transfer_object(scope, object) { + return Err(webidl::WebIdlError::cannot_convert(context, "DataTransfer")); + } + Ok(Self(object)) + } +} + +#[derive(Default, webidl::WebIdlDictionary)] +#[webidl(prefix = "DragEventInit")] +struct DragEventInitMembers<'s> { + #[webidl(nullable, converter = "raw")] + data_transfer: Option>, +} + +#[derive(Default)] +pub(super) struct DragEventInit<'s> { + mouse: MouseEventInit<'s>, + members: DragEventInitMembers<'s>, +} + +impl<'s> webidl::WebIdlDictionary<'s> for DragEventInit<'s> { + fn parse_dictionary( + scope: &mut v8::PinScope<'s, '_>, + object: v8::Local<'s, v8::Object>, + ) -> Result { + Ok(Self { + mouse: webidl::parse_dictionary_object(scope, object)?, + members: webidl::parse_dictionary_object(scope, object)?, + }) + } +} + +impl<'s> DragEventInit<'s> { + pub(super) fn event_flags(&self) -> (bool, bool, bool) { + self.mouse.event_flags() + } + + pub(super) fn initialize( + self, + scope: &mut v8::PinScope<'s, '_>, + event: v8::Local<'s, v8::Object>, + ) { + self.mouse.initialize(scope, event); + let data_transfer = self + .members + .data_transfer + .map(|data| data.0.into()) + .unwrap_or_else(|| v8::null(scope).into()); + DragEventInitDeclaration::new(data_transfer) + .initialize(scope, event) + .expect("DragEvent state should initialize"); + } } #[derive(WebApiObject)] #[webapi(plain, data_properties, enumerable)] -struct MouseEventBaseInitDeclaration<'scope> { - view: v8::Local<'scope, v8::Value>, - detail: f64, +struct DragEventInitDeclaration<'s> { + data_transfer: v8::Local<'s, v8::Value>, +} + +#[derive(WebApiObject)] +#[webapi(plain, data_properties, enumerable)] +struct MouseEventBaseInitDeclaration { screen_x: f64, screen_y: f64, client_x: f64, @@ -35,8 +369,8 @@ struct MouseEventBaseInitDeclaration<'scope> { y: f64, page_x: f64, page_y: f64, - button: f64, - buttons: f64, + button: i16, + buttons: u16, movement_x: f64, movement_y: f64, } @@ -53,22 +387,23 @@ struct WheelEventDeltaInitDeclaration { delta_x: f64, delta_y: f64, delta_z: f64, - delta_mode: f64, + delta_mode: u32, } #[derive(WebApiObject)] #[webapi(plain, data_properties, enumerable)] struct PointerEventNumberInitDeclaration { - pointer_id: f64, + pointer_id: i32, width: f64, height: f64, - pressure: f64, - tangential_pressure: f64, - tilt_x: f64, - tilt_y: f64, + pressure: f32, + tangential_pressure: f32, + tilt_x: i32, + tilt_y: i32, azimuth_angle: f64, altitude_angle: f64, - twist: f64, + twist: i32, + persistent_device_id: i32, } #[derive(WebApiObject)] @@ -86,17 +421,6 @@ struct PointerEventAngleInit { altitude_angle: f64, } -impl Default for PointerEventAngleInit { - fn default() -> Self { - Self { - tilt_x: 0, - tilt_y: 0, - azimuth_angle: 0.0, - altitude_angle: std::f64::consts::FRAC_PI_2, - } - } -} - fn normalized_tilt_degrees(tilt_degrees: i32) -> i32 { if (-90..=90).contains(&tilt_degrees) { return tilt_degrees; @@ -211,41 +535,11 @@ fn tilt_y_from_spherical(azimuth_angle: f64, altitude_angle: f64) -> i32 { } } -fn pointer_event_angle_init<'s>( - scope: &mut v8::PinScope<'s, '_>, - init: Option>, -) -> Result { - let Some(init) = init else { - return Ok(PointerEventAngleInit::default()); - }; - let tilt_x = webidl::optional_member::( - scope, - init, - "tiltX", - webidl::Context::member("PointerEventInit", "tiltX"), - )? - .map(|value| value.0); - let tilt_y = webidl::optional_member::( - scope, - init, - "tiltY", - webidl::Context::member("PointerEventInit", "tiltY"), - )? - .map(|value| value.0); - let azimuth_angle = webidl::optional_member::( - scope, - init, - "azimuthAngle", - webidl::Context::member("PointerEventInit", "azimuthAngle"), - )? - .map(|value| value.0); - let altitude_angle = webidl::optional_member::( - scope, - init, - "altitudeAngle", - webidl::Context::member("PointerEventInit", "altitudeAngle"), - )? - .map(|value| value.0); +fn pointer_event_angle_init(init: &PointerEventInitMembers<'_>) -> PointerEventAngleInit { + let tilt_x = init.tilt_x; + let tilt_y = init.tilt_y; + let azimuth_angle = init.azimuth_angle; + let altitude_angle = init.altitude_angle; let has_tilt = tilt_x.is_some() || tilt_y.is_some(); let has_spherical_angles = azimuth_angle.is_some() || altitude_angle.is_some(); @@ -267,7 +561,7 @@ fn pointer_event_angle_init<'s>( angles.tilt_x = tilt_x_from_spherical(normalized_azimuth, normalized_altitude); angles.tilt_y = tilt_y_from_spherical(normalized_azimuth, normalized_altitude); } - Ok(angles) + angles } fn store_pointer_event_sequence<'s>( @@ -305,17 +599,7 @@ fn pointer_event_sequence_callback<'s>( args: v8::FunctionCallbackArguments<'s>, mut rv: v8::ReturnValue<'_, v8::Value>, slot: &'static str, - method: &'static str, ) { - if super::super::event_subclass_kind(scope, args.this()) - != Some(EventSubclassKind::PointerEvent) - { - throw_type_error( - scope, - &format!("Failed to execute '{method}' on 'PointerEvent': Illegal invocation"), - ); - return; - } rv.set(pointer_event_sequence_copy(scope, args.this(), slot).into()); } @@ -324,13 +608,7 @@ pub(in crate::context_bootstrap) fn pointer_event_get_coalesced_events_callback< args: v8::FunctionCallbackArguments<'s>, rv: v8::ReturnValue<'_, v8::Value>, ) { - pointer_event_sequence_callback( - scope, - args, - rv, - POINTER_EVENT_COALESCED_EVENTS_SLOT, - "getCoalescedEvents", - ); + pointer_event_sequence_callback(scope, args, rv, POINTER_EVENT_COALESCED_EVENTS_SLOT); } pub(in crate::context_bootstrap) fn pointer_event_get_predicted_events_callback<'s>( @@ -338,158 +616,5 @@ pub(in crate::context_bootstrap) fn pointer_event_get_predicted_events_callback< args: v8::FunctionCallbackArguments<'s>, rv: v8::ReturnValue<'_, v8::Value>, ) { - pointer_event_sequence_callback( - scope, - args, - rv, - POINTER_EVENT_PREDICTED_EVENTS_SLOT, - "getPredictedEvents", - ); -} - -fn define_mouse_event_base_fields<'s>( - scope: &mut v8::PinScope<'s, '_>, - event: v8::Local<'s, v8::Object>, - init: Option>, - constructor_name: &'static str, -) -> Option> { - let Ok(view) = init_window_view_property(scope, init, constructor_name) else { - return None; - }; - let detail = init_number_property(scope, init, "detail", 0.0); - if !super::super::modifiers::initialize_event_modifiers(scope, event, init) { - return None; - } - let client_x = init_number_property(scope, init, "clientX", 0.0); - let client_y = init_number_property(scope, init, "clientY", 0.0); - let related_target = - init_value_property(scope, init, "relatedTarget").unwrap_or_else(|| v8::null(scope).into()); - - MouseEventBaseInitDeclaration::new( - view, - detail, - init_number_property(scope, init, "screenX", 0.0), - init_number_property(scope, init, "screenY", 0.0), - client_x, - client_y, - client_x, - client_y, - client_x, - client_y, - init_number_property(scope, init, "button", 0.0), - init_number_property(scope, init, "buttons", 0.0), - init_number_property(scope, init, "movementX", 0.0), - init_number_property(scope, init, "movementY", 0.0), - ) - .initialize(scope, event) - .expect("MouseEvent base init declaration should initialize"); - Some(related_target) -} - -fn define_mouse_event_related_target<'s>( - scope: &mut v8::PinScope<'s, '_>, - event: v8::Local<'s, v8::Object>, - related_target: v8::Local<'s, v8::Value>, -) { - MouseEventRelatedTargetDeclaration::new(related_target) - .initialize(scope, event) - .expect("MouseEvent relatedTarget declaration should initialize"); -} - -pub(in crate::context_bootstrap::events::subclasses) fn initialize_mouse_event<'s>( - scope: &mut v8::PinScope<'s, '_>, - event: v8::Local<'s, v8::Object>, - init: Option>, -) -> bool { - let Some(related_target) = define_mouse_event_base_fields(scope, event, init, "MouseEvent") - else { - return false; - }; - define_mouse_event_related_target(scope, event, related_target); - true -} - -pub(in crate::context_bootstrap::events::subclasses) fn initialize_wheel_event<'s>( - scope: &mut v8::PinScope<'s, '_>, - event: v8::Local<'s, v8::Object>, - init: Option>, -) -> bool { - let Some(related_target) = define_mouse_event_base_fields(scope, event, init, "WheelEvent") - else { - return false; - }; - WheelEventDeltaInitDeclaration::new( - init_number_property(scope, init, "deltaX", 0.0), - init_number_property(scope, init, "deltaY", 0.0), - init_number_property(scope, init, "deltaZ", 0.0), - init_number_property(scope, init, "deltaMode", 0.0), - ) - .initialize(scope, event) - .expect("WheelEvent delta init declaration should initialize"); - define_mouse_event_related_target(scope, event, related_target); - true -} - -pub(in crate::context_bootstrap::events::subclasses) fn initialize_pointer_event<'s>( - scope: &mut v8::PinScope<'s, '_>, - event: v8::Local<'s, v8::Object>, - init: Option>, -) -> bool { - let pointer_type = init_string_property(scope, init, "pointerType", ""); - let pointer_type = v8_string(scope, &pointer_type).expect("PointerEvent pointerType"); - let sequences = match init - .map(|init| webidl::parse_dictionary_object::(scope, init)) - .transpose() - { - Ok(sequences) => sequences.unwrap_or_default(), - Err(error) => { - webidl::throw_error(scope, &error); - return false; - } - }; - let Some(related_target) = define_mouse_event_base_fields(scope, event, init, "PointerEvent") - else { - return false; - }; - let angles = match pointer_event_angle_init(scope, init) { - Ok(angles) => angles, - Err(error) => { - webidl::throw_error(scope, &error); - return false; - } - }; - PointerEventNumberInitDeclaration::new( - init_number_property(scope, init, "pointerId", 0.0), - init_number_property(scope, init, "width", 1.0), - init_number_property(scope, init, "height", 1.0), - init_number_property(scope, init, "pressure", 0.0), - init_number_property(scope, init, "tangentialPressure", 0.0), - f64::from(angles.tilt_x), - f64::from(angles.tilt_y), - angles.azimuth_angle, - angles.altitude_angle, - init_number_property(scope, init, "twist", 0.0), - ) - .initialize(scope, event) - .expect("PointerEvent number init declaration should initialize"); - PointerEventTailInitDeclaration::new( - init_bool_property(scope, init, "isPrimary", false), - pointer_type, - ) - .initialize(scope, event) - .expect("PointerEvent tail init declaration should initialize"); - define_mouse_event_related_target(scope, event, related_target); - store_pointer_event_sequence( - scope, - event, - POINTER_EVENT_COALESCED_EVENTS_SLOT, - &sequences.coalesced_events, - ); - store_pointer_event_sequence( - scope, - event, - POINTER_EVENT_PREDICTED_EVENTS_SLOT, - &sequences.predicted_events, - ); - true + pointer_event_sequence_callback(scope, args, rv, POINTER_EVENT_PREDICTED_EVENTS_SLOT); } diff --git a/moli-renderer-v8/src/context_bootstrap/events/ui.rs b/moli-renderer-v8/src/context_bootstrap/events/ui.rs index 5289818a59..35be27db2e 100644 --- a/moli-renderer-v8/src/context_bootstrap/events/ui.rs +++ b/moli-renderer-v8/src/context_bootstrap/events/ui.rs @@ -153,7 +153,7 @@ pub(super) fn parse_composition_event_init<'s>( parse_ui_dictionary(scope, args, "CompositionEvent") } -fn parse_ui_dictionary<'s, T: Default + webidl::WebIdlDictionary<'s>>( +pub(super) fn parse_ui_dictionary<'s, T: Default + webidl::WebIdlDictionary<'s>>( scope: &mut v8::PinScope<'s, '_>, args: &v8::FunctionCallbackArguments<'s>, constructor: &'static str, 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 6beac47033..356786d976 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 @@ -54,6 +54,7 @@ mod message_event_init; mod message_port_events; mod message_port_lifecycle; mod misc; +mod mouse_event_init; mod native_event_construction; mod navigation; mod navigation_recursion; diff --git a/moli-renderer-v8/src/script_vm/tests/browser_api/mouse_event_init.js b/moli-renderer-v8/src/script_vm/tests/browser_api/mouse_event_init.js new file mode 100644 index 0000000000..28f69aec4e --- /dev/null +++ b/moli-renderer-v8/src/script_vm/tests/browser_api/mouse_event_init.js @@ -0,0 +1,121 @@ +globalThis.__mousePointerProbe = realms => { + const rows = [], errors = [], unexecuted = []; + const record = (label, checks, observed = null) => rows.push({label, checks, observed}); + const run = (label, work) => { try { work(); } catch (error) { errors.push({label, error: String(error.stack || error)}); } }; + const eq = (a, b) => JSON.stringify(a) === JSON.stringify(b); + const units = s => Array.from({length:s.length}, (_,i)=>s.charCodeAt(i)); + const integer = (n,bits,signed) => { n=Number(n); if(!Number.isFinite(n)||n===0)return 0; const m=2**bits; n=((Math.trunc(n)%m)+m)%m; return signed && n>=m/2?n-m:n; }; + const base = ['bubbles','cancelable','composed','detail','view','which']; + const modifiers = ['altKey','ctrlKey','metaKey','modifierAltGraph','modifierCapsLock','modifierFn','modifierFnLock','modifierHyper','modifierNumLock','modifierScrollLock','modifierSuper','modifierSymbol','modifierSymbolLock','shiftKey']; + const mouse = ['button','buttons','clientX','clientY','movementX','movementY','relatedTarget','screenX','screenY']; + const pointer = ['altitudeAngle','azimuthAngle','coalescedEvents','height','isPrimary','persistentDeviceId','pointerId','pointerType','predictedEvents','pressure','tangentialPressure','tiltX','tiltY','twist','width']; + const wheel = ['deltaMode','deltaX','deltaY','deltaZ']; + const mouseFields = ['screenX','screenY','clientX','clientY','button','buttons','ctrlKey','altKey','shiftKey','metaKey','movementX','movementY','pageX','pageY','x','y','relatedTarget']; + const pointerFields = pointer.filter(k=>!k.endsWith('Events')); + for (const [r,realm] of realms.entries()) for (const name of ['MouseEvent','WheelEvent','PointerEvent','DragEvent']) { + const C=realm[name], prefix=`${r}/${name}`, own=name==='MouseEvent'?mouseFields:name==='WheelEvent'?wheel:name==='PointerEvent'?pointerFields:['dataTransfer']; + const keys=[...base,...modifiers,...mouse,...(name==='WheelEvent'?wheel:name==='PointerEvent'?pointer:name==='DragEvent'?['dataTransfer']:[])]; + run(prefix+'/metadata',()=>{ + const e=new C('x'); + record(prefix+'/constructor',{name:C.name===name,length:C.length===1,parent:Object.getPrototypeOf(C.prototype)===(name==='MouseEvent'?realm.UIEvent:realm.MouseEvent).prototype,instance:e instanceof realm.MouseEvent}); + for(const field of own) { + const d=Object.getOwnPropertyDescriptor(C.prototype,field); + record(prefix+'/descriptor-'+field,{getter:typeof d?.get==='function',name:d?.get?.name==='get '+field,length:d?.get?.length===0,enumerable:d?.enumerable===true,configurable:d?.configurable===true,setter:d?.set===undefined,own:!Object.hasOwn(e,field)}); + } + }); + for(const [i,init] of [undefined,null,{},[],function(){},new Date(0)].entries())run(prefix+'/defaults-'+i,()=>{ + const e=new C('x',init); + record(prefix+'/defaults-'+i,{flags:!e.bubbles&&!e.cancelable&&!e.composed,ui:e.view===null&&e.detail===0&&e.which===0,mouse:mouseFields.filter(k=>k!=='relatedTarget').every(k=>e[k]===(k.endsWith('Key')?false:0)),related:e.relatedTarget===null,derived:name==='WheelEvent'?wheel.every(k=>e[k]===0):name==='PointerEvent'?e.pointerId===0&&e.width===1&&e.height===1&&e.pressure===0&&e.tangentialPressure===0&&e.tiltX===0&&e.tiltY===0&&e.twist===0&&e.altitudeAngle===Math.PI/2&&e.azimuthAngle===0&&e.pointerType===''&&!e.isPrimary&&e.persistentDeviceId===0:name==='DragEvent'?e.dataTransfer===null:true}); + }); + for(const [i,init] of [0,1,'',true,Symbol('init'),1n].entries())run(prefix+'/dictionary-'+i,()=>{ + let error;try{new C('x',init);}catch(e){error=e;}record(prefix+'/dictionary-'+i,{typeError:error instanceof realm.TypeError},error?.name||'returned'); + }); + run(prefix+'/order',()=>{ + const trace=[];new C('x',new Proxy({}, {get(_,key){trace.push(key);}}));record(prefix+'/order',{order:eq(trace,keys)},trace); + for(const [i,key] of keys.entries()){ + const sentinel={},reads=[];let error;try{new C('x',new Proxy({}, {get(_,k){reads.push(k);if(k===key)throw sentinel;}}));}catch(e){error=e;} + record(prefix+'/throw-get-'+key,{identity:error===sentinel,order:eq(reads,keys.slice(0,i+1))},reads); + } + const trace2=[];const type={[Symbol.toPrimitive](hint){trace2.push('type:'+hint);return '\uD800';}}; + const e=new C(type,new Proxy({}, {get(_,key){trace2.push(key);}}));record(prefix+'/type-first',{type:e.type==='\uD800',order:eq(trace2,['type:string',...keys])},trace2); + const sentinel={},reads=[];let error;try{new C({[Symbol.toPrimitive](){throw sentinel;}},new Proxy({}, {get(_,k){reads.push(k);}}));}catch(e){error=e;}record(prefix+'/type-throw',{identity:error===sentinel,noReads:reads.length===0}); + }); + const numeric={detail:['int',32,true,0],which:['int',32,false,0],button:['int',16,true,0],buttons:['int',16,false,0]}; + for(const k of ['screenX','screenY','clientX','clientY','movementX','movementY'])numeric[k]=['double',0,false,0]; + if(name==='WheelEvent'){numeric.deltaMode=['int',32,false,0];for(const k of ['deltaX','deltaY','deltaZ'])numeric[k]=['double',0,false,0];} + if(name==='PointerEvent'){ + for(const k of ['pointerId','persistentDeviceId','tiltX','tiltY','twist'])numeric[k]=['int',32,true,0]; + for(const k of ['width','height'])numeric[k]=['double',0,false,1]; + for(const k of ['pressure','tangentialPressure'])numeric[k]=['float',0,false,0]; + numeric.altitudeAngle=['double',0,false,Math.PI/2];numeric.azimuthAngle=['double',0,false,0]; + } + const values=[undefined,null,0,-0,-1,-3.5,3.5,65535,65536,2147483648,4294967295,4294967297,NaN,Infinity,-Infinity,true,false,'3.5','NaN',1e40]; + for(const [field,[kind,bits,signed,def]] of Object.entries(numeric)){ + for(const [i,value] of (['altitudeAngle','azimuthAngle'].includes(field)?[undefined,null,0,-0,-1,-3.5,3.5,NaN,Infinity,-Infinity,true,false,'3.5','NaN',Math.PI/4]:values).entries())run(prefix+'/number-'+field+'-'+i,()=>{ + const n=value===undefined?def:Number(value),expected=kind==='int'?integer(n,bits,signed):kind==='float'?Math.fround(n):n; + const valid=kind==='int'||Number.isFinite(expected);let e,error;try{e=new C('x',{[field]:value});}catch(caught){error=caught;} + record(prefix+'/number-'+field+'-'+i,valid?{value:Object.is(e?.[field],expected)}:{typeError:error instanceof realm.TypeError},{actual:e?.[field],expected:String(expected),error:error?.name}); + }); + for(const [i,value] of [Symbol('n'),1n].entries())run(prefix+'/invalid-number-'+field+'-'+i,()=>{let error;try{new C('x',{[field]:value});}catch(e){error=e;}record(prefix+'/invalid-number-'+field+'-'+i,{typeError:error instanceof realm.TypeError},error?.name||'returned');}); + run(prefix+'/number-hint-'+field,()=>{ + const trace=[],v={[Symbol.toPrimitive](hint){trace.push(hint);return 3.5;}};const e=new C('x',{[field]:v}); + record(prefix+'/number-hint-'+field,{hint:eq(trace,['number']),value:e[field]===(kind==='int'?3:3.5)},trace); + const sentinel={},reads=[];let error;try{new C('x',new Proxy({}, {get(_,key){reads.push(key);return key===field?{[Symbol.toPrimitive](){throw sentinel;}}:undefined;}}));}catch(e){error=e;} + record(prefix+'/throw-convert-'+field,{identity:error===sentinel,order:eq(reads,keys.slice(0,keys.indexOf(field)+1))},reads); + }); + } + for(const [i,key] of modifiers.entries())run(prefix+'/modifier-'+key,()=>{ + const e=new C('x',{[key]:{}}),names=['Alt','Control','Meta','AltGraph','CapsLock','Fn','FnLock','Hyper','NumLock','ScrollLock','Super','Symbol','SymbolLock','Shift']; + record(prefix+'/modifier-'+key,{state:e.getModifierState(names[i]),others:names.filter((_,j)=>i!==j).every(k=>!e.getModifierState(k))}); + }); + for(const [i,value] of [null,undefined,realm.document,realm.document.createElement('div'),new realm.EventTarget(),realms[(r+1)%realms.length].document,realm.document.implementation.createHTMLDocument('').createElement('select')].entries())run(prefix+'/related-valid-'+i,()=>{ + const e=new C('x',{relatedTarget:value});record(prefix+'/related-valid-'+i,{identity:e.relatedTarget===(value??null)}); + }); + const bad=[{},Object.create(realm.EventTarget.prototype),Object.create(realm.document),new Proxy(realm.document,{}),new Proxy(new realm.EventTarget(),{})]; + const revoked=Proxy.revocable(realm.document,{});revoked.revoke();bad.push(revoked.proxy,1,'x',Symbol('bad')); + for(const [i,value] of bad.entries())run(prefix+'/related-invalid-'+i,()=>{let error;try{new C('x',{relatedTarget:value});}catch(e){error=e;}record(prefix+'/related-invalid-'+i,{typeError:error instanceof realm.TypeError},error?.name||'returned');}); + run(prefix+'/related-no-traps',()=>{let traps=0,error;const p=new Proxy(realm.document,{get(){traps++;},getPrototypeOf(){traps++;}});try{new C('x',{relatedTarget:p});}catch(e){error=e;}record(prefix+'/related-no-traps',{typeError:error instanceof realm.TypeError,noTraps:traps===0},traps);}); + run(prefix+'/flags-aliases',()=>{ + for(let n=0;n<8;n++){ + const e=new C('x',{bubbles:!!(n&1),cancelable:!!(n&2),composed:!!(n&4),clientX:31,clientY:42,view:realm}); + record(prefix+'/flags-'+n,{bubbles:e.bubbles===!!(n&1),cancelable:e.cancelable===!!(n&2),composed:e.composed===!!(n&4),view:e.view===realm,aliases:e.x===31&&e.pageX===31&&e.y===42&&e.pageY===42&&e.offsetX===31&&e.offsetY===42}); + } + const e=new C('x');realm.document.body.dispatchEvent(e);record(prefix+'/dispatch',{target:e.target===realm.document.body,phase:e.eventPhase===0,untrusted:!e.isTrusted}); + }); + for(const field of own)run(prefix+'/receiver-'+field,()=>{ + const getter=Object.getOwnPropertyDescriptor(C.prototype,field)?.get; + if(typeof getter!=='function'){ + unexecuted.push({label:prefix+'/receiver-'+field,checks:12,reason:'missing prototype getter'});return; + } + const e=new C('x'),other=new realms[(r+1)%realms.length][name]('x'); + record(prefix+'/genuine-'+field,{same:Object.is(getter.call(e),e[field]),other:Object.is(getter.call(other),other[field])}); + const bad=[{},Object.create(C.prototype),Object.create(e),new Proxy(e,{})];let revoked=Proxy.revocable(e,{});revoked.revoke();bad.push(revoked.proxy); + for(const [i,receiver] of bad.entries()){let error;try{getter.call(receiver);}catch(e){error=e;}record(prefix+'/receiver-'+field+'-'+i,{typeError:error instanceof realm.TypeError,noWrongRealm:r===0||!(error instanceof realms[0].TypeError)},error?.name||'returned');} + }); + if(name==='PointerEvent')run(prefix+'/pointer',()=>{ + for(const [i,value] of [undefined,null,'pen','\uD800','\uDC00','x\uD800y','\uD83D\uDE80',1n,true,['a','b']].entries()){ + const actual=new C('x',{pointerType:value}).pointerType;record(prefix+'/pointerType-'+i,{value:actual===(value===undefined?'':String(value))},units(actual)); + } + const trace=[],input={[Symbol.toPrimitive](hint){trace.push(hint);return '\uD800';}},actual=new C('x',{pointerType:input}).pointerType;record(prefix+'/pointerType-hint',{hint:eq(trace,['string']),value:actual==='\uD800'},units(actual)); + let error;try{new C('x',{pointerType:Symbol('x')});}catch(e){error=e;}record(prefix+'/pointerType-symbol',{typeError:error instanceof realm.TypeError}); + for(const key of ['coalescedEvents','predictedEvents']){ + const a=new C('a'),b=new realms[(r+1)%realms.length].PointerEvent('b'),list=[a,b],e=new C('x',{[key]:list}),method=key==='coalescedEvents'?'getCoalescedEvents':'getPredictedEvents'; + if(typeof e[method]!=='function' && !realm.isSecureContext && method==='getCoalescedEvents'){record(prefix+'/'+key+'-secure',{absent:true});continue;} if(typeof e[method]!=='function'){unexecuted.push({label:prefix+'/'+key+'-copy',checks:5,reason:'missing method in secure context'});continue;} + list.length=0;const first=e[method](),second=e[method]();first.pop();record(prefix+'/'+key+'-copy',{copied:second.length===2,fresh:first!==second,identity:second[0]===a&&second[1]===b,stable:e[method]().length===2,array:second instanceof realm.Array}); + for(const [i,value] of [null,1,{},[{}],[new realm.MouseEvent('x')],[new Proxy(a,{})],[Object.create(a)],[Object.create(C.prototype)]].entries()){ + let error;try{new C('x',{[key]:value});}catch(e){error=e;}record(prefix+'/'+key+'-invalid-'+i,{typeError:error instanceof realm.TypeError},error?.name||'returned'); + } + const sentinel={},trace=[];let error;try{new C('x',{[key]:{[Symbol.iterator](){trace.push('iterator');return{next(){trace.push('next');throw sentinel;}};}}});}catch(e){error=e;}record(prefix+'/'+key+'-iterator-throw',{identity:error===sentinel,trace:eq(trace,['iterator','next'])},trace); + const fn=C.prototype[method];for(const [i,receiver] of [{},new realm.MouseEvent('x'),new Proxy(e,{})].entries()){let error;try{fn.call(receiver);}catch(e){error=e;}record(prefix+'/'+method+'-receiver-'+i,{typeError:error instanceof realm.TypeError});} + } + const supplied=new C('x',{tiltX:45,azimuthAngle:Math.PI/4}),derived=new C('x',{tiltY:45});record(prefix+'/angle-presence',{retained:supplied.tiltX===45&&supplied.azimuthAngle===Math.PI/4&&supplied.altitudeAngle===Math.PI/2,derived:derived.tiltX===0&&derived.tiltY===45&&derived.azimuthAngle===Math.PI/2&&Math.abs(derived.altitudeAngle-Math.PI/4)<1e-14}); + }); + if(name==='DragEvent')run(prefix+'/drag',()=>{ + const dt=new realm.DataTransfer(),e=new C('x',{dataTransfer:dt});record(prefix+'/dataTransfer-native',{identity:e.dataTransfer===dt}); + for(const [i,value] of [{},Object.create(realm.DataTransfer.prototype),Object.create(dt),new Proxy(dt,{})].entries()){let error;try{new C('x',{dataTransfer:value});}catch(e){error=e;}record(prefix+'/dataTransfer-invalid-'+i,{typeError:error instanceof realm.TypeError});} + }); + run(prefix+'/subclass',()=>{class Sub extends C{}const e=new Sub('x',{button:65535,buttons:-1});record(prefix+'/subclass',{identity:e instanceof Sub&&e instanceof C,conversion:e.button===-1&&e.buttons===65535});let missing,call;try{new C();}catch(e){missing=e;}try{C('x');}catch(e){call=e;}record(prefix+'/required',{missing:missing instanceof realm.TypeError,call:call instanceof realm.TypeError});}); + } + const executed=rows.reduce((n,r)=>n+Object.keys(r.checks).length,0),passed=rows.reduce((n,r)=>n+Object.values(r.checks).filter(Boolean).length,0),skipped=unexecuted.reduce((n,r)=>n+r.checks,0); + return {total:executed+skipped,executed,passed,failed:executed-passed,unexecutedChecks:skipped,unexecuted,errors,rows,complete:errors.length===0&&skipped===0&&passed===executed}; +}; diff --git a/moli-renderer-v8/src/script_vm/tests/browser_api/mouse_event_init.rs b/moli-renderer-v8/src/script_vm/tests/browser_api/mouse_event_init.rs new file mode 100644 index 0000000000..2438b5fb05 --- /dev/null +++ b/moli-renderer-v8/src/script_vm/tests/browser_api/mouse_event_init.rs @@ -0,0 +1,37 @@ +use super::*; + +#[tokio::test] +async fn mouse_event_dictionaries_and_prototypes_share_native_conversion_and_brands() { + let loader = ResourceRequestClient::new(&moli_fetch::FetchConfig::default()).unwrap(); + let mut vm = new_storage_page_task_executor_test_vm_with_loader( + "https://mouse-event-init.test/", + &loader, + ); + vm.eval( + r#" + if (!document.documentElement) document.appendChild(document.createElement('html')); + if (!document.body) document.documentElement.appendChild(document.createElement('body')); + const frame = document.body.appendChild(document.createElement('iframe')); + frame.id = 'child'; frame.srcdoc = ''; void frame.contentWindow; + 'ready' + "#, + ) + .unwrap(); + assert!( + vm.run_one_child_frame_task_executor_turn( + ChildFrameSemanticTurnKind::RealmMaterialization, + &loader + ) + .await + .unwrap() + ); + vm.drain_ready_page_task_executor_turns_for_setup(&loader, 128) + .await + .unwrap(); + let source = format!( + "{}; globalThis.__uiEventResults = __mousePointerProbe([globalThis, document.getElementById('child').contentWindow]); __uiEventResults.complete", + include_str!("mouse_event_init.js"), + ); + assert_eq!(vm.eval(&source).unwrap(), "true", "{}", + vm.eval("JSON.stringify({errors:__uiEventResults.errors,failures:__uiEventResults.rows.filter(row=>Object.values(row.checks).some(value=>value!==true))})").unwrap()); +}