1use std::default::Default;
8use std::ops::Range;
9
10use app_units::Au;
11use bitflags::bitflags;
12use embedder_traits::{EmbedderMsg, MouseButton, ScriptToEmbedderChan};
13use keyboard_types::{Key, KeyState, Modifiers, NamedKey, ShortcutMatcher};
14use script_bindings::codegen::GenericBindings::UIEventBinding::UIEventMethods;
15use script_bindings::match_domstring_ascii;
16use script_bindings::root::Dom;
17use script_bindings::trace::CustomTraceable;
18use script_traits::MouseButtons;
19use servo_base::generic_channel::GenericCallback;
20use servo_base::id::WebViewId;
21use servo_base::text::{Utf8CodeUnits, Utf16CodeUnits};
22use servo_base::{Rope, RopeIndex, RopeMovement, RopeSlice};
23
24use crate::dom::bindings::codegen::Bindings::EventBinding::Event_Binding::EventMethods;
25use crate::dom::bindings::inheritance::Castable;
26use crate::dom::bindings::refcounted::Trusted;
27use crate::dom::bindings::reflector::DomGlobal;
28use crate::dom::bindings::str::DOMString;
29use crate::dom::compositionevent::CompositionEvent;
30use crate::dom::event::Event;
31use crate::dom::eventtarget::EventTarget;
32use crate::dom::inputevent::{HitTestResult, InputEvent};
33use crate::dom::keyboardevent::KeyboardEvent;
34use crate::dom::mouseevent::MouseEvent;
35use crate::dom::text_control::TextControlElement;
36use crate::dom::types::{ClipboardEvent, HTMLInputElement, HTMLTextAreaElement, UIEvent};
37use crate::dom::{Element, NodeTraits};
38use crate::drag::drag_data_store::Kind;
39use crate::drag::drag_gesture::{DragGesture, DragHandler};
40
41pub trait ClipboardProvider {
44 fn get_text(&mut self) -> Result<String, String>;
46 fn set_text(&mut self, _: String);
48}
49
50#[derive(MallocSizeOf)]
51pub(crate) struct EmbedderClipboardProvider {
52 pub embedder_sender: ScriptToEmbedderChan,
53 pub webview_id: WebViewId,
54}
55
56impl ClipboardProvider for EmbedderClipboardProvider {
57 fn get_text(&mut self) -> Result<String, String> {
58 let (callback, rx) = GenericCallback::new_blocking().unwrap();
59 self.embedder_sender
60 .send(EmbedderMsg::GetClipboardText(self.webview_id, callback))
61 .unwrap();
62 rx.recv().unwrap()
63 }
64 fn set_text(&mut self, s: String) {
65 self.embedder_sender
66 .send(EmbedderMsg::SetClipboardText(self.webview_id, s))
67 .unwrap();
68 }
69}
70
71#[derive(Clone, Copy, PartialEq)]
72pub enum Selection {
73 Selected,
74 NotSelected,
75}
76
77#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)]
78pub enum SelectionDirection {
79 Forward,
80 Backward,
81 None,
82}
83
84impl From<DOMString> for SelectionDirection {
85 fn from(direction: DOMString) -> SelectionDirection {
86 match_domstring_ascii!(direction,
87 "forward" => SelectionDirection::Forward,
88 "backward" => SelectionDirection::Backward,
89 _ => SelectionDirection::None,
90 )
91 }
92}
93
94impl From<SelectionDirection> for DOMString {
95 fn from(direction: SelectionDirection) -> DOMString {
96 match direction {
97 SelectionDirection::Forward => DOMString::from("forward"),
98 SelectionDirection::Backward => DOMString::from("backward"),
99 SelectionDirection::None => DOMString::from("none"),
100 }
101 }
102}
103
104#[derive(Clone, Copy, JSTraceable, MallocSizeOf)]
105pub enum Lines {
106 Single,
107 Multiple,
108}
109
110impl Lines {
111 fn normalize(&self, contents: impl Into<String>) -> String {
112 let contents = contents.into().replace("\r\n", "\n");
113 match self {
114 Self::Multiple => {
115 contents.replace("\r", "\n")
117 },
118 Lines::Single => contents.replace(['\r', '\n'], " "),
122 }
123 }
124}
125
126#[derive(Clone, Copy, PartialEq)]
127pub(crate) struct SelectionState {
128 start: RopeIndex,
129 end: RopeIndex,
130 direction: SelectionDirection,
131}
132
133#[derive(JSTraceable, MallocSizeOf)]
135pub struct TextInput<T: ClipboardProvider> {
136 #[no_trace]
137 rope: Rope,
138
139 mode: Lines,
144
145 #[no_trace]
147 edit_point: RopeIndex,
148
149 #[no_trace]
152 selection_origin: Option<RopeIndex>,
153 selection_direction: SelectionDirection,
154
155 #[ignore_malloc_size_of = "Can't easily measure this generic type"]
156 clipboard_provider: T,
157
158 max_length: Option<Utf16CodeUnits>,
162 min_length: Option<Utf16CodeUnits>,
163
164 was_last_change_by_set_content: bool,
166}
167
168#[derive(Clone, Copy, PartialEq)]
169pub enum IsComposing {
170 Composing,
171 NotComposing,
172}
173
174impl From<IsComposing> for bool {
175 fn from(is_composing: IsComposing) -> Self {
176 match is_composing {
177 IsComposing::Composing => true,
178 IsComposing::NotComposing => false,
179 }
180 }
181}
182
183#[derive(Clone, Copy, PartialEq)]
185pub enum InputType {
186 InsertText,
187 InsertLineBreak,
188 InsertFromPaste,
189 InsertCompositionText,
190 DeleteByCut,
191 DeleteContentBackward,
192 DeleteContentForward,
193 Nothing,
194}
195
196impl InputType {
197 fn as_str(&self) -> &str {
198 match *self {
199 InputType::InsertText => "insertText",
200 InputType::InsertLineBreak => "insertLineBreak",
201 InputType::InsertFromPaste => "insertFromPaste",
202 InputType::InsertCompositionText => "insertCompositionText",
203 InputType::DeleteByCut => "deleteByCut",
204 InputType::DeleteContentBackward => "deleteContentBackward",
205 InputType::DeleteContentForward => "deleteContentForward",
206 InputType::Nothing => "",
207 }
208 }
209}
210
211pub enum KeyReaction {
213 TriggerDefaultAction,
214 DispatchInput(Option<String>, IsComposing, InputType),
215 RedrawSelection,
216 Nothing,
217}
218
219bitflags! {
220 #[derive(Clone, Copy)]
223 pub struct ClipboardEventFlags: u8 {
224 const QueueInputEvent = 1 << 0;
225 const FireClipboardChangedEvent = 1 << 1;
226 }
227}
228
229pub struct ClipboardEventReaction {
230 pub flags: ClipboardEventFlags,
231 pub text: Option<String>,
232 pub input_type: InputType,
233}
234
235impl ClipboardEventReaction {
236 fn new(flags: ClipboardEventFlags) -> Self {
237 Self {
238 flags,
239 text: None,
240 input_type: InputType::Nothing,
241 }
242 }
243
244 fn with_text(mut self, text: String) -> Self {
245 self.text = Some(text);
246 self
247 }
248
249 fn with_input_type(mut self, input_type: InputType) -> Self {
250 self.input_type = input_type;
251 self
252 }
253
254 fn empty() -> Self {
255 Self::new(ClipboardEventFlags::empty())
256 }
257}
258
259#[derive(Clone, Copy, Eq, PartialEq)]
261pub enum Direction {
262 Forward,
263 Backward,
264}
265
266#[cfg(target_os = "macos")]
268pub(crate) const CMD_OR_CONTROL: Modifiers = Modifiers::META;
269#[cfg(not(target_os = "macos"))]
270pub(crate) const CMD_OR_CONTROL: Modifiers = Modifiers::CONTROL;
271
272fn len_of_first_n_code_units(text: &DOMString, n: Utf16CodeUnits) -> Utf8CodeUnits {
276 let mut utf8_len = Utf8CodeUnits::zero();
277 let mut utf16_len = Utf16CodeUnits::zero();
278 for c in text.str().chars() {
279 utf16_len += Utf16CodeUnits(c.len_utf16());
280 if utf16_len > n {
281 break;
282 }
283 utf8_len += Utf8CodeUnits(c.len_utf8());
284 }
285 utf8_len
286}
287
288impl<T: ClipboardProvider> TextInput<T> {
289 pub fn new(lines: Lines, initial: DOMString, clipboard_provider: T) -> TextInput<T> {
291 Self {
292 rope: Rope::new(initial),
293 mode: lines,
294 edit_point: Default::default(),
295 selection_origin: None,
296 clipboard_provider,
297 max_length: Default::default(),
298 min_length: Default::default(),
299 selection_direction: SelectionDirection::None,
300 was_last_change_by_set_content: true,
301 }
302 }
303
304 pub fn edit_point(&self) -> RopeIndex {
305 self.edit_point
306 }
307
308 pub fn selection_origin(&self) -> Option<RopeIndex> {
309 self.selection_origin
310 }
311
312 pub fn selection_origin_or_edit_point(&self) -> RopeIndex {
315 self.selection_origin.unwrap_or(self.edit_point)
316 }
317
318 pub fn selection_direction(&self) -> SelectionDirection {
319 self.selection_direction
320 }
321
322 pub fn set_max_length(&mut self, length: Option<Utf16CodeUnits>) {
323 self.max_length = length;
324 }
325
326 pub fn set_min_length(&mut self, length: Option<Utf16CodeUnits>) {
327 self.min_length = length;
328 }
329
330 pub(crate) fn was_last_change_by_set_content(&self) -> bool {
332 self.was_last_change_by_set_content
333 }
334
335 fn delete_selection(&mut self) -> bool {
338 if self.selection_start() == self.selection_end() {
339 return false;
340 }
341 self.replace_selection(&DOMString::new());
342 true
343 }
344
345 pub fn delete_unit_or_selection(&mut self, unit: RopeMovement, direction: Direction) -> bool {
350 if !self.has_uncollapsed_selection() {
351 let amount = match direction {
352 Direction::Forward => 1,
353 Direction::Backward => -1,
354 };
355 self.modify_selection(amount, unit);
356 }
357 self.delete_selection()
358 }
359
360 pub fn insert<S: Into<String>>(&mut self, string: S) {
363 if self.selection_origin.is_none() {
364 self.selection_origin = Some(self.edit_point);
365 }
366 self.replace_selection(&DOMString::from(string.into()));
367 }
368
369 pub fn selection_start(&self) -> RopeIndex {
372 match self.selection_direction {
373 SelectionDirection::None | SelectionDirection::Forward => {
374 self.selection_origin_or_edit_point()
375 },
376 SelectionDirection::Backward => self.edit_point,
377 }
378 }
379
380 pub(crate) fn selection_start_utf16(&self) -> Utf16CodeUnits {
381 self.rope.index_to_utf16_offset(self.selection_start())
382 }
383
384 fn selection_start_offset(&self) -> Utf8CodeUnits {
386 self.rope.index_to_utf8_offset(self.selection_start())
387 }
388
389 pub fn selection_end(&self) -> RopeIndex {
392 match self.selection_direction {
393 SelectionDirection::None | SelectionDirection::Forward => self.edit_point,
394 SelectionDirection::Backward => self.selection_origin_or_edit_point(),
395 }
396 }
397
398 pub(crate) fn selection_end_utf16(&self) -> Utf16CodeUnits {
399 self.rope.index_to_utf16_offset(self.selection_end())
400 }
401
402 pub fn selection_end_offset(&self) -> Utf8CodeUnits {
404 self.rope.index_to_utf8_offset(self.selection_end())
405 }
406
407 #[inline]
410 pub(crate) fn has_uncollapsed_selection(&self) -> bool {
411 self.selection_origin
412 .is_some_and(|selection_origin| selection_origin != self.edit_point)
413 }
414
415 pub(crate) fn sorted_selection_offsets_range(&self) -> Range<Utf8CodeUnits> {
419 self.selection_start_offset()..self.selection_end_offset()
420 }
421
422 pub(crate) fn sorted_selection_character_offsets_range(&self) -> Range<usize> {
426 self.rope.index_to_character_offset(self.selection_start())..
427 self.rope.index_to_character_offset(self.selection_end())
428 }
429
430 pub(crate) fn selection_state(&self) -> SelectionState {
432 SelectionState {
433 start: self.selection_start(),
434 end: self.selection_end(),
435 direction: self.selection_direction,
436 }
437 }
438
439 fn assert_ok_selection(&self) {
441 debug!(
442 "edit_point: {:?}, selection_origin: {:?}, direction: {:?}",
443 self.edit_point, self.selection_origin, self.selection_direction
444 );
445
446 debug_assert_eq!(self.edit_point, self.rope.normalize_index(self.edit_point));
447 if let Some(selection_origin) = self.selection_origin {
448 debug_assert_eq!(
449 selection_origin,
450 self.rope.normalize_index(selection_origin)
451 );
452 match self.selection_direction {
453 SelectionDirection::None | SelectionDirection::Forward => {
454 debug_assert!(selection_origin <= self.edit_point)
455 },
456 SelectionDirection::Backward => debug_assert!(self.edit_point <= selection_origin),
457 }
458 }
459 }
460
461 fn selection_slice(&self) -> RopeSlice<'_> {
462 self.rope
463 .slice(Some(self.selection_start()), Some(self.selection_end()))
464 }
465
466 pub(crate) fn get_selection_text(&self) -> Option<String> {
467 let text: String = self.selection_slice().into();
468 if text.is_empty() {
469 return None;
470 }
471 Some(text)
472 }
473
474 fn selection_utf16_len(&self) -> Utf16CodeUnits {
476 Utf16CodeUnits(
477 self.selection_slice()
478 .chars()
479 .map(char::len_utf16)
480 .sum::<usize>(),
481 )
482 }
483
484 pub fn replace_selection(&mut self, insert: &DOMString) {
488 let string_to_insert = if let Some(max_length) = self.max_length {
489 let utf16_length_without_selection =
490 self.len_utf16().saturating_sub(self.selection_utf16_len());
491 let utf16_length_that_can_be_inserted =
492 max_length.saturating_sub(utf16_length_without_selection);
493 let Utf8CodeUnits(last_char_index) =
494 len_of_first_n_code_units(insert, utf16_length_that_can_be_inserted);
495 &insert.str()[..last_char_index]
496 } else {
497 &insert.str()
498 };
499 let string_to_insert = self.mode.normalize(string_to_insert);
500
501 let start = self.selection_start();
502 let end = self.selection_end();
503 let end_index_of_insertion = self.rope.replace_range(start..end, string_to_insert);
504
505 self.was_last_change_by_set_content = false;
506 self.clear_selection();
507 self.edit_point = end_index_of_insertion;
508 }
509
510 pub fn modify_edit_point(&mut self, amount: isize, movement: RopeMovement) {
511 if amount == 0 {
512 return;
513 }
514
515 if matches!(movement, RopeMovement::Line) || !self.has_uncollapsed_selection() {
518 self.clear_selection();
519 self.edit_point = self.rope.move_by(self.edit_point, movement, amount);
520 return;
521 }
522
523 let new_edit_point = if amount > 0 {
526 self.selection_end()
527 } else {
528 self.selection_start()
529 };
530 self.clear_selection();
531 self.edit_point = new_edit_point;
532 }
533
534 pub fn modify_selection(&mut self, amount: isize, movement: RopeMovement) {
535 let old_edit_point = self.edit_point;
536 self.edit_point = self.rope.move_by(old_edit_point, movement, amount);
537
538 if self.selection_origin.is_none() {
539 self.selection_origin = Some(old_edit_point);
540 }
541 self.update_selection_direction();
542 }
543
544 pub fn modify_selection_or_edit_point(
545 &mut self,
546 amount: isize,
547 movement: RopeMovement,
548 select: Selection,
549 ) {
550 match select {
551 Selection::Selected => self.modify_selection(amount, movement),
552 Selection::NotSelected => self.modify_edit_point(amount, movement),
553 }
554 self.assert_ok_selection();
555 }
556
557 fn update_selection_direction(&mut self) {
562 debug!(
563 "edit_point: {:?}, selection_origin: {:?}",
564 self.edit_point, self.selection_origin
565 );
566 self.selection_direction = if Some(self.edit_point) < self.selection_origin {
567 SelectionDirection::Backward
568 } else {
569 SelectionDirection::Forward
570 }
571 }
572
573 pub fn handle_return(&mut self) -> KeyReaction {
575 match self.mode {
576 Lines::Multiple => {
577 self.insert('\n');
578 KeyReaction::DispatchInput(
579 None,
580 IsComposing::NotComposing,
581 InputType::InsertLineBreak,
582 )
583 },
584 Lines::Single => KeyReaction::TriggerDefaultAction,
585 }
586 }
587
588 pub fn select_all(&mut self) {
590 self.selection_origin = Some(RopeIndex::default());
591 self.edit_point = self.rope.last_index();
592 self.selection_direction = SelectionDirection::Forward;
593 self.assert_ok_selection();
594 }
595
596 pub fn clear_selection(&mut self) {
598 self.selection_origin = None;
599 self.selection_direction = SelectionDirection::None;
600 }
601
602 pub(crate) fn clear_selection_to_end(&mut self) {
604 self.clear_selection();
605 self.edit_point = self.rope.last_index();
606 }
607
608 pub(crate) fn clear_selection_to_start(&mut self) {
609 self.clear_selection();
610 self.edit_point = Default::default();
611 }
612
613 pub(crate) fn handle_keydown(&mut self, event: &KeyboardEvent) -> KeyReaction {
615 let key = event.key();
616 let mods = event.modifiers();
617 self.handle_keydown_aux(key, mods, cfg!(target_os = "macos"))
618 }
619
620 pub fn handle_keydown_aux(
623 &mut self,
624 key: Key,
625 mut mods: Modifiers,
626 macos: bool,
627 ) -> KeyReaction {
628 let maybe_select = if mods.contains(Modifiers::SHIFT) {
629 Selection::Selected
630 } else {
631 Selection::NotSelected
632 };
633
634 let alt_or_control = if macos {
635 Modifiers::ALT
636 } else {
637 Modifiers::CONTROL
638 };
639
640 mods.remove(Modifiers::SHIFT);
641 ShortcutMatcher::new(KeyState::Down, key.clone(), mods)
642 .shortcut(Modifiers::CONTROL | Modifiers::ALT, 'B', || {
643 self.modify_selection_or_edit_point(-1, RopeMovement::Word, maybe_select);
644 KeyReaction::RedrawSelection
645 })
646 .shortcut(Modifiers::CONTROL | Modifiers::ALT, 'F', || {
647 self.modify_selection_or_edit_point(1, RopeMovement::Word, maybe_select);
648 KeyReaction::RedrawSelection
649 })
650 .shortcut(Modifiers::CONTROL | Modifiers::ALT, 'A', || {
651 self.modify_selection_or_edit_point(-1, RopeMovement::LineStartOrEnd, maybe_select);
652 KeyReaction::RedrawSelection
653 })
654 .shortcut(Modifiers::CONTROL | Modifiers::ALT, 'E', || {
655 self.modify_selection_or_edit_point(1, RopeMovement::LineStartOrEnd, maybe_select);
656 KeyReaction::RedrawSelection
657 })
658 .optional_shortcut(macos, Modifiers::CONTROL, 'A', || {
659 self.modify_selection_or_edit_point(-1, RopeMovement::LineStartOrEnd, maybe_select);
660 KeyReaction::RedrawSelection
661 })
662 .optional_shortcut(macos, Modifiers::CONTROL, 'E', || {
663 self.modify_selection_or_edit_point(1, RopeMovement::LineStartOrEnd, maybe_select);
664 KeyReaction::RedrawSelection
665 })
666 .shortcut(CMD_OR_CONTROL, 'A', || {
667 self.select_all();
668 KeyReaction::RedrawSelection
669 })
670 .shortcut(CMD_OR_CONTROL, 'X', || {
671 if let Some(text) = self.get_selection_text() {
672 self.clipboard_provider.set_text(text);
673 self.delete_selection();
674 }
675 KeyReaction::DispatchInput(None, IsComposing::NotComposing, InputType::DeleteByCut)
676 })
677 .shortcut(CMD_OR_CONTROL, 'C', || {
678 if let Some(text) = self.get_selection_text() {
680 self.clipboard_provider.set_text(text);
681 }
682 KeyReaction::DispatchInput(None, IsComposing::NotComposing, InputType::Nothing)
683 })
684 .shortcut(CMD_OR_CONTROL, 'V', || {
685 if let Ok(text_content) = self.clipboard_provider.get_text() {
686 self.insert(&text_content);
687 KeyReaction::DispatchInput(
688 Some(text_content),
689 IsComposing::NotComposing,
690 InputType::InsertFromPaste,
691 )
692 } else {
693 KeyReaction::DispatchInput(
694 Some("".to_string()),
695 IsComposing::NotComposing,
696 InputType::InsertFromPaste,
697 )
698 }
699 })
700 .shortcut(Modifiers::empty(), Key::Named(NamedKey::Delete), || {
701 if self.delete_unit_or_selection(RopeMovement::Grapheme, Direction::Forward) {
702 KeyReaction::DispatchInput(
703 None,
704 IsComposing::NotComposing,
705 InputType::DeleteContentForward,
706 )
707 } else {
708 KeyReaction::Nothing
709 }
710 })
711 .shortcut(Modifiers::empty(), Key::Named(NamedKey::Backspace), || {
712 if self.delete_unit_or_selection(RopeMovement::Grapheme, Direction::Backward) {
713 KeyReaction::DispatchInput(
714 None,
715 IsComposing::NotComposing,
716 InputType::DeleteContentBackward,
717 )
718 } else {
719 KeyReaction::Nothing
720 }
721 })
722 .shortcut(alt_or_control, Key::Named(NamedKey::Backspace), || {
723 if self.delete_unit_or_selection(RopeMovement::Word, Direction::Backward) {
724 KeyReaction::DispatchInput(
725 None,
726 IsComposing::NotComposing,
727 InputType::DeleteContentBackward,
728 )
729 } else {
730 KeyReaction::Nothing
731 }
732 })
733 .optional_shortcut(
734 macos,
735 Modifiers::META,
736 Key::Named(NamedKey::ArrowLeft),
737 || {
738 self.modify_selection_or_edit_point(
739 -1,
740 RopeMovement::LineStartOrEnd,
741 maybe_select,
742 );
743 KeyReaction::RedrawSelection
744 },
745 )
746 .optional_shortcut(
747 macos,
748 Modifiers::META,
749 Key::Named(NamedKey::ArrowRight),
750 || {
751 self.modify_selection_or_edit_point(
752 1,
753 RopeMovement::LineStartOrEnd,
754 maybe_select,
755 );
756 KeyReaction::RedrawSelection
757 },
758 )
759 .optional_shortcut(
760 macos,
761 Modifiers::META,
762 Key::Named(NamedKey::ArrowUp),
763 || {
764 self.modify_selection_or_edit_point(
765 -1,
766 RopeMovement::RopeStartOrEnd,
767 maybe_select,
768 );
769 KeyReaction::RedrawSelection
770 },
771 )
772 .optional_shortcut(
773 macos,
774 Modifiers::META,
775 Key::Named(NamedKey::ArrowDown),
776 || {
777 self.modify_selection_or_edit_point(
778 1,
779 RopeMovement::RopeStartOrEnd,
780 maybe_select,
781 );
782 KeyReaction::RedrawSelection
783 },
784 )
785 .shortcut(alt_or_control, Key::Named(NamedKey::ArrowLeft), || {
786 self.modify_selection_or_edit_point(-1, RopeMovement::Word, maybe_select);
787 KeyReaction::RedrawSelection
788 })
789 .shortcut(alt_or_control, Key::Named(NamedKey::ArrowRight), || {
790 self.modify_selection_or_edit_point(1, RopeMovement::Word, maybe_select);
791 KeyReaction::RedrawSelection
792 })
793 .shortcut(Modifiers::empty(), Key::Named(NamedKey::ArrowLeft), || {
794 self.modify_selection_or_edit_point(-1, RopeMovement::Grapheme, maybe_select);
795 KeyReaction::RedrawSelection
796 })
797 .shortcut(Modifiers::empty(), Key::Named(NamedKey::ArrowRight), || {
798 self.modify_selection_or_edit_point(1, RopeMovement::Grapheme, maybe_select);
799 KeyReaction::RedrawSelection
800 })
801 .shortcut(Modifiers::empty(), Key::Named(NamedKey::ArrowUp), || {
802 self.modify_selection_or_edit_point(-1, RopeMovement::Line, maybe_select);
803 KeyReaction::RedrawSelection
804 })
805 .shortcut(Modifiers::empty(), Key::Named(NamedKey::ArrowDown), || {
806 self.modify_selection_or_edit_point(1, RopeMovement::Line, maybe_select);
807 KeyReaction::RedrawSelection
808 })
809 .shortcut(Modifiers::empty(), Key::Named(NamedKey::Enter), || {
810 self.handle_return()
811 })
812 .optional_shortcut(
813 macos,
814 Modifiers::empty(),
815 Key::Named(NamedKey::Home),
816 || {
817 self.modify_selection_or_edit_point(
818 -1,
819 RopeMovement::RopeStartOrEnd,
820 maybe_select,
821 );
822 KeyReaction::RedrawSelection
823 },
824 )
825 .optional_shortcut(macos, Modifiers::empty(), Key::Named(NamedKey::End), || {
826 self.modify_selection_or_edit_point(1, RopeMovement::RopeStartOrEnd, maybe_select);
827 KeyReaction::RedrawSelection
828 })
829 .shortcut(Modifiers::empty(), Key::Named(NamedKey::PageUp), || {
830 self.modify_selection_or_edit_point(-28, RopeMovement::Line, maybe_select);
831 KeyReaction::RedrawSelection
832 })
833 .shortcut(Modifiers::empty(), Key::Named(NamedKey::PageDown), || {
834 self.modify_selection_or_edit_point(28, RopeMovement::Line, maybe_select);
835 KeyReaction::RedrawSelection
836 })
837 .otherwise(|| {
838 if let Key::Character(ref character) = key {
839 self.insert(character);
840 return KeyReaction::DispatchInput(
841 Some(character.to_string()),
842 IsComposing::NotComposing,
843 InputType::InsertText,
844 );
845 }
846 if matches!(key, Key::Named(NamedKey::Process)) {
847 return KeyReaction::DispatchInput(
848 None,
849 IsComposing::Composing,
850 InputType::Nothing,
851 );
852 }
853 KeyReaction::Nothing
854 })
855 .unwrap()
856 }
857
858 pub(crate) fn handle_compositionend(&mut self, event: &CompositionEvent) -> KeyReaction {
859 let insertion = event.data().str();
860 if insertion.is_empty() {
861 self.clear_selection();
862 return KeyReaction::RedrawSelection;
863 }
864
865 self.insert(insertion.to_string());
866 KeyReaction::DispatchInput(
867 Some(insertion.to_string()),
868 IsComposing::NotComposing,
869 InputType::InsertCompositionText,
870 )
871 }
872
873 pub(crate) fn handle_compositionupdate(&mut self, event: &CompositionEvent) -> KeyReaction {
874 let insertion = event.data().str();
875 if insertion.is_empty() {
876 return KeyReaction::Nothing;
877 }
878
879 let start = self.selection_start_offset();
880 let insertion = insertion.to_string();
881 self.insert(insertion.clone());
882 self.set_selection_range_utf8(
883 start,
884 start + event.data().len_utf8(),
885 SelectionDirection::Forward,
886 );
887 KeyReaction::DispatchInput(
888 Some(insertion),
889 IsComposing::Composing,
890 InputType::InsertCompositionText,
891 )
892 }
893
894 fn edit_point_for_hit_test_result(&self, hit_test_result: &HitTestResult) -> RopeIndex {
895 hit_test_result
896 .dom_position_for_selection
897 .as_ref()
898 .map(|(_, character_offset)| {
899 self.rope.move_by(
900 Default::default(),
901 RopeMovement::Character,
902 character_offset.0 as isize,
903 )
904 })
905 .unwrap_or_else(|| self.rope.last_index())
906 }
907
908 fn drag_moved(&mut self, element: &impl TextControlElement, hit_test_result: &HitTestResult) {
909 let point_in_viewport = hit_test_result.point_in_frame.map(Au::from_f32_px);
910 self.edit_point = element
911 .owner_window()
912 .text_index_query_on_node_for_event(element.upcast(), point_in_viewport)
913 .map(|(_, character_offset)| {
914 self.rope.move_by(
915 Default::default(),
916 RopeMovement::Character,
917 character_offset.0 as isize,
918 )
919 })
920 .unwrap_or_else(|| self.rope.last_index());
921
922 self.update_selection_direction();
923 }
924
925 pub(crate) fn handle_mousedown_event(
930 &mut self,
931 element: &Element,
932 mouse_event: &MouseEvent,
933 hit_test_result: &HitTestResult,
934 ) -> bool {
935 assert_eq!(mouse_event.upcast::<Event>().type_(), atom!("mousedown"));
936
937 let button = mouse_event.button();
938 let selection_changed = match mouse_event.upcast::<UIEvent>().Detail() {
939 3 if button == MouseButton::Primary => {
940 let word_boundaries = self.rope.line_boundaries(self.edit_point);
941 self.edit_point = word_boundaries.end;
942 self.selection_origin = Some(word_boundaries.start);
943 self.update_selection_direction();
944 true
945 },
946 2 if button == MouseButton::Primary => {
947 let word_boundaries = self.rope.relevant_word_boundaries(self.edit_point);
948 self.edit_point = word_boundaries.end;
949 self.selection_origin = Some(word_boundaries.start);
950 self.update_selection_direction();
951 true
952 },
953 1 if matches!(button, MouseButton::Primary | MouseButton::Auxiliary) => {
954 self.clear_selection();
955 self.edit_point = self.edit_point_for_hit_test_result(hit_test_result);
956 self.selection_origin = Some(self.edit_point);
957 self.update_selection_direction();
958 true
959 },
960 _ => {
961 false
965 },
966 };
967
968 if selection_changed && mouse_event.buttons().contains(MouseButtons::Primary) {
969 element
970 .owner_document()
971 .event_handler()
972 .install_drag_gesture(DragGesture::new(DragHandler::TextInputSelection(
973 TextInputSelectionDragHandler(Dom::from_ref(element)),
974 )));
975 }
976
977 selection_changed
978 }
979
980 pub(crate) fn is_empty(&self) -> bool {
982 self.rope.is_empty()
983 }
984
985 pub(crate) fn len_utf16(&self) -> Utf16CodeUnits {
987 self.rope.len_utf16()
988 }
989
990 pub fn get_content(&self) -> DOMString {
992 self.rope.contents().into()
993 }
994
995 pub fn set_content(&mut self, content: DOMString) {
1002 self.rope = Rope::new(content.str().replace("\r\n", "\n").replace("\r", "\n"));
1003 self.was_last_change_by_set_content = true;
1004
1005 self.edit_point = self.rope.normalize_index(self.edit_point());
1006 self.selection_origin = self
1007 .selection_origin
1008 .map(|selection_origin| self.rope.normalize_index(selection_origin));
1009 }
1010
1011 pub fn set_selection_range_utf16(
1012 &mut self,
1013 start: Utf16CodeUnits,
1014 end: Utf16CodeUnits,
1015 direction: SelectionDirection,
1016 ) {
1017 self.set_selection_range_utf8(
1018 self.rope.utf16_offset_to_utf8_offset(start),
1019 self.rope.utf16_offset_to_utf8_offset(end),
1020 direction,
1021 );
1022 }
1023
1024 pub fn set_selection_range_utf8(
1025 &mut self,
1026 mut start: Utf8CodeUnits,
1027 mut end: Utf8CodeUnits,
1028 direction: SelectionDirection,
1029 ) {
1030 let text_end = self.get_content().len_utf8();
1031 if end > text_end {
1032 end = text_end;
1033 }
1034 if start > end {
1035 start = end;
1036 }
1037
1038 self.selection_direction = direction;
1039
1040 match direction {
1041 SelectionDirection::None | SelectionDirection::Forward => {
1042 self.selection_origin = Some(self.rope.utf8_offset_to_rope_index(start));
1043 self.edit_point = self.rope.utf8_offset_to_rope_index(end);
1044 },
1045 SelectionDirection::Backward => {
1046 self.selection_origin = Some(self.rope.utf8_offset_to_rope_index(end));
1047 self.edit_point = self.rope.utf8_offset_to_rope_index(start);
1048 },
1049 }
1050
1051 self.assert_ok_selection();
1052 }
1053
1054 pub(crate) fn handle_clipboard_event(
1062 &mut self,
1063 clipboard_event: &ClipboardEvent,
1064 ) -> ClipboardEventReaction {
1065 let event = clipboard_event.upcast::<Event>();
1066 if !event.IsTrusted() {
1067 return ClipboardEventReaction::empty();
1068 }
1069
1070 if event.DefaultPrevented() {
1073 return ClipboardEventReaction::empty();
1076 }
1077
1078 let event_type = event.Type();
1079 match_domstring_ascii!(event_type,
1080 "copy" => {
1081 let selection = self.get_selection_text();
1083
1084 if let Some(text) = selection {
1086 self.clipboard_provider.set_text(text);
1087 }
1088
1089 ClipboardEventReaction::new(ClipboardEventFlags::FireClipboardChangedEvent)
1091 },
1092 "cut" => {
1093 let selection = self.get_selection_text();
1095
1096 let Some(text) = selection else {
1098 return ClipboardEventReaction::empty();
1100 };
1101
1102 self.clipboard_provider.set_text(text);
1104
1105 self.delete_selection();
1107
1108 ClipboardEventReaction::new(
1111 ClipboardEventFlags::FireClipboardChangedEvent |
1112 ClipboardEventFlags::QueueInputEvent,
1113 )
1114 .with_input_type(InputType::DeleteByCut)
1115 },
1116 "paste" => {
1117 let Some(data_transfer) = clipboard_event.get_clipboard_data() else {
1119 return ClipboardEventReaction::empty();
1120 };
1121 let Some(drag_data_store) = data_transfer.data_store() else {
1122 return ClipboardEventReaction::empty();
1123 };
1124
1125 let Some(text_content) =
1135 drag_data_store
1136 .iter_item_list()
1137 .find_map(|item| match item {
1138 Kind::Text { data, .. } => Some(data.to_string()),
1139 _ => None,
1140 })
1141 else {
1142 return ClipboardEventReaction::empty();
1143 };
1144 if text_content.is_empty() {
1145 return ClipboardEventReaction::empty();
1146 }
1147
1148 self.insert(&text_content);
1149
1150 ClipboardEventReaction::new(ClipboardEventFlags::QueueInputEvent)
1153 .with_text(text_content)
1154 .with_input_type(InputType::InsertFromPaste)
1155 },
1156 _ => ClipboardEventReaction::empty(),)
1157 }
1158
1159 pub(crate) fn queue_input_event(
1161 &self,
1162 target: &EventTarget,
1163 data: Option<String>,
1164 is_composing: IsComposing,
1165 input_type: InputType,
1166 ) {
1167 let global = target.global();
1168 let target = Trusted::new(target);
1169 global.task_manager().user_interaction_task_source().queue(
1170 task!(fire_input_event: move |cx| {
1171 let target = target.root();
1172 let global = target.global();
1173 let window = global.as_window();
1174 let event = InputEvent::new(
1175 cx,
1176 window,
1177 None,
1178 atom!("input"),
1179 true,
1180 false,
1181 Some(window),
1182 0,
1183 data.map(DOMString::from),
1184 is_composing.into(),
1185 input_type.as_str().into(),
1186 );
1187 let event = event.upcast::<Event>();
1188 event.set_composed(true);
1189 event.fire(cx, &target);
1190 }),
1191 );
1192 }
1193}
1194
1195#[derive(JSTraceable, MallocSizeOf)]
1196#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
1197pub(crate) struct TextInputSelectionDragHandler(Dom<Element>);
1198
1199impl TextInputSelectionDragHandler {
1200 pub(crate) fn still_connected(&self) -> bool {
1201 self.0.is_connected()
1202 }
1203
1204 pub(crate) fn moved(&self, hit_test_result: &HitTestResult) -> bool {
1208 if !self.0.is_connected() {
1209 return false;
1210 }
1211
1212 if let Some(input) = self.0.downcast::<HTMLInputElement>() {
1213 input.textinput_mut().drag_moved(input, hit_test_result);
1214 input.maybe_update_shared_selection();
1215 true
1216 } else if let Some(text_area) = self.0.downcast::<HTMLTextAreaElement>() {
1217 text_area
1218 .textinput_mut()
1219 .drag_moved(text_area, hit_test_result);
1220 text_area.maybe_update_shared_selection();
1221 true
1222 } else {
1223 false
1224 }
1225 }
1226}