diff --git a/moli-renderer-v8/src/context_bootstrap/event_legacy.rs b/moli-renderer-v8/src/context_bootstrap/event_legacy.rs index 66052503b6..4e5abbbda6 100644 --- a/moli-renderer-v8/src/context_bootstrap/event_legacy.rs +++ b/moli-renderer-v8/src/context_bootstrap/event_legacy.rs @@ -176,13 +176,6 @@ struct InitCompositionEventArgs<'s> { data: String, } -#[derive(webidl::WebIdlArgs)] -#[webidl(prefix = "KeyboardEvent.getModifierState")] -struct KeyboardEventGetModifierStateArgs { - #[webidl(required)] - key_arg: String, -} - #[derive(WebApiObject)] #[webapi(plain, data_properties, enumerable)] struct LegacyUiEventInitDeclaration<'scope> { @@ -223,10 +216,6 @@ struct LegacyMouseEventBaseInitDeclaration<'scope> { struct LegacyMouseEventTailInitDeclaration<'scope> { #[webapi(constructor_default = 0)] buttons: i32, - ctrl_key: bool, - alt_key: bool, - shift_key: bool, - meta_key: bool, related_target: v8::Local<'scope, v8::Value>, } @@ -242,14 +231,6 @@ struct LegacyKeyboardEventInitDeclaration<'scope> { repeat: bool, #[webapi(constructor_default = false)] is_composing: bool, - #[webapi(constructor_default = false)] - ctrl_key: bool, - #[webapi(constructor_default = false)] - shift_key: bool, - #[webapi(constructor_default = false)] - alt_key: bool, - #[webapi(constructor_default = false)] - meta_key: bool, } #[derive(WebApiObject)] @@ -399,15 +380,17 @@ pub(super) fn mouse_event_init_callback<'s>( ) .initialize(scope, event) .expect("legacy MouseEvent base init declaration should initialize"); - LegacyMouseEventTailInitDeclaration::new( + super::events::initialize_legacy_event_modifiers( + scope, + event, parsed.ctrl_key, parsed.alt_key, parsed.shift_key, parsed.meta_key, - related_target, - ) - .initialize(scope, event) - .expect("legacy MouseEvent tail init declaration should initialize"); + ); + LegacyMouseEventTailInitDeclaration::new(related_target) + .initialize(scope, event) + .expect("legacy MouseEvent tail init declaration should initialize"); } pub(super) fn keyboard_event_init_callback<'s>( @@ -432,6 +415,7 @@ pub(super) fn keyboard_event_init_callback<'s>( ); let key = v8_string(scope, &parsed.key).unwrap_or_else(|| v8str(scope, "")); let code = v8str(scope, ""); + super::events::initialize_legacy_event_modifiers(scope, event, false, false, false, false); LegacyKeyboardEventInitDeclaration::new(view, key, code, parsed.location, parsed.repeat) .initialize(scope, event) .expect("legacy KeyboardEvent init declaration should initialize"); @@ -517,38 +501,3 @@ pub(super) fn storage_event_init_callback<'s>( parsed.storage_area, ); } - -pub(super) fn keyboard_event_get_modifier_state_callback<'s>( - scope: &mut v8::PinScope<'s, '_>, - args: v8::FunctionCallbackArguments<'s>, - mut rv: v8::ReturnValue<'_, v8::Value>, -) { - let Some(parsed) = webidl::parse_args::(scope, &args) else { - return; - }; - let event = args.this(); - let key_name = match parsed.key_arg.as_str() { - "Alt" | "AltGraph" => "altKey", - "Control" => "ctrlKey", - "Shift" => "shiftKey", - "Meta" => "metaKey", - "Accel" => { - let ctrl = event - .get(scope, v8str(scope, "ctrlKey").into()) - .is_some_and(|value| value.boolean_value(scope)); - let meta = event - .get(scope, v8str(scope, "metaKey").into()) - .is_some_and(|value| value.boolean_value(scope)); - rv.set(v8::Boolean::new(scope, ctrl || meta).into()); - return; - } - _ => { - rv.set(v8::Boolean::new(scope, false).into()); - return; - } - }; - let value = event - .get(scope, v8str(scope, key_name).into()) - .is_some_and(|value| value.boolean_value(scope)); - rv.set(v8::Boolean::new(scope, value).into()); -} diff --git a/moli-renderer-v8/src/context_bootstrap/event_template.rs b/moli-renderer-v8/src/context_bootstrap/event_template.rs index 95a2dd4ddf..e5d5dc7ff5 100644 --- a/moli-renderer-v8/src/context_bootstrap/event_template.rs +++ b/moli-renderer-v8/src/context_bootstrap/event_template.rs @@ -2,9 +2,8 @@ use super::{ event_document::{document_create_event_callback, document_has_focus_callback}, event_legacy::{ composition_event_init_callback, custom_event_init_callback, event_init_event_callback, - keyboard_event_get_modifier_state_callback, keyboard_event_init_callback, - mouse_event_init_callback, storage_event_init_callback, text_event_init_callback, - ui_event_init_callback, + keyboard_event_init_callback, mouse_event_init_callback, storage_event_init_callback, + text_event_init_callback, ui_event_init_callback, }, events::{ before_unload_event_return_value_getter_function, @@ -18,11 +17,11 @@ use super::{ event_cancel_bubble_setter_function, event_cancelable_getter_function, event_composed_getter_function, event_composed_path_callback, event_current_target_getter_function, event_default_prevented_getter_function, - event_event_phase_getter_function, event_prevent_default_callback, - event_return_value_getter_function, event_return_value_setter_function, - event_src_element_getter_function, event_stop_immediate_propagation_callback, - event_stop_propagation_callback, event_target_getter_function, - event_time_stamp_getter_function, event_type_getter_function, + event_event_phase_getter_function, event_get_modifier_state_callback, + event_prevent_default_callback, event_return_value_getter_function, + event_return_value_setter_function, event_src_element_getter_function, + event_stop_immediate_propagation_callback, event_stop_propagation_callback, + event_target_getter_function, event_time_stamp_getter_function, event_type_getter_function, focus_event_related_target_getter_function, form_data_event_form_data_getter_function, mouse_event_related_target_getter_function, pointer_event_get_predicted_events_callback, submit_event_submitter_getter_function, toggle_event_source_getter_function, @@ -288,8 +287,9 @@ struct KeyboardEventTemplateMethodsDeclaration { #[webapi( method = "getModifierState", - length = 0, - callback = keyboard_event_get_modifier_state_callback + length = 1, + callback = event_get_modifier_state_callback, + receiver = web_api_interfaces::KeyboardEvent::is_instance )] get_modifier_state: (), } @@ -297,6 +297,14 @@ struct KeyboardEventTemplateMethodsDeclaration { #[derive(WebApiFunctionTemplate)] #[webapi(interface = web_api_interfaces::MouseEvent, enumerable)] struct MouseEventTemplateMethodsDeclaration { + #[webapi( + method = "getModifierState", + length = 1, + callback = event_get_modifier_state_callback, + receiver = web_api_interfaces::MouseEvent::is_instance + )] + get_modifier_state: (), + #[webapi( accessor_property = "relatedTarget", getter = mouse_event_related_target_getter_function diff --git a/moli-renderer-v8/src/context_bootstrap/events.rs b/moli-renderer-v8/src/context_bootstrap/events.rs index 1390d24fff..0230820ed4 100644 --- a/moli-renderer-v8/src/context_bootstrap/events.rs +++ b/moli-renderer-v8/src/context_bootstrap/events.rs @@ -6,8 +6,13 @@ mod base; mod init; mod kind; mod methods; +mod modifiers; mod subclasses; +pub(in crate::context_bootstrap) use modifiers::{ + event_get_modifier_state_callback, initialize_legacy_event_modifiers, +}; + const CLOSE_EVENT_WAS_CLEAN_SLOT: &str = "__moliCloseEventWasClean"; const CLOSE_EVENT_CODE_SLOT: &str = "__moliCloseEventCode"; const CLOSE_EVENT_REASON_SLOT: &str = "__moliCloseEventReason"; diff --git a/moli-renderer-v8/src/context_bootstrap/events/modifiers.rs b/moli-renderer-v8/src/context_bootstrap/events/modifiers.rs new file mode 100644 index 0000000000..580a65c8a6 --- /dev/null +++ b/moli-renderer-v8/src/context_bootstrap/events/modifiers.rs @@ -0,0 +1,169 @@ +use super::*; +use crate::webidl; + +const EVENT_MODIFIERS_SLOT: &str = "__moliEventModifiers"; +const MODIFIER_KEYS: [&str; 14] = [ + "Alt", + "Control", + "Meta", + "AltGraph", + "CapsLock", + "Fn", + "FnLock", + "Hyper", + "NumLock", + "ScrollLock", + "Super", + "Symbol", + "SymbolLock", + "Shift", +]; + +/// EventModifierInit members, in Web IDL dictionary order. +#[derive(Default, webidl::WebIdlDictionary)] +#[webidl(prefix = "EventModifierInit")] +struct EventModifierInitMembers { + #[webidl(default = false)] + alt_key: bool, + #[webidl(default = false)] + ctrl_key: bool, + #[webidl(default = false)] + meta_key: bool, + #[webidl(default = false)] + modifier_alt_graph: bool, + #[webidl(default = false)] + modifier_caps_lock: bool, + #[webidl(default = false)] + modifier_fn: bool, + #[webidl(default = false)] + modifier_fn_lock: bool, + #[webidl(default = false)] + modifier_hyper: bool, + #[webidl(default = false)] + modifier_num_lock: bool, + #[webidl(default = false)] + modifier_scroll_lock: bool, + #[webidl(default = false)] + modifier_super: bool, + #[webidl(default = false)] + modifier_symbol: bool, + #[webidl(default = false)] + modifier_symbol_lock: bool, + #[webidl(default = false)] + shift_key: bool, +} + +#[derive(WebApiObject)] +#[webapi(plain, data_properties, enumerable)] +struct EventModifierProperties { + alt_key: bool, + ctrl_key: bool, + meta_key: bool, + shift_key: bool, +} + +impl EventModifierInitMembers { + fn initialize<'s>(&self, scope: &mut v8::PinScope<'s, '_>, event: v8::Local<'s, v8::Object>) { + let values = [ + self.alt_key, + self.ctrl_key, + self.meta_key, + self.modifier_alt_graph, + self.modifier_caps_lock, + self.modifier_fn, + self.modifier_fn_lock, + self.modifier_hyper, + self.modifier_num_lock, + self.modifier_scroll_lock, + self.modifier_super, + self.modifier_symbol, + self.modifier_symbol_lock, + self.shift_key, + ]; + let bits = values + .into_iter() + .enumerate() + .fold(0, |bits, (index, active)| { + bits | (u32::from(active) << index) + }); + set_private_value( + scope, + event, + EVENT_MODIFIERS_SLOT, + v8::Integer::new_from_unsigned(scope, bits).into(), + ); + EventModifierProperties::new(self.alt_key, self.ctrl_key, self.meta_key, self.shift_key) + .initialize(scope, event) + .expect("event modifier properties should initialize"); + } +} + +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>, + ctrl_key: bool, + alt_key: bool, + shift_key: bool, + meta_key: bool, +) { + EventModifierInitMembers { + ctrl_key, + alt_key, + shift_key, + meta_key, + ..Default::default() + } + .initialize(scope, event); +} + +#[derive(webidl::WebIdlArgs)] +#[webidl(prefix = "getModifierState")] +struct GetModifierStateArgs { + #[webidl(required)] + key_arg: String, +} + +pub(in crate::context_bootstrap) fn event_get_modifier_state_callback<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + mut rv: v8::ReturnValue<'_, v8::Value>, +) { + let Some(parsed) = webidl::parse_args::(scope, &args) else { + return; + }; + // Retain the legacy accelerator alias for the native platform. + let key = match parsed.key_arg.as_str() { + "Accel" if cfg!(target_os = "macos") => "Meta", + "Accel" => "Control", + key => key, + }; + let bits = get_private_value(scope, args.this(), EVENT_MODIFIERS_SLOT) + .and_then(|value| value.uint32_value(scope)) + .unwrap_or_default(); + let active = MODIFIER_KEYS + .iter() + .position(|name| *name == key) + .is_some_and(|index| bits & (1 << index) != 0); + rv.set(v8::Boolean::new(scope, active).into()); +} diff --git a/moli-renderer-v8/src/context_bootstrap/events/subclasses/keyboard.rs b/moli-renderer-v8/src/context_bootstrap/events/subclasses/keyboard.rs index 50ea026123..ba48aaa1c6 100644 --- a/moli-renderer-v8/src/context_bootstrap/events/subclasses/keyboard.rs +++ b/moli-renderer-v8/src/context_bootstrap/events/subclasses/keyboard.rs @@ -14,10 +14,6 @@ struct KeyboardEventInitDeclaration<'scope> { which: f64, repeat: bool, is_composing: bool, - ctrl_key: bool, - shift_key: bool, - alt_key: bool, - meta_key: bool, } pub(in crate::context_bootstrap::events::subclasses) fn initialize_keyboard_event<'s>( @@ -29,6 +25,9 @@ pub(in crate::context_bootstrap::events::subclasses) fn initialize_keyboard_even return false; }; let detail = init_number_property(scope, init, "detail", 0.0); + if !super::super::modifiers::initialize_event_modifiers(scope, event, init) { + return false; + } let key = init_string_property(scope, init, "key", ""); let code = init_string_property(scope, init, "code", ""); let key = v8_string(scope, &key).expect("KeyboardEvent key"); @@ -45,10 +44,6 @@ pub(in crate::context_bootstrap::events::subclasses) fn initialize_keyboard_even init_number_property(scope, init, "which", 0.0), init_bool_property(scope, init, "repeat", false), init_bool_property(scope, init, "isComposing", false), - init_bool_property(scope, init, "ctrlKey", false), - init_bool_property(scope, init, "shiftKey", false), - init_bool_property(scope, init, "altKey", false), - init_bool_property(scope, init, "metaKey", false), ) .initialize(scope, event) .expect("KeyboardEvent init declaration should initialize"); 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 ab0707706a..a8b85b14d9 100644 --- a/moli-renderer-v8/src/context_bootstrap/events/subclasses/pointer.rs +++ b/moli-renderer-v8/src/context_bootstrap/events/subclasses/pointer.rs @@ -47,15 +47,6 @@ struct MouseEventBaseInitDeclaration<'scope> { movement_y: f64, } -#[derive(WebApiObject)] -#[webapi(plain, data_properties, enumerable)] -struct MouseEventModifierInitDeclaration { - ctrl_key: bool, - shift_key: bool, - alt_key: bool, - meta_key: bool, -} - #[derive(WebApiObject)] #[webapi(plain, data_properties, enumerable)] struct MouseEventRelatedTargetDeclaration<'scope> { @@ -389,9 +380,12 @@ fn define_mouse_event_base_fields<'s>( 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 detail = init_number_property(scope, init, "detail", 0.0); let related_target = init_value_property(scope, init, "relatedTarget").unwrap_or_else(|| v8::null(scope).into()); @@ -416,21 +410,6 @@ fn define_mouse_event_base_fields<'s>( Some(related_target) } -fn define_mouse_event_modifier_fields<'s>( - scope: &mut v8::PinScope<'s, '_>, - event: v8::Local<'s, v8::Object>, - init: Option>, -) { - MouseEventModifierInitDeclaration::new( - init_bool_property(scope, init, "ctrlKey", false), - init_bool_property(scope, init, "shiftKey", false), - init_bool_property(scope, init, "altKey", false), - init_bool_property(scope, init, "metaKey", false), - ) - .initialize(scope, event) - .expect("MouseEvent modifier init declaration should initialize"); -} - fn define_mouse_event_related_target<'s>( scope: &mut v8::PinScope<'s, '_>, event: v8::Local<'s, v8::Object>, @@ -450,7 +429,6 @@ pub(in crate::context_bootstrap::events::subclasses) fn initialize_mouse_event<' else { return false; }; - define_mouse_event_modifier_fields(scope, event, init); define_mouse_event_related_target(scope, event, related_target); true } @@ -472,7 +450,6 @@ pub(in crate::context_bootstrap::events::subclasses) fn initialize_wheel_event<' ) .initialize(scope, event) .expect("WheelEvent delta init declaration should initialize"); - define_mouse_event_modifier_fields(scope, event, init); define_mouse_event_related_target(scope, event, related_target); true } @@ -523,7 +500,6 @@ pub(in crate::context_bootstrap::events::subclasses) fn initialize_pointer_event ) .initialize(scope, event) .expect("PointerEvent number init declaration should initialize"); - define_mouse_event_modifier_fields(scope, event, init); PointerEventTailInitDeclaration::new( init_bool_property(scope, init, "isPrimary", false), pointer_type, diff --git a/moli-renderer-v8/src/script_vm/tests/dom_xhr/event_modifiers.rs b/moli-renderer-v8/src/script_vm/tests/dom_xhr/event_modifiers.rs new file mode 100644 index 0000000000..5724c87256 --- /dev/null +++ b/moli-renderer-v8/src/script_vm/tests/dom_xhr/event_modifiers.rs @@ -0,0 +1,199 @@ +use super::*; + +#[test] +fn event_modifier_state_constructor_keeps_each_modifier_independent() { + let mut vm = new_storage_test_vm("https://event-modifiers.test/"); + let result = vm + .eval( + r#" +(() => { + const modifiers = [ + ['Control', 'ctrlKey'], ['Shift', 'shiftKey'], ['Alt', 'altKey'], ['Meta', 'metaKey'], + ...['AltGraph', 'CapsLock', 'Fn', 'FnLock', 'Hyper', 'NumLock', 'ScrollLock', + 'Super', 'Symbol', 'SymbolLock'].map(name => [name, 'modifier' + name]) + ]; + for (const Ctor of [KeyboardEvent, MouseEvent, WheelEvent, PointerEvent, DragEvent]) { + const empty = new Ctor('event'); + for (const [key, member] of modifiers) { + if (empty.getModifierState(key)) return Ctor.name + ': default ' + key; + const event = new Ctor('event', {[member]: true}); + for (const [queried] of modifiers) { + if (event.getModifierState(queried) !== (queried === key)) { + return Ctor.name + ': ' + key + ' changed ' + queried; + } + } + for (const value of [false, 0, '', null, undefined]) { + if (new Ctor('event', {[member]: value}).getModifierState(key)) { + return Ctor.name + ': boolean conversion ' + key; + } + } + const truthy = {valueOf() { throw new Error('boolean conversion called valueOf'); }}; + if (!new Ctor('event', {[member]: truthy}).getModifierState(key)) return 'truthy ' + key; + if (member.startsWith('modifier') && member in event) return 'exposed init member ' + member; + } + for (const key of ['control', 'Unknown', '', 'Alt\0Graph']) { + if (empty.getModifierState(key)) return 'unknown modifier ' + key; + } + } + return 'ok'; +})() +"#, + ) + .expect("event modifier constructor probe should run"); + assert_eq!(result, "ok"); +} + +#[test] +fn event_modifier_state_ignores_shadow_properties_and_checks_receivers() { + let mut vm = new_storage_test_vm("https://event-modifiers.test/"); + let result = vm.eval(r#" +(() => { + for (const Ctor of [KeyboardEvent, MouseEvent, WheelEvent, PointerEvent, DragEvent]) { + const event = new Ctor('event', {ctrlKey: true, modifierAltGraph: true}); + const method = event.getModifierState; + Object.defineProperty(event, 'ctrlKey', {get() {throw Error('shadow property'); }}); + if (!method.call(event, 'Control') || !method.call(event, 'AltGraph')) return 'shadow ' + Ctor.name; + Object.setPrototypeOf(event, null); + if (!method.call(event, 'Control')) return 'changed prototype ' + Ctor.name; + for (const receiver of [{ctrlKey: true}, Object.create(Ctor.prototype), new Proxy(event, {}), new Event('event')]) { + let converted = false; + const key = {toString() {converted = true; return 'Control'; }}; + try {method.call(receiver, key); return 'accepted receiver ' + Ctor.name;} + catch (error) {if (!(error instanceof TypeError) || converted) return 'receiver order ' + Ctor.name;} + } + } + for (const [method, receiver] of [ + [KeyboardEvent.prototype.getModifierState, new MouseEvent('event')], + [MouseEvent.prototype.getModifierState, new KeyboardEvent('event')], + ]) { + try {method.call(receiver, 'Control'); return 'accepted unrelated interface';} + catch (error) {if (!(error instanceof TypeError)) return 'wrong receiver error';} + } + for (const Ctor of [MouseEvent, KeyboardEvent]) { + const descriptor = Object.getOwnPropertyDescriptor(Ctor.prototype, 'getModifierState'); + if (!descriptor.writable || !descriptor.enumerable || !descriptor.configurable || descriptor.value.length !== 1) { + return 'method descriptor ' + Ctor.name; + } + } + return 'ok'; +})() +"#).expect("event modifier receiver probe should run"); + assert_eq!(result, "ok"); +} + +#[test] +fn event_modifier_state_arguments_preserve_webidl_conversion_errors() { + let mut vm = new_storage_test_vm("https://event-modifiers.test/"); + let result = vm.eval(r#" +(() => { + for (const Ctor of [KeyboardEvent, MouseEvent, WheelEvent, PointerEvent, DragEvent]) { + const event = new Ctor('event', {ctrlKey: true}); + for (const args of [[], [Symbol('Control')]]) { + try {event.getModifierState(...args); return 'accepted argument ' + Ctor.name;} + catch (error) {if (!(error instanceof TypeError)) return 'wrong argument error';} + } + if (event.getModifierState(undefined) || event.getModifierState(null)) return 'nullable conversion'; + let reads = 0; + if (!event.getModifierState({toString() {reads++; return 'Control'; }}) || reads !== 1) return 'string conversion'; + const sentinel = new RangeError('key conversion'); + try {event.getModifierState({toString() {throw sentinel; }}); return 'swallowed argument exception';} + catch (error) {if (error !== sentinel) return 'replaced argument exception';} + } + return 'ok'; +})() +"#).expect("event modifier argument probe should run"); + assert_eq!(result, "ok"); +} + +#[test] +fn event_modifier_state_dictionary_reads_once_and_propagates_getter_exceptions() { + let mut vm = new_storage_test_vm("https://event-modifiers.test/"); + let result = vm.eval(r#" +(() => { + const members = ['ctrlKey', 'altKey', 'metaKey', 'shiftKey', + ...['AltGraph', 'CapsLock', 'Fn', 'FnLock', 'Hyper', 'NumLock', 'ScrollLock', 'Super', 'Symbol', 'SymbolLock'].map(name => 'modifier' + name) + ].sort(); + for (const Ctor of [KeyboardEvent, MouseEvent, WheelEvent, PointerEvent, DragEvent]) { + const reads = []; + const init = new Proxy({}, {get(target, key) { + if (members.includes(key)) {reads.push(key); return true;} + }}); + const event = new Ctor('event', init); + if (reads.join() !== members.join()) return Ctor.name + ': read order ' + reads; + if (!event.getModifierState('AltGraph') || reads.length !== members.length) return 'state rereads init'; + const sentinel = new RangeError('modifier getter'); + let laterRead = false; + try { + new Ctor('event', { + get modifierCapsLock() {throw sentinel;}, + get modifierFn() {laterRead = true; return true;}, + }); + return 'swallowed getter exception ' + Ctor.name; + } catch (error) { + if (error !== sentinel || laterRead) return 'getter exception order ' + Ctor.name; + } + } + return 'ok'; +})() +"#).expect("event modifier dictionary probe should run"); + assert_eq!(result, "ok"); +} + +#[test] +fn event_modifier_state_legacy_initializers_reset_only_when_allowed() { + let mut vm = new_storage_test_vm("https://event-modifiers.test/"); + let result = vm.eval(r#" +(() => { + const init = {ctrlKey: true, modifierAltGraph: true, modifierCapsLock: true}; + for (const Ctor of [MouseEvent, WheelEvent, PointerEvent, DragEvent]) { + const event = new Ctor('event', init); + event.initEvent('again'); + if (!event.getModifierState('Control') || !event.getModifierState('AltGraph')) return 'initEvent reset ' + Ctor.name; + const target = document.createElement('div'); + target.addEventListener('again', () => event.initMouseEvent('ignored')); + target.dispatchEvent(event); + if (event.type !== 'again' || !event.getModifierState('Control')) return 'reset during dispatch'; + event.initMouseEvent('changed', false, false, null, 0, 0, 0, 0, 0, false, true, true, false); + if (event.getModifierState('Control') || event.getModifierState('AltGraph') || event.getModifierState('CapsLock')) return 'retained old modifier'; + if (!event.getModifierState('Alt') || !event.getModifierState('Shift')) return 'legacy modifier arguments'; + event.initMouseEvent('empty'); + if (event.getModifierState('Alt') || event.getModifierState('Shift')) return 'legacy default modifiers'; + } + const keyboard = new KeyboardEvent('event', init); + keyboard.initKeyboardEvent('empty'); + if (keyboard.getModifierState('Control') || keyboard.getModifierState('AltGraph') || keyboard.getModifierState('CapsLock')) return 'legacy keyboard reset'; + const created = document.createEvent('MouseEvents'); + created.initMouseEvent('click', false, false, null, 0, 0, 0, 0, 0, true); + if (!created.getModifierState('Control')) return 'createEvent modifiers'; + return 'ok'; +})() +"#).expect("event modifier legacy initializer probe should run"); + assert_eq!(result, "ok"); +} + +#[test] +fn event_modifier_state_accepts_cross_realm_native_receivers() { + let mut vm = new_parsed_test_vm( + "https://event-modifiers.test/", + "", + ); + let result = vm.eval(r#" +(() => { + const frame = document.createElement('iframe'); + document.body.appendChild(frame); + const child = frame.contentWindow; + for (const name of ['MouseEvent', 'KeyboardEvent']) { + const local = new window[name]('event', {shiftKey: true}); + const foreign = new child[name]('event', {ctrlKey: true}); + const localMethod = window[name].prototype.getModifierState; + const foreignMethod = child[name].prototype.getModifierState; + if (!localMethod.call(foreign, 'Control') || !foreignMethod.call(local, 'Shift')) return 'cross-realm ' + name; + try {foreignMethod.call({}, 'Control'); return 'accepted forged receiver';} + catch (error) {if (!(error instanceof child.TypeError)) return 'wrong exception realm';} + } + frame.remove(); + return 'ok'; +})() +"#).expect("cross-realm event modifier probe should run"); + assert_eq!(result, "ok"); +} diff --git a/moli-renderer-v8/src/script_vm/tests/dom_xhr/mod.rs b/moli-renderer-v8/src/script_vm/tests/dom_xhr/mod.rs index c461aad805..ee2b3ab141 100644 --- a/moli-renderer-v8/src/script_vm/tests/dom_xhr/mod.rs +++ b/moli-renderer-v8/src/script_vm/tests/dom_xhr/mod.rs @@ -7,6 +7,7 @@ mod cors_response; mod cssom; mod data_response; mod dom; +mod event_modifiers; mod file_input; mod forms; mod misc;