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