script: Properly fire input events for clipboard use in input elements (#37100)

When using the clipboard to paste or modify the contents of an input
element the specification says says[^1] to

> Queue tasks to fire any events that should fire due to the
> modification, see § 5.3 Integration with other scripts and events for
> details.

This change does that, by turning `handle_text_clipboard_action` into
`TextInput::handle_clipboard_event` and having the caller responsible
for executing events. In addition, when content is changed, the node is
dirtied, forcing a relayout.

[^1]: https://www.w3.org/TR/clipboard-apis/#paste-action

Testing: This is difficult to test because we do not have test harness
support for input events currently. There is a manual test for this in
the linked bug which is now passing.
Fixes: #37074.

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
Martin Robinson 2025-08-11 11:23:58 +02:00 committed by GitHub
parent 6ddcce4889
commit 9d4004135b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 154 additions and 89 deletions

View file

@ -92,8 +92,7 @@ use crate::textinput::KeyReaction::{
};
use crate::textinput::Lines::Single;
use crate::textinput::{
Direction, SelectionDirection, TextInput, UTF8Bytes, UTF16CodeUnits,
handle_text_clipboard_action,
ClipboardEventReaction, Direction, SelectionDirection, TextInput, UTF8Bytes, UTF16CodeUnits,
};
const DEFAULT_SUBMIT_VALUE: &str = "Submit";
@ -3160,8 +3159,26 @@ impl VirtualMethods for HTMLInputElement {
event.mark_as_handled();
}
} else if let Some(clipboard_event) = event.downcast::<ClipboardEvent>() {
if !event.DefaultPrevented() {
handle_text_clipboard_action(self, &self.textinput, clipboard_event, can_gc);
let reaction = self
.textinput
.borrow_mut()
.handle_clipboard_event(clipboard_event);
if reaction.contains(ClipboardEventReaction::FireClipboardChangedEvent) {
self.owner_document().fire_clipboardchange_event(can_gc);
}
if reaction.contains(ClipboardEventReaction::QueueInputEvent) {
self.owner_global()
.task_manager()
.user_interaction_task_source()
.queue_event(
self.upcast(),
atom!("input"),
EventBubbles::Bubbles,
EventCancelable::NotCancelable,
);
}
if !reaction.is_empty() {
self.upcast::<Node>().dirty(NodeDamage::ContentOrHeritage);
}
}

View file

@ -43,8 +43,8 @@ use crate::dom::validitystate::{ValidationFlags, ValidityState};
use crate::dom::virtualmethods::VirtualMethods;
use crate::script_runtime::CanGc;
use crate::textinput::{
Direction, KeyReaction, Lines, SelectionDirection, TextInput, UTF8Bytes, UTF16CodeUnits,
handle_text_clipboard_action,
ClipboardEventReaction, Direction, KeyReaction, Lines, SelectionDirection, TextInput,
UTF8Bytes, UTF16CodeUnits,
};
#[dom_struct]
@ -698,8 +698,26 @@ impl VirtualMethods for HTMLTextAreaElement {
event.mark_as_handled();
}
} else if let Some(clipboard_event) = event.downcast::<ClipboardEvent>() {
if !event.DefaultPrevented() {
handle_text_clipboard_action(self, &self.textinput, clipboard_event, CanGc::note());
let reaction = self
.textinput
.borrow_mut()
.handle_clipboard_event(clipboard_event);
if reaction.contains(ClipboardEventReaction::FireClipboardChangedEvent) {
self.owner_document().fire_clipboardchange_event(can_gc);
}
if reaction.contains(ClipboardEventReaction::QueueInputEvent) {
self.owner_global()
.task_manager()
.user_interaction_task_source()
.queue_event(
self.upcast(),
atom!("input"),
EventBubbles::Bubbles,
EventCancelable::NotCancelable,
);
}
if !reaction.is_empty() {
self.upcast::<Node>().dirty(NodeDamage::ContentOrHeritage);
}
}