diff options
Diffstat (limited to 'components/script/dom/textcontrol.rs')
-rw-r--r-- | components/script/dom/textcontrol.rs | 319 |
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); + } +} |