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