diff --git a/components/script/dom/htmlinputelement.rs b/components/script/dom/htmlinputelement.rs
index 993e576f107..716c3eaf89b 100644
--- a/components/script/dom/htmlinputelement.rs
+++ b/components/script/dom/htmlinputelement.rs
@@ -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::() {
- 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::().dirty(NodeDamage::ContentOrHeritage);
}
}
diff --git a/components/script/dom/htmltextareaelement.rs b/components/script/dom/htmltextareaelement.rs
index ab129f9418f..df0817dcf63 100644
--- a/components/script/dom/htmltextareaelement.rs
+++ b/components/script/dom/htmltextareaelement.rs
@@ -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::() {
- 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::().dirty(NodeDamage::ContentOrHeritage);
}
}
diff --git a/components/script/textinput.rs b/components/script/textinput.rs
index 23a1b35e091..edb8248cfdc 100644
--- a/components/script/textinput.rs
+++ b/components/script/textinput.rs
@@ -9,21 +9,19 @@ use std::cmp::min;
use std::default::Default;
use std::ops::{Add, AddAssign, Range};
+use bitflags::bitflags;
use keyboard_types::{Key, KeyState, Modifiers, NamedKey, ShortcutMatcher};
use unicode_segmentation::UnicodeSegmentation;
-use crate::clipboard_provider::{ClipboardProvider, EmbedderClipboardProvider};
-use crate::dom::bindings::cell::DomRefCell;
+use crate::clipboard_provider::ClipboardProvider;
use crate::dom::bindings::codegen::Bindings::EventBinding::Event_Binding::EventMethods;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::str::DOMString;
use crate::dom::compositionevent::CompositionEvent;
use crate::dom::event::Event;
use crate::dom::keyboardevent::KeyboardEvent;
-use crate::dom::node::NodeTraits;
use crate::dom::types::ClipboardEvent;
-use crate::drag_data_store::{DragDataStore, Kind};
-use crate::script_runtime::CanGc;
+use crate::drag_data_store::Kind;
#[derive(Clone, Copy, PartialEq)]
pub enum Selection {
@@ -206,6 +204,15 @@ pub enum KeyReaction {
Nothing,
}
+bitflags! {
+ /// Resulting action to be taken by the owner of a text input that is handling a clipboard
+ /// event.
+ pub struct ClipboardEventReaction: u8 {
+ const QueueInputEvent = 1 << 0;
+ const FireClipboardChangedEvent = 1 << 1;
+ }
+}
+
impl Default for TextPoint {
fn default() -> TextPoint {
TextPoint {
@@ -338,7 +345,8 @@ impl TextInput {
self.insert_string(ch.to_string());
}
- /// Insert a string at the current editing point
+ /// Insert a string at the current editing point or replace the selection if
+ /// one exists.
pub fn insert_string>(&mut self, s: S) {
if self.selection_origin.is_none() {
self.selection_origin = Some(self.edit_point);
@@ -1199,81 +1207,103 @@ impl TextInput {
self.edit_point.index = byte_offset;
}
- fn paste_contents(&mut self, drag_data_store: &DragDataStore) {
- for item in drag_data_store.iter_item_list() {
- if let Kind::Text { data, .. } = item {
- self.insert_string(data.to_string());
- }
+ /// This implements step 3 onward from:
+ ///
+ /// -
+ /// -
+ /// -
+ ///
+ /// Earlier steps should have already been run by the callers.
+ pub(crate) fn handle_clipboard_event(
+ &mut self,
+ clipboard_event: &ClipboardEvent,
+ ) -> ClipboardEventReaction {
+ let event = clipboard_event.upcast::();
+ if !event.IsTrusted() {
+ return ClipboardEventReaction::empty();
+ }
+
+ // This step is common to all event types in the specification.
+ // Step 3: If the event was not canceled, then
+ if event.DefaultPrevented() {
+ // Step 4: Else, if the event was canceled
+ // Step 4.1: Return false.
+ return ClipboardEventReaction::empty();
+ }
+
+ match event.Type().str() {
+ "copy" => {
+ // These steps are from :
+ let selection = self.get_selection_text();
+
+ // Step 3.1 Copy the selected contents, if any, to the clipboard
+ if let Some(text) = selection {
+ self.clipboard_provider.set_text(text);
+ }
+
+ // Step 3.2 Fire a clipboard event named clipboardchange
+ ClipboardEventReaction::FireClipboardChangedEvent
+ },
+ "cut" => {
+ // These steps are from :
+ let selection = self.get_selection_text();
+
+ // Step 3.1 If there is a selection in an editable context where cutting is enabled, then
+ let Some(text) = selection else {
+ // Step 3.2 Else, if there is no selection or the context is not editable, then
+ return ClipboardEventReaction::empty();
+ };
+
+ // Step 3.1.1 Copy the selected contents, if any, to the clipboard
+ self.clipboard_provider.set_text(text);
+
+ // Step 3.1.2 Remove the contents of the selection from the document and collapse the selection.
+ self.delete_char(Direction::Backward);
+
+ // Step 3.1.3 Fire a clipboard event named clipboardchange
+ // Step 3.1.4 Queue tasks to fire any events that should fire due to the modification.
+ ClipboardEventReaction::FireClipboardChangedEvent |
+ ClipboardEventReaction::QueueInputEvent
+ },
+ "paste" => {
+ // These steps are from :
+ let Some(data_transfer) = clipboard_event.get_clipboard_data() else {
+ return ClipboardEventReaction::empty();
+ };
+ let Some(drag_data_store) = data_transfer.data_store() else {
+ return ClipboardEventReaction::empty();
+ };
+
+ // Step 3.1: If there is a selection or cursor in an editable context where pasting is
+ // enabled, then:
+ // TODO: Our TextInput always has a selection or an input point. It's likely that this
+ // shouldn't be the case when the entry loses the cursor.
+
+ // Step 3.1.1: Insert the most suitable content found on the clipboard, if any, into the
+ // context.
+ // TODO: Only text content is currently supported, but other data types should be supported
+ // in the future.
+ let Some(text_content) =
+ drag_data_store
+ .iter_item_list()
+ .find_map(|item| match item {
+ Kind::Text { data, .. } => Some(data.to_string()),
+ _ => None,
+ })
+ else {
+ return ClipboardEventReaction::empty();
+ };
+ if text_content.is_empty() {
+ return ClipboardEventReaction::empty();
+ }
+
+ self.insert_string(text_content);
+
+ // Step 3.1.2: Queue tasks to fire any events that should fire due to the
+ // modification, see ยง 5.3 Integration with other scripts and events for details.
+ ClipboardEventReaction::QueueInputEvent
+ },
+ _ => ClipboardEventReaction::empty(),
}
}
}
-
-/// step 3
-pub(crate) fn handle_text_clipboard_action(
- owning_node: &impl NodeTraits,
- textinput: &DomRefCell>,
- event: &ClipboardEvent,
- can_gc: CanGc,
-) -> bool {
- let e = event.upcast::();
-
- if !e.IsTrusted() {
- return false;
- }
-
- // Step 3
- match e.Type().str() {
- "copy" => {
- let selection = textinput.borrow().get_selection_text();
-
- // Step 3.1 Copy the selected contents, if any, to the clipboard
- if let Some(text) = selection {
- textinput.borrow_mut().clipboard_provider.set_text(text);
- }
-
- // Step 3.2 Fire a clipboard event named clipboardchange
- owning_node
- .owner_document()
- .fire_clipboardchange_event(can_gc);
- },
- "cut" => {
- let selection = textinput.borrow().get_selection_text();
-
- // Step 3.1 If there is a selection in an editable context where cutting is enabled, then
- if let Some(text) = selection {
- // Step 3.1.1 Copy the selected contents, if any, to the clipboard
- textinput.borrow_mut().clipboard_provider.set_text(text);
-
- // Step 3.1.2 Remove the contents of the selection from the document and collapse the selection.
- textinput.borrow_mut().delete_char(Direction::Backward);
-
- // Step 3.1.3 Fire a clipboard event named clipboardchange
- owning_node
- .owner_document()
- .fire_clipboardchange_event(can_gc);
-
- // Step 3.1.4 Queue tasks to fire any events that should fire due to the modification.
- } else {
- // Step 3.2 Else, if there is no selection or the context is not editable, then
- return false;
- }
- },
- "paste" => {
- // Step 3.1 If there is a selection or cursor in an editable context where pasting is enabled, then
- if let Some(data) = event.get_clipboard_data() {
- // Step 3.1.1 Insert the most suitable content found on the clipboard, if any, into the context.
- let drag_data_store = data.data_store().expect("This shouldn't fail");
- textinput.borrow_mut().paste_contents(&drag_data_store);
-
- // Step 3.1.2 Queue tasks to fire any events that should fire due to the modification.
- } else {
- // Step 3.2 Else return false.
- return false;
- }
- },
- _ => (),
- }
-
- //Step 5
- true
-}