aboutsummaryrefslogtreecommitdiffstats
path: root/components/script/dom/textcontrol.rs
diff options
context:
space:
mode:
Diffstat (limited to 'components/script/dom/textcontrol.rs')
-rw-r--r--components/script/dom/textcontrol.rs319
1 files changed, 319 insertions, 0 deletions
diff --git a/components/script/dom/textcontrol.rs b/components/script/dom/textcontrol.rs
new file mode 100644
index 00000000000..0f215b4cbc8
--- /dev/null
+++ b/components/script/dom/textcontrol.rs
@@ -0,0 +1,319 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+//! This is an abstraction used by `HTMLInputElement` and `HTMLTextAreaElement` to implement the
+//! text control selection DOM API.
+//!
+//! https://html.spec.whatwg.org/multipage/#textFieldSelection
+
+use crate::dom::bindings::cell::DomRefCell;
+use crate::dom::bindings::codegen::Bindings::HTMLFormElementBinding::SelectionMode;
+use crate::dom::bindings::conversions::DerivedFrom;
+use crate::dom::bindings::error::{Error, ErrorResult};
+use crate::dom::bindings::str::DOMString;
+use crate::dom::event::{EventBubbles, EventCancelable};
+use crate::dom::eventtarget::EventTarget;
+use crate::dom::node::{window_from_node, Node, NodeDamage};
+use crate::textinput::{SelectionDirection, SelectionState, TextInput, UTF8Bytes};
+use script_traits::ScriptToConstellationChan;
+
+pub trait TextControlElement: DerivedFrom<EventTarget> + DerivedFrom<Node> {
+ fn selection_api_applies(&self) -> bool;
+ fn has_selectable_text(&self) -> bool;
+ fn set_dirty_value_flag(&self, value: bool);
+}
+
+pub struct TextControlSelection<'a, E: TextControlElement> {
+ element: &'a E,
+ textinput: &'a DomRefCell<TextInput<ScriptToConstellationChan>>,
+}
+
+impl<'a, E: TextControlElement> TextControlSelection<'a, E> {
+ pub fn new(
+ element: &'a E,
+ textinput: &'a DomRefCell<TextInput<ScriptToConstellationChan>>,
+ ) -> Self {
+ TextControlSelection { element, textinput }
+ }
+
+ // https://html.spec.whatwg.org/multipage/#dom-textarea/input-select
+ pub fn dom_select(&self) {
+ // Step 1
+ if !self.element.has_selectable_text() {
+ return;
+ }
+
+ // Step 2
+ self.set_range(Some(0), Some(u32::max_value()), None, None);
+ }
+
+ // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionstart
+ pub fn dom_start(&self) -> Option<u32> {
+ // Step 1
+ if !self.element.selection_api_applies() {
+ return None;
+ }
+
+ // Steps 2-3
+ Some(self.start())
+ }
+
+ // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionstart
+ pub fn set_dom_start(&self, start: Option<u32>) -> ErrorResult {
+ // Step 1
+ if !self.element.selection_api_applies() {
+ return Err(Error::InvalidState);
+ }
+
+ // Step 2
+ let mut end = self.end();
+
+ // Step 3
+ if let Some(s) = start {
+ if end < s {
+ end = s;
+ }
+ }
+
+ // Step 4
+ self.set_range(start, Some(end), Some(self.direction()), None);
+ Ok(())
+ }
+
+ // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionend
+ pub fn dom_end(&self) -> Option<u32> {
+ // Step 1
+ if !self.element.selection_api_applies() {
+ return None;
+ }
+
+ // Steps 2-3
+ Some(self.end())
+ }
+
+ // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionend
+ pub fn set_dom_end(&self, end: Option<u32>) -> ErrorResult {
+ // Step 1
+ if !self.element.selection_api_applies() {
+ return Err(Error::InvalidState);
+ }
+
+ // Step 2
+ self.set_range(Some(self.start()), end, Some(self.direction()), None);
+ Ok(())
+ }
+
+ // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectiondirection
+ pub fn dom_direction(&self) -> Option<DOMString> {
+ // Step 1
+ if !self.element.selection_api_applies() {
+ return None;
+ }
+
+ Some(DOMString::from(self.direction()))
+ }
+
+ // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectiondirection
+ pub fn set_dom_direction(&self, direction: Option<DOMString>) -> ErrorResult {
+ // Step 1
+ if !self.element.selection_api_applies() {
+ return Err(Error::InvalidState);
+ }
+
+ // Step 2
+ self.set_range(
+ Some(self.start()),
+ Some(self.end()),
+ direction.map(|d| SelectionDirection::from(d)),
+ None,
+ );
+ Ok(())
+ }
+
+ // https://html.spec.whatwg.org/multipage/#dom-textarea/input-setselectionrange
+ pub fn set_dom_range(&self, start: u32, end: u32, direction: Option<DOMString>) -> ErrorResult {
+ // Step 1
+ if !self.element.selection_api_applies() {
+ return Err(Error::InvalidState);
+ }
+
+ // Step 2
+ self.set_range(
+ Some(start),
+ Some(end),
+ direction.map(|d| SelectionDirection::from(d)),
+ None,
+ );
+ Ok(())
+ }
+
+ // https://html.spec.whatwg.org/multipage/#dom-textarea/input-setrangetext
+ pub fn set_dom_range_text(
+ &self,
+ replacement: DOMString,
+ start: Option<u32>,
+ end: Option<u32>,
+ selection_mode: SelectionMode,
+ ) -> ErrorResult {
+ // Step 1
+ if !self.element.selection_api_applies() {
+ return Err(Error::InvalidState);
+ }
+
+ // Step 2
+ self.element.set_dirty_value_flag(true);
+
+ // Step 3
+ let mut start = start.unwrap_or_else(|| self.start());
+ let mut end = end.unwrap_or_else(|| self.end());
+
+ // Step 4
+ if start > end {
+ return Err(Error::IndexSize);
+ }
+
+ // Save the original selection state to later pass to set_selection_range, because we will
+ // change the selection state in order to replace the text in the range.
+ let original_selection_state = self.textinput.borrow().selection_state();
+
+ let UTF8Bytes(content_length) = self.textinput.borrow().len_utf8();
+ let content_length = content_length as u32;
+
+ // Step 5
+ if start > content_length {
+ start = content_length;
+ }
+
+ // Step 6
+ if end > content_length {
+ end = content_length;
+ }
+
+ // Step 7
+ let mut selection_start = self.start();
+
+ // Step 8
+ let mut selection_end = self.end();
+
+ // Step 11
+ // Must come before the textinput.replace_selection() call, as replacement gets moved in
+ // that call.
+ let new_length = replacement.len() as u32;
+
+ {
+ let mut textinput = self.textinput.borrow_mut();
+
+ // Steps 9-10
+ textinput.set_selection_range(start, end, SelectionDirection::None);
+ textinput.replace_selection(replacement);
+ }
+
+ // Step 12
+ let new_end = start + new_length;
+
+ // Step 13
+ match selection_mode {
+ SelectionMode::Select => {
+ selection_start = start;
+ selection_end = new_end;
+ },
+
+ SelectionMode::Start => {
+ selection_start = start;
+ selection_end = start;
+ },
+
+ SelectionMode::End => {
+ selection_start = new_end;
+ selection_end = new_end;
+ },
+
+ SelectionMode::Preserve => {
+ // Sub-step 1
+ let old_length = end - start;
+
+ // Sub-step 2
+ let delta = (new_length as isize) - (old_length as isize);
+
+ // Sub-step 3
+ if selection_start > end {
+ selection_start = ((selection_start as isize) + delta) as u32;
+ } else if selection_start > start {
+ selection_start = start;
+ }
+
+ // Sub-step 4
+ if selection_end > end {
+ selection_end = ((selection_end as isize) + delta) as u32;
+ } else if selection_end > start {
+ selection_end = new_end;
+ }
+ },
+ }
+
+ // Step 14
+ self.set_range(
+ Some(selection_start),
+ Some(selection_end),
+ None,
+ Some(original_selection_state),
+ );
+ Ok(())
+ }
+
+ fn start(&self) -> u32 {
+ let UTF8Bytes(offset) = self.textinput.borrow().selection_start_offset();
+ offset as u32
+ }
+
+ fn end(&self) -> u32 {
+ let UTF8Bytes(offset) = self.textinput.borrow().selection_end_offset();
+ offset as u32
+ }
+
+ fn direction(&self) -> SelectionDirection {
+ self.textinput.borrow().selection_direction()
+ }
+
+ // https://html.spec.whatwg.org/multipage/#set-the-selection-range
+ fn set_range(
+ &self,
+ start: Option<u32>,
+ end: Option<u32>,
+ direction: Option<SelectionDirection>,
+ original_selection_state: Option<SelectionState>,
+ ) {
+ let mut textinput = self.textinput.borrow_mut();
+ let original_selection_state =
+ original_selection_state.unwrap_or_else(|| textinput.selection_state());
+
+ // Step 1
+ let start = start.unwrap_or(0);
+
+ // Step 2
+ let end = end.unwrap_or(0);
+
+ // Steps 3-5
+ textinput.set_selection_range(start, end, direction.unwrap_or(SelectionDirection::None));
+
+ // Step 6
+ if textinput.selection_state() != original_selection_state {
+ let window = window_from_node(self.element);
+ window
+ .task_manager()
+ .user_interaction_task_source()
+ .queue_event(
+ &self.element.upcast::<EventTarget>(),
+ atom!("select"),
+ EventBubbles::Bubbles,
+ EventCancelable::NotCancelable,
+ &window,
+ );
+ }
+
+ self.element
+ .upcast::<Node>()
+ .dirty(NodeDamage::OtherNodeDamage);
+ }
+}