1use std::default::Default;
8use std::ops::Range;
9
10use bitflags::bitflags;
11use keyboard_types::{Key, KeyState, Modifiers, NamedKey, ShortcutMatcher};
12use script_bindings::codegen::GenericBindings::MouseEventBinding::MouseEventMethods;
13use script_bindings::codegen::GenericBindings::UIEventBinding::UIEventMethods;
14use script_bindings::match_domstring_ascii;
15use script_bindings::trace::CustomTraceable;
16use servo_base::text::{Utf8CodeUnitLength, Utf16CodeUnitLength};
17use servo_base::{Rope, RopeIndex, RopeMovement, RopeSlice};
18
19use crate::clipboard_provider::ClipboardProvider;
20use crate::dom::bindings::codegen::Bindings::EventBinding::Event_Binding::EventMethods;
21use crate::dom::bindings::inheritance::Castable;
22use crate::dom::bindings::refcounted::Trusted;
23use crate::dom::bindings::reflector::DomGlobal;
24use crate::dom::bindings::str::DOMString;
25use crate::dom::compositionevent::CompositionEvent;
26use crate::dom::event::Event;
27use crate::dom::eventtarget::EventTarget;
28use crate::dom::inputevent::InputEvent;
29use crate::dom::keyboardevent::KeyboardEvent;
30use crate::dom::mouseevent::MouseEvent;
31use crate::dom::node::{Node, NodeTraits};
32use crate::dom::types::{ClipboardEvent, UIEvent};
33use crate::drag_data_store::Kind;
34use crate::script_runtime::CanGc;
35
36#[derive(Clone, Copy, PartialEq)]
37pub enum Selection {
38 Selected,
39 NotSelected,
40}
41
42#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)]
43pub enum SelectionDirection {
44 Forward,
45 Backward,
46 None,
47}
48
49impl From<DOMString> for SelectionDirection {
50 fn from(direction: DOMString) -> SelectionDirection {
51 match_domstring_ascii!(direction,
52 "forward" => SelectionDirection::Forward,
53 "backward" => SelectionDirection::Backward,
54 _ => SelectionDirection::None,
55 )
56 }
57}
58
59impl From<SelectionDirection> for DOMString {
60 fn from(direction: SelectionDirection) -> DOMString {
61 match direction {
62 SelectionDirection::Forward => DOMString::from("forward"),
63 SelectionDirection::Backward => DOMString::from("backward"),
64 SelectionDirection::None => DOMString::from("none"),
65 }
66 }
67}
68
69#[derive(Clone, Copy, JSTraceable, MallocSizeOf)]
70pub enum Lines {
71 Single,
72 Multiple,
73}
74
75impl Lines {
76 fn normalize(&self, contents: impl Into<String>) -> String {
77 let contents = contents.into().replace("\r\n", "\n");
78 match self {
79 Self::Multiple => {
80 contents.replace("\r", "\n")
82 },
83 Lines::Single => contents.replace(['\r', '\n'], " "),
87 }
88 }
89}
90
91#[derive(Clone, Copy, PartialEq)]
92pub(crate) struct SelectionState {
93 start: RopeIndex,
94 end: RopeIndex,
95 direction: SelectionDirection,
96}
97
98#[derive(JSTraceable, MallocSizeOf)]
100pub struct TextInput<T: ClipboardProvider> {
101 #[no_trace]
102 rope: Rope,
103
104 mode: Lines,
109
110 #[no_trace]
112 edit_point: RopeIndex,
113
114 #[no_trace]
117 selection_origin: Option<RopeIndex>,
118 selection_direction: SelectionDirection,
119
120 #[ignore_malloc_size_of = "Can't easily measure this generic type"]
121 clipboard_provider: T,
122
123 max_length: Option<Utf16CodeUnitLength>,
127 min_length: Option<Utf16CodeUnitLength>,
128
129 was_last_change_by_set_content: bool,
131
132 currently_dragging: bool,
134}
135
136#[derive(Clone, Copy, PartialEq)]
137pub enum IsComposing {
138 Composing,
139 NotComposing,
140}
141
142impl From<IsComposing> for bool {
143 fn from(is_composing: IsComposing) -> Self {
144 match is_composing {
145 IsComposing::Composing => true,
146 IsComposing::NotComposing => false,
147 }
148 }
149}
150
151#[derive(Clone, Copy, PartialEq)]
153pub enum InputType {
154 InsertText,
155 InsertLineBreak,
156 InsertFromPaste,
157 InsertCompositionText,
158 DeleteByCut,
159 DeleteContentBackward,
160 DeleteContentForward,
161 Nothing,
162}
163
164impl InputType {
165 fn as_str(&self) -> &str {
166 match *self {
167 InputType::InsertText => "insertText",
168 InputType::InsertLineBreak => "insertLineBreak",
169 InputType::InsertFromPaste => "insertFromPaste",
170 InputType::InsertCompositionText => "insertCompositionText",
171 InputType::DeleteByCut => "deleteByCut",
172 InputType::DeleteContentBackward => "deleteContentBackward",
173 InputType::DeleteContentForward => "deleteContentForward",
174 InputType::Nothing => "",
175 }
176 }
177}
178
179pub enum KeyReaction {
181 TriggerDefaultAction,
182 DispatchInput(Option<String>, IsComposing, InputType),
183 RedrawSelection,
184 Nothing,
185}
186
187bitflags! {
188 #[derive(Clone, Copy)]
191 pub struct ClipboardEventFlags: u8 {
192 const QueueInputEvent = 1 << 0;
193 const FireClipboardChangedEvent = 1 << 1;
194 }
195}
196
197pub struct ClipboardEventReaction {
198 pub flags: ClipboardEventFlags,
199 pub text: Option<String>,
200 pub input_type: InputType,
201}
202
203impl ClipboardEventReaction {
204 fn new(flags: ClipboardEventFlags) -> Self {
205 Self {
206 flags,
207 text: None,
208 input_type: InputType::Nothing,
209 }
210 }
211
212 fn with_text(mut self, text: String) -> Self {
213 self.text = Some(text);
214 self
215 }
216
217 fn with_input_type(mut self, input_type: InputType) -> Self {
218 self.input_type = input_type;
219 self
220 }
221
222 fn empty() -> Self {
223 Self::new(ClipboardEventFlags::empty())
224 }
225}
226
227#[derive(Clone, Copy, Eq, PartialEq)]
229pub enum Direction {
230 Forward,
231 Backward,
232}
233
234#[cfg(target_os = "macos")]
236pub(crate) const CMD_OR_CONTROL: Modifiers = Modifiers::META;
237#[cfg(not(target_os = "macos"))]
238pub(crate) const CMD_OR_CONTROL: Modifiers = Modifiers::CONTROL;
239
240fn len_of_first_n_code_units(text: &DOMString, n: Utf16CodeUnitLength) -> Utf8CodeUnitLength {
244 let mut utf8_len = Utf8CodeUnitLength::zero();
245 let mut utf16_len = Utf16CodeUnitLength::zero();
246 for c in text.str().chars() {
247 utf16_len += Utf16CodeUnitLength(c.len_utf16());
248 if utf16_len > n {
249 break;
250 }
251 utf8_len += Utf8CodeUnitLength(c.len_utf8());
252 }
253 utf8_len
254}
255
256impl<T: ClipboardProvider> TextInput<T> {
257 pub fn new(lines: Lines, initial: DOMString, clipboard_provider: T) -> TextInput<T> {
259 Self {
260 rope: Rope::new(initial),
261 mode: lines,
262 edit_point: Default::default(),
263 selection_origin: None,
264 clipboard_provider,
265 max_length: Default::default(),
266 min_length: Default::default(),
267 selection_direction: SelectionDirection::None,
268 was_last_change_by_set_content: true,
269 currently_dragging: Default::default(),
270 }
271 }
272
273 pub fn edit_point(&self) -> RopeIndex {
274 self.edit_point
275 }
276
277 pub fn selection_origin(&self) -> Option<RopeIndex> {
278 self.selection_origin
279 }
280
281 pub fn selection_origin_or_edit_point(&self) -> RopeIndex {
284 self.selection_origin.unwrap_or(self.edit_point)
285 }
286
287 pub fn selection_direction(&self) -> SelectionDirection {
288 self.selection_direction
289 }
290
291 pub fn set_max_length(&mut self, length: Option<Utf16CodeUnitLength>) {
292 self.max_length = length;
293 }
294
295 pub fn set_min_length(&mut self, length: Option<Utf16CodeUnitLength>) {
296 self.min_length = length;
297 }
298
299 pub(crate) fn was_last_change_by_set_content(&self) -> bool {
301 self.was_last_change_by_set_content
302 }
303
304 fn delete_selection(&mut self) -> bool {
307 if self.selection_start() == self.selection_end() {
308 return false;
309 }
310 self.replace_selection(&DOMString::new());
311 true
312 }
313
314 pub fn delete_unit_or_selection(&mut self, unit: RopeMovement, direction: Direction) -> bool {
319 if !self.has_uncollapsed_selection() {
320 let amount = match direction {
321 Direction::Forward => 1,
322 Direction::Backward => -1,
323 };
324 self.modify_selection(amount, unit);
325 }
326 self.delete_selection()
327 }
328
329 pub fn insert<S: Into<String>>(&mut self, string: S) {
332 if self.selection_origin.is_none() {
333 self.selection_origin = Some(self.edit_point);
334 }
335 self.replace_selection(&DOMString::from(string.into()));
336 }
337
338 pub fn selection_start(&self) -> RopeIndex {
341 match self.selection_direction {
342 SelectionDirection::None | SelectionDirection::Forward => {
343 self.selection_origin_or_edit_point()
344 },
345 SelectionDirection::Backward => self.edit_point,
346 }
347 }
348
349 pub(crate) fn selection_start_utf16(&self) -> Utf16CodeUnitLength {
350 self.rope.index_to_utf16_offset(self.selection_start())
351 }
352
353 fn selection_start_offset(&self) -> Utf8CodeUnitLength {
355 self.rope.index_to_utf8_offset(self.selection_start())
356 }
357
358 pub fn selection_end(&self) -> RopeIndex {
361 match self.selection_direction {
362 SelectionDirection::None | SelectionDirection::Forward => self.edit_point,
363 SelectionDirection::Backward => self.selection_origin_or_edit_point(),
364 }
365 }
366
367 pub(crate) fn selection_end_utf16(&self) -> Utf16CodeUnitLength {
368 self.rope.index_to_utf16_offset(self.selection_end())
369 }
370
371 pub fn selection_end_offset(&self) -> Utf8CodeUnitLength {
373 self.rope.index_to_utf8_offset(self.selection_end())
374 }
375
376 #[inline]
379 pub(crate) fn has_uncollapsed_selection(&self) -> bool {
380 self.selection_origin
381 .is_some_and(|selection_origin| selection_origin != self.edit_point)
382 }
383
384 pub(crate) fn sorted_selection_offsets_range(&self) -> Range<Utf8CodeUnitLength> {
388 self.selection_start_offset()..self.selection_end_offset()
389 }
390
391 pub(crate) fn sorted_selection_character_offsets_range(&self) -> Range<usize> {
395 self.rope.index_to_character_offset(self.selection_start())..
396 self.rope.index_to_character_offset(self.selection_end())
397 }
398
399 pub(crate) fn selection_state(&self) -> SelectionState {
401 SelectionState {
402 start: self.selection_start(),
403 end: self.selection_end(),
404 direction: self.selection_direction,
405 }
406 }
407
408 fn assert_ok_selection(&self) {
410 debug!(
411 "edit_point: {:?}, selection_origin: {:?}, direction: {:?}",
412 self.edit_point, self.selection_origin, self.selection_direction
413 );
414
415 debug_assert_eq!(self.edit_point, self.rope.normalize_index(self.edit_point));
416 if let Some(selection_origin) = self.selection_origin {
417 debug_assert_eq!(
418 selection_origin,
419 self.rope.normalize_index(selection_origin)
420 );
421 match self.selection_direction {
422 SelectionDirection::None | SelectionDirection::Forward => {
423 debug_assert!(selection_origin <= self.edit_point)
424 },
425 SelectionDirection::Backward => debug_assert!(self.edit_point <= selection_origin),
426 }
427 }
428 }
429
430 fn selection_slice(&self) -> RopeSlice<'_> {
431 self.rope
432 .slice(Some(self.selection_start()), Some(self.selection_end()))
433 }
434
435 pub(crate) fn get_selection_text(&self) -> Option<String> {
436 let text: String = self.selection_slice().into();
437 if text.is_empty() {
438 return None;
439 }
440 Some(text)
441 }
442
443 fn selection_utf16_len(&self) -> Utf16CodeUnitLength {
445 Utf16CodeUnitLength(
446 self.selection_slice()
447 .chars()
448 .map(char::len_utf16)
449 .sum::<usize>(),
450 )
451 }
452
453 pub fn replace_selection(&mut self, insert: &DOMString) {
457 let string_to_insert = if let Some(max_length) = self.max_length {
458 let utf16_length_without_selection =
459 self.len_utf16().saturating_sub(self.selection_utf16_len());
460 let utf16_length_that_can_be_inserted =
461 max_length.saturating_sub(utf16_length_without_selection);
462 let Utf8CodeUnitLength(last_char_index) =
463 len_of_first_n_code_units(insert, utf16_length_that_can_be_inserted);
464 &insert.str()[..last_char_index]
465 } else {
466 &insert.str()
467 };
468 let string_to_insert = self.mode.normalize(string_to_insert);
469
470 let start = self.selection_start();
471 let end = self.selection_end();
472 let end_index_of_insertion = self.rope.replace_range(start..end, string_to_insert);
473
474 self.was_last_change_by_set_content = false;
475 self.clear_selection();
476 self.edit_point = end_index_of_insertion;
477 }
478
479 pub fn modify_edit_point(&mut self, amount: isize, movement: RopeMovement) {
480 if amount == 0 {
481 return;
482 }
483
484 if matches!(movement, RopeMovement::Line) || !self.has_uncollapsed_selection() {
487 self.clear_selection();
488 self.edit_point = self.rope.move_by(self.edit_point, movement, amount);
489 return;
490 }
491
492 let new_edit_point = if amount > 0 {
495 self.selection_end()
496 } else {
497 self.selection_start()
498 };
499 self.clear_selection();
500 self.edit_point = new_edit_point;
501 }
502
503 pub fn modify_selection(&mut self, amount: isize, movement: RopeMovement) {
504 let old_edit_point = self.edit_point;
505 self.edit_point = self.rope.move_by(old_edit_point, movement, amount);
506
507 if self.selection_origin.is_none() {
508 self.selection_origin = Some(old_edit_point);
509 }
510 self.update_selection_direction();
511 }
512
513 pub fn modify_selection_or_edit_point(
514 &mut self,
515 amount: isize,
516 movement: RopeMovement,
517 select: Selection,
518 ) {
519 match select {
520 Selection::Selected => self.modify_selection(amount, movement),
521 Selection::NotSelected => self.modify_edit_point(amount, movement),
522 }
523 self.assert_ok_selection();
524 }
525
526 fn update_selection_direction(&mut self) {
531 debug!(
532 "edit_point: {:?}, selection_origin: {:?}",
533 self.edit_point, self.selection_origin
534 );
535 self.selection_direction = if Some(self.edit_point) < self.selection_origin {
536 SelectionDirection::Backward
537 } else {
538 SelectionDirection::Forward
539 }
540 }
541
542 pub fn handle_return(&mut self) -> KeyReaction {
544 match self.mode {
545 Lines::Multiple => {
546 self.insert('\n');
547 KeyReaction::DispatchInput(
548 None,
549 IsComposing::NotComposing,
550 InputType::InsertLineBreak,
551 )
552 },
553 Lines::Single => KeyReaction::TriggerDefaultAction,
554 }
555 }
556
557 pub fn select_all(&mut self) {
559 self.selection_origin = Some(RopeIndex::default());
560 self.edit_point = self.rope.last_index();
561 self.selection_direction = SelectionDirection::Forward;
562 self.assert_ok_selection();
563 }
564
565 pub fn clear_selection(&mut self) {
567 self.selection_origin = None;
568 self.selection_direction = SelectionDirection::None;
569 }
570
571 pub(crate) fn clear_selection_to_end(&mut self) {
573 self.clear_selection();
574 self.edit_point = self.rope.last_index();
575 }
576
577 pub(crate) fn clear_selection_to_start(&mut self) {
578 self.clear_selection();
579 self.edit_point = Default::default();
580 }
581
582 pub(crate) fn handle_keydown(&mut self, event: &KeyboardEvent) -> KeyReaction {
584 let key = event.key();
585 let mods = event.modifiers();
586 self.handle_keydown_aux(key, mods, cfg!(target_os = "macos"))
587 }
588
589 pub fn handle_keydown_aux(
592 &mut self,
593 key: Key,
594 mut mods: Modifiers,
595 macos: bool,
596 ) -> KeyReaction {
597 let maybe_select = if mods.contains(Modifiers::SHIFT) {
598 Selection::Selected
599 } else {
600 Selection::NotSelected
601 };
602
603 let alt_or_control = if macos {
604 Modifiers::ALT
605 } else {
606 Modifiers::CONTROL
607 };
608
609 mods.remove(Modifiers::SHIFT);
610 ShortcutMatcher::new(KeyState::Down, key.clone(), mods)
611 .shortcut(Modifiers::CONTROL | Modifiers::ALT, 'B', || {
612 self.modify_selection_or_edit_point(-1, RopeMovement::Word, maybe_select);
613 KeyReaction::RedrawSelection
614 })
615 .shortcut(Modifiers::CONTROL | Modifiers::ALT, 'F', || {
616 self.modify_selection_or_edit_point(1, RopeMovement::Word, maybe_select);
617 KeyReaction::RedrawSelection
618 })
619 .shortcut(Modifiers::CONTROL | Modifiers::ALT, 'A', || {
620 self.modify_selection_or_edit_point(-1, RopeMovement::LineStartOrEnd, maybe_select);
621 KeyReaction::RedrawSelection
622 })
623 .shortcut(Modifiers::CONTROL | Modifiers::ALT, 'E', || {
624 self.modify_selection_or_edit_point(1, RopeMovement::LineStartOrEnd, maybe_select);
625 KeyReaction::RedrawSelection
626 })
627 .optional_shortcut(macos, Modifiers::CONTROL, 'A', || {
628 self.modify_selection_or_edit_point(-1, RopeMovement::LineStartOrEnd, maybe_select);
629 KeyReaction::RedrawSelection
630 })
631 .optional_shortcut(macos, Modifiers::CONTROL, 'E', || {
632 self.modify_selection_or_edit_point(1, RopeMovement::LineStartOrEnd, maybe_select);
633 KeyReaction::RedrawSelection
634 })
635 .shortcut(CMD_OR_CONTROL, 'A', || {
636 self.select_all();
637 KeyReaction::RedrawSelection
638 })
639 .shortcut(CMD_OR_CONTROL, 'X', || {
640 if let Some(text) = self.get_selection_text() {
641 self.clipboard_provider.set_text(text);
642 self.delete_selection();
643 }
644 KeyReaction::DispatchInput(None, IsComposing::NotComposing, InputType::DeleteByCut)
645 })
646 .shortcut(CMD_OR_CONTROL, 'C', || {
647 if let Some(text) = self.get_selection_text() {
649 self.clipboard_provider.set_text(text);
650 }
651 KeyReaction::DispatchInput(None, IsComposing::NotComposing, InputType::Nothing)
652 })
653 .shortcut(CMD_OR_CONTROL, 'V', || {
654 if let Ok(text_content) = self.clipboard_provider.get_text() {
655 self.insert(&text_content);
656 KeyReaction::DispatchInput(
657 Some(text_content),
658 IsComposing::NotComposing,
659 InputType::InsertFromPaste,
660 )
661 } else {
662 KeyReaction::DispatchInput(
663 Some("".to_string()),
664 IsComposing::NotComposing,
665 InputType::InsertFromPaste,
666 )
667 }
668 })
669 .shortcut(Modifiers::empty(), Key::Named(NamedKey::Delete), || {
670 if self.delete_unit_or_selection(RopeMovement::Grapheme, Direction::Forward) {
671 KeyReaction::DispatchInput(
672 None,
673 IsComposing::NotComposing,
674 InputType::DeleteContentForward,
675 )
676 } else {
677 KeyReaction::Nothing
678 }
679 })
680 .shortcut(Modifiers::empty(), Key::Named(NamedKey::Backspace), || {
681 if self.delete_unit_or_selection(RopeMovement::Grapheme, Direction::Backward) {
682 KeyReaction::DispatchInput(
683 None,
684 IsComposing::NotComposing,
685 InputType::DeleteContentBackward,
686 )
687 } else {
688 KeyReaction::Nothing
689 }
690 })
691 .shortcut(alt_or_control, Key::Named(NamedKey::Backspace), || {
692 if self.delete_unit_or_selection(RopeMovement::Word, Direction::Backward) {
693 KeyReaction::DispatchInput(
694 None,
695 IsComposing::NotComposing,
696 InputType::DeleteContentBackward,
697 )
698 } else {
699 KeyReaction::Nothing
700 }
701 })
702 .optional_shortcut(
703 macos,
704 Modifiers::META,
705 Key::Named(NamedKey::ArrowLeft),
706 || {
707 self.modify_selection_or_edit_point(
708 -1,
709 RopeMovement::LineStartOrEnd,
710 maybe_select,
711 );
712 KeyReaction::RedrawSelection
713 },
714 )
715 .optional_shortcut(
716 macos,
717 Modifiers::META,
718 Key::Named(NamedKey::ArrowRight),
719 || {
720 self.modify_selection_or_edit_point(
721 1,
722 RopeMovement::LineStartOrEnd,
723 maybe_select,
724 );
725 KeyReaction::RedrawSelection
726 },
727 )
728 .optional_shortcut(
729 macos,
730 Modifiers::META,
731 Key::Named(NamedKey::ArrowUp),
732 || {
733 self.modify_selection_or_edit_point(
734 -1,
735 RopeMovement::RopeStartOrEnd,
736 maybe_select,
737 );
738 KeyReaction::RedrawSelection
739 },
740 )
741 .optional_shortcut(
742 macos,
743 Modifiers::META,
744 Key::Named(NamedKey::ArrowDown),
745 || {
746 self.modify_selection_or_edit_point(
747 1,
748 RopeMovement::RopeStartOrEnd,
749 maybe_select,
750 );
751 KeyReaction::RedrawSelection
752 },
753 )
754 .shortcut(alt_or_control, Key::Named(NamedKey::ArrowLeft), || {
755 self.modify_selection_or_edit_point(-1, RopeMovement::Word, maybe_select);
756 KeyReaction::RedrawSelection
757 })
758 .shortcut(alt_or_control, Key::Named(NamedKey::ArrowRight), || {
759 self.modify_selection_or_edit_point(1, RopeMovement::Word, maybe_select);
760 KeyReaction::RedrawSelection
761 })
762 .shortcut(Modifiers::empty(), Key::Named(NamedKey::ArrowLeft), || {
763 self.modify_selection_or_edit_point(-1, RopeMovement::Grapheme, maybe_select);
764 KeyReaction::RedrawSelection
765 })
766 .shortcut(Modifiers::empty(), Key::Named(NamedKey::ArrowRight), || {
767 self.modify_selection_or_edit_point(1, RopeMovement::Grapheme, maybe_select);
768 KeyReaction::RedrawSelection
769 })
770 .shortcut(Modifiers::empty(), Key::Named(NamedKey::ArrowUp), || {
771 self.modify_selection_or_edit_point(-1, RopeMovement::Line, maybe_select);
772 KeyReaction::RedrawSelection
773 })
774 .shortcut(Modifiers::empty(), Key::Named(NamedKey::ArrowDown), || {
775 self.modify_selection_or_edit_point(1, RopeMovement::Line, maybe_select);
776 KeyReaction::RedrawSelection
777 })
778 .shortcut(Modifiers::empty(), Key::Named(NamedKey::Enter), || {
779 self.handle_return()
780 })
781 .optional_shortcut(
782 macos,
783 Modifiers::empty(),
784 Key::Named(NamedKey::Home),
785 || {
786 self.modify_selection_or_edit_point(
787 -1,
788 RopeMovement::RopeStartOrEnd,
789 maybe_select,
790 );
791 KeyReaction::RedrawSelection
792 },
793 )
794 .optional_shortcut(macos, Modifiers::empty(), Key::Named(NamedKey::End), || {
795 self.modify_selection_or_edit_point(1, RopeMovement::RopeStartOrEnd, maybe_select);
796 KeyReaction::RedrawSelection
797 })
798 .shortcut(Modifiers::empty(), Key::Named(NamedKey::PageUp), || {
799 self.modify_selection_or_edit_point(-28, RopeMovement::Line, maybe_select);
800 KeyReaction::RedrawSelection
801 })
802 .shortcut(Modifiers::empty(), Key::Named(NamedKey::PageDown), || {
803 self.modify_selection_or_edit_point(28, RopeMovement::Line, maybe_select);
804 KeyReaction::RedrawSelection
805 })
806 .otherwise(|| {
807 if let Key::Character(ref character) = key {
808 self.insert(character);
809 return KeyReaction::DispatchInput(
810 Some(character.to_string()),
811 IsComposing::NotComposing,
812 InputType::InsertText,
813 );
814 }
815 if matches!(key, Key::Named(NamedKey::Process)) {
816 return KeyReaction::DispatchInput(
817 None,
818 IsComposing::Composing,
819 InputType::Nothing,
820 );
821 }
822 KeyReaction::Nothing
823 })
824 .unwrap()
825 }
826
827 pub(crate) fn handle_compositionend(&mut self, event: &CompositionEvent) -> KeyReaction {
828 let insertion = event.data().str();
829 if insertion.is_empty() {
830 self.clear_selection();
831 return KeyReaction::RedrawSelection;
832 }
833
834 self.insert(insertion.to_string());
835 KeyReaction::DispatchInput(
836 Some(insertion.to_string()),
837 IsComposing::NotComposing,
838 InputType::InsertCompositionText,
839 )
840 }
841
842 pub(crate) fn handle_compositionupdate(&mut self, event: &CompositionEvent) -> KeyReaction {
843 let insertion = event.data().str();
844 if insertion.is_empty() {
845 return KeyReaction::Nothing;
846 }
847
848 let start = self.selection_start_offset();
849 let insertion = insertion.to_string();
850 self.insert(insertion.clone());
851 self.set_selection_range_utf8(
852 start,
853 start + event.data().len_utf8(),
854 SelectionDirection::Forward,
855 );
856 KeyReaction::DispatchInput(
857 Some(insertion),
858 IsComposing::Composing,
859 InputType::InsertCompositionText,
860 )
861 }
862
863 fn edit_point_for_mouse_event(&self, node: &Node, event: &MouseEvent) -> RopeIndex {
864 node.owner_window()
865 .text_index_query_on_node_for_event(node, event)
866 .map(|grapheme_index| {
867 self.rope.move_by(
868 Default::default(),
869 RopeMovement::Character,
870 grapheme_index as isize,
871 )
872 })
873 .unwrap_or_else(|| self.rope.last_index())
874 }
875
876 pub(crate) fn handle_mouse_event(&mut self, node: &Node, mouse_event: &MouseEvent) -> bool {
879 let event_type = mouse_event.upcast::<Event>().type_();
882 if event_type == atom!("mouseup") || mouse_event.Buttons() & 1 != 1 {
883 self.currently_dragging = false;
884 }
885
886 if event_type == atom!("mousedown") {
887 return self.handle_mousedown(node, mouse_event);
888 }
889
890 if event_type == atom!("mousemove") && self.currently_dragging {
891 self.edit_point = self.edit_point_for_mouse_event(node, mouse_event);
892 self.update_selection_direction();
893 return true;
894 }
895
896 false
897 }
898
899 fn handle_mousedown(&mut self, node: &Node, mouse_event: &MouseEvent) -> bool {
904 assert_eq!(mouse_event.upcast::<Event>().type_(), atom!("mousedown"));
905
906 if mouse_event.Button() != 0 {
913 return false;
914 }
915
916 self.currently_dragging = true;
917 match mouse_event.upcast::<UIEvent>().Detail() {
918 3 => {
919 let word_boundaries = self.rope.line_boundaries(self.edit_point);
920 self.edit_point = word_boundaries.end;
921 self.selection_origin = Some(word_boundaries.start);
922 self.update_selection_direction();
923 true
924 },
925 2 => {
926 let word_boundaries = self.rope.relevant_word_boundaries(self.edit_point);
927 self.edit_point = word_boundaries.end;
928 self.selection_origin = Some(word_boundaries.start);
929 self.update_selection_direction();
930 true
931 },
932 1 => {
933 self.clear_selection();
934 self.edit_point = self.edit_point_for_mouse_event(node, mouse_event);
935 self.selection_origin = Some(self.edit_point);
936 self.update_selection_direction();
937 true
938 },
939 _ => {
940 false
944 },
945 }
946 }
947
948 pub(crate) fn is_empty(&self) -> bool {
950 self.rope.is_empty()
951 }
952
953 pub(crate) fn len_utf16(&self) -> Utf16CodeUnitLength {
955 self.rope.len_utf16()
956 }
957
958 pub fn get_content(&self) -> DOMString {
960 self.rope.contents().into()
961 }
962
963 pub fn set_content(&mut self, content: DOMString) {
970 self.rope = Rope::new(
971 content
972 .str()
973 .to_string()
974 .replace("\r\n", "\n")
975 .replace("\r", "\n"),
976 );
977 self.was_last_change_by_set_content = true;
978
979 self.edit_point = self.rope.normalize_index(self.edit_point());
980 self.selection_origin = self
981 .selection_origin
982 .map(|selection_origin| self.rope.normalize_index(selection_origin));
983 }
984
985 pub fn set_selection_range_utf16(
986 &mut self,
987 start: Utf16CodeUnitLength,
988 end: Utf16CodeUnitLength,
989 direction: SelectionDirection,
990 ) {
991 self.set_selection_range_utf8(
992 self.rope.utf16_offset_to_utf8_offset(start),
993 self.rope.utf16_offset_to_utf8_offset(end),
994 direction,
995 );
996 }
997
998 pub fn set_selection_range_utf8(
999 &mut self,
1000 mut start: Utf8CodeUnitLength,
1001 mut end: Utf8CodeUnitLength,
1002 direction: SelectionDirection,
1003 ) {
1004 let text_end = self.get_content().len_utf8();
1005 if end > text_end {
1006 end = text_end;
1007 }
1008 if start > end {
1009 start = end;
1010 }
1011
1012 self.selection_direction = direction;
1013
1014 match direction {
1015 SelectionDirection::None | SelectionDirection::Forward => {
1016 self.selection_origin = Some(self.rope.utf8_offset_to_rope_index(start));
1017 self.edit_point = self.rope.utf8_offset_to_rope_index(end);
1018 },
1019 SelectionDirection::Backward => {
1020 self.selection_origin = Some(self.rope.utf8_offset_to_rope_index(end));
1021 self.edit_point = self.rope.utf8_offset_to_rope_index(start);
1022 },
1023 }
1024
1025 self.assert_ok_selection();
1026 }
1027
1028 pub(crate) fn handle_clipboard_event(
1036 &mut self,
1037 clipboard_event: &ClipboardEvent,
1038 ) -> ClipboardEventReaction {
1039 let event = clipboard_event.upcast::<Event>();
1040 if !event.IsTrusted() {
1041 return ClipboardEventReaction::empty();
1042 }
1043
1044 if event.DefaultPrevented() {
1047 return ClipboardEventReaction::empty();
1050 }
1051
1052 let event_type = event.Type();
1053 match_domstring_ascii!(event_type,
1054 "copy" => {
1055 let selection = self.get_selection_text();
1057
1058 if let Some(text) = selection {
1060 self.clipboard_provider.set_text(text);
1061 }
1062
1063 ClipboardEventReaction::new(ClipboardEventFlags::FireClipboardChangedEvent)
1065 },
1066 "cut" => {
1067 let selection = self.get_selection_text();
1069
1070 let Some(text) = selection else {
1072 return ClipboardEventReaction::empty();
1074 };
1075
1076 self.clipboard_provider.set_text(text);
1078
1079 self.delete_selection();
1081
1082 ClipboardEventReaction::new(
1085 ClipboardEventFlags::FireClipboardChangedEvent |
1086 ClipboardEventFlags::QueueInputEvent,
1087 )
1088 .with_input_type(InputType::DeleteByCut)
1089 },
1090 "paste" => {
1091 let Some(data_transfer) = clipboard_event.get_clipboard_data() else {
1093 return ClipboardEventReaction::empty();
1094 };
1095 let Some(drag_data_store) = data_transfer.data_store() else {
1096 return ClipboardEventReaction::empty();
1097 };
1098
1099 let Some(text_content) =
1109 drag_data_store
1110 .iter_item_list()
1111 .find_map(|item| match item {
1112 Kind::Text { data, .. } => Some(data.to_string()),
1113 _ => None,
1114 })
1115 else {
1116 return ClipboardEventReaction::empty();
1117 };
1118 if text_content.is_empty() {
1119 return ClipboardEventReaction::empty();
1120 }
1121
1122 self.insert(&text_content);
1123
1124 ClipboardEventReaction::new(ClipboardEventFlags::QueueInputEvent)
1127 .with_text(text_content)
1128 .with_input_type(InputType::InsertFromPaste)
1129 },
1130 _ => ClipboardEventReaction::empty(),)
1131 }
1132
1133 pub(crate) fn queue_input_event(
1135 &self,
1136 target: &EventTarget,
1137 data: Option<String>,
1138 is_composing: IsComposing,
1139 input_type: InputType,
1140 ) {
1141 let global = target.global();
1142 let target = Trusted::new(target);
1143 global.task_manager().user_interaction_task_source().queue(
1144 task!(fire_input_event: move || {
1145 let target = target.root();
1146 let global = target.global();
1147 let window = global.as_window();
1148 let event = InputEvent::new(
1149 window,
1150 None,
1151 atom!("input"),
1152 true,
1153 false,
1154 Some(window),
1155 0,
1156 data.map(DOMString::from),
1157 is_composing.into(),
1158 input_type.as_str().into(),
1159 CanGc::note(),
1160 );
1161 let event = event.upcast::<Event>();
1162 event.set_composed(true);
1163 event.fire(&target, CanGc::note());
1164 }),
1165 );
1166 }
1167}