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::{RangeAny, Utf8CodeUnits, Utf16CodeUnits, Utf32CodeUnits};
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_static("forward"),
98 SelectionDirection::Backward => DOMString::from_static("backward"),
99 SelectionDirection::None => DOMString::from_static("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 #[no_trace]
168 pub(crate) previous_selection_range: Range<RopeIndex>,
169 #[no_trace]
170 pub(crate) selection_for_layout: Option<RangeAny<Utf32CodeUnits>>,
171}
172
173#[derive(Clone, Copy, PartialEq)]
174pub enum IsComposing {
175 Composing,
176 NotComposing,
177}
178
179impl From<IsComposing> for bool {
180 fn from(is_composing: IsComposing) -> Self {
181 match is_composing {
182 IsComposing::Composing => true,
183 IsComposing::NotComposing => false,
184 }
185 }
186}
187
188#[derive(Clone, Copy, PartialEq)]
190pub enum InputType {
191 InsertText,
192 InsertLineBreak,
193 InsertFromPaste,
194 InsertCompositionText,
195 DeleteByCut,
196 DeleteContentBackward,
197 DeleteContentForward,
198 Nothing,
199}
200
201impl InputType {
202 fn as_str(&self) -> &str {
203 match *self {
204 InputType::InsertText => "insertText",
205 InputType::InsertLineBreak => "insertLineBreak",
206 InputType::InsertFromPaste => "insertFromPaste",
207 InputType::InsertCompositionText => "insertCompositionText",
208 InputType::DeleteByCut => "deleteByCut",
209 InputType::DeleteContentBackward => "deleteContentBackward",
210 InputType::DeleteContentForward => "deleteContentForward",
211 InputType::Nothing => "",
212 }
213 }
214}
215
216pub enum KeyReaction {
218 TriggerDefaultAction,
219 DispatchInput(Option<String>, IsComposing, InputType),
220 RedrawSelection,
221 Nothing,
222}
223
224bitflags! {
225 #[derive(Clone, Copy)]
228 pub struct ClipboardEventFlags: u8 {
229 const QueueInputEvent = 1 << 0;
230 const FireClipboardChangedEvent = 1 << 1;
231 }
232}
233
234pub struct ClipboardEventReaction {
235 pub flags: ClipboardEventFlags,
236 pub text: Option<String>,
237 pub input_type: InputType,
238}
239
240impl ClipboardEventReaction {
241 fn new(flags: ClipboardEventFlags) -> Self {
242 Self {
243 flags,
244 text: None,
245 input_type: InputType::Nothing,
246 }
247 }
248
249 fn with_text(mut self, text: String) -> Self {
250 self.text = Some(text);
251 self
252 }
253
254 fn with_input_type(mut self, input_type: InputType) -> Self {
255 self.input_type = input_type;
256 self
257 }
258
259 fn empty() -> Self {
260 Self::new(ClipboardEventFlags::empty())
261 }
262}
263
264#[derive(Clone, Copy, Eq, PartialEq)]
266pub enum Direction {
267 Forward,
268 Backward,
269}
270
271#[cfg(target_os = "macos")]
273pub(crate) const CMD_OR_CONTROL: Modifiers = Modifiers::META;
274#[cfg(not(target_os = "macos"))]
275pub(crate) const CMD_OR_CONTROL: Modifiers = Modifiers::CONTROL;
276
277fn len_of_first_n_code_units(text: &DOMString, n: Utf16CodeUnits) -> Utf8CodeUnits {
281 let mut utf8_len = Utf8CodeUnits::zero();
282 let mut utf16_len = Utf16CodeUnits::zero();
283 for c in text.str().chars() {
284 utf16_len += Utf16CodeUnits(c.len_utf16());
285 if utf16_len > n {
286 break;
287 }
288 utf8_len += Utf8CodeUnits(c.len_utf8());
289 }
290 utf8_len
291}
292
293impl<T: ClipboardProvider> TextInput<T> {
294 pub fn new(lines: Lines, initial: DOMString, clipboard_provider: T) -> TextInput<T> {
296 Self {
297 rope: Rope::new(initial),
298 mode: lines,
299 edit_point: Default::default(),
300 selection_origin: None,
301 clipboard_provider,
302 max_length: Default::default(),
303 min_length: Default::default(),
304 selection_direction: SelectionDirection::None,
305 was_last_change_by_set_content: true,
306 previous_selection_range: Default::default(),
307 selection_for_layout: None,
308 }
309 }
310
311 pub fn edit_point(&self) -> RopeIndex {
312 self.edit_point
313 }
314
315 pub fn selection_origin(&self) -> Option<RopeIndex> {
316 self.selection_origin
317 }
318
319 pub fn selection_origin_or_edit_point(&self) -> RopeIndex {
322 self.selection_origin.unwrap_or(self.edit_point)
323 }
324
325 pub fn selection_direction(&self) -> SelectionDirection {
326 self.selection_direction
327 }
328
329 pub fn set_max_length(&mut self, length: Option<Utf16CodeUnits>) {
330 self.max_length = length;
331 }
332
333 pub fn set_min_length(&mut self, length: Option<Utf16CodeUnits>) {
334 self.min_length = length;
335 }
336
337 pub(crate) fn was_last_change_by_set_content(&self) -> bool {
339 self.was_last_change_by_set_content
340 }
341
342 fn delete_selection(&mut self) -> bool {
345 if self.selection_start() == self.selection_end() {
346 return false;
347 }
348 self.replace_selection(&DOMString::new());
349 true
350 }
351
352 pub fn delete_unit_or_selection(&mut self, unit: RopeMovement, direction: Direction) -> bool {
357 if !self.has_uncollapsed_selection() {
358 let amount = match direction {
359 Direction::Forward => 1,
360 Direction::Backward => -1,
361 };
362 self.modify_selection(amount, unit);
363 }
364 self.delete_selection()
365 }
366
367 pub fn insert<S: Into<String>>(&mut self, string: S) {
370 if self.selection_origin.is_none() {
371 self.selection_origin = Some(self.edit_point);
372 }
373 self.replace_selection(&DOMString::from(string.into()));
374 }
375
376 pub fn selection_start(&self) -> RopeIndex {
379 match self.selection_direction {
380 SelectionDirection::None | SelectionDirection::Forward => {
381 self.selection_origin_or_edit_point()
382 },
383 SelectionDirection::Backward => self.edit_point,
384 }
385 }
386
387 pub(crate) fn selection_start_utf16(&self) -> Utf16CodeUnits {
388 self.rope.index_to_utf16_offset(self.selection_start())
389 }
390
391 fn selection_start_offset(&self) -> Utf8CodeUnits {
393 self.rope.index_to_utf8_offset(self.selection_start())
394 }
395
396 pub fn selection_end(&self) -> RopeIndex {
399 match self.selection_direction {
400 SelectionDirection::None | SelectionDirection::Forward => self.edit_point,
401 SelectionDirection::Backward => self.selection_origin_or_edit_point(),
402 }
403 }
404
405 pub(crate) fn selection_end_utf16(&self) -> Utf16CodeUnits {
406 self.rope.index_to_utf16_offset(self.selection_end())
407 }
408
409 #[inline]
412 pub(crate) fn has_uncollapsed_selection(&self) -> bool {
413 self.selection_origin
414 .is_some_and(|selection_origin| selection_origin != self.edit_point)
415 }
416
417 pub(crate) fn sorted_selection_character_offsets_range(&self) -> RangeAny<Utf32CodeUnits> {
424 let rope = &self.rope;
425 let start = self.selection_start();
426 let end = self.selection_end();
427 let start = (start != rope.first_index()).then(|| rope.index_to_character_offset(start));
428 let end = Some(rope.index_to_character_offset(end));
433 RangeAny { start, end }
434 }
435
436 pub(crate) fn selection_state(&self) -> SelectionState {
438 SelectionState {
439 start: self.selection_start(),
440 end: self.selection_end(),
441 direction: self.selection_direction,
442 }
443 }
444
445 fn assert_ok_selection(&self) {
447 debug!(
448 "edit_point: {:?}, selection_origin: {:?}, direction: {:?}",
449 self.edit_point, self.selection_origin, self.selection_direction
450 );
451
452 debug_assert_eq!(self.edit_point, self.rope.normalize_index(self.edit_point));
453 if let Some(selection_origin) = self.selection_origin {
454 debug_assert_eq!(
455 selection_origin,
456 self.rope.normalize_index(selection_origin)
457 );
458 match self.selection_direction {
459 SelectionDirection::None | SelectionDirection::Forward => {
460 debug_assert!(selection_origin <= self.edit_point)
461 },
462 SelectionDirection::Backward => debug_assert!(self.edit_point <= selection_origin),
463 }
464 }
465 }
466
467 fn selection_slice(&self) -> RopeSlice<'_> {
468 self.rope
469 .slice(Some(self.selection_start()), Some(self.selection_end()))
470 }
471
472 pub(crate) fn get_selection_text(&self) -> Option<String> {
473 let text: String = self.selection_slice().into();
474 if text.is_empty() {
475 return None;
476 }
477 Some(text)
478 }
479
480 fn selection_utf16_len(&self) -> Utf16CodeUnits {
482 Utf16CodeUnits(
483 self.selection_slice()
484 .chars()
485 .map(char::len_utf16)
486 .sum::<usize>(),
487 )
488 }
489
490 pub fn replace_selection(&mut self, insert: &DOMString) {
494 let string_to_insert = if let Some(max_length) = self.max_length {
495 let utf16_length_without_selection =
496 self.len_utf16().saturating_sub(self.selection_utf16_len());
497 let utf16_length_that_can_be_inserted =
498 max_length.saturating_sub(utf16_length_without_selection);
499 let Utf8CodeUnits(last_char_index) =
500 len_of_first_n_code_units(insert, utf16_length_that_can_be_inserted);
501 &insert.str()[..last_char_index]
502 } else {
503 &insert.str()
504 };
505 let string_to_insert = self.mode.normalize(string_to_insert);
506
507 let start = self.selection_start();
508 let end = self.selection_end();
509 let end_index_of_insertion = self.rope.replace_range(start..end, string_to_insert);
510
511 self.was_last_change_by_set_content = false;
512 self.clear_selection();
513 self.edit_point = end_index_of_insertion;
514 }
515
516 pub fn modify_edit_point(&mut self, amount: isize, movement: RopeMovement) {
517 if amount == 0 {
518 return;
519 }
520
521 if matches!(movement, RopeMovement::Line) || !self.has_uncollapsed_selection() {
524 self.clear_selection();
525 self.edit_point = self.rope.move_by(self.edit_point, movement, amount);
526 return;
527 }
528
529 let new_edit_point = if amount > 0 {
532 self.selection_end()
533 } else {
534 self.selection_start()
535 };
536 self.clear_selection();
537 self.edit_point = new_edit_point;
538 }
539
540 pub fn modify_selection(&mut self, amount: isize, movement: RopeMovement) {
541 let old_edit_point = self.edit_point;
542 self.edit_point = self.rope.move_by(old_edit_point, movement, amount);
543
544 if self.selection_origin.is_none() {
545 self.selection_origin = Some(old_edit_point);
546 }
547 self.update_selection_direction();
548 }
549
550 pub fn modify_selection_or_edit_point(
551 &mut self,
552 amount: isize,
553 movement: RopeMovement,
554 select: Selection,
555 ) {
556 match select {
557 Selection::Selected => self.modify_selection(amount, movement),
558 Selection::NotSelected => self.modify_edit_point(amount, movement),
559 }
560 self.assert_ok_selection();
561 }
562
563 fn update_selection_direction(&mut self) {
568 debug!(
569 "edit_point: {:?}, selection_origin: {:?}",
570 self.edit_point, self.selection_origin
571 );
572 self.selection_direction = if Some(self.edit_point) < self.selection_origin {
573 SelectionDirection::Backward
574 } else {
575 SelectionDirection::Forward
576 }
577 }
578
579 pub fn handle_return(&mut self) -> KeyReaction {
581 match self.mode {
582 Lines::Multiple => {
583 self.insert('\n');
584 KeyReaction::DispatchInput(
585 None,
586 IsComposing::NotComposing,
587 InputType::InsertLineBreak,
588 )
589 },
590 Lines::Single => KeyReaction::TriggerDefaultAction,
591 }
592 }
593
594 pub fn select_all(&mut self) {
596 self.selection_origin = Some(RopeIndex::default());
597 self.edit_point = self.rope.last_index();
598 self.selection_direction = SelectionDirection::Forward;
599 self.assert_ok_selection();
600 }
601
602 pub fn clear_selection(&mut self) {
604 self.selection_origin = None;
605 self.selection_direction = SelectionDirection::None;
606 }
607
608 pub(crate) fn clear_selection_to_end(&mut self) {
610 self.clear_selection();
611 self.edit_point = self.rope.last_index();
612 }
613
614 pub(crate) fn clear_selection_to_start(&mut self) {
615 self.clear_selection();
616 self.edit_point = Default::default();
617 }
618
619 pub(crate) fn handle_keydown(&mut self, event: &KeyboardEvent) -> KeyReaction {
621 let key = event.key();
622 let mods = event.modifiers();
623 self.handle_keydown_aux(key, mods, cfg!(target_os = "macos"))
624 }
625
626 pub fn handle_keydown_aux(
629 &mut self,
630 key: Key,
631 mut mods: Modifiers,
632 macos: bool,
633 ) -> KeyReaction {
634 let maybe_select = if mods.contains(Modifiers::SHIFT) {
635 Selection::Selected
636 } else {
637 Selection::NotSelected
638 };
639
640 let alt_or_control = if macos {
641 Modifiers::ALT
642 } else {
643 Modifiers::CONTROL
644 };
645
646 mods.remove(Modifiers::SHIFT);
647 ShortcutMatcher::new(KeyState::Down, key.clone(), mods)
648 .shortcut(Modifiers::CONTROL | Modifiers::ALT, 'B', || {
649 self.modify_selection_or_edit_point(-1, RopeMovement::Word, maybe_select);
650 KeyReaction::RedrawSelection
651 })
652 .shortcut(Modifiers::CONTROL | Modifiers::ALT, 'F', || {
653 self.modify_selection_or_edit_point(1, RopeMovement::Word, maybe_select);
654 KeyReaction::RedrawSelection
655 })
656 .shortcut(Modifiers::CONTROL | Modifiers::ALT, 'A', || {
657 self.modify_selection_or_edit_point(-1, RopeMovement::LineStartOrEnd, maybe_select);
658 KeyReaction::RedrawSelection
659 })
660 .shortcut(Modifiers::CONTROL | Modifiers::ALT, 'E', || {
661 self.modify_selection_or_edit_point(1, RopeMovement::LineStartOrEnd, maybe_select);
662 KeyReaction::RedrawSelection
663 })
664 .optional_shortcut(macos, Modifiers::CONTROL, 'A', || {
665 self.modify_selection_or_edit_point(-1, RopeMovement::LineStartOrEnd, maybe_select);
666 KeyReaction::RedrawSelection
667 })
668 .optional_shortcut(macos, Modifiers::CONTROL, 'E', || {
669 self.modify_selection_or_edit_point(1, RopeMovement::LineStartOrEnd, maybe_select);
670 KeyReaction::RedrawSelection
671 })
672 .shortcut(CMD_OR_CONTROL, 'A', || {
673 self.select_all();
674 KeyReaction::RedrawSelection
675 })
676 .shortcut(CMD_OR_CONTROL, 'X', || {
677 if let Some(text) = self.get_selection_text() {
678 self.clipboard_provider.set_text(text);
679 self.delete_selection();
680 }
681 KeyReaction::DispatchInput(None, IsComposing::NotComposing, InputType::DeleteByCut)
682 })
683 .shortcut(CMD_OR_CONTROL, 'C', || {
684 if let Some(text) = self.get_selection_text() {
686 self.clipboard_provider.set_text(text);
687 }
688 KeyReaction::DispatchInput(None, IsComposing::NotComposing, InputType::Nothing)
689 })
690 .shortcut(CMD_OR_CONTROL, 'V', || {
691 if let Ok(text_content) = self.clipboard_provider.get_text() {
692 self.insert(&text_content);
693 KeyReaction::DispatchInput(
694 Some(text_content),
695 IsComposing::NotComposing,
696 InputType::InsertFromPaste,
697 )
698 } else {
699 KeyReaction::DispatchInput(
700 Some("".to_string()),
701 IsComposing::NotComposing,
702 InputType::InsertFromPaste,
703 )
704 }
705 })
706 .shortcut(Modifiers::empty(), Key::Named(NamedKey::Delete), || {
707 if self.delete_unit_or_selection(RopeMovement::Grapheme, Direction::Forward) {
708 KeyReaction::DispatchInput(
709 None,
710 IsComposing::NotComposing,
711 InputType::DeleteContentForward,
712 )
713 } else {
714 KeyReaction::Nothing
715 }
716 })
717 .shortcut(Modifiers::empty(), Key::Named(NamedKey::Backspace), || {
718 if self.delete_unit_or_selection(RopeMovement::Grapheme, Direction::Backward) {
719 KeyReaction::DispatchInput(
720 None,
721 IsComposing::NotComposing,
722 InputType::DeleteContentBackward,
723 )
724 } else {
725 KeyReaction::Nothing
726 }
727 })
728 .shortcut(alt_or_control, Key::Named(NamedKey::Backspace), || {
729 if self.delete_unit_or_selection(RopeMovement::Word, Direction::Backward) {
730 KeyReaction::DispatchInput(
731 None,
732 IsComposing::NotComposing,
733 InputType::DeleteContentBackward,
734 )
735 } else {
736 KeyReaction::Nothing
737 }
738 })
739 .optional_shortcut(
740 macos,
741 Modifiers::META,
742 Key::Named(NamedKey::ArrowLeft),
743 || {
744 self.modify_selection_or_edit_point(
745 -1,
746 RopeMovement::LineStartOrEnd,
747 maybe_select,
748 );
749 KeyReaction::RedrawSelection
750 },
751 )
752 .optional_shortcut(
753 macos,
754 Modifiers::META,
755 Key::Named(NamedKey::ArrowRight),
756 || {
757 self.modify_selection_or_edit_point(
758 1,
759 RopeMovement::LineStartOrEnd,
760 maybe_select,
761 );
762 KeyReaction::RedrawSelection
763 },
764 )
765 .optional_shortcut(
766 macos,
767 Modifiers::META,
768 Key::Named(NamedKey::ArrowUp),
769 || {
770 self.modify_selection_or_edit_point(
771 -1,
772 RopeMovement::RopeStartOrEnd,
773 maybe_select,
774 );
775 KeyReaction::RedrawSelection
776 },
777 )
778 .optional_shortcut(
779 macos,
780 Modifiers::META,
781 Key::Named(NamedKey::ArrowDown),
782 || {
783 self.modify_selection_or_edit_point(
784 1,
785 RopeMovement::RopeStartOrEnd,
786 maybe_select,
787 );
788 KeyReaction::RedrawSelection
789 },
790 )
791 .shortcut(alt_or_control, Key::Named(NamedKey::ArrowLeft), || {
792 self.modify_selection_or_edit_point(-1, RopeMovement::Word, maybe_select);
793 KeyReaction::RedrawSelection
794 })
795 .shortcut(alt_or_control, Key::Named(NamedKey::ArrowRight), || {
796 self.modify_selection_or_edit_point(1, RopeMovement::Word, maybe_select);
797 KeyReaction::RedrawSelection
798 })
799 .shortcut(Modifiers::empty(), Key::Named(NamedKey::ArrowLeft), || {
800 self.modify_selection_or_edit_point(-1, RopeMovement::Grapheme, maybe_select);
801 KeyReaction::RedrawSelection
802 })
803 .shortcut(Modifiers::empty(), Key::Named(NamedKey::ArrowRight), || {
804 self.modify_selection_or_edit_point(1, RopeMovement::Grapheme, maybe_select);
805 KeyReaction::RedrawSelection
806 })
807 .shortcut(Modifiers::empty(), Key::Named(NamedKey::ArrowUp), || {
808 self.modify_selection_or_edit_point(-1, RopeMovement::Line, maybe_select);
809 KeyReaction::RedrawSelection
810 })
811 .shortcut(Modifiers::empty(), Key::Named(NamedKey::ArrowDown), || {
812 self.modify_selection_or_edit_point(1, RopeMovement::Line, maybe_select);
813 KeyReaction::RedrawSelection
814 })
815 .shortcut(Modifiers::empty(), Key::Named(NamedKey::Enter), || {
816 self.handle_return()
817 })
818 .optional_shortcut(
819 macos,
820 Modifiers::empty(),
821 Key::Named(NamedKey::Home),
822 || {
823 self.modify_selection_or_edit_point(
824 -1,
825 RopeMovement::RopeStartOrEnd,
826 maybe_select,
827 );
828 KeyReaction::RedrawSelection
829 },
830 )
831 .optional_shortcut(macos, Modifiers::empty(), Key::Named(NamedKey::End), || {
832 self.modify_selection_or_edit_point(1, RopeMovement::RopeStartOrEnd, maybe_select);
833 KeyReaction::RedrawSelection
834 })
835 .shortcut(Modifiers::empty(), Key::Named(NamedKey::PageUp), || {
836 self.modify_selection_or_edit_point(-28, RopeMovement::Line, maybe_select);
837 KeyReaction::RedrawSelection
838 })
839 .shortcut(Modifiers::empty(), Key::Named(NamedKey::PageDown), || {
840 self.modify_selection_or_edit_point(28, RopeMovement::Line, maybe_select);
841 KeyReaction::RedrawSelection
842 })
843 .otherwise(|| {
844 if let Key::Character(ref character) = key {
845 self.insert(character);
846 return KeyReaction::DispatchInput(
847 Some(character.to_string()),
848 IsComposing::NotComposing,
849 InputType::InsertText,
850 );
851 }
852 if matches!(key, Key::Named(NamedKey::Process)) {
853 return KeyReaction::DispatchInput(
854 None,
855 IsComposing::Composing,
856 InputType::Nothing,
857 );
858 }
859 KeyReaction::Nothing
860 })
861 .unwrap()
862 }
863
864 pub(crate) fn handle_compositionend(&mut self, event: &CompositionEvent) -> KeyReaction {
865 let insertion = event.data().str();
866 if insertion.is_empty() {
867 self.clear_selection();
868 return KeyReaction::RedrawSelection;
869 }
870
871 self.insert(insertion.to_string());
872 KeyReaction::DispatchInput(
873 Some(insertion.to_string()),
874 IsComposing::NotComposing,
875 InputType::InsertCompositionText,
876 )
877 }
878
879 pub(crate) fn handle_compositionupdate(&mut self, event: &CompositionEvent) -> KeyReaction {
880 let insertion = event.data().str();
881 if insertion.is_empty() {
882 return KeyReaction::Nothing;
883 }
884
885 let start = self.selection_start_offset();
886 let insertion = insertion.to_string();
887 self.insert(insertion.clone());
888 self.set_selection_range_utf8(
889 start,
890 start + event.data().len_utf8(),
891 SelectionDirection::Forward,
892 );
893 KeyReaction::DispatchInput(
894 Some(insertion),
895 IsComposing::Composing,
896 InputType::InsertCompositionText,
897 )
898 }
899
900 fn edit_point_for_hit_test_result(&self, hit_test_result: &HitTestResult) -> RopeIndex {
901 hit_test_result
902 .dom_position_for_selection
903 .as_ref()
904 .map(|(_, character_offset)| {
905 self.rope.move_by(
906 Default::default(),
907 RopeMovement::Character,
908 character_offset.0 as isize,
909 )
910 })
911 .unwrap_or_else(|| self.rope.last_index())
912 }
913
914 fn drag_moved(&mut self, element: &impl TextControlElement, hit_test_result: &HitTestResult) {
915 let point_in_viewport = hit_test_result.point_in_frame.map(Au::from_f32_px);
916 self.edit_point = element
917 .owner_window()
918 .text_index_query_on_node_for_event(element.upcast(), point_in_viewport)
919 .map(|(_, character_offset)| {
920 self.rope.move_by(
921 Default::default(),
922 RopeMovement::Character,
923 character_offset.0 as isize,
924 )
925 })
926 .unwrap_or_else(|| self.rope.last_index());
927
928 self.update_selection_direction();
929 }
930
931 pub(crate) fn handle_mousedown_event(
936 &mut self,
937 element: &Element,
938 mouse_event: &MouseEvent,
939 hit_test_result: &HitTestResult,
940 ) -> bool {
941 assert_eq!(mouse_event.upcast::<Event>().type_(), atom!("mousedown"));
942
943 let button = mouse_event.button();
944 let selection_changed = match mouse_event.upcast::<UIEvent>().Detail() {
945 3 if button == MouseButton::Primary => {
946 let word_boundaries = self.rope.line_boundaries(self.edit_point);
947 self.edit_point = word_boundaries.end;
948 self.selection_origin = Some(word_boundaries.start);
949 self.update_selection_direction();
950 true
951 },
952 2 if button == MouseButton::Primary => {
953 let word_boundaries = self.rope.relevant_word_boundaries(self.edit_point);
954 self.edit_point = word_boundaries.end;
955 self.selection_origin = Some(word_boundaries.start);
956 self.update_selection_direction();
957 true
958 },
959 1 if matches!(button, MouseButton::Primary | MouseButton::Auxiliary) => {
960 self.clear_selection();
961 self.edit_point = self.edit_point_for_hit_test_result(hit_test_result);
962 self.selection_origin = Some(self.edit_point);
963 self.update_selection_direction();
964 true
965 },
966 _ => {
967 false
971 },
972 };
973
974 if selection_changed && mouse_event.buttons().contains(MouseButtons::Primary) {
975 element
976 .owner_document()
977 .event_handler()
978 .install_drag_gesture(DragGesture::new(DragHandler::TextInputSelection(
979 TextInputSelectionDragHandler(Dom::from_ref(element)),
980 )));
981 }
982
983 selection_changed
984 }
985
986 pub(crate) fn is_empty(&self) -> bool {
988 self.rope.is_empty()
989 }
990
991 pub(crate) fn len_utf16(&self) -> Utf16CodeUnits {
993 self.rope.len_utf16()
994 }
995
996 pub fn get_content(&self) -> DOMString {
998 self.rope.contents().into()
999 }
1000
1001 pub fn set_content(&mut self, content: DOMString) {
1008 self.rope = Rope::new(content.str().replace("\r\n", "\n").replace("\r", "\n"));
1009 self.was_last_change_by_set_content = true;
1010
1011 self.edit_point = self.rope.normalize_index(self.edit_point());
1012 self.selection_origin = self
1013 .selection_origin
1014 .map(|selection_origin| self.rope.normalize_index(selection_origin));
1015 }
1016
1017 pub fn set_selection_range_utf16(
1018 &mut self,
1019 start: Utf16CodeUnits,
1020 end: Utf16CodeUnits,
1021 direction: SelectionDirection,
1022 ) {
1023 self.set_selection_range_utf8(
1024 self.rope.utf16_offset_to_utf8_offset(start),
1025 self.rope.utf16_offset_to_utf8_offset(end),
1026 direction,
1027 );
1028 }
1029
1030 pub fn set_selection_range_utf8(
1031 &mut self,
1032 mut start: Utf8CodeUnits,
1033 mut end: Utf8CodeUnits,
1034 direction: SelectionDirection,
1035 ) {
1036 let text_end = self.get_content().len_utf8();
1037 if end > text_end {
1038 end = text_end;
1039 }
1040 if start > end {
1041 start = end;
1042 }
1043
1044 self.selection_direction = direction;
1045
1046 match direction {
1047 SelectionDirection::None | SelectionDirection::Forward => {
1048 self.selection_origin = Some(self.rope.utf8_offset_to_rope_index(start));
1049 self.edit_point = self.rope.utf8_offset_to_rope_index(end);
1050 },
1051 SelectionDirection::Backward => {
1052 self.selection_origin = Some(self.rope.utf8_offset_to_rope_index(end));
1053 self.edit_point = self.rope.utf8_offset_to_rope_index(start);
1054 },
1055 }
1056
1057 self.assert_ok_selection();
1058 }
1059
1060 pub(crate) fn handle_clipboard_event(
1068 &mut self,
1069 clipboard_event: &ClipboardEvent,
1070 ) -> ClipboardEventReaction {
1071 let event = clipboard_event.upcast::<Event>();
1072 if !event.IsTrusted() {
1073 return ClipboardEventReaction::empty();
1074 }
1075
1076 if event.DefaultPrevented() {
1079 return ClipboardEventReaction::empty();
1082 }
1083
1084 let event_type = event.Type();
1085 match_domstring_ascii!(event_type,
1086 "copy" => {
1087 let selection = self.get_selection_text();
1089
1090 if let Some(text) = selection {
1092 self.clipboard_provider.set_text(text);
1093 }
1094
1095 ClipboardEventReaction::new(ClipboardEventFlags::FireClipboardChangedEvent)
1097 },
1098 "cut" => {
1099 let selection = self.get_selection_text();
1101
1102 let Some(text) = selection else {
1104 return ClipboardEventReaction::empty();
1106 };
1107
1108 self.clipboard_provider.set_text(text);
1110
1111 self.delete_selection();
1113
1114 ClipboardEventReaction::new(
1117 ClipboardEventFlags::FireClipboardChangedEvent |
1118 ClipboardEventFlags::QueueInputEvent,
1119 )
1120 .with_input_type(InputType::DeleteByCut)
1121 },
1122 "paste" => {
1123 let Some(data_transfer) = clipboard_event.get_clipboard_data() else {
1125 return ClipboardEventReaction::empty();
1126 };
1127 let Some(drag_data_store) = data_transfer.data_store() else {
1128 return ClipboardEventReaction::empty();
1129 };
1130
1131 let Some(text_content) =
1141 drag_data_store
1142 .iter_item_list()
1143 .find_map(|item| match item {
1144 Kind::Text { data, .. } => Some(data.to_string()),
1145 _ => None,
1146 })
1147 else {
1148 return ClipboardEventReaction::empty();
1149 };
1150 if text_content.is_empty() {
1151 return ClipboardEventReaction::empty();
1152 }
1153
1154 self.insert(&text_content);
1155
1156 ClipboardEventReaction::new(ClipboardEventFlags::QueueInputEvent)
1159 .with_text(text_content)
1160 .with_input_type(InputType::InsertFromPaste)
1161 },
1162 _ => ClipboardEventReaction::empty(),)
1163 }
1164
1165 pub(crate) fn queue_input_event(
1167 &self,
1168 target: &EventTarget,
1169 data: Option<String>,
1170 is_composing: IsComposing,
1171 input_type: InputType,
1172 ) {
1173 let global = target.global();
1174 let target = Trusted::new(target);
1175 global.task_manager().user_interaction_task_source().queue(
1176 task!(fire_input_event: move |cx| {
1177 let target = target.root();
1178 let global = target.global();
1179 let window = global.as_window();
1180 let event = InputEvent::new(
1181 cx,
1182 window,
1183 None,
1184 atom!("input"),
1185 true,
1186 false,
1187 Some(window),
1188 0,
1189 data.map(DOMString::from),
1190 is_composing.into(),
1191 input_type.as_str().into(),
1192 );
1193 let event = event.upcast::<Event>();
1194 event.set_composed(true);
1195 event.fire(cx, &target);
1196 }),
1197 );
1198 }
1199}
1200
1201#[derive(JSTraceable, MallocSizeOf)]
1202#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
1203pub(crate) struct TextInputSelectionDragHandler(Dom<Element>);
1204
1205impl TextInputSelectionDragHandler {
1206 pub(crate) fn still_connected(&self) -> bool {
1207 self.0.is_connected()
1208 }
1209
1210 pub(crate) fn moved(&self, hit_test_result: &HitTestResult) -> bool {
1214 if !self.0.is_connected() {
1215 return false;
1216 }
1217
1218 if let Some(input) = self.0.downcast::<HTMLInputElement>() {
1219 input.textinput_mut().drag_moved(input, hit_test_result);
1220 input.maybe_update_shared_selection();
1221 true
1222 } else if let Some(text_area) = self.0.downcast::<HTMLTextAreaElement>() {
1223 text_area
1224 .textinput_mut()
1225 .drag_moved(text_area, hit_test_result);
1226 text_area.maybe_update_shared_selection();
1227 true
1228 } else {
1229 false
1230 }
1231 }
1232}