1use std::default::Default;
8use std::ops::Range;
9
10use app_units::Au;
11use embedder_traits::{
12 EditingAction, EditingDirection, EditingMotion, EmbedderMsg, ModifySelection,
13 ScriptToEmbedderChan,
14};
15use js::context::JSContext;
16use layout_api::SegmentGranularity;
17use script_bindings::match_domstring_ascii;
18use script_bindings::root::Dom;
19use script_bindings::trace::CustomTraceable;
20use script_traits::MouseButtons;
21use servo_base::generic_channel::GenericCallback;
22use servo_base::id::WebViewId;
23use servo_base::text::{AssumeUnder4GB, RangeAny, Utf8CodeUnits, Utf16CodeUnits, Utf32CodeUnits};
24use servo_base::{Rope, RopeIndex, RopeMovement, RopeSlice};
25
26use crate::dom::bindings::inheritance::Castable;
27use crate::dom::bindings::str::DOMString;
28use crate::dom::editing::MouseButtonSelectionAction;
29use crate::dom::event::Event;
30use crate::dom::inputevent::HitTestResult;
31use crate::dom::mouseevent::MouseEvent;
32use crate::dom::text_control::TextControlElement;
33use crate::dom::types::{HTMLInputElement, HTMLTextAreaElement};
34use crate::dom::{Element, NodeTraits};
35use crate::drag::drag_gesture::{DragGesture, DragHandler};
36
37pub trait ClipboardProvider {
40 fn get_text(&mut self) -> Result<String, String>;
42 fn set_text(&mut self, _: String);
44}
45
46#[derive(MallocSizeOf)]
47pub(crate) struct EmbedderClipboardProvider {
48 pub embedder_sender: ScriptToEmbedderChan,
49 pub webview_id: WebViewId,
50}
51
52impl ClipboardProvider for EmbedderClipboardProvider {
53 fn get_text(&mut self) -> Result<String, String> {
54 let (callback, rx) = GenericCallback::new_blocking().unwrap();
55 self.embedder_sender
56 .send(EmbedderMsg::GetClipboardText(self.webview_id, callback))
57 .unwrap();
58 rx.recv().unwrap()
59 }
60 fn set_text(&mut self, s: String) {
61 self.embedder_sender
62 .send(EmbedderMsg::SetClipboardText(self.webview_id, s))
63 .unwrap();
64 }
65}
66
67#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)]
68pub enum SelectionDirection {
69 Forward,
70 Backward,
71 None,
72}
73
74impl From<DOMString> for SelectionDirection {
75 fn from(direction: DOMString) -> SelectionDirection {
76 match_domstring_ascii!(direction,
77 "forward" => SelectionDirection::Forward,
78 "backward" => SelectionDirection::Backward,
79 _ => SelectionDirection::None,
80 )
81 }
82}
83
84impl From<SelectionDirection> for DOMString {
85 fn from(direction: SelectionDirection) -> DOMString {
86 match direction {
87 SelectionDirection::Forward => DOMString::from_static("forward"),
88 SelectionDirection::Backward => DOMString::from_static("backward"),
89 SelectionDirection::None => DOMString::from_static("none"),
90 }
91 }
92}
93
94#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)]
95pub enum Lines {
96 Single,
97 Multiple,
98}
99
100impl Lines {
101 fn normalize(&self, contents: impl Into<String>) -> String {
102 let contents = contents.into().replace("\r\n", "\n");
103 match self {
104 Self::Multiple => {
105 contents.replace("\r", "\n")
107 },
108 Lines::Single => contents.replace(['\r', '\n'], " "),
112 }
113 }
114}
115
116#[derive(Clone, Copy, PartialEq)]
117pub(crate) struct SelectionState {
118 start: RopeIndex,
119 end: RopeIndex,
120 direction: SelectionDirection,
121}
122
123#[derive(JSTraceable, MallocSizeOf)]
125pub struct TextInput<T: ClipboardProvider> {
126 #[no_trace]
127 rope: Rope,
128
129 mode: Lines,
134
135 #[no_trace]
137 edit_point: RopeIndex,
138
139 #[no_trace]
142 selection_origin: Option<RopeIndex>,
143 selection_direction: SelectionDirection,
144
145 #[ignore_malloc_size_of = "Can't easily measure this generic type"]
146 clipboard_provider: T,
147
148 max_length: Option<Utf16CodeUnits>,
152 min_length: Option<Utf16CodeUnits>,
153
154 was_last_change_by_set_content: bool,
156
157 #[no_trace]
158 pub(crate) previous_selection_range: Range<RopeIndex>,
159 #[no_trace]
160 pub(crate) selection_for_layout: Option<RangeAny<Utf32CodeUnits>>,
161}
162
163#[derive(Clone, Copy, PartialEq)]
165pub enum InputEventType {
166 InsertText,
167 InsertLineBreak,
168 InsertFromPaste,
169 InsertCompositionText,
170 DeleteByCut,
171 DeleteContentBackward,
172 DeleteContentForward,
173}
174
175impl InputEventType {
176 pub(crate) fn as_str(&self) -> &str {
177 match *self {
178 Self::InsertText => "insertText",
179 Self::InsertLineBreak => "insertLineBreak",
180 Self::InsertFromPaste => "insertFromPaste",
181 Self::InsertCompositionText => "insertCompositionText",
182 Self::DeleteByCut => "deleteByCut",
183 Self::DeleteContentBackward => "deleteContentBackward",
184 Self::DeleteContentForward => "deleteContentForward",
185 }
186 }
187
188 pub(crate) fn is_beforeinput_cancelable(self) -> bool {
190 self != Self::InsertCompositionText
191 }
192
193 pub(crate) fn is_composing(self) -> bool {
194 self == Self::InsertCompositionText
195 }
196}
197
198#[derive(PartialEq)]
200pub enum KeyReaction {
201 TriggerDefaultAction,
202 DispatchInput(Option<String>, InputEventType),
203 RedrawSelection,
204 Nothing,
205}
206
207impl<T: ClipboardProvider> TextInput<T> {
208 pub fn new(lines: Lines, initial: DOMString, clipboard_provider: T) -> TextInput<T> {
210 Self {
211 rope: Rope::new(initial),
212 mode: lines,
213 edit_point: Default::default(),
214 selection_origin: None,
215 clipboard_provider,
216 max_length: Default::default(),
217 min_length: Default::default(),
218 selection_direction: SelectionDirection::None,
219 was_last_change_by_set_content: true,
220 previous_selection_range: Default::default(),
221 selection_for_layout: None,
222 }
223 }
224
225 pub fn edit_point(&self) -> RopeIndex {
226 self.edit_point
227 }
228
229 pub fn selection_origin(&self) -> Option<RopeIndex> {
230 self.selection_origin
231 }
232
233 pub fn selection_origin_or_edit_point(&self) -> RopeIndex {
236 self.selection_origin.unwrap_or(self.edit_point)
237 }
238
239 pub fn selection_direction(&self) -> SelectionDirection {
240 self.selection_direction
241 }
242
243 pub fn set_max_length(&mut self, length: Option<Utf16CodeUnits>) {
244 self.max_length = length;
245 }
246
247 pub fn set_min_length(&mut self, length: Option<Utf16CodeUnits>) {
248 self.min_length = length;
249 }
250
251 pub(crate) fn was_last_change_by_set_content(&self) -> bool {
253 self.was_last_change_by_set_content
254 }
255
256 pub(crate) fn clone_content_from(&mut self, other: &Self) {
257 self.set_content(other.get_content());
258 self.was_last_change_by_set_content = other.was_last_change_by_set_content;
259 }
260
261 pub(crate) fn delete_selection(&mut self) -> bool {
264 if self.selection_start() == self.selection_end() {
265 return false;
266 }
267 self.replace_selection(&DOMString::new());
268 true
269 }
270
271 pub fn delete_unit_or_selection(
276 &mut self,
277 unit: RopeMovement,
278 direction: EditingDirection,
279 ) -> bool {
280 if !self.has_uncollapsed_selection() {
281 let amount = match direction {
282 EditingDirection::Forward => 1,
283 EditingDirection::Backward => -1,
284 };
285 self.modify_selection(amount, unit);
286 }
287 self.delete_selection()
288 }
289
290 pub fn insert<S: Into<String>>(&mut self, string: S) {
293 if self.selection_origin.is_none() {
294 self.selection_origin = Some(self.edit_point);
295 }
296 self.replace_selection(&DOMString::from(string.into()));
297 }
298
299 pub fn selection_start(&self) -> RopeIndex {
302 match self.selection_direction {
303 SelectionDirection::None | SelectionDirection::Forward => {
304 self.selection_origin_or_edit_point()
305 },
306 SelectionDirection::Backward => self.edit_point,
307 }
308 }
309
310 pub(crate) fn selection_start_utf16(&self) -> Utf16CodeUnits {
311 self.rope.index_to_utf16_offset(self.selection_start())
312 }
313
314 fn selection_start_offset(&self) -> Utf8CodeUnits {
316 self.rope.index_to_utf8_offset(self.selection_start())
317 }
318
319 pub fn selection_end(&self) -> RopeIndex {
322 match self.selection_direction {
323 SelectionDirection::None | SelectionDirection::Forward => self.edit_point,
324 SelectionDirection::Backward => self.selection_origin_or_edit_point(),
325 }
326 }
327
328 pub(crate) fn selection_end_utf16(&self) -> Utf16CodeUnits {
329 self.rope.index_to_utf16_offset(self.selection_end())
330 }
331
332 #[inline]
335 pub(crate) fn has_uncollapsed_selection(&self) -> bool {
336 self.selection_origin
337 .is_some_and(|selection_origin| selection_origin != self.edit_point)
338 }
339
340 pub(crate) fn sorted_selection_character_offsets_range(&self) -> RangeAny<Utf32CodeUnits> {
347 let rope = &self.rope;
348 let start = self.selection_start();
349 let end = self.selection_end();
350 let start = (start != rope.first_index()).then(|| rope.index_to_character_offset(start));
351 let end = (end != rope.last_index()).then(|| rope.index_to_character_offset(end));
352 RangeAny::new(start, end)
353 }
354
355 pub(crate) fn selection_state(&self) -> SelectionState {
357 SelectionState {
358 start: self.selection_start(),
359 end: self.selection_end(),
360 direction: self.selection_direction,
361 }
362 }
363
364 fn assert_ok_selection(&self) {
366 debug!(
367 "edit_point: {:?}, selection_origin: {:?}, direction: {:?}",
368 self.edit_point, self.selection_origin, self.selection_direction
369 );
370
371 debug_assert_eq!(self.edit_point, self.rope.normalize_index(self.edit_point));
372 if let Some(selection_origin) = self.selection_origin {
373 debug_assert_eq!(
374 selection_origin,
375 self.rope.normalize_index(selection_origin)
376 );
377 match self.selection_direction {
378 SelectionDirection::None | SelectionDirection::Forward => {
379 debug_assert!(selection_origin <= self.edit_point)
380 },
381 SelectionDirection::Backward => debug_assert!(self.edit_point <= selection_origin),
382 }
383 }
384 }
385
386 fn selection_slice(&self) -> RopeSlice<'_> {
387 self.rope
388 .slice(Some(self.selection_start()), Some(self.selection_end()))
389 }
390
391 pub(crate) fn selection_content(&self) -> Option<String> {
392 let text: String = self.selection_slice().into();
393 if text.is_empty() {
394 return None;
395 }
396 Some(text)
397 }
398
399 fn selection_utf16_len(&self) -> Utf16CodeUnits {
401 self.selection_slice().len_utf16()
402 }
403
404 pub fn replace_selection(&mut self, insert: &DOMString) {
408 let string_to_insert = if let Some(max_length) = self.max_length {
409 let utf16_length_without_selection =
410 self.len_utf16().saturating_sub(self.selection_utf16_len());
411 let utf16_length_that_can_be_inserted =
412 max_length.saturating_sub(utf16_length_without_selection);
413 let last_char_index = usize::from(
415 utf16_length_that_can_be_inserted
416 .to_utf8_code_units_in(AssumeUnder4GB, &insert.str()),
417 );
418 &insert.str()[..last_char_index]
419 } else {
420 &insert.str()
421 };
422 let string_to_insert = self.mode.normalize(string_to_insert);
423
424 let start = self.selection_start();
425 let end = self.selection_end();
426 let end_index_of_insertion = self.rope.replace_range(start..end, string_to_insert);
427
428 self.was_last_change_by_set_content = false;
429 self.clear_selection();
430 self.edit_point = end_index_of_insertion;
431 }
432
433 pub fn modify_edit_point(&mut self, amount: isize, movement: RopeMovement) {
434 if amount == 0 {
435 return;
436 }
437
438 if matches!(movement, RopeMovement::Line) || !self.has_uncollapsed_selection() {
441 self.clear_selection();
442 self.edit_point = self.rope.move_by(self.edit_point, movement, amount);
443 return;
444 }
445
446 let new_edit_point = if amount > 0 {
449 self.selection_end()
450 } else {
451 self.selection_start()
452 };
453 self.clear_selection();
454 self.edit_point = new_edit_point;
455 }
456
457 pub fn modify_selection(&mut self, amount: isize, movement: RopeMovement) {
458 let old_edit_point = self.edit_point;
459 self.edit_point = self.rope.move_by(old_edit_point, movement, amount);
460
461 if self.selection_origin.is_none() {
462 self.selection_origin = Some(old_edit_point);
463 }
464 self.update_selection_direction();
465 }
466
467 pub fn modify_selection_or_edit_point(
468 &mut self,
469 amount: isize,
470 movement: RopeMovement,
471 update_selection: ModifySelection,
472 ) {
473 match update_selection {
474 ModifySelection::Yes => self.modify_selection(amount, movement),
475 ModifySelection::No => self.modify_edit_point(amount, movement),
476 }
477 self.assert_ok_selection();
478 }
479
480 pub fn mode(&self) -> Lines {
481 self.mode
482 }
483
484 fn update_selection_direction(&mut self) {
489 debug!(
490 "edit_point: {:?}, selection_origin: {:?}",
491 self.edit_point, self.selection_origin
492 );
493 self.selection_direction = if Some(self.edit_point) < self.selection_origin {
494 SelectionDirection::Backward
495 } else {
496 SelectionDirection::Forward
497 }
498 }
499
500 pub fn handle_return(&mut self) -> KeyReaction {
502 match self.mode {
503 Lines::Multiple => {
504 self.insert('\n');
505 KeyReaction::DispatchInput(None, InputEventType::InsertLineBreak)
506 },
507 Lines::Single => KeyReaction::TriggerDefaultAction,
508 }
509 }
510
511 pub fn select_all(&mut self) {
513 self.selection_origin = Some(RopeIndex::default());
514 self.edit_point = self.rope.last_index();
515 self.selection_direction = SelectionDirection::Forward;
516 self.assert_ok_selection();
517 }
518
519 pub fn clear_selection(&mut self) {
521 self.selection_origin = None;
522 self.selection_direction = SelectionDirection::None;
523 }
524
525 pub(crate) fn clear_selection_to_end(&mut self) {
527 self.clear_selection();
528 self.edit_point = self.rope.last_index();
529 }
530
531 pub(crate) fn clear_selection_to_start(&mut self) {
532 self.clear_selection();
533 self.edit_point = Default::default();
534 }
535
536 pub fn perform_editing_action(&mut self, action: EditingAction) -> KeyReaction {
540 match action {
541 EditingAction::MoveCursor(direction, motion, selection) => {
542 let movement = match motion {
543 EditingMotion::Character => RopeMovement::Character,
544 EditingMotion::Grapheme => RopeMovement::Grapheme,
545 EditingMotion::Word => RopeMovement::Word,
546 EditingMotion::Line => RopeMovement::Line,
547 EditingMotion::LineStartOrEnd => RopeMovement::LineStartOrEnd,
548 EditingMotion::Page => RopeMovement::Line,
549 EditingMotion::DocumentStartOrEnd => RopeMovement::RopeStartOrEnd,
550 };
551 let amount = match (direction, motion) {
552 (EditingDirection::Forward, EditingMotion::Page) => 28,
554 (EditingDirection::Backward, EditingMotion::Page) => -28,
555 (EditingDirection::Forward, _) => 1,
556 (EditingDirection::Backward, _) => -1,
557 };
558 self.modify_selection_or_edit_point(amount, movement, selection);
559 KeyReaction::RedrawSelection
560 },
561 EditingAction::InsertNewline | EditingAction::InsertParagraph => self.handle_return(),
562 EditingAction::InsertText(text) => {
563 self.insert(&text);
564 KeyReaction::DispatchInput(Some(text), InputEventType::InsertText)
565 },
566 EditingAction::InsertCompositionText(text) => {
567 self.insert_for_composition(&text);
568 KeyReaction::DispatchInput(Some(text), InputEventType::InsertCompositionText)
569 },
570 EditingAction::EndComposition(..) => {
571 self.clear_selection();
572 KeyReaction::RedrawSelection
573 },
574 EditingAction::Delete => {
575 if self.delete_unit_or_selection(RopeMovement::Grapheme, EditingDirection::Forward)
576 {
577 KeyReaction::DispatchInput(None, InputEventType::DeleteContentForward)
578 } else {
579 KeyReaction::Nothing
580 }
581 },
582 EditingAction::Backspace(motion) => {
583 let movement = match motion {
584 EditingMotion::Character => RopeMovement::Character,
585 EditingMotion::Grapheme => RopeMovement::Grapheme,
586 EditingMotion::Word => RopeMovement::Word,
587 EditingMotion::Line => RopeMovement::Line,
588 EditingMotion::LineStartOrEnd => RopeMovement::LineStartOrEnd,
589 EditingMotion::Page => return KeyReaction::Nothing,
590 EditingMotion::DocumentStartOrEnd => RopeMovement::RopeStartOrEnd,
591 };
592 if self.delete_unit_or_selection(movement, EditingDirection::Backward) {
593 KeyReaction::DispatchInput(None, InputEventType::DeleteContentBackward)
594 } else {
595 KeyReaction::Nothing
596 }
597 },
598 EditingAction::SelectAll |
599 EditingAction::Clipboard(..) |
600 EditingAction::StartComposition => KeyReaction::Nothing,
601 }
602 }
603
604 fn insert_for_composition<S: Into<String>>(&mut self, insertion: S) {
605 let start = self.selection_start_offset();
606 let insertion = insertion.into();
607 self.insert(&insertion);
608 self.set_selection_range_utf8(
609 start,
610 start + Utf8CodeUnits(insertion.len() as u32),
611 SelectionDirection::Forward,
612 );
613 }
614
615 fn edit_point_for_hit_test_result(&self, hit_test_result: &HitTestResult) -> RopeIndex {
616 hit_test_result
617 .dom_position_for_selection
618 .as_ref()
619 .map(|boundary| {
620 self.rope.move_by(
621 Default::default(),
622 RopeMovement::Character,
623 boundary.utf32_offset().0 as isize,
624 )
625 })
626 .unwrap_or_else(|| self.rope.last_index())
627 }
628
629 fn drag_moved(
630 &mut self,
631 cx: &mut JSContext,
632 element: &impl TextControlElement,
633 hit_test_result: &HitTestResult,
634 ) {
635 let point_in_viewport = hit_test_result.point_in_frame.map(Au::from_f32_px);
636 let element = element.as_element();
637 self.edit_point = element
638 .owner_window()
639 .text_index_query_on_node_for_event(cx, element.upcast(), point_in_viewport)
640 .map(|(_, character_offset)| {
641 self.rope.move_by(
642 Default::default(),
643 RopeMovement::Character,
644 character_offset.0 as isize,
645 )
646 })
647 .unwrap_or_else(|| self.rope.last_index());
648
649 self.update_selection_direction();
650 }
651
652 pub(crate) fn handle_mousedown_event(
657 &mut self,
658 element: &Element,
659 mouse_event: &MouseEvent,
660 hit_test_result: &HitTestResult,
661 ) -> bool {
662 assert_eq!(mouse_event.upcast::<Event>().type_(), atom!("mousedown"));
663
664 let selection_changed = match MouseButtonSelectionAction::from_mouse_event(mouse_event) {
665 Some(MouseButtonSelectionAction::SelectRange(granularity)) => {
666 let boundaries = match granularity {
667 SegmentGranularity::Word => self.rope.relevant_word_boundaries(self.edit_point),
668 SegmentGranularity::HardLineBreak => self.rope.line_boundaries(self.edit_point),
669 };
670 self.edit_point = boundaries.end;
671 self.selection_origin = Some(boundaries.start);
672 self.update_selection_direction();
673 true
674 },
675 Some(MouseButtonSelectionAction::MoveCursor) => {
676 self.clear_selection();
677 self.edit_point = self.edit_point_for_hit_test_result(hit_test_result);
678 self.selection_origin = Some(self.edit_point);
679 self.update_selection_direction();
680 true
681 },
682 _ => false,
683 };
684
685 if selection_changed && mouse_event.buttons().contains(MouseButtons::Primary) {
686 element
687 .owner_document()
688 .event_handler()
689 .install_drag_gesture(DragGesture::new(DragHandler::TextInputSelection(
690 TextInputSelectionDragHandler(Dom::from_ref(element)),
691 )));
692 }
693
694 selection_changed
695 }
696
697 pub(crate) fn is_empty(&self) -> bool {
699 self.rope.is_empty()
700 }
701
702 pub(crate) fn len_utf16(&self) -> Utf16CodeUnits {
704 self.rope.len_utf16()
705 }
706
707 pub fn get_content(&self) -> DOMString {
709 self.rope.contents().into()
710 }
711
712 pub fn set_content(&mut self, content: DOMString) {
719 self.rope = Rope::new(content.str().replace("\r\n", "\n").replace("\r", "\n"));
720 self.was_last_change_by_set_content = true;
721
722 self.edit_point = self.rope.normalize_index(self.edit_point());
723 self.selection_origin = self
724 .selection_origin
725 .map(|selection_origin| self.rope.normalize_index(selection_origin));
726 }
727
728 pub fn set_selection_range_utf16(
729 &mut self,
730 start: Utf16CodeUnits,
731 end: Utf16CodeUnits,
732 direction: SelectionDirection,
733 ) {
734 self.set_selection_range_utf8(
735 self.rope.utf16_offset_to_utf8_offset(start),
736 self.rope.utf16_offset_to_utf8_offset(end),
737 direction,
738 );
739 }
740
741 pub fn set_selection_range_utf8(
742 &mut self,
743 mut start: Utf8CodeUnits,
744 mut end: Utf8CodeUnits,
745 direction: SelectionDirection,
746 ) {
747 let text_end = self.get_content().len_utf8();
748 if end > text_end {
749 end = text_end;
750 }
751 if start > end {
752 start = end;
753 }
754
755 self.selection_direction = direction;
756
757 match direction {
758 SelectionDirection::None | SelectionDirection::Forward => {
759 self.selection_origin = Some(self.rope.utf8_offset_to_rope_index(start));
760 self.edit_point = self.rope.utf8_offset_to_rope_index(end);
761 },
762 SelectionDirection::Backward => {
763 self.selection_origin = Some(self.rope.utf8_offset_to_rope_index(end));
764 self.edit_point = self.rope.utf8_offset_to_rope_index(start);
765 },
766 }
767
768 self.assert_ok_selection();
769 }
770}
771
772#[derive(JSTraceable, MallocSizeOf)]
773#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
774pub(crate) struct TextInputSelectionDragHandler(Dom<Element>);
775
776impl TextInputSelectionDragHandler {
777 pub(crate) fn still_connected(&self) -> bool {
778 self.0.is_connected()
779 }
780
781 pub(crate) fn moved(&self, cx: &mut JSContext, hit_test_result: &HitTestResult) -> bool {
785 if !self.0.is_connected() {
786 return false;
787 }
788
789 if let Some(input) = self.0.downcast::<HTMLInputElement>() {
790 input
791 .text_input_mut()
792 .drag_moved(cx, input, hit_test_result);
793 input.maybe_update_shared_selection();
794 true
795 } else if let Some(text_area) = self.0.downcast::<HTMLTextAreaElement>() {
796 text_area
797 .text_input_mut()
798 .drag_moved(cx, text_area, hit_test_result);
799 text_area.maybe_update_shared_selection();
800 true
801 } else {
802 false
803 }
804 }
805}