script/dom/
textcontrol.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! This is an abstraction used by `HTMLInputElement` and `HTMLTextAreaElement` to implement the
6//! text control selection DOM API.
7//!
8//! <https://html.spec.whatwg.org/multipage/#textFieldSelection>
9
10use crate::clipboard_provider::EmbedderClipboardProvider;
11use crate::dom::bindings::cell::DomRefCell;
12use crate::dom::bindings::codegen::Bindings::HTMLFormElementBinding::SelectionMode;
13use crate::dom::bindings::conversions::DerivedFrom;
14use crate::dom::bindings::error::{Error, ErrorResult};
15use crate::dom::bindings::str::DOMString;
16use crate::dom::event::{EventBubbles, EventCancelable};
17use crate::dom::eventtarget::EventTarget;
18use crate::dom::node::{Node, NodeDamage, NodeTraits};
19use crate::textinput::{SelectionDirection, SelectionState, TextInput, UTF8Bytes};
20
21pub(crate) trait TextControlElement: DerivedFrom<EventTarget> + DerivedFrom<Node> {
22    fn selection_api_applies(&self) -> bool;
23    fn has_selectable_text(&self) -> bool;
24    fn has_selection(&self) -> bool;
25    fn set_dirty_value_flag(&self, value: bool);
26    fn select_all(&self);
27}
28
29pub(crate) struct TextControlSelection<'a, E: TextControlElement> {
30    element: &'a E,
31    textinput: &'a DomRefCell<TextInput<EmbedderClipboardProvider>>,
32}
33
34impl<'a, E: TextControlElement> TextControlSelection<'a, E> {
35    pub(crate) fn new(
36        element: &'a E,
37        textinput: &'a DomRefCell<TextInput<EmbedderClipboardProvider>>,
38    ) -> Self {
39        TextControlSelection { element, textinput }
40    }
41
42    /// <https://html.spec.whatwg.org/multipage/#dom-textarea/input-select>
43    pub(crate) fn dom_select(&self) {
44        // Step 1: If this element is an input element, and either select() does not apply
45        // to this element or the corresponding control has no selectable text, return.
46        if !self.element.has_selectable_text() {
47            return;
48        }
49
50        // Step 2
51        self.set_range(Some(0), Some(u32::MAX), None, None);
52    }
53
54    // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionstart
55    pub(crate) fn dom_start(&self) -> Option<u32> {
56        // Step 1
57        if !self.element.selection_api_applies() {
58            return None;
59        }
60
61        // Steps 2-3
62        Some(self.start())
63    }
64
65    // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionstart
66    pub(crate) fn set_dom_start(&self, start: Option<u32>) -> ErrorResult {
67        // Step 1
68        if !self.element.selection_api_applies() {
69            return Err(Error::InvalidState(None));
70        }
71
72        // Step 2
73        let mut end = self.end();
74
75        // Step 3
76        if let Some(s) = start {
77            if end < s {
78                end = s;
79            }
80        }
81
82        // Step 4
83        self.set_range(start, Some(end), Some(self.direction()), None);
84        Ok(())
85    }
86
87    // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionend
88    pub(crate) fn dom_end(&self) -> Option<u32> {
89        // Step 1
90        if !self.element.selection_api_applies() {
91            return None;
92        }
93
94        // Steps 2-3
95        Some(self.end())
96    }
97
98    // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionend
99    pub(crate) fn set_dom_end(&self, end: Option<u32>) -> ErrorResult {
100        // Step 1
101        if !self.element.selection_api_applies() {
102            return Err(Error::InvalidState(None));
103        }
104
105        // Step 2
106        self.set_range(Some(self.start()), end, Some(self.direction()), None);
107        Ok(())
108    }
109
110    // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectiondirection
111    pub(crate) fn dom_direction(&self) -> Option<DOMString> {
112        // Step 1
113        if !self.element.selection_api_applies() {
114            return None;
115        }
116
117        Some(DOMString::from(self.direction()))
118    }
119
120    // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectiondirection
121    pub(crate) fn set_dom_direction(&self, direction: Option<DOMString>) -> ErrorResult {
122        // Step 1
123        if !self.element.selection_api_applies() {
124            return Err(Error::InvalidState(None));
125        }
126
127        // Step 2
128        self.set_range(
129            Some(self.start()),
130            Some(self.end()),
131            direction.map(SelectionDirection::from),
132            None,
133        );
134        Ok(())
135    }
136
137    // https://html.spec.whatwg.org/multipage/#dom-textarea/input-setselectionrange
138    pub(crate) fn set_dom_range(
139        &self,
140        start: u32,
141        end: u32,
142        direction: Option<DOMString>,
143    ) -> ErrorResult {
144        // Step 1
145        if !self.element.selection_api_applies() {
146            return Err(Error::InvalidState(None));
147        }
148
149        // Step 2
150        self.set_range(
151            Some(start),
152            Some(end),
153            direction.map(SelectionDirection::from),
154            None,
155        );
156        Ok(())
157    }
158
159    // https://html.spec.whatwg.org/multipage/#dom-textarea/input-setrangetext
160    pub(crate) fn set_dom_range_text(
161        &self,
162        replacement: DOMString,
163        start: Option<u32>,
164        end: Option<u32>,
165        selection_mode: SelectionMode,
166    ) -> ErrorResult {
167        // Step 1
168        if !self.element.selection_api_applies() {
169            return Err(Error::InvalidState(None));
170        }
171
172        // Step 2
173        self.element.set_dirty_value_flag(true);
174
175        // Step 3
176        let mut start = start.unwrap_or_else(|| self.start());
177        let mut end = end.unwrap_or_else(|| self.end());
178
179        // Step 4
180        if start > end {
181            return Err(Error::IndexSize(None));
182        }
183
184        // Save the original selection state to later pass to set_selection_range, because we will
185        // change the selection state in order to replace the text in the range.
186        let original_selection_state = self.textinput.borrow().selection_state();
187
188        let UTF8Bytes(content_length) = self.textinput.borrow().len_utf8();
189        let content_length = content_length as u32;
190
191        // Step 5
192        if start > content_length {
193            start = content_length;
194        }
195
196        // Step 6
197        if end > content_length {
198            end = content_length;
199        }
200
201        // Step 7
202        let mut selection_start = self.start();
203
204        // Step 8
205        let mut selection_end = self.end();
206
207        // Step 11
208        // Must come before the textinput.replace_selection() call, as replacement gets moved in
209        // that call.
210        let new_length = replacement.len() as u32;
211
212        {
213            let mut textinput = self.textinput.borrow_mut();
214
215            // Steps 9-10
216            textinput.set_selection_range(start, end, SelectionDirection::None);
217            textinput.replace_selection(replacement);
218        }
219
220        // Step 12
221        let new_end = start + new_length;
222
223        // Step 13
224        match selection_mode {
225            SelectionMode::Select => {
226                selection_start = start;
227                selection_end = new_end;
228            },
229
230            SelectionMode::Start => {
231                selection_start = start;
232                selection_end = start;
233            },
234
235            SelectionMode::End => {
236                selection_start = new_end;
237                selection_end = new_end;
238            },
239
240            SelectionMode::Preserve => {
241                // Sub-step 1
242                let old_length = end - start;
243
244                // Sub-step 2
245                let delta = (new_length as isize) - (old_length as isize);
246
247                // Sub-step 3
248                if selection_start > end {
249                    selection_start = ((selection_start as isize) + delta) as u32;
250                } else if selection_start > start {
251                    selection_start = start;
252                }
253
254                // Sub-step 4
255                if selection_end > end {
256                    selection_end = ((selection_end as isize) + delta) as u32;
257                } else if selection_end > start {
258                    selection_end = new_end;
259                }
260            },
261        }
262
263        // Step 14
264        self.set_range(
265            Some(selection_start),
266            Some(selection_end),
267            None,
268            Some(original_selection_state),
269        );
270        Ok(())
271    }
272
273    fn start(&self) -> u32 {
274        let UTF8Bytes(offset) = self.textinput.borrow().selection_start_offset();
275        offset as u32
276    }
277
278    fn end(&self) -> u32 {
279        let UTF8Bytes(offset) = self.textinput.borrow().selection_end_offset();
280        offset as u32
281    }
282
283    fn direction(&self) -> SelectionDirection {
284        self.textinput.borrow().selection_direction()
285    }
286
287    /// <https://html.spec.whatwg.org/multipage/#set-the-selection-range>
288    fn set_range(
289        &self,
290        start: Option<u32>,
291        end: Option<u32>,
292        direction: Option<SelectionDirection>,
293        original_selection_state: Option<SelectionState>,
294    ) {
295        let mut textinput = self.textinput.borrow_mut();
296        let original_selection_state =
297            original_selection_state.unwrap_or_else(|| textinput.selection_state());
298
299        // Step 1
300        let start = start.unwrap_or(0);
301
302        // Step 2
303        let end = end.unwrap_or(0);
304
305        // Steps 3-5
306        textinput.set_selection_range(start, end, direction.unwrap_or(SelectionDirection::None));
307
308        // Step 6
309        if textinput.selection_state() != original_selection_state {
310            self.element
311                .owner_global()
312                .task_manager()
313                .user_interaction_task_source()
314                .queue_event(
315                    self.element.upcast::<EventTarget>(),
316                    atom!("select"),
317                    EventBubbles::Bubbles,
318                    EventCancelable::NotCancelable,
319                );
320        }
321
322        self.element.upcast::<Node>().dirty(NodeDamage::Other);
323    }
324}