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 pub fn delete_char(&mut self, direction: Direction) -> bool {
308 if !self.has_uncollapsed_selection() {
309 let amount = match direction {
310 Direction::Forward => 1,
311 Direction::Backward => -1,
312 };
313 self.modify_selection(amount, RopeMovement::Grapheme);
314 }
315
316 if self.selection_start() == self.selection_end() {
317 return false;
318 }
319
320 self.replace_selection(&DOMString::new());
321 true
322 }
323
324 pub fn insert<S: Into<String>>(&mut self, string: S) {
327 if self.selection_origin.is_none() {
328 self.selection_origin = Some(self.edit_point);
329 }
330 self.replace_selection(&DOMString::from(string.into()));
331 }
332
333 pub fn selection_start(&self) -> RopeIndex {
336 match self.selection_direction {
337 SelectionDirection::None | SelectionDirection::Forward => {
338 self.selection_origin_or_edit_point()
339 },
340 SelectionDirection::Backward => self.edit_point,
341 }
342 }
343
344 pub(crate) fn selection_start_utf16(&self) -> Utf16CodeUnitLength {
345 self.rope.index_to_utf16_offset(self.selection_start())
346 }
347
348 fn selection_start_offset(&self) -> Utf8CodeUnitLength {
350 self.rope.index_to_utf8_offset(self.selection_start())
351 }
352
353 pub fn selection_end(&self) -> RopeIndex {
356 match self.selection_direction {
357 SelectionDirection::None | SelectionDirection::Forward => self.edit_point,
358 SelectionDirection::Backward => self.selection_origin_or_edit_point(),
359 }
360 }
361
362 pub(crate) fn selection_end_utf16(&self) -> Utf16CodeUnitLength {
363 self.rope.index_to_utf16_offset(self.selection_end())
364 }
365
366 pub fn selection_end_offset(&self) -> Utf8CodeUnitLength {
368 self.rope.index_to_utf8_offset(self.selection_end())
369 }
370
371 #[inline]
374 pub(crate) fn has_uncollapsed_selection(&self) -> bool {
375 self.selection_origin
376 .is_some_and(|selection_origin| selection_origin != self.edit_point)
377 }
378
379 pub(crate) fn sorted_selection_offsets_range(&self) -> Range<Utf8CodeUnitLength> {
383 self.selection_start_offset()..self.selection_end_offset()
384 }
385
386 pub(crate) fn sorted_selection_character_offsets_range(&self) -> Range<usize> {
390 self.rope.index_to_character_offset(self.selection_start())..
391 self.rope.index_to_character_offset(self.selection_end())
392 }
393
394 pub(crate) fn selection_state(&self) -> SelectionState {
396 SelectionState {
397 start: self.selection_start(),
398 end: self.selection_end(),
399 direction: self.selection_direction,
400 }
401 }
402
403 fn assert_ok_selection(&self) {
405 debug!(
406 "edit_point: {:?}, selection_origin: {:?}, direction: {:?}",
407 self.edit_point, self.selection_origin, self.selection_direction
408 );
409
410 debug_assert_eq!(self.edit_point, self.rope.normalize_index(self.edit_point));
411 if let Some(selection_origin) = self.selection_origin {
412 debug_assert_eq!(
413 selection_origin,
414 self.rope.normalize_index(selection_origin)
415 );
416 match self.selection_direction {
417 SelectionDirection::None | SelectionDirection::Forward => {
418 debug_assert!(selection_origin <= self.edit_point)
419 },
420 SelectionDirection::Backward => debug_assert!(self.edit_point <= selection_origin),
421 }
422 }
423 }
424
425 fn selection_slice(&self) -> RopeSlice<'_> {
426 self.rope
427 .slice(Some(self.selection_start()), Some(self.selection_end()))
428 }
429
430 pub(crate) fn get_selection_text(&self) -> Option<String> {
431 let text: String = self.selection_slice().into();
432 if text.is_empty() {
433 return None;
434 }
435 Some(text)
436 }
437
438 fn selection_utf16_len(&self) -> Utf16CodeUnitLength {
440 Utf16CodeUnitLength(
441 self.selection_slice()
442 .chars()
443 .map(char::len_utf16)
444 .sum::<usize>(),
445 )
446 }
447
448 pub fn replace_selection(&mut self, insert: &DOMString) {
452 let string_to_insert = if let Some(max_length) = self.max_length {
453 let utf16_length_without_selection =
454 self.len_utf16().saturating_sub(self.selection_utf16_len());
455 let utf16_length_that_can_be_inserted =
456 max_length.saturating_sub(utf16_length_without_selection);
457 let Utf8CodeUnitLength(last_char_index) =
458 len_of_first_n_code_units(insert, utf16_length_that_can_be_inserted);
459 &insert.str()[..last_char_index]
460 } else {
461 &insert.str()
462 };
463 let string_to_insert = self.mode.normalize(string_to_insert);
464
465 let start = self.selection_start();
466 let end = self.selection_end();
467 let end_index_of_insertion = self.rope.replace_range(start..end, string_to_insert);
468
469 self.was_last_change_by_set_content = false;
470 self.clear_selection();
471 self.edit_point = end_index_of_insertion;
472 }
473
474 pub fn modify_edit_point(&mut self, amount: isize, movement: RopeMovement) {
475 if amount == 0 {
476 return;
477 }
478
479 if matches!(movement, RopeMovement::Line) || !self.has_uncollapsed_selection() {
482 self.clear_selection();
483 self.edit_point = self.rope.move_by(self.edit_point, movement, amount);
484 return;
485 }
486
487 let new_edit_point = if amount > 0 {
490 self.selection_end()
491 } else {
492 self.selection_start()
493 };
494 self.clear_selection();
495 self.edit_point = new_edit_point;
496 }
497
498 pub fn modify_selection(&mut self, amount: isize, movement: RopeMovement) {
499 let old_edit_point = self.edit_point;
500 self.edit_point = self.rope.move_by(old_edit_point, movement, amount);
501
502 if self.selection_origin.is_none() {
503 self.selection_origin = Some(old_edit_point);
504 }
505 self.update_selection_direction();
506 }
507
508 pub fn modify_selection_or_edit_point(
509 &mut self,
510 amount: isize,
511 movement: RopeMovement,
512 select: Selection,
513 ) {
514 match select {
515 Selection::Selected => self.modify_selection(amount, movement),
516 Selection::NotSelected => self.modify_edit_point(amount, movement),
517 }
518 self.assert_ok_selection();
519 }
520
521 fn update_selection_direction(&mut self) {
526 debug!(
527 "edit_point: {:?}, selection_origin: {:?}",
528 self.edit_point, self.selection_origin
529 );
530 self.selection_direction = if Some(self.edit_point) < self.selection_origin {
531 SelectionDirection::Backward
532 } else {
533 SelectionDirection::Forward
534 }
535 }
536
537 pub fn handle_return(&mut self) -> KeyReaction {
539 match self.mode {
540 Lines::Multiple => {
541 self.insert('\n');
542 KeyReaction::DispatchInput(
543 None,
544 IsComposing::NotComposing,
545 InputType::InsertLineBreak,
546 )
547 },
548 Lines::Single => KeyReaction::TriggerDefaultAction,
549 }
550 }
551
552 pub fn select_all(&mut self) {
554 self.selection_origin = Some(RopeIndex::default());
555 self.edit_point = self.rope.last_index();
556 self.selection_direction = SelectionDirection::Forward;
557 self.assert_ok_selection();
558 }
559
560 pub fn clear_selection(&mut self) {
562 self.selection_origin = None;
563 self.selection_direction = SelectionDirection::None;
564 }
565
566 pub(crate) fn clear_selection_to_end(&mut self) {
568 self.clear_selection();
569 self.edit_point = self.rope.last_index();
570 }
571
572 pub(crate) fn clear_selection_to_start(&mut self) {
573 self.clear_selection();
574 self.edit_point = Default::default();
575 }
576
577 pub(crate) fn handle_keydown(&mut self, event: &KeyboardEvent) -> KeyReaction {
579 let key = event.key();
580 let mods = event.modifiers();
581 self.handle_keydown_aux(key, mods, cfg!(target_os = "macos"))
582 }
583
584 pub fn handle_keydown_aux(
587 &mut self,
588 key: Key,
589 mut mods: Modifiers,
590 macos: bool,
591 ) -> KeyReaction {
592 let maybe_select = if mods.contains(Modifiers::SHIFT) {
593 Selection::Selected
594 } else {
595 Selection::NotSelected
596 };
597
598 let alt_or_control = if macos {
599 Modifiers::ALT
600 } else {
601 Modifiers::CONTROL
602 };
603
604 mods.remove(Modifiers::SHIFT);
605 ShortcutMatcher::new(KeyState::Down, key.clone(), mods)
606 .shortcut(Modifiers::CONTROL | Modifiers::ALT, 'B', || {
607 self.modify_selection_or_edit_point(-1, RopeMovement::Word, maybe_select);
608 KeyReaction::RedrawSelection
609 })
610 .shortcut(Modifiers::CONTROL | Modifiers::ALT, 'F', || {
611 self.modify_selection_or_edit_point(1, RopeMovement::Word, maybe_select);
612 KeyReaction::RedrawSelection
613 })
614 .shortcut(Modifiers::CONTROL | Modifiers::ALT, 'A', || {
615 self.modify_selection_or_edit_point(-1, RopeMovement::LineStartOrEnd, maybe_select);
616 KeyReaction::RedrawSelection
617 })
618 .shortcut(Modifiers::CONTROL | Modifiers::ALT, 'E', || {
619 self.modify_selection_or_edit_point(1, RopeMovement::LineStartOrEnd, maybe_select);
620 KeyReaction::RedrawSelection
621 })
622 .optional_shortcut(macos, Modifiers::CONTROL, 'A', || {
623 self.modify_selection_or_edit_point(-1, RopeMovement::LineStartOrEnd, maybe_select);
624 KeyReaction::RedrawSelection
625 })
626 .optional_shortcut(macos, Modifiers::CONTROL, 'E', || {
627 self.modify_selection_or_edit_point(1, RopeMovement::LineStartOrEnd, maybe_select);
628 KeyReaction::RedrawSelection
629 })
630 .shortcut(CMD_OR_CONTROL, 'A', || {
631 self.select_all();
632 KeyReaction::RedrawSelection
633 })
634 .shortcut(CMD_OR_CONTROL, 'X', || {
635 if let Some(text) = self.get_selection_text() {
636 self.clipboard_provider.set_text(text);
637 self.delete_char(Direction::Backward);
638 }
639 KeyReaction::DispatchInput(None, IsComposing::NotComposing, InputType::DeleteByCut)
640 })
641 .shortcut(CMD_OR_CONTROL, 'C', || {
642 if let Some(text) = self.get_selection_text() {
644 self.clipboard_provider.set_text(text);
645 }
646 KeyReaction::DispatchInput(None, IsComposing::NotComposing, InputType::Nothing)
647 })
648 .shortcut(CMD_OR_CONTROL, 'V', || {
649 if let Ok(text_content) = self.clipboard_provider.get_text() {
650 self.insert(&text_content);
651 KeyReaction::DispatchInput(
652 Some(text_content),
653 IsComposing::NotComposing,
654 InputType::InsertFromPaste,
655 )
656 } else {
657 KeyReaction::DispatchInput(
658 Some("".to_string()),
659 IsComposing::NotComposing,
660 InputType::InsertFromPaste,
661 )
662 }
663 })
664 .shortcut(Modifiers::empty(), Key::Named(NamedKey::Delete), || {
665 if self.delete_char(Direction::Forward) {
666 KeyReaction::DispatchInput(
667 None,
668 IsComposing::NotComposing,
669 InputType::DeleteContentForward,
670 )
671 } else {
672 KeyReaction::Nothing
673 }
674 })
675 .shortcut(Modifiers::empty(), Key::Named(NamedKey::Backspace), || {
676 if self.delete_char(Direction::Backward) {
677 KeyReaction::DispatchInput(
678 None,
679 IsComposing::NotComposing,
680 InputType::DeleteContentBackward,
681 )
682 } else {
683 KeyReaction::Nothing
684 }
685 })
686 .optional_shortcut(
687 macos,
688 Modifiers::META,
689 Key::Named(NamedKey::ArrowLeft),
690 || {
691 self.modify_selection_or_edit_point(
692 -1,
693 RopeMovement::LineStartOrEnd,
694 maybe_select,
695 );
696 KeyReaction::RedrawSelection
697 },
698 )
699 .optional_shortcut(
700 macos,
701 Modifiers::META,
702 Key::Named(NamedKey::ArrowRight),
703 || {
704 self.modify_selection_or_edit_point(
705 1,
706 RopeMovement::LineStartOrEnd,
707 maybe_select,
708 );
709 KeyReaction::RedrawSelection
710 },
711 )
712 .optional_shortcut(
713 macos,
714 Modifiers::META,
715 Key::Named(NamedKey::ArrowUp),
716 || {
717 self.modify_selection_or_edit_point(
718 -1,
719 RopeMovement::RopeStartOrEnd,
720 maybe_select,
721 );
722 KeyReaction::RedrawSelection
723 },
724 )
725 .optional_shortcut(
726 macos,
727 Modifiers::META,
728 Key::Named(NamedKey::ArrowDown),
729 || {
730 self.modify_selection_or_edit_point(
731 1,
732 RopeMovement::RopeStartOrEnd,
733 maybe_select,
734 );
735 KeyReaction::RedrawSelection
736 },
737 )
738 .shortcut(alt_or_control, Key::Named(NamedKey::ArrowLeft), || {
739 self.modify_selection_or_edit_point(-1, RopeMovement::Word, maybe_select);
740 KeyReaction::RedrawSelection
741 })
742 .shortcut(alt_or_control, Key::Named(NamedKey::ArrowRight), || {
743 self.modify_selection_or_edit_point(1, RopeMovement::Word, maybe_select);
744 KeyReaction::RedrawSelection
745 })
746 .shortcut(Modifiers::empty(), Key::Named(NamedKey::ArrowLeft), || {
747 self.modify_selection_or_edit_point(-1, RopeMovement::Grapheme, maybe_select);
748 KeyReaction::RedrawSelection
749 })
750 .shortcut(Modifiers::empty(), Key::Named(NamedKey::ArrowRight), || {
751 self.modify_selection_or_edit_point(1, RopeMovement::Grapheme, maybe_select);
752 KeyReaction::RedrawSelection
753 })
754 .shortcut(Modifiers::empty(), Key::Named(NamedKey::ArrowUp), || {
755 self.modify_selection_or_edit_point(-1, RopeMovement::Line, maybe_select);
756 KeyReaction::RedrawSelection
757 })
758 .shortcut(Modifiers::empty(), Key::Named(NamedKey::ArrowDown), || {
759 self.modify_selection_or_edit_point(1, RopeMovement::Line, maybe_select);
760 KeyReaction::RedrawSelection
761 })
762 .shortcut(Modifiers::empty(), Key::Named(NamedKey::Enter), || {
763 self.handle_return()
764 })
765 .optional_shortcut(
766 macos,
767 Modifiers::empty(),
768 Key::Named(NamedKey::Home),
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(macos, Modifiers::empty(), Key::Named(NamedKey::End), || {
779 self.modify_selection_or_edit_point(1, RopeMovement::RopeStartOrEnd, maybe_select);
780 KeyReaction::RedrawSelection
781 })
782 .shortcut(Modifiers::empty(), Key::Named(NamedKey::PageUp), || {
783 self.modify_selection_or_edit_point(-28, RopeMovement::Line, maybe_select);
784 KeyReaction::RedrawSelection
785 })
786 .shortcut(Modifiers::empty(), Key::Named(NamedKey::PageDown), || {
787 self.modify_selection_or_edit_point(28, RopeMovement::Line, maybe_select);
788 KeyReaction::RedrawSelection
789 })
790 .otherwise(|| {
791 if let Key::Character(ref character) = key {
792 self.insert(character);
793 return KeyReaction::DispatchInput(
794 Some(character.to_string()),
795 IsComposing::NotComposing,
796 InputType::InsertText,
797 );
798 }
799 if matches!(key, Key::Named(NamedKey::Process)) {
800 return KeyReaction::DispatchInput(
801 None,
802 IsComposing::Composing,
803 InputType::Nothing,
804 );
805 }
806 KeyReaction::Nothing
807 })
808 .unwrap()
809 }
810
811 pub(crate) fn handle_compositionend(&mut self, event: &CompositionEvent) -> KeyReaction {
812 let insertion = event.data().str();
813 if insertion.is_empty() {
814 self.clear_selection();
815 return KeyReaction::RedrawSelection;
816 }
817
818 self.insert(insertion.to_string());
819 KeyReaction::DispatchInput(
820 Some(insertion.to_string()),
821 IsComposing::NotComposing,
822 InputType::InsertCompositionText,
823 )
824 }
825
826 pub(crate) fn handle_compositionupdate(&mut self, event: &CompositionEvent) -> KeyReaction {
827 let insertion = event.data().str();
828 if insertion.is_empty() {
829 return KeyReaction::Nothing;
830 }
831
832 let start = self.selection_start_offset();
833 let insertion = insertion.to_string();
834 self.insert(insertion.clone());
835 self.set_selection_range_utf8(
836 start,
837 start + event.data().len_utf8(),
838 SelectionDirection::Forward,
839 );
840 KeyReaction::DispatchInput(
841 Some(insertion),
842 IsComposing::Composing,
843 InputType::InsertCompositionText,
844 )
845 }
846
847 fn edit_point_for_mouse_event(&self, node: &Node, event: &MouseEvent) -> RopeIndex {
848 node.owner_window()
849 .text_index_query_on_node_for_event(node, event)
850 .map(|grapheme_index| {
851 self.rope.move_by(
852 Default::default(),
853 RopeMovement::Character,
854 grapheme_index as isize,
855 )
856 })
857 .unwrap_or_else(|| self.rope.last_index())
858 }
859
860 pub(crate) fn handle_mouse_event(&mut self, node: &Node, mouse_event: &MouseEvent) -> bool {
863 let event_type = mouse_event.upcast::<Event>().type_();
866 if event_type == atom!("mouseup") || mouse_event.Buttons() & 1 != 1 {
867 self.currently_dragging = false;
868 }
869
870 if event_type == atom!("mousedown") {
871 return self.handle_mousedown(node, mouse_event);
872 }
873
874 if event_type == atom!("mousemove") && self.currently_dragging {
875 self.edit_point = self.edit_point_for_mouse_event(node, mouse_event);
876 self.update_selection_direction();
877 return true;
878 }
879
880 false
881 }
882
883 fn handle_mousedown(&mut self, node: &Node, mouse_event: &MouseEvent) -> bool {
888 assert_eq!(mouse_event.upcast::<Event>().type_(), atom!("mousedown"));
889
890 if mouse_event.Button() != 0 {
897 return false;
898 }
899
900 self.currently_dragging = true;
901 match mouse_event.upcast::<UIEvent>().Detail() {
902 3 => {
903 let word_boundaries = self.rope.line_boundaries(self.edit_point);
904 self.edit_point = word_boundaries.end;
905 self.selection_origin = Some(word_boundaries.start);
906 self.update_selection_direction();
907 true
908 },
909 2 => {
910 let word_boundaries = self.rope.relevant_word_boundaries(self.edit_point);
911 self.edit_point = word_boundaries.end;
912 self.selection_origin = Some(word_boundaries.start);
913 self.update_selection_direction();
914 true
915 },
916 1 => {
917 self.clear_selection();
918 self.edit_point = self.edit_point_for_mouse_event(node, mouse_event);
919 self.selection_origin = Some(self.edit_point);
920 self.update_selection_direction();
921 true
922 },
923 _ => {
924 false
928 },
929 }
930 }
931
932 pub(crate) fn is_empty(&self) -> bool {
934 self.rope.is_empty()
935 }
936
937 pub(crate) fn len_utf16(&self) -> Utf16CodeUnitLength {
939 self.rope.len_utf16()
940 }
941
942 pub fn get_content(&self) -> DOMString {
944 self.rope.contents().into()
945 }
946
947 pub fn set_content(&mut self, content: DOMString) {
954 self.rope = Rope::new(
955 content
956 .str()
957 .to_string()
958 .replace("\r\n", "\n")
959 .replace("\r", "\n"),
960 );
961 self.was_last_change_by_set_content = true;
962
963 self.edit_point = self.rope.normalize_index(self.edit_point());
964 self.selection_origin = self
965 .selection_origin
966 .map(|selection_origin| self.rope.normalize_index(selection_origin));
967 }
968
969 pub fn set_selection_range_utf16(
970 &mut self,
971 start: Utf16CodeUnitLength,
972 end: Utf16CodeUnitLength,
973 direction: SelectionDirection,
974 ) {
975 self.set_selection_range_utf8(
976 self.rope.utf16_offset_to_utf8_offset(start),
977 self.rope.utf16_offset_to_utf8_offset(end),
978 direction,
979 );
980 }
981
982 pub fn set_selection_range_utf8(
983 &mut self,
984 mut start: Utf8CodeUnitLength,
985 mut end: Utf8CodeUnitLength,
986 direction: SelectionDirection,
987 ) {
988 let text_end = self.get_content().len_utf8();
989 if end > text_end {
990 end = text_end;
991 }
992 if start > end {
993 start = end;
994 }
995
996 self.selection_direction = direction;
997
998 match direction {
999 SelectionDirection::None | SelectionDirection::Forward => {
1000 self.selection_origin = Some(self.rope.utf8_offset_to_rope_index(start));
1001 self.edit_point = self.rope.utf8_offset_to_rope_index(end);
1002 },
1003 SelectionDirection::Backward => {
1004 self.selection_origin = Some(self.rope.utf8_offset_to_rope_index(end));
1005 self.edit_point = self.rope.utf8_offset_to_rope_index(start);
1006 },
1007 }
1008
1009 self.assert_ok_selection();
1010 }
1011
1012 pub(crate) fn handle_clipboard_event(
1020 &mut self,
1021 clipboard_event: &ClipboardEvent,
1022 ) -> ClipboardEventReaction {
1023 let event = clipboard_event.upcast::<Event>();
1024 if !event.IsTrusted() {
1025 return ClipboardEventReaction::empty();
1026 }
1027
1028 if event.DefaultPrevented() {
1031 return ClipboardEventReaction::empty();
1034 }
1035
1036 let event_type = event.Type();
1037 match_domstring_ascii!(event_type,
1038 "copy" => {
1039 let selection = self.get_selection_text();
1041
1042 if let Some(text) = selection {
1044 self.clipboard_provider.set_text(text);
1045 }
1046
1047 ClipboardEventReaction::new(ClipboardEventFlags::FireClipboardChangedEvent)
1049 },
1050 "cut" => {
1051 let selection = self.get_selection_text();
1053
1054 let Some(text) = selection else {
1056 return ClipboardEventReaction::empty();
1058 };
1059
1060 self.clipboard_provider.set_text(text);
1062
1063 self.delete_char(Direction::Backward);
1065
1066 ClipboardEventReaction::new(
1069 ClipboardEventFlags::FireClipboardChangedEvent |
1070 ClipboardEventFlags::QueueInputEvent,
1071 )
1072 .with_input_type(InputType::DeleteByCut)
1073 },
1074 "paste" => {
1075 let Some(data_transfer) = clipboard_event.get_clipboard_data() else {
1077 return ClipboardEventReaction::empty();
1078 };
1079 let Some(drag_data_store) = data_transfer.data_store() else {
1080 return ClipboardEventReaction::empty();
1081 };
1082
1083 let Some(text_content) =
1093 drag_data_store
1094 .iter_item_list()
1095 .find_map(|item| match item {
1096 Kind::Text { data, .. } => Some(data.to_string()),
1097 _ => None,
1098 })
1099 else {
1100 return ClipboardEventReaction::empty();
1101 };
1102 if text_content.is_empty() {
1103 return ClipboardEventReaction::empty();
1104 }
1105
1106 self.insert(&text_content);
1107
1108 ClipboardEventReaction::new(ClipboardEventFlags::QueueInputEvent)
1111 .with_text(text_content)
1112 .with_input_type(InputType::InsertFromPaste)
1113 },
1114 _ => ClipboardEventReaction::empty(),)
1115 }
1116
1117 pub(crate) fn queue_input_event(
1119 &self,
1120 target: &EventTarget,
1121 data: Option<String>,
1122 is_composing: IsComposing,
1123 input_type: InputType,
1124 ) {
1125 let global = target.global();
1126 let target = Trusted::new(target);
1127 global.task_manager().user_interaction_task_source().queue(
1128 task!(fire_input_event: move || {
1129 let target = target.root();
1130 let global = target.global();
1131 let window = global.as_window();
1132 let event = InputEvent::new(
1133 window,
1134 None,
1135 atom!("input"),
1136 true,
1137 false,
1138 Some(window),
1139 0,
1140 data.map(DOMString::from),
1141 is_composing.into(),
1142 input_type.as_str().into(),
1143 CanGc::note(),
1144 );
1145 let event = event.upcast::<Event>();
1146 event.set_composed(true);
1147 event.fire(&target, CanGc::note());
1148 }),
1149 );
1150 }
1151}