mirror of
https://github.com/lexmount/moli.git
synced 2026-11-06 00:07:32 +00:00
fix(events): preserve KeyboardEvent initialization semantics
This commit is contained in:
@@ -124,33 +124,6 @@ struct InitMouseEventArgs<'s> {
|
||||
related_target: Option<v8::Local<'s, v8::Object>>,
|
||||
}
|
||||
|
||||
#[derive(webidl::WebIdlArgs)]
|
||||
#[webidl(prefix = "KeyboardEvent.initKeyboardEvent")]
|
||||
struct InitKeyboardEventArgs<'s> {
|
||||
#[webidl(
|
||||
required,
|
||||
converter = "raw",
|
||||
missing_message = "Failed to execute 'initKeyboardEvent': 1 argument required."
|
||||
)]
|
||||
event_type: webidl::DomString16,
|
||||
#[webidl(default = false)]
|
||||
bubbles: bool,
|
||||
#[webidl(default = false)]
|
||||
cancelable: bool,
|
||||
#[webidl(index = 3, converter = "raw")]
|
||||
view: Option<v8::Local<'s, v8::Value>>,
|
||||
#[webidl(default = "", index = 4)]
|
||||
key: String,
|
||||
#[webidl(default = 0, index = 5)]
|
||||
location: i32,
|
||||
#[webidl(default = "", index = 6)]
|
||||
_modifiers_list: String,
|
||||
#[webidl(default = false, index = 7)]
|
||||
repeat: bool,
|
||||
#[webidl(default = "", index = 8)]
|
||||
_locale: String,
|
||||
}
|
||||
|
||||
#[derive(webidl::WebIdlArgs)]
|
||||
#[webidl(prefix = "TextEvent.initTextEvent")]
|
||||
struct InitTextEventArgs<'s> {
|
||||
@@ -189,13 +162,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> {
|
||||
@@ -243,28 +209,6 @@ struct LegacyMouseEventTailInitDeclaration<'scope> {
|
||||
related_target: v8::Local<'scope, v8::Value>,
|
||||
}
|
||||
|
||||
#[derive(WebApiObject)]
|
||||
#[webapi(plain, data_properties, enumerable)]
|
||||
struct LegacyKeyboardEventInitDeclaration<'scope> {
|
||||
view: v8::Local<'scope, v8::Value>,
|
||||
#[webapi(constructor_default = 0)]
|
||||
detail: i32,
|
||||
key: v8::Local<'scope, v8::String>,
|
||||
code: v8::Local<'scope, v8::String>,
|
||||
location: i32,
|
||||
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)]
|
||||
#[webapi(plain, data_properties, enumerable)]
|
||||
struct LegacyCustomEventInitDeclaration<'scope> {
|
||||
@@ -420,32 +364,6 @@ pub(super) fn mouse_event_init_callback<'s>(
|
||||
.expect("legacy MouseEvent tail init declaration should initialize");
|
||||
}
|
||||
|
||||
pub(super) fn keyboard_event_init_callback<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
_rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
let event = super::events::event_backing(scope, args.this());
|
||||
let Some(parsed) = webidl::parse_args::<InitKeyboardEventArgs>(scope, &args) else {
|
||||
return;
|
||||
};
|
||||
let view = legacy_event_view_or_global(scope, parsed.view);
|
||||
if !reinitialize_event_object(
|
||||
scope,
|
||||
event,
|
||||
&parsed.event_type.0,
|
||||
parsed.bubbles,
|
||||
parsed.cancelable,
|
||||
) {
|
||||
return;
|
||||
}
|
||||
let key = v8_string(scope, &parsed.key).unwrap_or_else(|| v8str(scope, ""));
|
||||
let code = v8str(scope, "");
|
||||
LegacyKeyboardEventInitDeclaration::new(view, key, code, parsed.location, parsed.repeat)
|
||||
.initialize(scope, event)
|
||||
.expect("legacy KeyboardEvent init declaration should initialize");
|
||||
}
|
||||
|
||||
pub(super) fn composition_event_init_callback<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
@@ -523,38 +441,3 @@ pub(super) fn storage_event_init_callback<'s>(
|
||||
parsed.storage_area.map(|value| value.0.into()),
|
||||
);
|
||||
}
|
||||
|
||||
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::<KeyboardEventGetModifierStateArgs>(scope, &args) else {
|
||||
return;
|
||||
};
|
||||
let event = super::events::event_backing(scope, 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());
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ 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,
|
||||
},
|
||||
@@ -13,16 +12,18 @@ use super::{
|
||||
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_get_modifier_state_callback, event_platform_attribute_getter,
|
||||
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_value_attribute_getter, focus_event_related_target_getter_function,
|
||||
form_data_event_form_data_getter_function, message_event_init_callback,
|
||||
mouse_event_related_target_getter_function, pointer_event_get_predicted_events_callback,
|
||||
submit_event_agent_invoked_getter_function, submit_event_respond_with_callback,
|
||||
submit_event_submitter_getter_function, track_event_track_getter_function,
|
||||
ui_event_pseudo_target_getter_function, ui_event_which_getter_function,
|
||||
form_data_event_form_data_getter_function, keyboard_event_init_callback,
|
||||
message_event_init_callback, mouse_event_related_target_getter_function,
|
||||
pointer_event_get_predicted_events_callback, submit_event_agent_invoked_getter_function,
|
||||
submit_event_respond_with_callback, submit_event_submitter_getter_function,
|
||||
track_event_track_getter_function, ui_event_pseudo_target_getter_function,
|
||||
ui_event_which_getter_function,
|
||||
},
|
||||
selection_surface::document_get_selection_callback,
|
||||
specs::ConstructorSpec,
|
||||
@@ -166,7 +167,7 @@ struct FormDataEventTemplateAccessorsDeclaration {
|
||||
#[derive(WebApiFunctionTemplate)]
|
||||
#[webapi(interface = web_api_interfaces::UIEvent, enumerable, receiver)]
|
||||
struct UiEventTemplateMethodsDeclaration {
|
||||
#[webapi(accessor_property = "view", getter = event_value_attribute_getter, data = crate::util::v8str(scope, "view"))]
|
||||
#[webapi(accessor_property = "view", getter = event_platform_attribute_getter, data = crate::util::v8str(scope, "view"))]
|
||||
view: (),
|
||||
#[webapi(accessor_property = "detail", getter = event_value_attribute_getter, data = crate::util::v8str(scope, "detail"))]
|
||||
detail: (),
|
||||
@@ -301,8 +302,8 @@ struct KeyboardEventTemplateMethodsDeclaration {
|
||||
|
||||
#[webapi(
|
||||
method = "getModifierState",
|
||||
length = 0,
|
||||
callback = keyboard_event_get_modifier_state_callback
|
||||
length = 1,
|
||||
callback = event_get_modifier_state_callback
|
||||
)]
|
||||
get_modifier_state: (),
|
||||
}
|
||||
|
||||
@@ -9,8 +9,10 @@ mod kind;
|
||||
mod message;
|
||||
pub(crate) use message::construct_original_message_event;
|
||||
mod methods;
|
||||
mod modifiers;
|
||||
mod subclasses;
|
||||
mod submit;
|
||||
mod ui;
|
||||
mod value;
|
||||
mod wrappers;
|
||||
|
||||
@@ -499,7 +501,12 @@ pub(crate) use wrappers::{
|
||||
};
|
||||
|
||||
pub(in crate::context_bootstrap) use message::message_event_init_callback;
|
||||
pub(in crate::context_bootstrap) use wrappers::event_value_attribute_getter;
|
||||
pub(in crate::context_bootstrap) use wrappers::{
|
||||
event_platform_attribute_getter, event_value_attribute_getter,
|
||||
};
|
||||
|
||||
pub(in crate::context_bootstrap) use modifiers::event_get_modifier_state_callback;
|
||||
pub(in crate::context_bootstrap) use subclasses::keyboard_event_init_callback;
|
||||
|
||||
pub(crate) use subclasses::construct_original_error_event;
|
||||
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
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")]
|
||||
pub(super) 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 {
|
||||
pub(super) fn initialize<'s>(
|
||||
&self,
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
event: v8::Local<'s, v8::Object>,
|
||||
) {
|
||||
let event = event_backing(scope, event);
|
||||
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(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::<GetModifierStateArgs>(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 = event_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());
|
||||
}
|
||||
@@ -26,3 +26,4 @@ pub(in crate::context_bootstrap) use pointer::{
|
||||
pub(in crate::context_bootstrap::events) use data::initialize_navigate_event_methods;
|
||||
|
||||
pub(crate) use error::construct_original_error_event;
|
||||
pub(in crate::context_bootstrap) use keyboard::keyboard_event_init_callback;
|
||||
|
||||
@@ -51,6 +51,14 @@ fn event_subclass_constructor_callback<'s>(
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let keyboard_event_init = if kind == EventSubclassKind::KeyboardEvent {
|
||||
let Some(init) = keyboard::parse_keyboard_event_init(scope, &args) else {
|
||||
return;
|
||||
};
|
||||
Some(init)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let security_policy_init = if kind == EventSubclassKind::SecurityPolicyViolationEvent {
|
||||
let Some(init) = security_policy::parse_security_policy_violation_event_init(scope, &args)
|
||||
else {
|
||||
@@ -80,6 +88,11 @@ fn event_subclass_constructor_callback<'s>(
|
||||
.as_ref()
|
||||
.map(security_policy::SecurityPolicyViolationEventInit::event_flags)
|
||||
.or_else(|| error_init.as_ref().map(error::ErrorEventInit::event_flags))
|
||||
.or_else(|| {
|
||||
keyboard_event_init
|
||||
.as_ref()
|
||||
.map(keyboard::KeyboardEventInit::event_flags)
|
||||
})
|
||||
.or_else(|| {
|
||||
storage_event_init
|
||||
.as_ref()
|
||||
@@ -141,9 +154,11 @@ fn event_subclass_constructor_callback<'s>(
|
||||
}
|
||||
}
|
||||
EventSubclassKind::KeyboardEvent => {
|
||||
if !keyboard::initialize_keyboard_event(scope, event, init) {
|
||||
return;
|
||||
}
|
||||
keyboard::initialize_keyboard_event(
|
||||
scope,
|
||||
event,
|
||||
keyboard_event_init.expect("KeyboardEvent init should be parsed"),
|
||||
);
|
||||
}
|
||||
EventSubclassKind::InputEvent => data::initialize_input_event(scope, event, init),
|
||||
EventSubclassKind::WheelEvent => {
|
||||
|
||||
@@ -1,56 +1,179 @@
|
||||
use super::super::ui::{UiEventInit, initialize_legacy_ui_event};
|
||||
use super::*;
|
||||
use crate::webidl;
|
||||
use moli_webapi_declare::WebApiObject;
|
||||
|
||||
/// Own KeyboardEventInit members, including the legacy code members.
|
||||
#[derive(webidl::WebIdlDictionary)]
|
||||
#[webidl(prefix = "KeyboardEventInit")]
|
||||
struct KeyboardEventInitMembers {
|
||||
#[webidl(default = 0)]
|
||||
char_code: u32,
|
||||
#[webidl(default = webidl::DomString16(Vec::new()), converter = "raw")]
|
||||
code: webidl::DomString16,
|
||||
#[webidl(default = false)]
|
||||
is_composing: bool,
|
||||
#[webidl(default = webidl::DomString16(Vec::new()), converter = "raw")]
|
||||
key: webidl::DomString16,
|
||||
#[webidl(default = 0)]
|
||||
key_code: u32,
|
||||
#[webidl(default = 0)]
|
||||
location: u32,
|
||||
#[webidl(default = false)]
|
||||
repeat: bool,
|
||||
}
|
||||
|
||||
impl Default for KeyboardEventInitMembers {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
char_code: 0,
|
||||
code: webidl::DomString16(Vec::new()),
|
||||
is_composing: false,
|
||||
key: webidl::DomString16(Vec::new()),
|
||||
key_code: 0,
|
||||
location: 0,
|
||||
repeat: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(super) struct KeyboardEventInit<'s> {
|
||||
ui: UiEventInit<'s>,
|
||||
modifiers: super::super::modifiers::EventModifierInitMembers,
|
||||
keyboard: KeyboardEventInitMembers,
|
||||
}
|
||||
|
||||
impl KeyboardEventInit<'_> {
|
||||
pub(super) fn event_flags(&self) -> (bool, bool, bool) {
|
||||
self.ui.event_flags()
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn parse_keyboard_event_init<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: &v8::FunctionCallbackArguments<'s>,
|
||||
) -> Option<KeyboardEventInit<'s>> {
|
||||
let parsed = webidl::dictionary_arg(args, 1, webidl::Context::argument("KeyboardEvent", 2))
|
||||
.and_then(|object| {
|
||||
let Some(object) = object else {
|
||||
return Ok(KeyboardEventInit::default());
|
||||
};
|
||||
// Parse the inheritance chain base-first. Each group's members are
|
||||
// read once in lexical order, before initializing any event state.
|
||||
Ok(KeyboardEventInit {
|
||||
ui: webidl::parse_dictionary_object(scope, object)?,
|
||||
modifiers: webidl::parse_dictionary_object(scope, object)?,
|
||||
keyboard: webidl::parse_dictionary_object(scope, object)?,
|
||||
})
|
||||
});
|
||||
match parsed {
|
||||
Ok(init) => Some(init),
|
||||
Err(error) => {
|
||||
webidl::throw_error(scope, &error);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(WebApiObject)]
|
||||
#[webapi(plain, data_properties, enumerable)]
|
||||
struct KeyboardEventInitDeclaration<'scope> {
|
||||
view: v8::Local<'scope, v8::Value>,
|
||||
detail: f64,
|
||||
key: v8::Local<'scope, v8::String>,
|
||||
code: v8::Local<'scope, v8::String>,
|
||||
location: f64,
|
||||
char_code: f64,
|
||||
key_code: f64,
|
||||
which: f64,
|
||||
location: u32,
|
||||
char_code: u32,
|
||||
key_code: u32,
|
||||
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>(
|
||||
pub(super) fn initialize_keyboard_event<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
event: v8::Local<'s, v8::Object>,
|
||||
init: Option<v8::Local<'s, v8::Object>>,
|
||||
) -> bool {
|
||||
let Ok(view) = init_window_view_property(scope, init, "KeyboardEvent") else {
|
||||
return false;
|
||||
};
|
||||
let detail = init_number_property(scope, init, "detail", 0.0);
|
||||
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");
|
||||
let code = v8_string(scope, &code).expect("KeyboardEvent code");
|
||||
let location = init_number_property(scope, init, "location", 0.0);
|
||||
parsed: KeyboardEventInit<'s>,
|
||||
) {
|
||||
parsed.ui.initialize(scope, event);
|
||||
let key = v8_string_from_utf16_units(scope, &parsed.keyboard.key.0).expect("KeyboardEvent key");
|
||||
let code =
|
||||
v8_string_from_utf16_units(scope, &parsed.keyboard.code.0).expect("KeyboardEvent code");
|
||||
parsed.modifiers.initialize(scope, event);
|
||||
KeyboardEventInitDeclaration::new(
|
||||
view,
|
||||
detail,
|
||||
key,
|
||||
code,
|
||||
location,
|
||||
init_number_property(scope, init, "charCode", 0.0),
|
||||
init_number_property(scope, init, "keyCode", 0.0),
|
||||
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),
|
||||
parsed.keyboard.location,
|
||||
parsed.keyboard.char_code,
|
||||
parsed.keyboard.key_code,
|
||||
parsed.keyboard.repeat,
|
||||
parsed.keyboard.is_composing,
|
||||
)
|
||||
.initialize(scope, event)
|
||||
.expect("KeyboardEvent init declaration should initialize");
|
||||
true
|
||||
}
|
||||
|
||||
#[derive(webidl::WebIdlArgs)]
|
||||
#[webidl(prefix = "KeyboardEvent.initKeyboardEvent")]
|
||||
struct InitKeyboardEventArgs<'s> {
|
||||
#[webidl(required, converter = "raw")]
|
||||
event_type: webidl::DomString16,
|
||||
#[webidl(default = false)]
|
||||
bubbles: bool,
|
||||
#[webidl(default = false)]
|
||||
cancelable: bool,
|
||||
#[webidl(nullable, interface = crate::web_api_interfaces::Window, brand_check = crate::context_bootstrap::is_window_receiver)]
|
||||
view: Option<v8::Local<'s, v8::Object>>,
|
||||
#[webidl(default = webidl::DomString16(Vec::new()), converter = "raw")]
|
||||
key: webidl::DomString16,
|
||||
#[webidl(default = 0)]
|
||||
location: u32,
|
||||
#[webidl(default = false)]
|
||||
ctrl_key: bool,
|
||||
#[webidl(default = false)]
|
||||
alt_key: bool,
|
||||
#[webidl(default = false)]
|
||||
shift_key: bool,
|
||||
#[webidl(default = false)]
|
||||
meta_key: bool,
|
||||
}
|
||||
|
||||
#[derive(WebApiObject)]
|
||||
#[webapi(plain, data_properties, enumerable)]
|
||||
struct LegacyKeyboardEventInitDeclaration<'scope> {
|
||||
key: v8::Local<'scope, v8::String>,
|
||||
location: u32,
|
||||
#[webapi(constructor_default = false)]
|
||||
repeat: bool,
|
||||
}
|
||||
|
||||
pub(in crate::context_bootstrap) fn keyboard_event_init_callback<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
_rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
let Some(parsed) = webidl::parse_args::<InitKeyboardEventArgs>(scope, &args) else {
|
||||
return;
|
||||
};
|
||||
let event = event_backing(scope, args.this());
|
||||
if !reinitialize_event_object(
|
||||
scope,
|
||||
event,
|
||||
&parsed.event_type.0,
|
||||
parsed.bubbles,
|
||||
parsed.cancelable,
|
||||
) {
|
||||
return;
|
||||
}
|
||||
initialize_legacy_ui_event(scope, event, parsed.view, 0);
|
||||
let key = v8_string_from_utf16_units(scope, &parsed.key.0).expect("KeyboardEvent key");
|
||||
super::super::modifiers::initialize_legacy_event_modifiers(
|
||||
scope,
|
||||
event,
|
||||
parsed.ctrl_key,
|
||||
parsed.alt_key,
|
||||
parsed.shift_key,
|
||||
parsed.meta_key,
|
||||
);
|
||||
LegacyKeyboardEventInitDeclaration::new(key, parsed.location)
|
||||
.initialize(scope, event)
|
||||
.expect("legacy KeyboardEvent init declaration should initialize");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
use crate::webidl;
|
||||
use moli_webapi_declare::WebApiObject;
|
||||
|
||||
fn nullable_window_value<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
window: Option<v8::Local<'s, v8::Object>>,
|
||||
) -> v8::Local<'s, v8::Value> {
|
||||
window
|
||||
.map(Into::into)
|
||||
.unwrap_or_else(|| v8::null(scope).into())
|
||||
}
|
||||
|
||||
/// Own UIEventInit members, including legacy which, in dictionary order.
|
||||
#[derive(Default, webidl::WebIdlDictionary)]
|
||||
#[webidl(prefix = "UIEventInit")]
|
||||
struct UiEventInitMembers<'s> {
|
||||
#[webidl(default = 0)]
|
||||
detail: i32,
|
||||
#[webidl(nullable, interface = crate::web_api_interfaces::Window, brand_check = crate::context_bootstrap::is_window_receiver)]
|
||||
view: Option<v8::Local<'s, v8::Object>>,
|
||||
#[webidl(default = 0)]
|
||||
which: u32,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(super) struct UiEventInit<'s> {
|
||||
flags: (bool, bool, bool),
|
||||
members: UiEventInitMembers<'s>,
|
||||
}
|
||||
|
||||
impl<'s> webidl::WebIdlDictionary<'s> for UiEventInit<'s> {
|
||||
fn parse_dictionary(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
object: v8::Local<'s, v8::Object>,
|
||||
) -> Result<Self, webidl::WebIdlError> {
|
||||
// Read inherited members before own members. Derived dictionaries
|
||||
// delegate here so each observable getter runs once, base-first.
|
||||
Ok(Self {
|
||||
flags: super::init::parse_event_init(scope, Some(object))?,
|
||||
members: webidl::parse_dictionary_object(scope, object)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> UiEventInit<'s> {
|
||||
pub(super) fn event_flags(&self) -> (bool, bool, bool) {
|
||||
self.flags
|
||||
}
|
||||
|
||||
pub(super) fn initialize(
|
||||
self,
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
event: v8::Local<'s, v8::Object>,
|
||||
) {
|
||||
let view = nullable_window_value(scope, self.members.view);
|
||||
UiEventState::new(view, self.members.detail, self.members.which)
|
||||
.initialize(scope, event)
|
||||
.expect("UIEvent state should initialize");
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(WebApiObject)]
|
||||
#[webapi(plain, data_properties, enumerable)]
|
||||
struct UiEventState<'s> {
|
||||
view: v8::Local<'s, v8::Value>,
|
||||
detail: i32,
|
||||
which: u32,
|
||||
}
|
||||
|
||||
#[derive(WebApiObject)]
|
||||
#[webapi(plain, data_properties, enumerable)]
|
||||
struct LegacyUiEventState<'s> {
|
||||
view: v8::Local<'s, v8::Value>,
|
||||
detail: i32,
|
||||
}
|
||||
|
||||
pub(in crate::context_bootstrap) fn initialize_legacy_ui_event<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
event: v8::Local<'s, v8::Object>,
|
||||
view: Option<v8::Local<'s, v8::Object>>,
|
||||
detail: i32,
|
||||
) {
|
||||
let view = nullable_window_value(scope, view);
|
||||
LegacyUiEventState::new(view, detail)
|
||||
.initialize(scope, event)
|
||||
.expect("legacy UIEvent state should initialize");
|
||||
}
|
||||
@@ -248,6 +248,14 @@ pub(in crate::context_bootstrap) fn event_value_attribute_getter<'s>(
|
||||
event_attribute_getter::<0>(scope, args, rv);
|
||||
}
|
||||
|
||||
pub(in crate::context_bootstrap) fn event_platform_attribute_getter<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
event_attribute_getter::<2>(scope, args, rv);
|
||||
}
|
||||
|
||||
pub(super) fn event_attribute_in_wrapper<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
wrapper: v8::Local<'s, v8::Object>,
|
||||
|
||||
@@ -0,0 +1,216 @@
|
||||
(() => {
|
||||
const rows = [], observed = {};
|
||||
const check = (name, run) => {
|
||||
try {
|
||||
if (run() !== true) throw new Error('assertion failed');
|
||||
rows.push({name, passed: true});
|
||||
} catch (error) {
|
||||
rows.push({name, passed: false, error: String(error), stack: error.stack});
|
||||
}
|
||||
};
|
||||
const typeError = (run, realm = globalThis) => {
|
||||
try { run(); } catch (error) { return error instanceof realm.TypeError; }
|
||||
return false;
|
||||
};
|
||||
const throws = (run, sentinel) => {
|
||||
try { run(); } catch (error) { return error === sentinel; }
|
||||
return false;
|
||||
};
|
||||
const other = document.getElementById('child').contentWindow;
|
||||
const legacy = (key, location = 0, view = null) => {
|
||||
const event = new KeyboardEvent('before');
|
||||
event.initKeyboardEvent('after', false, false, view, key, location);
|
||||
return event;
|
||||
};
|
||||
const snapshot = event => JSON.stringify([
|
||||
event.type, event.bubbles, event.cancelable, event.composed, event.defaultPrevented,
|
||||
event.detail, event.key, event.code, event.location, event.which, event.charCode,
|
||||
event.keyCode, event.repeat, event.isComposing, event.ctrlKey, event.altKey,
|
||||
event.shiftKey, event.metaKey, event.getModifierState('AltGraph')
|
||||
]);
|
||||
const texts = ['', '\ud800', '\udc00', '\ud83d\ude00', 'a\ud800b\udc00\u0000\ud83d\ude00'];
|
||||
for (let i = 0; i < texts.length; i++) {
|
||||
const text = texts[i];
|
||||
for (const field of ['key', 'code']) {
|
||||
check(`constructor ${field} UTF-16 ${i}`, () => new KeyboardEvent('x', {[field]: text})[field] === text);
|
||||
}
|
||||
check(`legacy key UTF-16 ${i}`, () => legacy(text).key === text);
|
||||
}
|
||||
for (const [value, expected] of [[undefined, ''], [null, 'null'], [23, '23'], [true, 'true']]) {
|
||||
for (const field of ['key', 'code']) {
|
||||
check(`constructor ${field} ${String(value)}`, () => new KeyboardEvent('x', {[field]: value})[field] === expected);
|
||||
}
|
||||
check(`legacy key ${String(value)}`, () => legacy(value).key === expected);
|
||||
}
|
||||
for (const field of ['key', 'code']) {
|
||||
check(`constructor ${field} string hint once`, () => {
|
||||
let count = 0;
|
||||
const value = {[Symbol.toPrimitive](hint) { if (hint !== 'string') throw new Error(hint); count++; return texts[4]; }};
|
||||
return new KeyboardEvent('x', {[field]: value})[field] === texts[4] && count === 1;
|
||||
});
|
||||
check(`constructor ${field} Symbol rejects`, () => typeError(() => new KeyboardEvent('x', {[field]: Symbol()})));
|
||||
}
|
||||
check('legacy key string hint once', () => {
|
||||
let count = 0;
|
||||
const value = {[Symbol.toPrimitive](hint) { if (hint !== 'string') throw new Error(hint); count++; return texts[4]; }};
|
||||
return legacy(value).key === texts[4] && count === 1;
|
||||
});
|
||||
check('legacy Symbol key rejects', () => typeError(() => legacy(Symbol())));
|
||||
check('constructor default payload', () => {
|
||||
const e = new KeyboardEvent('x');
|
||||
return e.view === null && e.detail === 0 && e.key === '' && e.code === '' && e.location === 0 &&
|
||||
e.charCode === 0 && e.keyCode === 0 && e.which === 0 && !e.repeat && !e.isComposing && !e.isTrusted;
|
||||
});
|
||||
for (const value of [undefined, null]) {
|
||||
check(`nullish dictionary ${String(value)}`, () => new KeyboardEvent('x', value).view === null);
|
||||
}
|
||||
for (const value of [false, 1, '', Symbol(), 1n]) {
|
||||
check(`invalid dictionary ${typeof value}`, () => typeError(() => new KeyboardEvent('x', value)));
|
||||
}
|
||||
for (const [field, value, expected] of [
|
||||
['detail', 4.9, 4], ['detail', -4.9, -4], ['detail', 2147483648, -2147483648],
|
||||
['location', -1, 4294967295], ['location', 4294967297, 1], ['location', 1.9, 1],
|
||||
['charCode', -1, 4294967295], ['keyCode', -1, 4294967295], ['which', -1, 4294967295],
|
||||
]) {
|
||||
check(`constructor ${field} integer ${value}`, () => new KeyboardEvent('x', {[field]: value})[field] === expected);
|
||||
}
|
||||
for (const field of ['detail', 'location', 'charCode', 'keyCode', 'which']) {
|
||||
for (const value of [NaN, Infinity, -Infinity]) {
|
||||
check(`constructor ${field} nonfinite ${value}`, () => new KeyboardEvent('x', {[field]: value})[field] === 0);
|
||||
}
|
||||
for (const value of [1n, Symbol()]) {
|
||||
check(`constructor ${field} rejects ${typeof value}`, () => typeError(() => new KeyboardEvent('x', {[field]: value})));
|
||||
}
|
||||
}
|
||||
for (const [value, expected] of [[-1, 4294967295], [4294967297, 1], [1.9, 1], [NaN, 0], [Infinity, 0]]) {
|
||||
check(`legacy location integer ${value}`, () => legacy('', value).location === expected);
|
||||
}
|
||||
check('legacy BigInt location rejects', () => typeError(() => legacy('', 1n)));
|
||||
const modifierMembers = ['altKey', 'ctrlKey', 'metaKey', 'modifierAltGraph', 'modifierCapsLock',
|
||||
'modifierFn', 'modifierFnLock', 'modifierHyper', 'modifierNumLock', 'modifierScrollLock',
|
||||
'modifierSuper', 'modifierSymbol', 'modifierSymbolLock', 'shiftKey'];
|
||||
const order = ['bubbles', 'cancelable', 'composed', 'detail', 'view', 'which', ...modifierMembers,
|
||||
'charCode', 'code', 'isComposing', 'key', 'keyCode', 'location', 'repeat'];
|
||||
check('complete inherited dictionary order', () => {
|
||||
const seen = [];
|
||||
new KeyboardEvent('x', new Proxy({}, {get(_target, name) { seen.push(name); }}));
|
||||
observed.dictionaryReads = seen;
|
||||
return seen.join() === order.join();
|
||||
});
|
||||
for (const stop of order) {
|
||||
check(`dictionary getter exception stops at ${stop}`, () => {
|
||||
const sentinel = {};
|
||||
const seen = [];
|
||||
const init = new Proxy({}, {get(_target, name) { seen.push(name); if (name === stop) throw sentinel; }});
|
||||
return throws(() => new KeyboardEvent('x', init), sentinel) && seen.join() === order.slice(0, order.indexOf(stop) + 1).join();
|
||||
});
|
||||
}
|
||||
for (const field of ['detail', 'which', 'code', 'key', 'charCode', 'keyCode', 'location']) {
|
||||
check(`constructor ${field} conversion exception stops`, () => {
|
||||
const sentinel = {}, seen = [];
|
||||
const init = new Proxy({}, {get(_target, name) {
|
||||
seen.push(name);
|
||||
if (name === field) return {[Symbol.toPrimitive]() { throw sentinel; }};
|
||||
}});
|
||||
return throws(() => new KeyboardEvent('x', init), sentinel) && seen.join() === order.slice(0, order.indexOf(field) + 1).join();
|
||||
});
|
||||
}
|
||||
check('constructor type converts before dictionary', () => {
|
||||
const seen = [];
|
||||
const type = {toString() {seen.push('type'); return texts[4];}};
|
||||
const e = new KeyboardEvent(type, new Proxy({}, {get(_target, name) {seen.push(name);}}));
|
||||
return e.type === texts[4] && seen.join() === ['type', ...order].join();
|
||||
});
|
||||
check('constructor inherits dictionary members', () => {
|
||||
const e = new KeyboardEvent('x', Object.create({key: texts[4], code: texts[3], location: -1, view: other, ctrlKey: true}));
|
||||
return e.key === texts[4] && e.code === texts[3] && e.location === 4294967295 && e.view === other && e.getModifierState('Control');
|
||||
});
|
||||
const views = [['window', window], ['child', other], ['null', null], ['undefined', undefined]];
|
||||
for (const [name, view] of views) {
|
||||
const expected = view === undefined ? null : view;
|
||||
check(`constructor accepts ${name} view`, () => new KeyboardEvent('x', {view}).view === expected);
|
||||
check(`legacy accepts ${name} view`, () => legacy('', 0, view).view === expected);
|
||||
}
|
||||
let traps = 0;
|
||||
const handler = {get() {traps++; throw new Error('get trap');}, getPrototypeOf() {traps++; throw new Error('prototype trap');}};
|
||||
const revoked = Proxy.revocable(window, handler); revoked.revoke();
|
||||
for (const [name, view] of [['ordinary', {}], ['prototype', Object.create(window)], ['proxy', new Proxy(window, handler)], ['revoked', revoked.proxy], ['number', 1]]) {
|
||||
check(`constructor rejects ${name} view before later conversion`, () => {
|
||||
let converted = false;
|
||||
const result = typeError(() => new KeyboardEvent('x', {view, code: {toString() {converted = true; return '';}}}));
|
||||
return result && !converted && traps === 0;
|
||||
});
|
||||
check(`legacy rejects ${name} view before key conversion`, () => {
|
||||
let converted = false;
|
||||
const result = typeError(() => legacy({toString() {converted = true; return '';}}, 0, view));
|
||||
return result && !converted && traps === 0;
|
||||
});
|
||||
}
|
||||
check('legacy method descriptor', () => {
|
||||
const d = Object.getOwnPropertyDescriptor(KeyboardEvent.prototype, 'initKeyboardEvent');
|
||||
return d.value.name === 'initKeyboardEvent' && d.value.length === 1 && d.enumerable && d.configurable && d.writable;
|
||||
});
|
||||
check('legacy missing required type', () => typeError(() => new KeyboardEvent('x').initKeyboardEvent()));
|
||||
check('legacy modifier booleans do not invoke author conversion', () => {
|
||||
const e = new KeyboardEvent('x', {modifierAltGraph: true, modifierCapsLock: true});
|
||||
const truthy = {[Symbol.toPrimitive]() {throw new Error('boolean conversion');}};
|
||||
e.initKeyboardEvent('y', false, false, null, '', 0, truthy, false, truthy, false);
|
||||
return e.ctrlKey && !e.altKey && e.shiftKey && !e.metaKey && e.getModifierState('Control') &&
|
||||
e.getModifierState('Shift') && !e.getModifierState('AltGraph') && !e.getModifierState('CapsLock');
|
||||
});
|
||||
check('legacy payload defaults preserve unrelated code and composing state', () => {
|
||||
const e = new KeyboardEvent('before', {code:'KeyB', isComposing:true, repeat:true, composed:true, key:'old', view:window, detail:7, keyCode:8, charCode:9, which:10});
|
||||
const result = e.initKeyboardEvent('after');
|
||||
return result === undefined && e.type === 'after' && e.view === null && e.detail === 0 && e.key === '' &&
|
||||
e.code === 'KeyB' && e.isComposing && !e.repeat && e.location === 0 && e.keyCode === 8 && e.charCode === 9 && e.which === 10 && e.composed;
|
||||
});
|
||||
check('legacy arguments convert left to right before mutation', () => {
|
||||
const e = new KeyboardEvent('before', {key:'old', location:3});
|
||||
const old = snapshot(e), seen = [], sentinel = {};
|
||||
const key = {toString() {seen.push('key'); return 'new';}};
|
||||
const location = {valueOf() {seen.push('location'); throw sentinel;}};
|
||||
return throws(() => e.initKeyboardEvent({toString() {seen.push('type'); return 'new';}}, true, true, null, key, location), sentinel) &&
|
||||
seen.join() === 'type,key,location' && snapshot(e) === old;
|
||||
});
|
||||
check('legacy ignored extra arguments are not converted', () => {
|
||||
const e = new KeyboardEvent('before');
|
||||
e.initKeyboardEvent('after', false, false, null, '', 0, false, false, false, false, {[Symbol.toPrimitive]() {throw new Error('extra conversion');}});
|
||||
return e.type === 'after';
|
||||
});
|
||||
check('legacy dispatch guard follows conversions and preserves state', () => {
|
||||
const e = new KeyboardEvent('before', {key:'old', ctrlKey:true, code:'KeyB', composed:true});
|
||||
const before = snapshot(e), target = new EventTarget();
|
||||
let converted = false, ignored = false, propagated = false;
|
||||
target.addEventListener('before', () => {
|
||||
e.initKeyboardEvent('after', false, false, null, {toString() {converted = true; return 'new';}}, 1, false, true);
|
||||
ignored = snapshot(e) === before;
|
||||
propagated = typeError(() => e.initKeyboardEvent('after', false, false, null, Symbol()));
|
||||
});
|
||||
return target.dispatchEvent(e) && converted && ignored && propagated && snapshot(e) === before;
|
||||
});
|
||||
const real = new KeyboardEvent('before');
|
||||
const revokedEvent = Proxy.revocable(real, handler); revokedEvent.revoke();
|
||||
for (const [name, receiver] of [['ordinary', {}], ['prototype', Object.create(KeyboardEvent.prototype)], ['inherit-real', Object.create(real)], ['proxy', new Proxy(real, handler)], ['revoked', revokedEvent.proxy], ['other-interface', new MouseEvent('x')]]) {
|
||||
check(`legacy rejects ${name} receiver before conversion`, () => {
|
||||
let converted = false;
|
||||
const type = {toString() {converted = true; return 'x';}};
|
||||
return typeError(() => KeyboardEvent.prototype.initKeyboardEvent.call(receiver, type)) && !converted && traps === 0;
|
||||
});
|
||||
}
|
||||
check('cross-realm genuine receiver and Window view', () => {
|
||||
const e = new other.KeyboardEvent('before');
|
||||
KeyboardEvent.prototype.initKeyboardEvent.call(e, 'after', true, false, window, texts[4], -1, true);
|
||||
return e.type === 'after' && e.key === texts[4] && e.view === window && e.location === 4294967295 && e.ctrlKey && e instanceof other.KeyboardEvent;
|
||||
});
|
||||
check('cross-realm method TypeError uses callee realm', () => typeError(() => other.KeyboardEvent.prototype.initKeyboardEvent.call({}, 'x'), other));
|
||||
check('cross-realm constructor TypeError uses callee realm', () => typeError(() => new other.KeyboardEvent('x', {view:{}}), other));
|
||||
check('cross-realm argument TypeError uses callee realm', () => typeError(() => other.KeyboardEvent.prototype.initKeyboardEvent.call(real, 'x', false, false, null, Symbol()), other));
|
||||
check('genuine receiver remains valid after prototype replacement', () => {
|
||||
const e = new KeyboardEvent('before');
|
||||
Object.setPrototypeOf(e, null);
|
||||
KeyboardEvent.prototype.initKeyboardEvent.call(e, 'after');
|
||||
return Object.getOwnPropertyDescriptor(Event.prototype, 'type').get.call(e) === 'after';
|
||||
});
|
||||
globalThis.__keyboardEventResults = {rows, observed, total: rows.length, passed: rows.filter(row => row.passed).length};
|
||||
return rows.every(row => row.passed);
|
||||
})()
|
||||
@@ -0,0 +1,16 @@
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn keyboard_event_initializers_preserve_dom_strings_and_convert_complete_dictionaries() {
|
||||
let mut vm = new_storage_html_test_vm("https://keyboard-event-init-webidl.test/");
|
||||
vm.eval("document.body.innerHTML = '<iframe id=child></iframe>';")
|
||||
.unwrap();
|
||||
let result = vm.eval(include_str!("keyboard_event_init.js")).unwrap();
|
||||
assert_eq!(
|
||||
result,
|
||||
"true",
|
||||
"{}",
|
||||
vm.eval("JSON.stringify(__keyboardEventResults.rows.filter(row=>!row.passed))")
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
@@ -72,6 +72,7 @@ mod document_domain_lifetime;
|
||||
mod document_domain_setter;
|
||||
|
||||
mod error_event_init;
|
||||
mod keyboard_event_init;
|
||||
mod message_event_init;
|
||||
mod window_current_event_private;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user