Let input JS event be dispatched by keydown instead of keypress (#37078)

1. Let `input` JS event be dispatched by `keydown` instead of
`keypress`, according to spec
2. Fire `input` event for Backspace and Delete. But do so only when
something is actually deleted

Testing: Manually tested and compared with other browsers.
Fixes: #37051
cc @xiaochengh

Signed-off-by: Euclid Ye <yezhizhenjiakang@gmail.com>
This commit is contained in:
Euclid Ye 2025-05-28 16:51:05 +08:00 committed by GitHub
parent 8ebf344e5e
commit 45072ae2e0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 51 additions and 28 deletions

View file

@ -2374,6 +2374,9 @@ impl Document {
let mut cancel_state = event.get_cancel_state();
// https://w3c.github.io/uievents/#keys-cancelable-keys
// it MUST prevent the respective beforeinput and input
// (and keypress if supported) events from being generated
// TODO: keypress should be deprecated and superceded by beforeinput
if keyboard_event.state == KeyState::Down &&
is_character_value_key(&(keyboard_event.key)) &&
!keyboard_event.is_composing &&

View file

@ -2883,6 +2883,17 @@ impl VirtualMethods for HTMLInputElement {
self.implicit_submission(can_gc);
},
DispatchInput => {
if event.IsTrusted() {
self.owner_global()
.task_manager()
.user_interaction_task_source()
.queue_event(
self.upcast(),
atom!("input"),
EventBubbles::Bubbles,
EventCancelable::NotCancelable,
);
}
self.value_dirty.set(true);
self.update_placeholder_shown_state();
self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
@ -2899,17 +2910,9 @@ impl VirtualMethods for HTMLInputElement {
!event.DefaultPrevented() &&
self.input_type().is_textual_or_password()
{
if event.IsTrusted() {
self.owner_global()
.task_manager()
.user_interaction_task_source()
.queue_event(
self.upcast(),
atom!("input"),
EventBubbles::Bubbles,
EventCancelable::NotCancelable,
);
}
// keypress should be deprecated and replaced by beforeinput.
// keypress was supposed to fire "blur" and "focus" events
// but already done in `document.rs`
} else if (event.type_() == atom!("compositionstart") ||
event.type_() == atom!("compositionupdate") ||
event.type_() == atom!("compositionend")) &&

View file

@ -643,6 +643,17 @@ impl VirtualMethods for HTMLTextAreaElement {
match action {
KeyReaction::TriggerDefaultAction => (),
KeyReaction::DispatchInput => {
if event.IsTrusted() {
self.owner_global()
.task_manager()
.user_interaction_task_source()
.queue_event(
self.upcast(),
atom!("input"),
EventBubbles::Bubbles,
EventCancelable::NotCancelable,
);
}
self.value_dirty.set(true);
self.update_placeholder_shown_state();
self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
@ -656,17 +667,9 @@ impl VirtualMethods for HTMLTextAreaElement {
}
}
} else if event.type_() == atom!("keypress") && !event.DefaultPrevented() {
if event.IsTrusted() {
self.owner_global()
.task_manager()
.user_interaction_task_source()
.queue_event(
self.upcast(),
atom!("input"),
EventBubbles::Bubbles,
EventCancelable::NotCancelable,
);
}
// keypress should be deprecated and replaced by beforeinput.
// keypress was supposed to fire "blur" and "focus" events
// but already done in `document.rs`
} else if event.type_() == atom!("compositionstart") ||
event.type_() == atom!("compositionupdate") ||
event.type_() == atom!("compositionend")

View file

@ -319,11 +319,18 @@ impl<T: ClipboardProvider> TextInput<T> {
}
/// Remove a character at the current editing point
pub fn delete_char(&mut self, dir: Direction) {
///
/// Returns true if any character was deleted
pub fn delete_char(&mut self, dir: Direction) -> bool {
if self.selection_origin.is_none() || self.selection_origin == Some(self.edit_point) {
self.adjust_horizontal_by_one(dir, Selection::Selected);
}
self.replace_selection(DOMString::new());
if self.selection_start() == self.selection_end() {
false
} else {
self.replace_selection(DOMString::new());
true
}
}
/// Insert a character at the current editing point
@ -895,6 +902,7 @@ impl<T: ClipboardProvider> TextInput<T> {
KeyReaction::RedrawSelection
})
.shortcut(CMD_OR_CONTROL, 'X', || {
// FIXME: this is unreachable because ClipboardEvent is fired instead of keydown
if let Some(text) = self.get_selection_text() {
self.clipboard_provider.set_text(text);
self.delete_char(Direction::Backward);
@ -914,12 +922,18 @@ impl<T: ClipboardProvider> TextInput<T> {
KeyReaction::DispatchInput
})
.shortcut(Modifiers::empty(), Key::Delete, || {
self.delete_char(Direction::Forward);
KeyReaction::DispatchInput
if self.delete_char(Direction::Forward) {
KeyReaction::DispatchInput
} else {
KeyReaction::Nothing
}
})
.shortcut(Modifiers::empty(), Key::Backspace, || {
self.delete_char(Direction::Backward);
KeyReaction::DispatchInput
if self.delete_char(Direction::Backward) {
KeyReaction::DispatchInput
} else {
KeyReaction::Nothing
}
})
.optional_shortcut(macos, Modifiers::META, Key::ArrowLeft, || {
self.adjust_horizontal_to_line_end(Direction::Backward, maybe_select);