1use std::default::Default;
8use std::ops::Range;
9
10use base::text::{Utf8CodeUnitLength, Utf16CodeUnitLength};
11use base::{Rope, RopeIndex, RopeMovement, RopeSlice};
12use bitflags::bitflags;
13use keyboard_types::{Key, KeyState, Modifiers, NamedKey, ShortcutMatcher};
14use script_bindings::codegen::GenericBindings::MouseEventBinding::MouseEventMethods;
15use script_bindings::codegen::GenericBindings::UIEventBinding::UIEventMethods;
16use script_bindings::match_domstring_ascii;
17use script_bindings::trace::CustomTraceable;
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 mods.remove(Modifiers::SHIFT);
598 ShortcutMatcher::new(KeyState::Down, key.clone(), mods)
599 .shortcut(Modifiers::CONTROL | Modifiers::ALT, 'B', || {
600 self.modify_selection_or_edit_point(-1, RopeMovement::Word, maybe_select);
601 KeyReaction::RedrawSelection
602 })
603 .shortcut(Modifiers::CONTROL | Modifiers::ALT, 'F', || {
604 self.modify_selection_or_edit_point(1, RopeMovement::Word, maybe_select);
605 KeyReaction::RedrawSelection
606 })
607 .shortcut(Modifiers::CONTROL | Modifiers::ALT, 'A', || {
608 self.modify_selection_or_edit_point(-1, RopeMovement::LineStartOrEnd, maybe_select);
609 KeyReaction::RedrawSelection
610 })
611 .shortcut(Modifiers::CONTROL | Modifiers::ALT, 'E', || {
612 self.modify_selection_or_edit_point(1, RopeMovement::LineStartOrEnd, maybe_select);
613 KeyReaction::RedrawSelection
614 })
615 .optional_shortcut(macos, Modifiers::CONTROL, 'A', || {
616 self.modify_selection_or_edit_point(-1, RopeMovement::LineStartOrEnd, maybe_select);
617 KeyReaction::RedrawSelection
618 })
619 .optional_shortcut(macos, Modifiers::CONTROL, 'E', || {
620 self.modify_selection_or_edit_point(1, RopeMovement::LineStartOrEnd, maybe_select);
621 KeyReaction::RedrawSelection
622 })
623 .shortcut(CMD_OR_CONTROL, 'A', || {
624 self.select_all();
625 KeyReaction::RedrawSelection
626 })
627 .shortcut(CMD_OR_CONTROL, 'X', || {
628 if let Some(text) = self.get_selection_text() {
629 self.clipboard_provider.set_text(text);
630 self.delete_char(Direction::Backward);
631 }
632 KeyReaction::DispatchInput(None, IsComposing::NotComposing, InputType::DeleteByCut)
633 })
634 .shortcut(CMD_OR_CONTROL, 'C', || {
635 if let Some(text) = self.get_selection_text() {
637 self.clipboard_provider.set_text(text);
638 }
639 KeyReaction::DispatchInput(None, IsComposing::NotComposing, InputType::Nothing)
640 })
641 .shortcut(CMD_OR_CONTROL, 'V', || {
642 if let Ok(text_content) = self.clipboard_provider.get_text() {
643 self.insert(&text_content);
644 KeyReaction::DispatchInput(
645 Some(text_content),
646 IsComposing::NotComposing,
647 InputType::InsertFromPaste,
648 )
649 } else {
650 KeyReaction::DispatchInput(
651 Some("".to_string()),
652 IsComposing::NotComposing,
653 InputType::InsertFromPaste,
654 )
655 }
656 })
657 .shortcut(Modifiers::empty(), Key::Named(NamedKey::Delete), || {
658 if self.delete_char(Direction::Forward) {
659 KeyReaction::DispatchInput(
660 None,
661 IsComposing::NotComposing,
662 InputType::DeleteContentForward,
663 )
664 } else {
665 KeyReaction::Nothing
666 }
667 })
668 .shortcut(Modifiers::empty(), Key::Named(NamedKey::Backspace), || {
669 if self.delete_char(Direction::Backward) {
670 KeyReaction::DispatchInput(
671 None,
672 IsComposing::NotComposing,
673 InputType::DeleteContentBackward,
674 )
675 } else {
676 KeyReaction::Nothing
677 }
678 })
679 .optional_shortcut(
680 macos,
681 Modifiers::META,
682 Key::Named(NamedKey::ArrowLeft),
683 || {
684 self.modify_selection_or_edit_point(
685 -1,
686 RopeMovement::LineStartOrEnd,
687 maybe_select,
688 );
689 KeyReaction::RedrawSelection
690 },
691 )
692 .optional_shortcut(
693 macos,
694 Modifiers::META,
695 Key::Named(NamedKey::ArrowRight),
696 || {
697 self.modify_selection_or_edit_point(
698 1,
699 RopeMovement::LineStartOrEnd,
700 maybe_select,
701 );
702 KeyReaction::RedrawSelection
703 },
704 )
705 .optional_shortcut(
706 macos,
707 Modifiers::META,
708 Key::Named(NamedKey::ArrowUp),
709 || {
710 self.modify_selection_or_edit_point(
711 -1,
712 RopeMovement::RopeStartOrEnd,
713 maybe_select,
714 );
715 KeyReaction::RedrawSelection
716 },
717 )
718 .optional_shortcut(
719 macos,
720 Modifiers::META,
721 Key::Named(NamedKey::ArrowDown),
722 || {
723 self.modify_selection_or_edit_point(
724 1,
725 RopeMovement::RopeStartOrEnd,
726 maybe_select,
727 );
728 KeyReaction::RedrawSelection
729 },
730 )
731 .shortcut(Modifiers::ALT, Key::Named(NamedKey::ArrowLeft), || {
732 self.modify_selection_or_edit_point(-1, RopeMovement::Word, maybe_select);
733 KeyReaction::RedrawSelection
734 })
735 .shortcut(Modifiers::ALT, Key::Named(NamedKey::ArrowRight), || {
736 self.modify_selection_or_edit_point(1, RopeMovement::Word, maybe_select);
737 KeyReaction::RedrawSelection
738 })
739 .shortcut(Modifiers::empty(), Key::Named(NamedKey::ArrowLeft), || {
740 self.modify_selection_or_edit_point(-1, RopeMovement::Grapheme, maybe_select);
741 KeyReaction::RedrawSelection
742 })
743 .shortcut(Modifiers::empty(), Key::Named(NamedKey::ArrowRight), || {
744 self.modify_selection_or_edit_point(1, RopeMovement::Grapheme, maybe_select);
745 KeyReaction::RedrawSelection
746 })
747 .shortcut(Modifiers::empty(), Key::Named(NamedKey::ArrowUp), || {
748 self.modify_selection_or_edit_point(-1, RopeMovement::Line, maybe_select);
749 KeyReaction::RedrawSelection
750 })
751 .shortcut(Modifiers::empty(), Key::Named(NamedKey::ArrowDown), || {
752 self.modify_selection_or_edit_point(1, RopeMovement::Line, maybe_select);
753 KeyReaction::RedrawSelection
754 })
755 .shortcut(Modifiers::empty(), Key::Named(NamedKey::Enter), || {
756 self.handle_return()
757 })
758 .optional_shortcut(
759 macos,
760 Modifiers::empty(),
761 Key::Named(NamedKey::Home),
762 || {
763 self.modify_selection_or_edit_point(
764 -1,
765 RopeMovement::RopeStartOrEnd,
766 maybe_select,
767 );
768 KeyReaction::RedrawSelection
769 },
770 )
771 .optional_shortcut(macos, Modifiers::empty(), Key::Named(NamedKey::End), || {
772 self.modify_selection_or_edit_point(1, RopeMovement::RopeStartOrEnd, maybe_select);
773 KeyReaction::RedrawSelection
774 })
775 .shortcut(Modifiers::empty(), Key::Named(NamedKey::PageUp), || {
776 self.modify_selection_or_edit_point(-28, RopeMovement::Line, maybe_select);
777 KeyReaction::RedrawSelection
778 })
779 .shortcut(Modifiers::empty(), Key::Named(NamedKey::PageDown), || {
780 self.modify_selection_or_edit_point(28, RopeMovement::Line, maybe_select);
781 KeyReaction::RedrawSelection
782 })
783 .otherwise(|| {
784 if let Key::Character(ref character) = key {
785 self.insert(character);
786 return KeyReaction::DispatchInput(
787 Some(character.to_string()),
788 IsComposing::NotComposing,
789 InputType::InsertText,
790 );
791 }
792 if matches!(key, Key::Named(NamedKey::Process)) {
793 return KeyReaction::DispatchInput(
794 None,
795 IsComposing::Composing,
796 InputType::Nothing,
797 );
798 }
799 KeyReaction::Nothing
800 })
801 .unwrap()
802 }
803
804 pub(crate) fn handle_compositionend(&mut self, event: &CompositionEvent) -> KeyReaction {
805 let insertion = event.data().str();
806 if insertion.is_empty() {
807 self.clear_selection();
808 return KeyReaction::RedrawSelection;
809 }
810
811 self.insert(insertion.to_string());
812 KeyReaction::DispatchInput(
813 Some(insertion.to_string()),
814 IsComposing::NotComposing,
815 InputType::InsertCompositionText,
816 )
817 }
818
819 pub(crate) fn handle_compositionupdate(&mut self, event: &CompositionEvent) -> KeyReaction {
820 let insertion = event.data().str();
821 if insertion.is_empty() {
822 return KeyReaction::Nothing;
823 }
824
825 let start = self.selection_start_offset();
826 let insertion = insertion.to_string();
827 self.insert(insertion.clone());
828 self.set_selection_range_utf8(
829 start,
830 start + event.data().len_utf8(),
831 SelectionDirection::Forward,
832 );
833 KeyReaction::DispatchInput(
834 Some(insertion),
835 IsComposing::Composing,
836 InputType::InsertCompositionText,
837 )
838 }
839
840 fn edit_point_for_mouse_event(&self, node: &Node, event: &MouseEvent) -> RopeIndex {
841 node.owner_window()
842 .text_index_query_on_node_for_event(node, event)
843 .map(|grapheme_index| {
844 self.rope.move_by(
845 Default::default(),
846 RopeMovement::Character,
847 grapheme_index as isize,
848 )
849 })
850 .unwrap_or_else(|| self.rope.last_index())
851 }
852
853 pub(crate) fn handle_mouse_event(&mut self, node: &Node, mouse_event: &MouseEvent) -> bool {
856 let event_type = mouse_event.upcast::<Event>().type_();
859 if event_type == atom!("mouseup") || mouse_event.Buttons() & 1 != 1 {
860 self.currently_dragging = false;
861 }
862
863 if event_type == atom!("mousedown") {
864 return self.handle_mousedown(node, mouse_event);
865 }
866
867 if event_type == atom!("mousemove") && self.currently_dragging {
868 self.edit_point = self.edit_point_for_mouse_event(node, mouse_event);
869 self.update_selection_direction();
870 return true;
871 }
872
873 false
874 }
875
876 fn handle_mousedown(&mut self, node: &Node, mouse_event: &MouseEvent) -> bool {
881 assert_eq!(mouse_event.upcast::<Event>().type_(), atom!("mousedown"));
882
883 if mouse_event.Button() != 0 {
890 return false;
891 }
892
893 self.currently_dragging = true;
894 match mouse_event.upcast::<UIEvent>().Detail() {
895 3 => {
896 let word_boundaries = self.rope.line_boundaries(self.edit_point);
897 self.edit_point = word_boundaries.end;
898 self.selection_origin = Some(word_boundaries.start);
899 self.update_selection_direction();
900 true
901 },
902 2 => {
903 let word_boundaries = self.rope.relevant_word_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 1 => {
910 self.clear_selection();
911 self.edit_point = self.edit_point_for_mouse_event(node, mouse_event);
912 self.selection_origin = Some(self.edit_point);
913 self.update_selection_direction();
914 true
915 },
916 _ => {
917 false
921 },
922 }
923 }
924
925 pub(crate) fn is_empty(&self) -> bool {
927 self.rope.is_empty()
928 }
929
930 pub(crate) fn len_utf16(&self) -> Utf16CodeUnitLength {
932 self.rope.len_utf16()
933 }
934
935 pub fn get_content(&self) -> DOMString {
937 self.rope.contents().into()
938 }
939
940 pub fn set_content(&mut self, content: DOMString) {
947 self.rope = Rope::new(
948 content
949 .str()
950 .to_string()
951 .replace("\r\n", "\n")
952 .replace("\r", "\n"),
953 );
954 self.was_last_change_by_set_content = true;
955
956 self.edit_point = self.rope.normalize_index(self.edit_point());
957 self.selection_origin = self
958 .selection_origin
959 .map(|selection_origin| self.rope.normalize_index(selection_origin));
960 }
961
962 pub fn set_selection_range_utf16(
963 &mut self,
964 start: Utf16CodeUnitLength,
965 end: Utf16CodeUnitLength,
966 direction: SelectionDirection,
967 ) {
968 self.set_selection_range_utf8(
969 self.rope.utf16_offset_to_utf8_offset(start),
970 self.rope.utf16_offset_to_utf8_offset(end),
971 direction,
972 );
973 }
974
975 pub fn set_selection_range_utf8(
976 &mut self,
977 mut start: Utf8CodeUnitLength,
978 mut end: Utf8CodeUnitLength,
979 direction: SelectionDirection,
980 ) {
981 let text_end = self.get_content().len_utf8();
982 if end > text_end {
983 end = text_end;
984 }
985 if start > end {
986 start = end;
987 }
988
989 self.selection_direction = direction;
990
991 match direction {
992 SelectionDirection::None | SelectionDirection::Forward => {
993 self.selection_origin = Some(self.rope.utf8_offset_to_rope_index(start));
994 self.edit_point = self.rope.utf8_offset_to_rope_index(end);
995 },
996 SelectionDirection::Backward => {
997 self.selection_origin = Some(self.rope.utf8_offset_to_rope_index(end));
998 self.edit_point = self.rope.utf8_offset_to_rope_index(start);
999 },
1000 }
1001
1002 self.assert_ok_selection();
1003 }
1004
1005 pub(crate) fn handle_clipboard_event(
1013 &mut self,
1014 clipboard_event: &ClipboardEvent,
1015 ) -> ClipboardEventReaction {
1016 let event = clipboard_event.upcast::<Event>();
1017 if !event.IsTrusted() {
1018 return ClipboardEventReaction::empty();
1019 }
1020
1021 if event.DefaultPrevented() {
1024 return ClipboardEventReaction::empty();
1027 }
1028
1029 let event_type = event.Type();
1030 match_domstring_ascii!(event_type,
1031 "copy" => {
1032 let selection = self.get_selection_text();
1034
1035 if let Some(text) = selection {
1037 self.clipboard_provider.set_text(text);
1038 }
1039
1040 ClipboardEventReaction::new(ClipboardEventFlags::FireClipboardChangedEvent)
1042 },
1043 "cut" => {
1044 let selection = self.get_selection_text();
1046
1047 let Some(text) = selection else {
1049 return ClipboardEventReaction::empty();
1051 };
1052
1053 self.clipboard_provider.set_text(text);
1055
1056 self.delete_char(Direction::Backward);
1058
1059 ClipboardEventReaction::new(
1062 ClipboardEventFlags::FireClipboardChangedEvent |
1063 ClipboardEventFlags::QueueInputEvent,
1064 )
1065 .with_input_type(InputType::DeleteByCut)
1066 },
1067 "paste" => {
1068 let Some(data_transfer) = clipboard_event.get_clipboard_data() else {
1070 return ClipboardEventReaction::empty();
1071 };
1072 let Some(drag_data_store) = data_transfer.data_store() else {
1073 return ClipboardEventReaction::empty();
1074 };
1075
1076 let Some(text_content) =
1086 drag_data_store
1087 .iter_item_list()
1088 .find_map(|item| match item {
1089 Kind::Text { data, .. } => Some(data.to_string()),
1090 _ => None,
1091 })
1092 else {
1093 return ClipboardEventReaction::empty();
1094 };
1095 if text_content.is_empty() {
1096 return ClipboardEventReaction::empty();
1097 }
1098
1099 self.insert(&text_content);
1100
1101 ClipboardEventReaction::new(ClipboardEventFlags::QueueInputEvent)
1104 .with_text(text_content)
1105 .with_input_type(InputType::InsertFromPaste)
1106 },
1107 _ => ClipboardEventReaction::empty(),)
1108 }
1109
1110 pub(crate) fn queue_input_event(
1112 &self,
1113 target: &EventTarget,
1114 data: Option<String>,
1115 is_composing: IsComposing,
1116 input_type: InputType,
1117 ) {
1118 let global = target.global();
1119 let target = Trusted::new(target);
1120 global.task_manager().user_interaction_task_source().queue(
1121 task!(fire_input_event: move || {
1122 let target = target.root();
1123 let global = target.global();
1124 let window = global.as_window();
1125 let event = InputEvent::new(
1126 window,
1127 None,
1128 DOMString::from("input"),
1129 true,
1130 false,
1131 Some(window),
1132 0,
1133 data.map(DOMString::from),
1134 is_composing.into(),
1135 input_type.as_str().into(),
1136 CanGc::note(),
1137 );
1138 let event = event.upcast::<Event>();
1139 event.set_composed(true);
1140 event.fire(&target, CanGc::note());
1141 }),
1142 );
1143 }
1144}