fix(events): convert inherited mouse dictionaries and validate native getters

This commit is contained in:
ldm0
2026-10-11 18:06:46 +08:00
parent 4accfd74ec
commit aaa52c6f9e
10 changed files with 632 additions and 339 deletions
@@ -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<EventTemplateDeclaratio
EventTemplateDeclaration::new::<KeyboardEventTemplateMethodsDeclaration>()
}
"MouseEvent" => EventTemplateDeclaration::new::<MouseEventTemplateMethodsDeclaration>(),
"DragEvent" => EventTemplateDeclaration::new::<DragEventTemplateAccessorsDeclaration>(),
"WheelEvent" => EventTemplateDeclaration::new::<WheelEventTemplateConstantsDeclaration>(),
"PointerEvent" => EventTemplateDeclaration::new::<PointerEventTemplateMethodsDeclaration>(),
"CloseEvent" => EventTemplateDeclaration::new::<CloseEventTemplateAccessorsDeclaration>(),
@@ -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",
@@ -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<v8::Local<'s, v8::Object>>,
) -> bool {
let modifiers = match init {
Some(init) => {
match webidl::parse_dictionary_object::<EventModifierInitMembers>(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>,
@@ -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
@@ -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<v8::Local<'s, v8::Object>>,
) -> 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::<v8::Object>::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>,
@@ -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<v8::Local<'scope, v8::Object>>,
#[webidl(sequence, interface = crate::web_api_interfaces::PointerEvent, brand_check = is_pointer_event_sequence_item, default = Vec::new())]
predicted_events: Vec<v8::Local<'scope, v8::Object>>,
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<Self, webidl::WebIdlError> {
let target = webidl::convert::<v8::Local<'s, v8::Object>>(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<EventTargetReference<'s>>,
#[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<Self, webidl::WebIdlError> {
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<Self, webidl::WebIdlError> {
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<f64>,
#[webidl(converter = "double")]
azimuth_angle: Option<f64>,
#[webidl(sequence, interface = web_api_interfaces::PointerEvent, default = Vec::new())]
coalesced_events: Vec<v8::Local<'s, v8::Object>>,
#[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<v8::Local<'s, v8::Object>>,
#[webidl(default = 0.0)]
pressure: f32,
#[webidl(default = 0.0)]
tangential_pressure: f32,
tilt_x: Option<i32>,
tilt_y: Option<i32>,
#[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<Self, webidl::WebIdlError> {
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<Self, webidl::WebIdlError> {
let object = webidl::convert::<v8::Local<'s, v8::Object>>(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<DataTransferReference<'s>>,
}
#[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<Self, webidl::WebIdlError> {
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<v8::Local<'s, v8::Object>>,
) -> Result<PointerEventAngleInit, webidl::WebIdlError> {
let Some(init) = init else {
return Ok(PointerEventAngleInit::default());
};
let tilt_x = webidl::optional_member::<webidl::Long>(
scope,
init,
"tiltX",
webidl::Context::member("PointerEventInit", "tiltX"),
)?
.map(|value| value.0);
let tilt_y = webidl::optional_member::<webidl::Long>(
scope,
init,
"tiltY",
webidl::Context::member("PointerEventInit", "tiltY"),
)?
.map(|value| value.0);
let azimuth_angle = webidl::optional_member::<webidl::Double>(
scope,
init,
"azimuthAngle",
webidl::Context::member("PointerEventInit", "azimuthAngle"),
)?
.map(|value| value.0);
let altitude_angle = webidl::optional_member::<webidl::Double>(
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<v8::Local<'s, v8::Object>>,
constructor_name: &'static str,
) -> Option<v8::Local<'s, v8::Value>> {
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<v8::Local<'s, v8::Object>>,
) -> 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<v8::Local<'s, v8::Object>>,
) -> 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<v8::Local<'s, v8::Object>>,
) -> 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::<PointerEventSequences>(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);
}
@@ -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,
@@ -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;
@@ -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};
};
@@ -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 = '<head></head><body></body>'; 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());
}